diff --git a/README.md b/README.md index bd874fd..a91a15f 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,8 @@ type Environment interface { - 6.1: `pids.peak` in cgroup v2 - 5.19: `memory.peak` in cgroup v2 +- 5.7: `clone3` with `CLONE_INTO_CGROUP` +- 5.3: `clone3` - 4.15: cgroup v2 (also need support in the Linux distribution) - 4.14: SECCOMP_RET_KILL_PROCESS - 4.6: CLONE_NEWCGROUP diff --git a/pkg/forkexec/clone3_linux.go b/pkg/forkexec/clone3_linux.go new file mode 100644 index 0000000..51f7d66 --- /dev/null +++ b/pkg/forkexec/clone3_linux.go @@ -0,0 +1,17 @@ +package forkexec + +// cloneArgs holds arguments for clone3 Linux syscall. +// from src/syscall/exec_linux.go:196 +type cloneArgs struct { + flags uint64 // Flags bit mask + pidFD uint64 // Where to store PID file descriptor (int *) + childTID uint64 // Where to store child TID, in child's memory (pid_t *) + parentTID uint64 // Where to store child TID, in parent's memory (pid_t *) + exitSignal uint64 // Signal to deliver to parent on child termination + stack uint64 // Pointer to lowest byte of stack + stackSize uint64 // Size of stack + tls uint64 // Location of new TLS + setTID uint64 // Pointer to a pid_t array (since Linux 5.5) + setTIDSize uint64 // Number of elements in set_tid (since Linux 5.5) + cgroup uint64 // File descriptor for target cgroup of child (since Linux 5.7) +} diff --git a/pkg/forkexec/fork_child_linux.go b/pkg/forkexec/fork_child_linux.go index 67992a0..b3c0521 100644 --- a/pkg/forkexec/fork_child_linux.go +++ b/pkg/forkexec/fork_child_linux.go @@ -1,6 +1,7 @@ package forkexec import ( + "runtime" "syscall" "unsafe" @@ -11,9 +12,21 @@ import ( // //go:norace func forkAndExecInChild(r *Runner, argv0 *byte, argv, env []*byte, workdir, hostname, domainname, pivotRoot *byte, p [2]int) (r1 uintptr, err1 syscall.Errno) { + var ( + clone3 *cloneArgs + ) // similar to exec_linux, avoid side effect by shuffling around fd, nextfd := prepareFds(r.Files) + // use clone3 if cgroupFd specified + if r.CgroupFd > 0 { + clone3 = &cloneArgs{ + flags: uint64(r.CloneFlags) | unix.CLONE_INTO_CGROUP, + exitSignal: uint64(syscall.SIGCHLD), + cgroup: uint64(r.CgroupFd), + } + } + // Acquire the fork lock so that no other threads // create new fds that are not yet close-on-exec // before we fork. @@ -24,7 +37,16 @@ func forkAndExecInChild(r *Runner, argv0 *byte, argv, env []*byte, workdir, host beforeFork() // UnshareFlags (new namespaces) is activated by clone syscall - r1, _, err1 = syscall.RawSyscall6(syscall.SYS_CLONE, uintptr(syscall.SIGCHLD)|(r.CloneFlags&UnshareFlags), 0, 0, 0, 0, 0) + if clone3 != nil { + r1, _, err1 = syscall.RawSyscall6(unix.SYS_CLONE3, uintptr(unsafe.Pointer(clone3)), unsafe.Sizeof(*clone3), 0, 0, 0, 0) + } else { + if runtime.GOARCH == "s390x" { + // On Linux/s390, the first two arguments of clone(2) are swapped. + r1, _, err1 = syscall.RawSyscall6(syscall.SYS_CLONE, 0, uintptr(syscall.SIGCHLD)|(r.CloneFlags&UnshareFlags), 0, 0, 0, 0) + } else { + r1, _, err1 = syscall.RawSyscall6(syscall.SYS_CLONE, uintptr(syscall.SIGCHLD)|(r.CloneFlags&UnshareFlags), 0, 0, 0, 0, 0) + } + } if err1 != 0 || r1 != 0 { // in parent process, immediate return return diff --git a/pkg/forkexec/runner_linux.go b/pkg/forkexec/runner_linux.go index 49aa0fd..0909545 100644 --- a/pkg/forkexec/runner_linux.go +++ b/pkg/forkexec/runner_linux.go @@ -61,6 +61,9 @@ type Runner struct { UIDMappings []syscall.SysProcIDMap GIDMappings []syscall.SysProcIDMap + // CgroupFd to use when clone3 with CLONE_INTO_CGROUP with kernel >=5.7 and cgroup v2 + CgroupFd uintptr + // Credential holds user and group identities to be assumed // by a child process started by StartProcess. Credential *syscall.Credential