mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-09-26 23:19:11 +08:00
Compare commits
8 Commits
a6205a428c
...
878c56e102
Author | SHA1 | Date | |
---|---|---|---|
![]() |
878c56e102 | ||
![]() |
2d0d63be76 | ||
![]() |
7aa1c5a28b | ||
![]() |
c63c27d3c2 | ||
![]() |
638345b6eb | ||
![]() |
369229e0d5 | ||
![]() |
d58b2485a2 | ||
![]() |
6407c89d90 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
7
Dockerfile
Normal 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
|
@ -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"},
|
||||
},
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
6
go.mod
@ -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
16
go.sum
@ -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=
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
80
pkg/memfd/memfd_linux_test.go
Normal file
80
pkg/memfd/memfd_linux_test.go
Normal 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 }
|
@ -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
|
||||
|
124
pkg/mount/builder_linux_test.go
Normal file
124
pkg/mount/builder_linux_test.go
Normal 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])
|
||||
}
|
||||
}
|
112
pkg/mount/mount_linux_test.go
Normal file
112
pkg/mount/mount_linux_test.go
Normal 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
101
pkg/pipe/buffer_test.go
Normal 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
136
pkg/rlimit/rlimit_test.go
Normal 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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
155
runner/ptrace/filehandler/fileset_test.go
Normal file
155
runner/ptrace/filehandler/fileset_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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
10
test_c/Makefile
Normal 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
|
6
test_c/helloworld/main.cpp
Normal file
6
test_c/helloworld/main.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
#include<iostream>
|
||||
|
||||
int main() {
|
||||
std::cout << "Hello, world" << std::endl;
|
||||
return 0;
|
||||
}
|
3
test_c/nonzeroexit/main.cpp
Normal file
3
test_c/nonzeroexit/main.cpp
Normal file
@ -0,0 +1,3 @@
|
||||
int main() {
|
||||
return 100;
|
||||
}
|
Loading…
Reference in New Issue
Block a user