Compare commits

...

8 Commits

Author SHA1 Message Date
Gorgeous-Patrick
878c56e102
Merge 369229e0d5 into 2d0d63be76 2025-07-07 15:59:29 +08:00
criyle
2d0d63be76 build(deps): update upstream seccomp library to fix ptracer
elastic/go-seccomp-bpf#40
fixes #8
2025-06-21 16:54:37 -04:00
criyle
7aa1c5a28b refactor: normalize error message & replace path with filepath 2025-05-25 22:02:23 -04:00
criyle
c63c27d3c2 test(pkg): add some unit tests 2025-05-25 20:24:59 -04:00
DNEGEL3125
638345b6eb
test(fileset): add unit tests for FileSet (#15)
* test(FileSet): add unit test for IsInSetSmart

* test(FileSet): add unit test for Add

* test(fileset): add unit test for AddRange
2025-05-22 13:21:42 -04:00
Patrick Li
369229e0d5 fix: allow more essential syscalls 2024-12-05 18:36:33 -05:00
Gorgeous-Patrick
d58b2485a2 test: makefile for test 2024-12-05 14:56:57 -05:00
Gorgeous-Patrick
6407c89d90 DOCKER NOT WORKING 2024-12-05 14:47:05 -05:00
35 changed files with 991 additions and 184 deletions

3
.gitignore vendored
View File

@ -2,7 +2,6 @@
.DS_Store
# Test Env
test*/
env*.sh
# Test Files
@ -10,3 +9,5 @@ env*.sh
/test
.vscode
runprog
*.test

7
Dockerfile Normal file
View File

@ -0,0 +1,7 @@
FROM golang
COPY . /app
WORKDIR /app/cmd/runprog
RUN apt update && apt install -y gcc libstdc++6 libseccomp-dev
RUN go build

View File

@ -16,6 +16,10 @@ var (
"/dev/urandom",
"/proc/meminfo",
"/etc/localtime",
"/usr/lib/libstdc++.so.6",
"/usr/lib/libc.so.6",
"/usr/lib/libm.so.6",
"/usr/lib/libgcc_s.so.1",
}
// default write permission files
@ -75,6 +79,17 @@ var (
"clock_gettime",
"restart_syscall",
"futex",
"gettid",
"getpid",
"prlimit64",
"getrandom",
"set_tid_address",
"set_robust_list",
"rseq",
"newfstatat",
}
// default syscalls to trace
@ -108,71 +123,11 @@ var (
// config for different type of program
// workpath and arg0 have additional read / stat permission
runptraceConfig = map[string]ProgramConfig{
"python2.7": {
Syscall: SyscallConfig{
ExtraAllow: []string{
"futex", "getdents", "getdents64", "prlimit64", "getpid", "sysinfo",
},
ExtraCount: map[string]int{
"set_tid_address": 1,
"set_robust_list": 1,
},
},
FileAccess: FileAccessConfig{
ExtraRead: []string{
"/usr/bin/python2.7",
"/usr/lib/python2.7/",
"/usr/bin/lib/python2.7/",
"/usr/local/lib/python2.7/",
"/usr/lib/pymodules/python2.7/",
"/usr/bin/Modules/",
"/usr/bin/pybuilddir.txt",
"/usr/lib/locale/",
"./answer.code",
},
ExtraStat: []string{
"/usr", "/usr/bin",
},
},
RunCommand: []string{"/usr/bin/python2.7", "-E", "-s", "-B"},
},
"python3": {
Syscall: SyscallConfig{
ExtraAllow: []string{
"futex", "getdents", "getdents64", "prlimit64", "getpid", "sysinfo", "getrandom",
},
ExtraCount: map[string]int{
"set_tid_address": 1,
"set_robust_list": 1,
},
},
FileAccess: FileAccessConfig{
ExtraRead: []string{
"/usr/bin/python3",
"/usr/lib/python3/",
"/usr/bin/python3.6",
"/usr/lib/python3.6/",
"/usr/bin/lib/python3.6/",
"/usr/local/lib/python3.6/",
"/usr/bin/pyvenv.cfg",
"/usr/pyvenv.cfg",
"/usr/bin/Modules",
"/usr/bin/pybuilddir.txt",
"/usr/lib/dist-python",
"/usr/lib/locale/",
"./answer.code",
},
ExtraStat: []string{
"/usr", "/usr/bin", "/usr/lib", "/usr/lib/python36.zip",
},
},
RunCommand: []string{"/usr/bin/python3", "-I", "-B"},
},
"compiler": {
Syscall: SyscallConfig{
ExtraAllow: []string{
"gettid", "set_tid_address", "set_robust_list", "futex",
"getpid", "vfork", "fork", "clone", "execve", "wait4",
"set_tid_address", "set_robust_list", "futex",
"vfork", "fork", "clone", "execve", "wait4",
"clock_gettime", "clock_getres",
"setrlimit", "pipe",
"getdents64", "getdents",
@ -182,7 +137,7 @@ var (
"sched_getaffinity", "sched_yield",
"uname", "sysinfo",
"prlimit64", "getrandom",
"fchmodat",
"fchmodat", "rseq",
},
ExtraBan: []string{"socket", "connect", "geteuid", "getuid"},
},

View File

@ -117,7 +117,7 @@ func start() (*runner.Result, error) {
if profilePath != "" {
c, err := os.ReadFile(profilePath)
if err != nil {
return nil, fmt.Errorf("profile: %v", err)
return nil, fmt.Errorf("profile: %w", err)
}
profile = string(c)
}

View File

@ -237,11 +237,11 @@ func start() (*runner.Result, error) {
if memfile {
fin, err := os.Open(args[0])
if err != nil {
return nil, fmt.Errorf("failed to open args[0]: %v", err)
return nil, fmt.Errorf("failed to open args[0]: %w", err)
}
execf, err := memfd.DupToMemfd("run_program", fin)
if err != nil {
return nil, fmt.Errorf("dup to memfd failed: %v", err)
return nil, fmt.Errorf("dup to memfd failed: %w", err)
}
fin.Close()
defer execf.Close()
@ -252,7 +252,7 @@ func start() (*runner.Result, error) {
// open input / output / err files
files, err := prepareFiles(inputFileName, outputFileName, errorFileName)
if err != nil {
return nil, fmt.Errorf("failed to prepare files: %v", err)
return nil, fmt.Errorf("failed to prepare files: %w", err)
}
defer closeFiles(files)
@ -295,7 +295,7 @@ func start() (*runner.Result, error) {
if !unsafe || runt != "container" {
filter, err = builder.Build()
if err != nil {
return nil, fmt.Errorf("failed to create seccomp filter %v", err)
return nil, fmt.Errorf("failed to create seccomp filter: %w", err)
}
}
@ -329,12 +329,12 @@ func start() (*runner.Result, error) {
m, err := b.Build()
if err != nil {
return nil, fmt.Errorf("failed to new container: %v", err)
return nil, fmt.Errorf("failed to new container: %w", err)
}
defer m.Destroy()
err = m.Ping()
if err != nil {
return nil, fmt.Errorf("failed to ping container: %v", err)
return nil, fmt.Errorf("failed to ping container: %w", err)
}
if unsafe {
filter = nil

View File

@ -47,6 +47,9 @@ func (c *containerServer) handleOpen(open []OpenCmd) error {
for _, o := range open {
outFile, err := os.OpenFile(o.Path, o.Flag, o.Perm)
if err != nil {
for _, f := range fileToClose {
f.Close()
}
return c.sendErrorReply("open: %v", err)
}
fileToClose = append(fileToClose, outFile)
@ -85,6 +88,7 @@ func readDotEnv() ([]string, error) {
if errors.Is(err, os.ErrNotExist) {
return nil, nil
}
return nil, fmt.Errorf("dotenv: open /.env: %w", err)
}
defer f.Close()
@ -96,7 +100,7 @@ func readDotEnv() ([]string, error) {
continue
}
if !strings.Contains(line, "=") {
return nil, fmt.Errorf("dotenv: invalid line %s", line)
return nil, fmt.Errorf("dotenv: invalid line: %s", line)
}
ret = append(ret, line)
}

View File

@ -66,14 +66,14 @@ func (c *containerServer) handleExecve(cmd *execCmd, msg unixsocket.Msg) error {
},
}
if err := c.sendReply(reply{}, msg); err != nil {
return fmt.Errorf("syncFunc: sendReply %v", err)
return fmt.Errorf("sync func: send reply: %w", err)
}
cmd, _, err := c.recvCmd()
if err != nil {
return fmt.Errorf("syncFunc: recvCmd %v", err)
return fmt.Errorf("sync func: recv cmd: %w", err)
}
if cmd.Cmd == cmdKill {
return fmt.Errorf("syncFunc: received kill")
return fmt.Errorf("sync func: received kill")
}
return nil
}
@ -179,7 +179,7 @@ func convertReply(ret waitPidResult) reply {
if ret.Err != nil {
return reply{
Error: &errorReply{
Msg: fmt.Sprintf("execve: wait4 %v", ret.Err),
Msg: fmt.Sprintf("execve: wait4: %v", ret.Err),
},
}
}
@ -229,7 +229,7 @@ func convertReply(ret waitPidResult) reply {
default:
return reply{
Error: &errorReply{
Msg: fmt.Sprintf("execve: unknown status %v", waitStatus),
Msg: fmt.Sprintf("execve: unknown status: %v", waitStatus),
},
}
}

View File

@ -88,14 +88,14 @@ func Init() (err error) {
// ensure there's no fd leak to child process (e.g. VSCode leaks ptmx fd)
if err := closeOnExecAllFds(); err != nil {
return fmt.Errorf("container_init: failed to close_on_exec all fd %v", err)
return fmt.Errorf("container_init: failed to close on exec all fds: %w", err)
}
// new_container environment shared the socket at fd 3 (marked close_exec)
const defaultFd = 3
soc, err := unixsocket.NewSocket(defaultFd)
if err != nil {
return fmt.Errorf("container_init: failed to new socket %v", err)
return fmt.Errorf("container_init: failed to create new socket: %w", err)
}
// serve forever
@ -197,10 +197,10 @@ func (c *containerServer) serve() error {
for {
cmd, msg, err := c.recvCmd()
if err != nil {
return fmt.Errorf("serve: recvCmd %v", err)
return fmt.Errorf("serve: recvCmd: %w", err)
}
if err := c.handleCmd(cmd, msg); err != nil {
return fmt.Errorf("serve: failed to execute cmd %v", err)
return fmt.Errorf("serve: failed to execute cmd: %w", err)
}
}
}
@ -255,53 +255,53 @@ func initFileSystem(c containerConfig) error {
// mount tmpfs as root
const tmpfs = "tmpfs"
if err := syscall.Mount(tmpfs, c.ContainerRoot, tmpfs, 0, ""); err != nil {
return fmt.Errorf("init_fs: mount / %v", err)
return fmt.Errorf("init_fs: mount /: %w", err)
}
// change dir to container root
if err := syscall.Chdir(c.ContainerRoot); err != nil {
return fmt.Errorf("init_fs: chdir %v", err)
return fmt.Errorf("init_fs: chdir: %w", err)
}
// performing mounts
for _, m := range c.Mounts {
if err := m.Mount(); err != nil {
return fmt.Errorf("init_fs: mount %v %v", m, err)
return fmt.Errorf("init_fs: mount %v: %w", m, err)
}
}
// pivot root
const oldRoot = "old_root"
if err := os.Mkdir(oldRoot, 0755); err != nil {
return fmt.Errorf("init_fs: mkdir(old_root) %v", err)
return fmt.Errorf("init_fs: mkdir old_root: %w", err)
}
if err := syscall.PivotRoot(c.ContainerRoot, oldRoot); err != nil {
return fmt.Errorf("init_fs: pivot_root(%s, %s) %v", c.ContainerRoot, oldRoot, err)
return fmt.Errorf("init_fs: pivot_root(%s, %s): %w", c.ContainerRoot, oldRoot, err)
}
if err := syscall.Unmount(oldRoot, syscall.MNT_DETACH); err != nil {
return fmt.Errorf("init_fs: unmount(old_root) %v", err)
return fmt.Errorf("init_fs: unmount old_root: %w", err)
}
if err := os.Remove(oldRoot); err != nil {
return fmt.Errorf("init_fs: unlink(old_root) %v", err)
return fmt.Errorf("init_fs: unlink old_root: %w", err)
}
// create symlinks
for _, l := range c.SymbolicLinks {
// ensure dir exists
dir := filepath.Dir(l.LinkPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("init_fs: mkdir_all(%s) %v", dir, err)
return fmt.Errorf("init_fs: mkdir_all(%s): %w", dir, err)
}
if err := os.Symlink(l.Target, l.LinkPath); err != nil {
return fmt.Errorf("init_fs: symlink %v", err)
return fmt.Errorf("init_fs: symlink: %w", err)
}
}
// mask paths
for _, p := range c.MaskPaths {
if err := maskPath(p); err != nil {
return fmt.Errorf("init_fs: mask path %v", err)
return fmt.Errorf("init_fs: mask path: %w", err)
}
}
// readonly root
const remountFlag = syscall.MS_BIND | syscall.MS_REMOUNT | syscall.MS_RDONLY | syscall.MS_NOATIME | syscall.MS_NOSUID
if err := syscall.Mount(tmpfs, "/", tmpfs, remountFlag, ""); err != nil {
return fmt.Errorf("init_fs: readonly remount / %v", err)
return fmt.Errorf("init_fs: readonly remount /: %w", err)
}
return nil
}
@ -368,7 +368,7 @@ func maskPath(path string) error {
// otherwise, mount tmpfs to mask it
return syscall.Mount("tmpfs", path, "tmpfs", syscall.MS_RDONLY, "")
}
return err
return fmt.Errorf("mask path: %w", err)
}
return nil
}

View File

@ -127,7 +127,7 @@ func (b *Builder) Build() (Environment, error) {
// avoid non cinit enabled executable running as container init process
if err = c.Ping(); err != nil {
c.Destroy()
return nil, fmt.Errorf("container: container init not responding to ping %v", err)
return nil, fmt.Errorf("container: init not responding to ping: %w", err)
}
// container mount points
@ -154,13 +154,13 @@ func (b *Builder) Build() (Environment, error) {
root := b.Root
if b.TmpRoot != "" {
if root, err = os.MkdirTemp(b.Root, b.TmpRoot); err != nil {
return nil, fmt.Errorf("container: failed to make tmp container root at %s %v", b.Root, err)
return nil, fmt.Errorf("container: failed to make tmp container root at %s: %w", b.Root, err)
}
defer os.Remove(root)
}
if root == "" {
if root, err = os.Getwd(); err != nil {
return nil, fmt.Errorf("container: failed to get work directory %v", err)
return nil, fmt.Errorf("container: failed to get work directory: %w", err)
}
}
workDir := containerWD
@ -206,14 +206,14 @@ func (b *Builder) startContainer() (*container, error) {
// prepare host <-> container unix socket
ins, outs, err := newPassCredSocketPair()
if err != nil {
return nil, fmt.Errorf("container: failed to create socket: %v", err)
return nil, fmt.Errorf("container: failed to create socket: %w", err)
}
defer outs.Close()
outf, err := outs.File()
if err != nil {
ins.Close()
return nil, fmt.Errorf("container: failed to dup container socket fd %v", err)
return nil, fmt.Errorf("container: failed to dup container socket fd: %w", err)
}
defer outf.Close()
@ -258,7 +258,7 @@ func (b *Builder) startContainer() (*container, error) {
}
if err = r.Start(); err != nil {
ins.Close()
return nil, fmt.Errorf("container: failed to start container %v", err)
return nil, fmt.Errorf("container: failed to start container: %w", err)
}
c := &container{
process: r.Process,
@ -386,10 +386,10 @@ func (b *Builder) getIDMapping(cred *syscall.Credential) ([]syscall.SysProcIDMap
func (c *container) recvAckReply(name string) error {
reply, _, err := c.recvReply()
if err != nil {
return fmt.Errorf("%v: recvAck %v", name, err)
return fmt.Errorf("%s: recv ack: %w", name, err)
}
if reply.Error != nil {
return fmt.Errorf("%v: container error %v", name, reply.Error)
return fmt.Errorf("%s: container error: %v", name, reply.Error)
}
return nil
}

View File

@ -24,7 +24,7 @@ func (c *container) Ping() error {
Cmd: cmdPing,
}
if err := c.sendCmd(cmd, unixsocket.Msg{}); err != nil {
return fmt.Errorf("ping: %v", err)
return fmt.Errorf("ping: %w", err)
}
// receive no error
return c.recvAckReply("ping")
@ -40,7 +40,7 @@ func (c *container) conf(conf *containerConfig) error {
ConfCmd: &confCmd{Conf: *conf},
}
if err := c.sendCmd(cmd, unixsocket.Msg{}); err != nil {
return fmt.Errorf("conf: %v", err)
return fmt.Errorf("conf: %w", err)
}
return c.recvAckReply("conf")
}
@ -59,18 +59,18 @@ func (c *container) Open(p []OpenCmd) ([]*os.File, error) {
OpenCmd: p,
}
if err := c.sendCmd(cmd, unixsocket.Msg{}); err != nil {
return nil, fmt.Errorf("open: %v", err)
return nil, fmt.Errorf("open: %w", err)
}
reply, msg, err := c.recvReply()
if err != nil {
return nil, fmt.Errorf("open: %v", err)
return nil, fmt.Errorf("open: %w", err)
}
if reply.Error != nil {
return nil, fmt.Errorf("open: %v", reply.Error)
}
if len(msg.Fds) != len(p) {
closeFds(msg.Fds)
return nil, fmt.Errorf("open: unexpected number of fd %v / %v", len(msg.Fds), len(p))
return nil, fmt.Errorf("open: unexpected number of fds: got %d, want %d", len(msg.Fds), len(p))
}
ret := make([]*os.File, 0, len(p))
@ -79,7 +79,7 @@ func (c *container) Open(p []OpenCmd) ([]*os.File, error) {
f := os.NewFile(uintptr(fd), p[i].Path)
if f == nil {
closeFds(msg.Fds)
return nil, fmt.Errorf("open: failed NewFile %v", fd)
return nil, fmt.Errorf("open: failed to create file for fd: %d", fd)
}
ret = append(ret, f)
}
@ -96,7 +96,7 @@ func (c *container) Delete(p string) error {
DeleteCmd: &deleteCmd{Path: p},
}
if err := c.sendCmd(cmd, unixsocket.Msg{}); err != nil {
return fmt.Errorf("delete: %v", err)
return fmt.Errorf("delete: %w", err)
}
return c.recvAckReply("delete")
}
@ -110,7 +110,7 @@ func (c *container) Reset() error {
Cmd: cmdReset,
}
if err := c.sendCmd(cmd, unixsocket.Msg{}); err != nil {
return fmt.Errorf("reset: %v", err)
return fmt.Errorf("reset: %w", err)
}
return c.recvAckReply("reset")
}

View File

@ -43,27 +43,27 @@ func newSocket(s *unixsocket.Socket) *socket {
return &soc
}
func (s *socket) RecvMsg(e interface{}) (msg unixsocket.Msg, err error) {
func (s *socket) RecvMsg(e any) (msg unixsocket.Msg, err error) {
n, msg, err := s.Socket.RecvMsg(s.buff)
if err != nil {
return msg, fmt.Errorf("RecvMsg: %v", err)
return msg, fmt.Errorf("recv msg: %w", err)
}
s.recvBuff.Rotate(bytes.NewBuffer(s.buff[:n]))
if err := s.decoder.Decode(e); err != nil {
return msg, fmt.Errorf("RecvMsg: failed to decode %v", err)
return msg, fmt.Errorf("recv msg: decode: %w", err)
}
return msg, nil
}
func (s *socket) SendMsg(e interface{}, msg unixsocket.Msg) error {
func (s *socket) SendMsg(e any, msg unixsocket.Msg) error {
s.sendBuff.Reset()
if err := s.encoder.Encode(e); err != nil {
return fmt.Errorf("SendMsg: failed to encode %v", err)
return fmt.Errorf("send msg: encode: %w", err)
}
if err := s.Socket.SendMsg(s.sendBuff.Bytes(), msg); err != nil {
return fmt.Errorf("SendMsg: failed to SendMsg %v", err)
return fmt.Errorf("send msg: %w", err)
}
return nil
}

View File

@ -2,7 +2,7 @@ package container
import (
"os"
"path"
"path/filepath"
"syscall"
)
@ -54,10 +54,10 @@ func removeContents(dir string) error {
}
for _, name := range names {
err = os.RemoveAll(path.Join(dir, name))
err1 := os.RemoveAll(filepath.Join(dir, name))
if err != nil {
return err
err = err1
}
}
return nil
return err
}

6
go.mod
View File

@ -3,7 +3,7 @@ module github.com/criyle/go-sandbox
go 1.24
require (
github.com/elastic/go-seccomp-bpf v1.5.0
golang.org/x/net v0.38.0
golang.org/x/sys v0.31.0
github.com/elastic/go-seccomp-bpf v1.6.0
golang.org/x/net v0.41.0
golang.org/x/sys v0.33.0
)

16
go.sum
View File

@ -1,14 +1,14 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/elastic/go-seccomp-bpf v1.5.0 h1:gJV+U1iP+YC70ySyGUUNk2YLJW5/IkEw4FZBJfW8ZZY=
github.com/elastic/go-seccomp-bpf v1.5.0/go.mod h1:umdhQ/3aybliBF2jjiZwS492I/TOKz+ZRvsLT3hVe1o=
github.com/elastic/go-seccomp-bpf v1.6.0 h1:NYduiYxRJ0ZkIyQVwlSskcqPPSg6ynu5pK0/d7SQATs=
github.com/elastic/go-seccomp-bpf v1.6.0/go.mod h1:5tFsTvH4NtWGfpjsOQD53H8HdVQ+zSZFRUDSGevC0Kc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -4,7 +4,7 @@ import (
"bufio"
"fmt"
"os"
"path"
"path/filepath"
"strconv"
"strings"
)
@ -178,7 +178,7 @@ func GetAvailableControllerV2() (*Controllers, error) {
}
func getAvailableControllerV2(prefix string) (*Controllers, error) {
return getAvailableControllerV2path(path.Join(basePath, prefix, cgroupControllers))
return getAvailableControllerV2path(filepath.Join(basePath, prefix, cgroupControllers))
}
func getAvailableControllerV2path(p string) (*Controllers, error) {

View File

@ -4,7 +4,7 @@ import (
"errors"
"fmt"
"os"
"path"
"path/filepath"
"strings"
)
@ -136,7 +136,7 @@ func newV1(prefix string, ct *Controllers) (cg Cgroup, err error) {
func newV2(prefix string, ct *Controllers) (cg Cgroup, err error) {
v2 := &V2{
path: path.Join(basePath, prefix),
path: filepath.Join(basePath, prefix),
control: ct,
}
if _, err := os.Stat(v2.path); err == nil {
@ -159,8 +159,8 @@ func newV2(prefix string, ct *Controllers) (cg Cgroup, err error) {
parent := current
current = current + "/" + e
// try mkdir if not exists
if _, err := os.Stat(path.Join(basePath, current)); os.IsNotExist(err) {
if err := os.Mkdir(path.Join(basePath, current), dirPerm); err != nil {
if _, err := os.Stat(filepath.Join(basePath, current)); os.IsNotExist(err) {
if err := os.Mkdir(filepath.Join(basePath, current), dirPerm); err != nil {
return nil, err
}
} else if err != nil {
@ -175,7 +175,7 @@ func newV2(prefix string, ct *Controllers) (cg Cgroup, err error) {
if ect.Contains(ct) {
continue
}
if err := writeFile(path.Join(basePath, parent, cgroupSubtreeControl), controlMsg, filePerm); err != nil {
if err := writeFile(filepath.Join(basePath, parent, cgroupSubtreeControl), controlMsg, filePerm); err != nil {
return nil, err
}
}
@ -197,7 +197,7 @@ func openExistingV1(prefix string, ct *Controllers) (cg Cgroup, err error) {
}
if err = loopV1Controllers(ct, v1, func(name string, cg **v1controller) error {
p := path.Join(basePath, name, prefix)
p := filepath.Join(basePath, name, prefix)
*cg = newV1Controller(p)
// os.IsNotExist
if _, err := os.Stat(p); err != nil {
@ -224,10 +224,10 @@ func openExistingV2(prefix string, ct *Controllers) (cg Cgroup, err error) {
return nil, err
}
if !ect.Contains(ct) {
return nil, fmt.Errorf("openCgroupV2: requesting %v controllers but %v found", ct, ect)
return nil, fmt.Errorf("open cgroup v2: requesting %v controllers but %v found", ct, ect)
}
return &V2{
path: path.Join(basePath, prefix),
path: filepath.Join(basePath, prefix),
control: ect,
existing: true,
}, nil

View File

@ -6,7 +6,7 @@ import (
"io/fs"
"math/rand/v2"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"syscall"
@ -24,7 +24,7 @@ func EnsureDirExists(path string) error {
// CreateV1ControllerPath create path for controller with given group, prefix
func CreateV1ControllerPath(controller, prefix string) (string, error) {
p := path.Join(basePath, controller, prefix)
p := filepath.Join(basePath, controller, prefix)
return p, EnsureDirExists(p)
}
@ -37,7 +37,7 @@ func EnableV2Nesting() error {
return nil
}
p, err := readFile(path.Join(basePath, cgroupProcs))
p, err := readFile(filepath.Join(basePath, cgroupProcs))
if err != nil {
return err
}
@ -47,11 +47,11 @@ func EnableV2Nesting() error {
}
// mkdir init
if err := os.Mkdir(path.Join(basePath, initPath), dirPerm); err != nil && !errors.Is(err, os.ErrExist) {
if err := os.Mkdir(filepath.Join(basePath, initPath), dirPerm); err != nil && !errors.Is(err, os.ErrExist) {
return err
}
// move all process into init cgroup
procFile, err := os.OpenFile(path.Join(basePath, initPath, cgroupProcs), os.O_RDWR, filePerm)
procFile, err := os.OpenFile(filepath.Join(basePath, initPath, cgroupProcs), os.O_RDWR, filePerm)
if err != nil {
return err
}
@ -165,7 +165,7 @@ func nextRandom() string {
func randomBuild(pattern string, build func(string) (Cgroup, error)) (Cgroup, error) {
prefix, suffix, err := prefixAndSuffix(pattern)
if err != nil {
return nil, fmt.Errorf("cgroup.builder: random %v", err)
return nil, fmt.Errorf("cgroup.builder: random %w", err)
}
try := 0
@ -181,6 +181,6 @@ func randomBuild(pattern string, build func(string) (Cgroup, error)) (Cgroup, er
}
return nil, fmt.Errorf("cgroup.builder: tried 10000 times but failed")
}
return nil, fmt.Errorf("cgroup.builder: random %v", err)
return nil, fmt.Errorf("cgroup.builder: random %w", err)
}
}

View File

@ -4,7 +4,6 @@ import (
"bytes"
"fmt"
"os"
"path"
"path/filepath"
"strings"
)
@ -65,13 +64,13 @@ func (c *V1) Processes() ([]int, error) {
if len(c.all) == 0 {
return nil, os.ErrInvalid
}
return ReadProcesses(path.Join(c.all[0].path, cgroupProcs))
return ReadProcesses(filepath.Join(c.all[0].path, cgroupProcs))
}
// New creates a sub-cgroup based on the existing one
func (c *V1) New(name string) (cg Cgroup, err error) {
v1 := &V1{
prefix: path.Join(c.prefix, name),
prefix: filepath.Join(c.prefix, name),
}
defer func() {
if err != nil {
@ -93,7 +92,7 @@ func (c *V1) New(name string) (cg Cgroup, err error) {
if v.now == nil {
continue
}
p := path.Join(v.now.path, name)
p := filepath.Join(v.now.path, name)
*v.new = &v1controller{path: p}
err = EnsureDirExists(p)
if os.IsExist(err) {

View File

@ -2,7 +2,7 @@ package cgroup
import (
"errors"
"path"
"path/filepath"
"strconv"
"strings"
)
@ -50,7 +50,7 @@ func (c *v1controller) WriteFile(name string, content []byte) error {
if c == nil || c.path == "" {
return ErrNotInitialized
}
p := path.Join(c.path, name)
p := filepath.Join(c.path, name)
return writeFile(p, content, filePerm)
}
@ -60,10 +60,10 @@ func (c *v1controller) ReadFile(name string) ([]byte, error) {
if c == nil || c.path == "" {
return nil, nil
}
p := path.Join(c.path, name)
p := filepath.Join(c.path, name)
return readFile(p)
}
func (c *v1controller) AddProc(pids ...int) error {
return AddProcesses(path.Join(c.path, cgroupProcs), pids)
return AddProcesses(filepath.Join(c.path, cgroupProcs), pids)
}

View File

@ -4,7 +4,7 @@ import (
"bufio"
"bytes"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
@ -26,18 +26,18 @@ func (c *V2) Open() (*os.File, error) {
}
func (c *V2) String() string {
ct, _ := getAvailableControllerV2path(path.Join(c.path, cgroupControllers))
ct, _ := getAvailableControllerV2path(filepath.Join(c.path, cgroupControllers))
return "v2(" + c.path + ")" + ct.String()
}
// AddProc adds processes into the cgroup
func (c *V2) AddProc(pids ...int) error {
return AddProcesses(path.Join(c.path, cgroupProcs), pids)
return AddProcesses(filepath.Join(c.path, cgroupProcs), pids)
}
// Processes returns all processes within the cgroup
func (c *V2) Processes() ([]int, error) {
return ReadProcesses(path.Join(c.path, cgroupProcs))
return ReadProcesses(filepath.Join(c.path, cgroupProcs))
}
// New creates a sub-cgroup based on the existing one
@ -46,7 +46,7 @@ func (c *V2) New(name string) (Cgroup, error) {
return nil, err
}
v2 := &V2{
path: path.Join(c.path, name),
path: filepath.Join(c.path, name),
control: c.control,
}
if err := os.Mkdir(v2.path, dirPerm); err != nil {
@ -61,7 +61,7 @@ func (c *V2) New(name string) (Cgroup, error) {
// Nest creates a sub-cgroup, moves current process into that cgroup
func (c *V2) Nest(name string) (Cgroup, error) {
v2 := &V2{
path: path.Join(c.path, name),
path: filepath.Join(c.path, name),
control: c.control,
}
if err := os.Mkdir(v2.path, dirPerm); err != nil {
@ -85,12 +85,12 @@ func (c *V2) Nest(name string) (Cgroup, error) {
func (c *V2) enableSubtreeControl() error {
c.subtreeOnce.Do(func() {
ct, err := getAvailableControllerV2path(path.Join(c.path, cgroupControllers))
ct, err := getAvailableControllerV2path(filepath.Join(c.path, cgroupControllers))
if err != nil {
c.subtreeErr = err
return
}
ect, err := getAvailableControllerV2path(path.Join(c.path, cgroupSubtreeControl))
ect, err := getAvailableControllerV2path(filepath.Join(c.path, cgroupSubtreeControl))
if err != nil {
c.subtreeErr = err
return
@ -100,7 +100,7 @@ func (c *V2) enableSubtreeControl() error {
}
s := ct.Names()
controlMsg := []byte("+" + strings.Join(s, " +"))
c.subtreeErr = writeFile(path.Join(c.path, cgroupSubtreeControl), controlMsg, filePerm)
c.subtreeErr = writeFile(filepath.Join(c.path, cgroupSubtreeControl), controlMsg, filePerm)
})
return c.subtreeErr
}
@ -221,13 +221,13 @@ func (c *V2) ReadUint(filename string) (uint64, error) {
// WriteFile writes cgroup file and handles potential EINTR error while writes to
// the slow device (cgroup)
func (c *V2) WriteFile(name string, content []byte) error {
p := path.Join(c.path, name)
p := filepath.Join(c.path, name)
return writeFile(p, content, filePerm)
}
// ReadFile reads cgroup file and handles potential EINTR error while read to
// the slow device (cgroup)
func (c *V2) ReadFile(name string) ([]byte, error) {
p := path.Join(c.path, name)
p := filepath.Join(c.path, name)
return readFile(p)
}

View File

@ -15,12 +15,12 @@ const roSeal = unix.F_SEAL_SEAL | unix.F_SEAL_SHRINK | unix.F_SEAL_GROW | unix.F
func New(name string) (*os.File, error) {
fd, err := unix.MemfdCreate(name, createFlag)
if err != nil {
return nil, fmt.Errorf("memfd: memfd_create failed %v", err)
return nil, fmt.Errorf("memfd: memfd_create: %w", err)
}
file := os.NewFile(uintptr(fd), name)
if file == nil {
unix.Close(fd)
return nil, fmt.Errorf("memfd: NewFile failed for %v", name)
return nil, fmt.Errorf("memfd: new file failed for %q", name)
}
return file, nil
}
@ -29,21 +29,21 @@ func New(name string) (*os.File, error) {
func DupToMemfd(name string, reader io.Reader) (*os.File, error) {
file, err := New(name)
if err != nil {
return nil, fmt.Errorf("DupToMemfd: %v", err)
return nil, fmt.Errorf("memfd: dup: %w", err)
}
// linux syscall sendfile might be more efficient here if reader is a file
if _, err = file.ReadFrom(reader); err != nil {
file.Close()
return nil, fmt.Errorf("DupToMemfd: read from %v", err)
return nil, fmt.Errorf("memfd: read from: %w", err)
}
// make memfd readonly
if _, err = unix.FcntlInt(file.Fd(), unix.F_ADD_SEALS, roSeal); err != nil {
file.Close()
return nil, fmt.Errorf("DupToMemfd: memfd seal %v", err)
return nil, fmt.Errorf("memfd: seal: %w", err)
}
if _, err := file.Seek(0, 0); err != nil {
file.Close()
return nil, fmt.Errorf("DupToMemfd: file seek %v", err)
return nil, fmt.Errorf("memfd: seek: %w", err)
}
return file, nil
}

View File

@ -0,0 +1,80 @@
package memfd
import (
"bytes"
"io"
"os"
"testing"
)
func TestNew(t *testing.T) {
f, err := New("test-memfd")
if err != nil {
t.Fatalf("New() error: %v", err)
}
defer f.Close()
// Write and read to verify it's a valid file
data := []byte("hello world")
n, err := f.Write(data)
if err != nil {
t.Fatalf("Write error: %v", err)
}
if n != len(data) {
t.Errorf("Write n = %d, want %d", n, len(data))
}
_, err = f.Seek(0, io.SeekStart)
if err != nil {
t.Fatalf("Seek error: %v", err)
}
read := make([]byte, len(data))
n, err = f.Read(read)
if err != nil && err != io.EOF {
t.Fatalf("Read error: %v", err)
}
if string(read[:n]) != string(data) {
t.Errorf("Read = %q, want %q", string(read[:n]), string(data))
}
}
func TestDupToMemfd(t *testing.T) {
content := []byte("memfd content")
r := bytes.NewReader(content)
f, err := DupToMemfd("dup-memfd", r)
if err != nil {
t.Fatalf("DupToMemfd error: %v", err)
}
defer f.Close()
// Should be sealed (readonly), so writing should fail
_, err = f.Write([]byte("fail"))
if err == nil {
t.Error("expected write to sealed memfd to fail, but it succeeded")
}
// Should be able to read the content
_, err = f.Seek(0, io.SeekStart)
if err != nil {
t.Fatalf("Seek error: %v", err)
}
got, err := io.ReadAll(f)
if err != nil {
t.Fatalf("ReadAll error: %v", err)
}
if string(got) != string(content) {
t.Errorf("ReadAll = %q, want %q", string(got), string(content))
}
}
func TestDupToMemfd_ErrorPropagation(t *testing.T) {
// Pass a reader that always errors
r := errorReader{}
_, err := DupToMemfd("dup-memfd-err", r)
if err == nil {
t.Error("expected error from DupToMemfd, got nil")
}
}
type errorReader struct{}
func (errorReader) Read([]byte) (int, error) { return 0, os.ErrInvalid }

View File

@ -9,7 +9,7 @@ import (
"runtime"
)
var errNotImplemented = fmt.Errorf("memfd: unsupported on platform %s", runtime.GOOS)
var errNotImplemented = fmt.Errorf("memfd: unsupported on platform: %s", runtime.GOOS)
func New(name string) (*os.File, error) {
return nil, errNotImplemented

View File

@ -0,0 +1,124 @@
package mount
import (
"os"
"strings"
"testing"
)
func TestBuilder_WithBind(t *testing.T) {
b := NewBuilder().WithBind("/src", "/dst", true)
if len(b.Mounts) != 1 {
t.Fatalf("expected 1 mount, got %d", len(b.Mounts))
}
m := b.Mounts[0]
if m.Source != "/src" || m.Target != "/dst" {
t.Errorf("unexpected mount: %+v", m)
}
if !m.IsBindMount() {
t.Errorf("expected bind mount")
}
if !m.IsReadOnly() {
t.Errorf("expected readonly mount")
}
}
func TestBuilder_WithTmpfs(t *testing.T) {
b := NewBuilder().WithTmpfs("/tmp", "size=64m")
if len(b.Mounts) != 1 {
t.Fatalf("expected 1 mount, got %d", len(b.Mounts))
}
m := b.Mounts[0]
if !m.IsTmpFs() {
t.Errorf("expected tmpfs mount")
}
if m.Target != "/tmp" || m.Data != "size=64m" {
t.Errorf("unexpected mount: %+v", m)
}
}
func TestBuilder_WithProc(t *testing.T) {
b := NewBuilder().WithProc()
if len(b.Mounts) != 1 {
t.Fatalf("expected 1 mount, got %d", len(b.Mounts))
}
m := b.Mounts[0]
if m.FsType != "proc" {
t.Errorf("expected proc fsType")
}
if !m.IsReadOnly() {
t.Errorf("expected readonly proc mount")
}
}
func TestBuilder_WithProcRW(t *testing.T) {
b := NewBuilder().WithProcRW(true)
if len(b.Mounts) != 1 {
t.Fatalf("expected 1 mount, got %d", len(b.Mounts))
}
m := b.Mounts[0]
if m.FsType != "proc" {
t.Errorf("expected proc fsType")
}
if m.IsReadOnly() {
t.Errorf("expected read-write proc mount")
}
}
func TestBuilder_WithMounts(t *testing.T) {
m1 := Mount{Source: "/a", Target: "/b"}
m2 := Mount{Source: "/c", Target: "/d"}
b := NewBuilder().WithMounts([]Mount{m1, m2})
if len(b.Mounts) != 2 {
t.Fatalf("expected 2 mounts, got %d", len(b.Mounts))
}
}
func TestBuilder_WithMount(t *testing.T) {
m := Mount{Source: "/a", Target: "/b"}
b := NewBuilder().WithMount(m)
if len(b.Mounts) != 1 {
t.Fatalf("expected 1 mount, got %d", len(b.Mounts))
}
}
func TestBuilder_String(t *testing.T) {
b := NewBuilder().
WithBind("/src", "/dst", false).
WithTmpfs("/tmp", "size=1m").
WithProc()
s := b.String()
if !strings.HasPrefix(s, "Mounts: ") {
t.Errorf("unexpected prefix: %q", s)
}
if !strings.Contains(s, "bind[/src:/dst:rw]") {
t.Errorf("missing bind: %q", s)
}
if !strings.Contains(s, "tmpfs[/tmp]") {
t.Errorf("missing tmpfs: %q", s)
}
if !strings.Contains(s, "proc[ro]") {
t.Errorf("missing proc: %q", s)
}
}
func TestBuilder_FilterNotExist(t *testing.T) {
tmpDir := t.TempDir()
tmpFilePath := tmpDir + "/mounttest"
f, err := os.Create(tmpFilePath)
if err != nil {
t.Fatal(err)
}
f.Close()
b := NewBuilder().
WithBind(f.Name(), "/dst1", false).
WithBind("/not/exist", "/dst2", false)
b.FilterNotExist()
if len(b.Mounts) != 1 {
t.Errorf("expected 1 mount after filter, got %d", len(b.Mounts))
}
if b.Mounts[0].Source != f.Name() {
t.Errorf("unexpected mount: %+v", b.Mounts[0])
}
}

View File

@ -0,0 +1,112 @@
package mount
import (
"os"
"path/filepath"
"syscall"
"testing"
)
func TestMount_IsBindMount(t *testing.T) {
m := Mount{Flags: syscall.MS_BIND}
if !m.IsBindMount() {
t.Errorf("expected IsBindMount true")
}
m.Flags = 0
if m.IsBindMount() {
t.Errorf("expected IsBindMount false")
}
}
func TestMount_IsReadOnly(t *testing.T) {
m := Mount{Flags: syscall.MS_RDONLY}
if !m.IsReadOnly() {
t.Errorf("expected IsReadOnly true")
}
m.Flags = 0
if m.IsReadOnly() {
t.Errorf("expected IsReadOnly false")
}
}
func TestMount_IsTmpFs(t *testing.T) {
m := Mount{FsType: "tmpfs"}
if !m.IsTmpFs() {
t.Errorf("expected IsTmpFs true")
}
m.FsType = "other"
if m.IsTmpFs() {
t.Errorf("expected IsTmpFs false")
}
}
func TestMount_String(t *testing.T) {
tests := []struct {
m Mount
want string
}{
{
m: Mount{Source: "/src", Target: "/dst", Flags: syscall.MS_BIND, FsType: "", Data: ""},
want: "bind[/src:/dst:rw]",
},
{
m: Mount{Source: "/src", Target: "/dst", Flags: syscall.MS_BIND | syscall.MS_RDONLY, FsType: "", Data: ""},
want: "bind[/src:/dst:ro]",
},
{
m: Mount{Source: "", Target: "/tmp", FsType: "tmpfs"},
want: "tmpfs[/tmp]",
},
{
m: Mount{Source: "", Target: "proc", FsType: "proc", Flags: syscall.MS_RDONLY},
want: "proc[ro]",
},
{
m: Mount{Source: "src", Target: "dst", FsType: "other", Flags: 0, Data: "data"},
want: "mount[other,src:dst:0,data]",
},
}
for _, tt := range tests {
got := tt.m.String()
if got != tt.want {
t.Errorf("Mount.String() = %q, want %q", got, tt.want)
}
}
}
func TestEnsureMountTargetExists_Dir(t *testing.T) {
tmpDir := t.TempDir()
target := filepath.Join(tmpDir, "foo/bar")
err := ensureMountTargetExists(tmpDir, target)
if err != nil {
t.Fatalf("ensureMountTargetExists error: %v", err)
}
info, err := os.Stat(target)
if err != nil {
t.Fatalf("stat error: %v", err)
}
if !info.IsDir() {
t.Errorf("expected directory at %s", target)
}
}
func TestEnsureMountTargetExists_File(t *testing.T) {
tmpDir := t.TempDir()
srcFile := filepath.Join(tmpDir, "srcfile")
if err := os.WriteFile(srcFile, []byte("x"), 0644); err != nil {
t.Fatalf("write srcfile: %v", err)
}
target := filepath.Join(tmpDir, "targetfile")
err := ensureMountTargetExists(srcFile, target)
if err != nil {
t.Fatalf("ensureMountTargetExists error: %v", err)
}
// Should be a file or at least exist
info, err := os.Lstat(target)
if err != nil {
t.Fatalf("lstat error: %v", err)
}
if info.IsDir() {
t.Errorf("expected file at %s, got directory", target)
}
}

101
pkg/pipe/buffer_test.go Normal file
View File

@ -0,0 +1,101 @@
package pipe
import (
"io"
"strings"
"testing"
"time"
)
func TestNewBuffer_WriteAndRead(t *testing.T) {
const max = 10
buf, err := NewBuffer(max)
if err != nil {
t.Fatalf("NewBuffer error: %v", err)
}
defer buf.W.Close()
// Write less than max bytes
input := "hello"
n, err := buf.W.Write([]byte(input))
if err != nil {
t.Fatalf("Write error: %v", err)
}
if n != len(input) {
t.Errorf("Write bytes = %d, want %d", n, len(input))
}
buf.W.Close()
<-buf.Done
got := buf.Buffer.String()
if got != input {
t.Errorf("Buffer content = %q, want %q", got, input)
}
}
func TestNewBuffer_MaxBytes(t *testing.T) {
const max = 5
buf, err := NewBuffer(max)
if err != nil {
t.Fatalf("NewBuffer error: %v", err)
}
defer buf.W.Close()
// Write more than max bytes
input := "toolonginput"
_, err = io.Copy(buf.W, strings.NewReader(input))
if err != nil {
t.Fatalf("Copy error: %v", err)
}
buf.W.Close()
<-buf.Done
got := buf.Buffer.String()
if len(got) != int(max+1) {
t.Errorf("Buffer length = %d, want %d", len(got), max+1)
}
if got != input[:max+1] {
t.Errorf("Buffer content = %q, want %q", got, input[:max+1])
}
}
func TestBuffer_String(t *testing.T) {
const max = 8
buf, err := NewBuffer(max)
if err != nil {
t.Fatalf("NewBuffer error: %v", err)
}
defer buf.W.Close()
_, _ = buf.W.Write([]byte("abc"))
buf.W.Close()
<-buf.Done
want := "Buffer[3/8]"
if buf.String() != want {
t.Errorf("String() = %q, want %q", buf.String(), want)
}
}
func TestNewBuffer_DoneCloses(t *testing.T) {
const max = 4
buf, err := NewBuffer(max)
if err != nil {
t.Fatalf("NewBuffer error: %v", err)
}
defer buf.W.Close()
done := make(chan struct{})
go func() {
_, _ = buf.W.Write([]byte("test"))
buf.W.Close()
close(done)
}()
select {
case <-buf.Done:
// ok
case <-time.After(1 * time.Second):
t.Fatal("timeout waiting for Done channel")
}
}

136
pkg/rlimit/rlimit_test.go Normal file
View File

@ -0,0 +1,136 @@
//go:build linux
package rlimit
import (
"syscall"
"testing"
)
func TestPrepareRLimit(t *testing.T) {
tests := []struct {
name string
rl RLimits
expect []int
}{
{
name: "Empty",
rl: RLimits{},
expect: []int{},
},
{
name: "CPU only",
rl: RLimits{CPU: 1},
expect: []int{syscall.RLIMIT_CPU},
},
{
name: "Data only",
rl: RLimits{Data: 1024},
expect: []int{syscall.RLIMIT_DATA},
},
{
name: "All fields",
rl: RLimits{CPU: 1, CPUHard: 2, Data: 1024, FileSize: 2048, Stack: 4096, AddressSpace: 8192, OpenFile: 16, DisableCore: true},
expect: []int{syscall.RLIMIT_CPU, syscall.RLIMIT_DATA, syscall.RLIMIT_FSIZE, syscall.RLIMIT_STACK, syscall.RLIMIT_AS, syscall.RLIMIT_NOFILE, syscall.RLIMIT_CORE},
},
{
name: "DisableCore only",
rl: RLimits{DisableCore: true},
expect: []int{syscall.RLIMIT_CORE},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rls := tt.rl.PrepareRLimit()
if len(rls) != len(tt.expect) {
t.Fatalf("expected %d rlimits, got %d", len(tt.expect), len(rls))
}
for i, r := range rls {
if r.Res != tt.expect[i] {
t.Errorf("expected Res %d at %d, got %d", tt.expect[i], i, r.Res)
}
}
})
}
}
func TestRLimitString(t *testing.T) {
tests := []struct {
name string
rl RLimit
want string
}{
{
name: "CPU",
rl: RLimit{Res: syscall.RLIMIT_CPU, Rlim: syscall.Rlimit{Cur: 1, Max: 2}},
want: "CPU[1 s:2 s]",
},
{
name: "NOFILE",
rl: RLimit{Res: syscall.RLIMIT_NOFILE, Rlim: syscall.Rlimit{Cur: 10, Max: 20}},
want: "OpenFile[10:20]",
},
{
name: "DATA",
rl: RLimit{Res: syscall.RLIMIT_DATA, Rlim: syscall.Rlimit{Cur: 1024, Max: 2048}},
want: "Data[1.0 KiB:2.0 KiB]",
},
{
name: "FSIZE",
rl: RLimit{Res: syscall.RLIMIT_FSIZE, Rlim: syscall.Rlimit{Cur: 100, Max: 200}},
want: "File[100 B:200 B]",
},
{
name: "STACK",
rl: RLimit{Res: syscall.RLIMIT_STACK, Rlim: syscall.Rlimit{Cur: 4096, Max: 8192}},
want: "Stack[4.0 KiB:8.0 KiB]",
},
{
name: "AS",
rl: RLimit{Res: syscall.RLIMIT_AS, Rlim: syscall.Rlimit{Cur: 123, Max: 456}},
want: "AddressSpace[123 B:456 B]",
},
{
name: "CORE",
rl: RLimit{Res: syscall.RLIMIT_CORE, Rlim: syscall.Rlimit{Cur: 0, Max: 0}},
want: "Core[0 B:0 B]",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.rl.String()
if got != tt.want {
t.Errorf("got %q, want %q", got, tt.want)
}
})
}
}
func TestRLimitsString(t *testing.T) {
rl := RLimits{
CPU: 1,
CPUHard: 2,
Data: 1024,
FileSize: 2048,
Stack: 4096,
AddressSpace: 8192,
OpenFile: 16,
DisableCore: true,
}
want := "RLimits[CPU[1 s:2 s],Data[1.0 KiB:1.0 KiB],File[2.0 KiB:2.0 KiB],Stack[4.0 KiB:4.0 KiB],AddressSpace[8.0 KiB:8.0 KiB],OpenFile[16:16],Core[0 B:0 B]]"
got := rl.String()
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
func TestRLimitsString_Empty(t *testing.T) {
rl := RLimits{}
want := "RLimits[]"
got := rl.String()
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}

View File

@ -15,7 +15,7 @@ func ToSyscallName(sysno uint) (string, error) {
}
n, ok := info.SyscallNumbers[int(sysno)]
if !ok {
return "", fmt.Errorf("syscall no %d does not exits", sysno)
return "", fmt.Errorf("syscall number does not exist: %d", sysno)
}
return n, nil
}

View File

@ -45,19 +45,19 @@ func NewSocket(fd int) (*Socket, error) {
file := os.NewFile(uintptr(fd), "unix-socket")
if file == nil {
return nil, fmt.Errorf("NewSocket: %d is not a valid fd", fd)
return nil, fmt.Errorf("new socket: %d is not a valid fd", fd)
}
defer file.Close()
conn, err := net.FileConn(file)
if err != nil {
return nil, err
return nil, fmt.Errorf("new socket: fileconn: %w", err)
}
unixConn, ok := conn.(*net.UnixConn)
if !ok {
conn.Close()
return nil, fmt.Errorf("NewSocket: %d is not a valid unix socket connection", fd)
return nil, fmt.Errorf("new socket: %d is not a valid unix socket connection", fd)
}
return newSocket(unixConn), nil
}
@ -66,21 +66,21 @@ func NewSocket(fd int) (*Socket, error) {
func NewSocketPair() (*Socket, *Socket, error) {
fd, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_SEQPACKET|syscall.SOCK_CLOEXEC, 0)
if err != nil {
return nil, nil, fmt.Errorf("NewSocketPair: failed to call socketpair %v", err)
return nil, nil, fmt.Errorf("new socket pair: socketpair: %w", err)
}
ins, err := NewSocket(fd[0])
if err != nil {
syscall.Close(fd[0])
syscall.Close(fd[1])
return nil, nil, fmt.Errorf("NewSocketPair: failed to call NewSocket on sender %v", err)
return nil, nil, fmt.Errorf("new socket pair: sender: %w", err)
}
outs, err := NewSocket(fd[1])
if err != nil {
ins.Close()
syscall.Close(fd[1])
return nil, nil, fmt.Errorf("NewSocketPair: failed to call NewSocket receiver %v", err)
return nil, nil, fmt.Errorf("new socket pair: receiver: %w", err)
}
return ins, outs, nil

View File

@ -2,6 +2,8 @@ package unixsocket
import (
"bytes"
"os"
"syscall"
"testing"
)
@ -26,3 +28,115 @@ func TestBaseline(t *testing.T) {
t.Fatal("not equal")
}
}
func TestSendRecvMsg_Fds(t *testing.T) {
a, b, err := NewSocketPair()
if err != nil {
t.Fatal(err)
}
defer a.Close()
defer b.Close()
// Create a file to send its fd
tmpfile, err := os.CreateTemp("", "unixsocket-fd")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpfile.Name())
defer tmpfile.Close()
msg := []byte("fdtest")
go func() {
a.SendMsg(msg, Msg{Fds: []int{int(tmpfile.Fd())}})
}()
buf := make([]byte, 64)
n, m, err := b.RecvMsg(buf)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(buf[:n], msg) {
t.Errorf("RecvMsg got %q, want %q", buf[:n], msg)
}
if len(m.Fds) != 1 {
t.Errorf("expected 1 fd, got %d", len(m.Fds))
}
if m.Fds != nil {
syscall.Close(m.Fds[0])
}
}
func TestSendRecvMsg_Cred(t *testing.T) {
if os.Geteuid() != 0 {
t.Skip("skipping credential test: requires root privileges")
return
}
a, b, err := NewSocketPair()
if err != nil {
t.Fatal(err)
}
defer a.Close()
defer b.Close()
// Enable credential passing
if err := a.SetPassCred(1); err != nil {
t.Fatal(err)
}
if err := b.SetPassCred(1); err != nil {
t.Fatal(err)
}
msg := []byte("credtest")
go func() {
a.SendMsg(msg, Msg{Cred: &syscall.Ucred{Pid: 123, Uid: 456, Gid: 789}})
}()
buf := make([]byte, 64)
n, m, err := b.RecvMsg(buf)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(buf[:n], msg) {
t.Errorf("RecvMsg got %q, want %q", buf[:n], msg)
}
if m.Cred == nil {
t.Error("expected credential, got nil")
}
}
func TestNewSocketPair_Close(t *testing.T) {
a, b, err := NewSocketPair()
if err != nil {
t.Fatal(err)
}
if err := a.Close(); err != nil {
t.Errorf("a.Close() error: %v", err)
}
if err := b.Close(); err != nil {
t.Errorf("b.Close() error: %v", err)
}
}
func TestNewSocket_InvalidFd(t *testing.T) {
// Use an invalid fd
_, err := NewSocket(-1)
if err == nil {
t.Error("expected error for invalid fd, got nil")
}
}
func TestSetPassCred_InvalidSocket(t *testing.T) {
a, b, err := NewSocketPair()
if err != nil {
t.Fatal(err)
}
defer a.Close()
defer b.Close()
// Close the socket to make it invalid
a.Close()
err = a.SetPassCred(1)
if err == nil {
t.Error("expected error on SetPassCred for closed socket, got nil")
}
}

View File

@ -0,0 +1,155 @@
package filehandler
import (
"maps"
"testing"
)
// Unit test for IsInSetSmart
func TestFileSet_IsInSetSmart(t *testing.T) {
// Create a new FileSet
fs := NewFileSet()
// Add paths to the FileSet
fs.Add("/path/to/file")
fs.Add("/path/to/dir/")
fs.Add("/path/to/dir/*")
fs.Add("/")
// Test cases
tests := []struct {
name string
input string
expected bool
}{
{"Exact match", "/path/to/file", true},
{"Directory match", "/path/to/dir", true},
{"Wildcard match", "/path/to/dir/subfile", true},
{"Root match", "/", true},
{"Non-existent path", "/non/existent/path", false},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := fs.IsInSetSmart(test.input)
if result != test.expected {
t.Errorf("IsInSetSmart(%q) = %v; expected %v", test.input, result, test.expected)
}
})
}
}
// Unit test for Add method
func TestFileSet_Add(t *testing.T) {
// Create a new FileSet
fs := NewFileSet()
if fs.SystemRoot {
t.Errorf("NewFileSet() failed; expected SystemRoot to be false")
}
// Test adding a path that is not the root directory
fs.Add("/path/to/file")
if fs.SystemRoot {
t.Errorf("Add(\"/path/to/file\") failed; expected SystemRoot to be false")
}
// Test adding the root directory
fs.Add("/")
if !fs.SystemRoot {
t.Errorf("Add(\"/\") failed; expected SystemRoot to be true")
}
// Test adding a regular path
fs.Add("/path/to/file")
if !fs.Set["/path/to/file"] {
t.Errorf("Add(\"/path/to/file\") failed; expected path to be in the set")
}
// Test adding another path
fs.Add("/another/path")
if !fs.Set["/another/path"] {
t.Errorf("Add(\"/another/path\") failed; expected path to be in the set")
}
// Test adding a path with a trailing slash
fs.Add("/path/to/dir/")
if !fs.Set["/path/to/dir/"] {
t.Errorf("Add(\"/path/to/dir/\") failed; expected path to be in the set")
}
// Test adding a path with a wildcard
fs.Add("/path/to/dir/*")
if !fs.Set["/path/to/dir/*"] {
t.Errorf("Add(\"/path/to/dir/*\") failed; expected path to be in the set")
}
// Test adding a relative path
fs.Add("relative/path")
if !fs.Set["relative/path"] {
t.Errorf("Add(\"relative/path\") failed; expected path to be in the set")
}
}
// Unit test for AddRange method
func TestFileSet_AddRange(t *testing.T) {
// Create a new FileSet
fs := NewFileSet()
// Test cases
tests := []struct {
name string
paths []string
workPath string
expected map[string]bool
systemRoot bool
}{
{
name: "Add absolute paths",
paths: []string{"/path/to/file", "/another/path"},
workPath: "/work/dir",
expected: map[string]bool{
"/path/to/file": true,
"/another/path": true,
},
systemRoot: false,
},
{
name: "Add root directory",
paths: []string{"/"},
workPath: "/work/dir",
expected: map[string]bool{},
systemRoot: true,
},
{
name: "Add relative paths",
paths: []string{"relative/path", "another/relative/path"},
workPath: "/work/dir",
expected: map[string]bool{
"/work/dir/relative/path/": true,
"/work/dir/another/relative/path/": true,
},
systemRoot: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// Reset the FileSet
fs = NewFileSet()
// Call AddRange
fs.AddRange(test.paths, test.workPath)
// Check SystemRoot
if fs.SystemRoot != test.systemRoot {
t.Errorf("SystemRoot = %v; expected %v", fs.SystemRoot, test.systemRoot)
}
// Check the Set
if !maps.Equal(fs.Set, test.expected) {
t.Errorf("Set = %v; expected %v", fs.Set, test.expected)
}
})
}
}

View File

@ -3,7 +3,7 @@ package ptrace
import (
"fmt"
"os"
"path"
"path/filepath"
"syscall"
"github.com/criyle/go-sandbox/pkg/seccomp/libseccomp"
@ -156,8 +156,8 @@ func getProcCwd(pid int) string {
// built-in function did the dirty works to resolve relative paths
func absPath(pid int, p string) string {
// if relative path
if !path.IsAbs(p) {
return path.Join(getProcCwd(pid), p)
if !filepath.IsAbs(p) {
return filepath.Join(getProcCwd(pid), p)
}
return path.Clean(p)
return filepath.Clean(p)
}

10
test_c/Makefile Normal file
View File

@ -0,0 +1,10 @@
all: helloworld/main.test nonzeroexit/main.test
helloworld/main.test: helloworld/main.cpp
g++ helloworld/main.cpp -o helloworld/main.test -DNDEBUG -O3
nonzeroexit/main.test: nonzeroexit/main.cpp
g++ nonzeroexit/main.cpp -o nonzeroexit/main.test -DNDEBUG -O3
clean:
rm -rf helloworld/*.test nonzeroexit/*.test

View File

@ -0,0 +1,6 @@
#include<iostream>
int main() {
std::cout << "Hello, world" << std::endl;
return 0;
}

View File

@ -0,0 +1,3 @@
int main() {
return 100;
}