mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-11-04 14:49:53 +08:00
553 lines
19 KiB
Go
553 lines
19 KiB
Go
package forkexec
|
|
|
|
import (
|
|
"runtime"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"github.com/criyle/go-sandbox/pkg/forkexec/vfork"
|
|
"github.com/criyle/go-sandbox/pkg/rlimit"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// Reference to src/syscall/exec_linux.go
|
|
//
|
|
//go:noinline
|
|
//go:norace
|
|
//go:nocheckptr
|
|
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
|
|
pid uintptr
|
|
err2 syscall.Errno
|
|
unshareUser = r.CloneFlags&unix.CLONE_NEWUSER == unix.CLONE_NEWUSER
|
|
i int
|
|
rlim rlimit.RLimit
|
|
)
|
|
pipe := p[1]
|
|
|
|
// similar to exec_linux, avoid side effect by shuffling around
|
|
fd, nextfd := prepareFds(r.Files)
|
|
|
|
flag := r.CloneFlags & UnshareFlags
|
|
if r.SyncFunc == nil && !(r.StopBeforeSeccomp || (r.Seccomp != nil && r.Ptrace)) && flag&syscall.CLONE_NEWUSER != syscall.CLONE_NEWUSER {
|
|
flag |= syscall.CLONE_VM | syscall.CLONE_VFORK
|
|
}
|
|
|
|
// use clone3 if cgroupFd specified
|
|
if r.CgroupFd > 0 {
|
|
clone3 = &cloneArgs{
|
|
flags: uint64(flag) | unix.CLONE_INTO_CGROUP,
|
|
exitSignal: uint64(syscall.SIGCHLD),
|
|
cgroup: uint64(r.CgroupFd),
|
|
}
|
|
}
|
|
flag |= uintptr(syscall.SIGCHLD)
|
|
|
|
// Acquire the fork lock so that no other threads
|
|
// create new fds that are not yet close-on-exec
|
|
// before we fork.
|
|
syscall.ForkLock.Lock()
|
|
|
|
// About to call fork.
|
|
// No more allocation or calls of non-assembly functions.
|
|
beforeFork()
|
|
|
|
// UnshareFlags (new namespaces) is activated by clone syscall
|
|
if clone3 != nil {
|
|
r1, err1 = vfork.RawVforkSyscall(unix.SYS_CLONE3, uintptr(unsafe.Pointer(clone3)), unsafe.Sizeof(*clone3), 0)
|
|
} else {
|
|
if runtime.GOARCH == "s390x" {
|
|
// On Linux/s390, the first two arguments of clone(2) are swapped.
|
|
r1, err1 = vfork.RawVforkSyscall(syscall.SYS_CLONE, 0, flag, 0)
|
|
} else {
|
|
r1, err1 = vfork.RawVforkSyscall(syscall.SYS_CLONE, flag, 0, 0)
|
|
}
|
|
}
|
|
if err1 != 0 || r1 != 0 {
|
|
// in parent process, immediate return
|
|
return
|
|
}
|
|
|
|
// In child process
|
|
afterForkInChild()
|
|
// Notice: cannot call any GO functions beyond this point
|
|
|
|
// Close write end of pipe
|
|
if _, _, err1 = syscall.RawSyscall(syscall.SYS_CLOSE, uintptr(p[0]), 0, 0); err1 != 0 {
|
|
childExitError(pipe, LocCloseWrite, err1)
|
|
}
|
|
|
|
// If usernamespace is unshared, uid map and gid map is required to create folders
|
|
// and files
|
|
// We need parent to setup uid_map / gid_map for us since we do not have capabilities
|
|
// in the original namespace
|
|
// At the same time, socket pair / pipe synchronization is required as well
|
|
if unshareUser {
|
|
r1, _, err1 = syscall.RawSyscall(syscall.SYS_READ, uintptr(pipe), uintptr(unsafe.Pointer(&err2)), unsafe.Sizeof(err2))
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocUnshareUserRead, err1)
|
|
}
|
|
if r1 != unsafe.Sizeof(err2) {
|
|
err1 = syscall.EINVAL
|
|
childExitError(pipe, LocUnshareUserRead, err1)
|
|
}
|
|
if err2 != 0 {
|
|
err1 = err2
|
|
childExitError(pipe, LocUnshareUserRead, err1)
|
|
}
|
|
}
|
|
|
|
// Get pid of child
|
|
pid, _, err1 = syscall.RawSyscall(syscall.SYS_GETPID, 0, 0, 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocGetPid, err1)
|
|
}
|
|
|
|
// keep capabilities through set_uid / set_gid calls (make sure we can use unshare cgroup), later dropped
|
|
if r.Credential != nil || r.UnshareCgroupAfterSync {
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_SECUREBITS,
|
|
_SECURE_KEEP_CAPS_LOCKED|_SECURE_NO_SETUID_FIXUP|_SECURE_NO_SETUID_FIXUP_LOCKED, 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocKeepCapability, err1)
|
|
}
|
|
}
|
|
|
|
// set the credential for the child process(exec_linux.go)
|
|
if cred := r.Credential; cred != nil {
|
|
ngroups := uintptr(len(cred.Groups))
|
|
groups := uintptr(0)
|
|
if ngroups > 0 {
|
|
groups = uintptr(unsafe.Pointer(&cred.Groups[0]))
|
|
}
|
|
if !(r.GIDMappings != nil && !r.GIDMappingsEnableSetgroups && ngroups == 0) && !cred.NoSetGroups {
|
|
_, _, err1 = syscall.RawSyscall(unix.SYS_SETGROUPS, ngroups, groups, 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocSetGroups, err1)
|
|
}
|
|
}
|
|
_, _, err1 = syscall.RawSyscall(unix.SYS_SETGID, uintptr(cred.Gid), 0, 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocSetGid, err1)
|
|
}
|
|
_, _, err1 = syscall.RawSyscall(unix.SYS_SETUID, uintptr(cred.Uid), 0, 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocSetUid, err1)
|
|
}
|
|
}
|
|
|
|
// Pass 1 & pass 2 assigns fds for child process
|
|
// Pass 1: fd[i] < i => nextfd
|
|
if pipe < nextfd {
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_DUP3, uintptr(pipe), uintptr(nextfd), syscall.O_CLOEXEC)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocDup3, err1)
|
|
}
|
|
pipe = nextfd
|
|
nextfd++
|
|
}
|
|
if r.ExecFile > 0 && int(r.ExecFile) < nextfd {
|
|
// Avoid fd rewrite
|
|
for nextfd == pipe {
|
|
nextfd++
|
|
}
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_DUP3, r.ExecFile, uintptr(nextfd), syscall.O_CLOEXEC)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocDup3, err1)
|
|
}
|
|
r.ExecFile = uintptr(nextfd)
|
|
nextfd++
|
|
}
|
|
for i = 0; i < len(fd); i++ {
|
|
if fd[i] >= 0 && fd[i] < int(i) {
|
|
// Avoid fd rewrite
|
|
for nextfd == pipe || (r.ExecFile > 0 && nextfd == int(r.ExecFile)) {
|
|
nextfd++
|
|
}
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_DUP3, uintptr(fd[i]), uintptr(nextfd), syscall.O_CLOEXEC)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocDup3, err1)
|
|
}
|
|
// Set up close on exec
|
|
fd[i] = nextfd
|
|
nextfd++
|
|
}
|
|
}
|
|
// Pass 2: fd[i] => i
|
|
for i = 0; i < len(fd); i++ {
|
|
if fd[i] == -1 {
|
|
syscall.RawSyscall(syscall.SYS_CLOSE, uintptr(i), 0, 0)
|
|
continue
|
|
}
|
|
if fd[i] == int(i) {
|
|
// dup2(i, i) will not clear close on exec flag, need to reset the flag
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_FCNTL, uintptr(fd[i]), syscall.F_SETFD, 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocFcntl, err1)
|
|
}
|
|
continue
|
|
}
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_DUP3, uintptr(fd[i]), uintptr(i), 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocDup3, err1)
|
|
}
|
|
}
|
|
|
|
// Set the session ID
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_SETSID, 0, 0, 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocSetSid, err1)
|
|
}
|
|
|
|
// Set the controlling TTY
|
|
if r.CTTY {
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_IOCTL, uintptr(0), uintptr(syscall.TIOCSCTTY), 1)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocIoctl, err1)
|
|
}
|
|
}
|
|
|
|
// Mount file system
|
|
{
|
|
// If mount point is unshared, mark root as private to avoid propagate
|
|
// outside to the original mount namespace
|
|
if r.CloneFlags&syscall.CLONE_NEWNS == syscall.CLONE_NEWNS {
|
|
_, _, err1 = syscall.RawSyscall6(syscall.SYS_MOUNT, uintptr(unsafe.Pointer(&none[0])),
|
|
uintptr(unsafe.Pointer(&slash[0])), 0, syscall.MS_REC|syscall.MS_PRIVATE, 0, 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocMountRoot, err1)
|
|
}
|
|
}
|
|
|
|
// mount tmpfs & chdir to new root before performing mounts
|
|
if pivotRoot != nil {
|
|
// mount("tmpfs", root, "tmpfs", 0, "")
|
|
_, _, err1 = syscall.RawSyscall6(syscall.SYS_MOUNT, uintptr(unsafe.Pointer(&tmpfs[0])),
|
|
uintptr(unsafe.Pointer(pivotRoot)), uintptr(unsafe.Pointer(&tmpfs[0])), 0,
|
|
uintptr(unsafe.Pointer(&empty[0])), 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocMountTmpfs, err1)
|
|
}
|
|
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_CHDIR, uintptr(unsafe.Pointer(pivotRoot)), 0, 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocMountChdir, err1)
|
|
}
|
|
}
|
|
|
|
// performing mounts
|
|
for i, m := range r.Mounts {
|
|
// mkdirs(target)
|
|
for j, p := range m.Prefixes {
|
|
// if target mount point is a file, mknod(target)
|
|
if j == len(m.Prefixes)-1 && m.MakeNod {
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_MKNODAT, uintptr(_AT_FDCWD), uintptr(unsafe.Pointer(p)), 0755)
|
|
if err1 != 0 && err1 != syscall.EEXIST {
|
|
childExitErrorWithIndex(pipe, LocMountMkdir, i, err1)
|
|
}
|
|
break
|
|
}
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_MKDIRAT, uintptr(_AT_FDCWD), uintptr(unsafe.Pointer(p)), 0755)
|
|
if err1 != 0 && err1 != syscall.EEXIST {
|
|
childExitErrorWithIndex(pipe, LocMountMkdir, i, err1)
|
|
}
|
|
}
|
|
// mount(source, target, fsType, flags, data)
|
|
_, _, err1 = syscall.RawSyscall6(syscall.SYS_MOUNT, uintptr(unsafe.Pointer(m.Source)),
|
|
uintptr(unsafe.Pointer(m.Target)), uintptr(unsafe.Pointer(m.FsType)), uintptr(m.Flags),
|
|
uintptr(unsafe.Pointer(m.Data)), 0)
|
|
if err1 != 0 {
|
|
childExitErrorWithIndex(pipe, LocMount, i, err1)
|
|
}
|
|
// bind mount is not respect ro flag so that read-only bind mount needs remount
|
|
if m.Flags&bindRo == bindRo {
|
|
// Ensure the flag retains for bind mount
|
|
const mask = syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_NOEXEC | syscall.MS_NOATIME | syscall.MS_NODIRATIME | syscall.MS_RELATIME
|
|
var s syscall.Statfs_t
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_STATFS, uintptr(unsafe.Pointer(m.Source)), uintptr(unsafe.Pointer(&s)), 0)
|
|
if err1 != 0 {
|
|
childExitErrorWithIndex(pipe, LocMount, i, err1)
|
|
}
|
|
|
|
flag := m.Flags | syscall.MS_REMOUNT | uintptr(s.Flags&mask)
|
|
_, _, err1 = syscall.RawSyscall6(syscall.SYS_MOUNT, uintptr(unsafe.Pointer(&empty[0])),
|
|
uintptr(unsafe.Pointer(m.Target)), uintptr(unsafe.Pointer(m.FsType)),
|
|
uintptr(flag), uintptr(unsafe.Pointer(m.Data)), 0)
|
|
if err1 != 0 {
|
|
childExitErrorWithIndex(pipe, LocMount, i, err1)
|
|
}
|
|
}
|
|
}
|
|
|
|
// pivot_root
|
|
if pivotRoot != nil {
|
|
// mkdir("old_root")
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_MKDIRAT, uintptr(_AT_FDCWD), uintptr(unsafe.Pointer(&oldRoot[0])), 0755)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocPivotRoot, err1)
|
|
}
|
|
|
|
// pivot_root(root, "old_root")
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_PIVOT_ROOT, uintptr(unsafe.Pointer(pivotRoot)), uintptr(unsafe.Pointer(&oldRoot[0])), 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocPivotRoot, err1)
|
|
}
|
|
|
|
// umount("old_root", MNT_DETACH)
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_UMOUNT2, uintptr(unsafe.Pointer(&oldRoot[0])), syscall.MNT_DETACH, 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocPivotRoot, err1)
|
|
}
|
|
|
|
// rmdir("old_root")
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_UNLINKAT, uintptr(_AT_FDCWD), uintptr(unsafe.Pointer(&oldRoot[0])), uintptr(unix.AT_REMOVEDIR))
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocPivotRoot, err1)
|
|
}
|
|
|
|
// mount("tmpfs", "/", "tmpfs", MS_BIND | MS_REMOUNT | MS_RDONLY | MS_NOATIME | MS_NOSUID, nil)
|
|
_, _, err1 = syscall.RawSyscall6(syscall.SYS_MOUNT, uintptr(unsafe.Pointer(&tmpfs[0])),
|
|
uintptr(unsafe.Pointer(&slash[0])), uintptr(unsafe.Pointer(&tmpfs[0])),
|
|
uintptr(syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_NOATIME|syscall.MS_NOSUID),
|
|
uintptr(unsafe.Pointer(&empty[0])), 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocPivotRoot, err1)
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetHostName
|
|
if hostname != nil {
|
|
syscall.RawSyscall(syscall.SYS_SETHOSTNAME,
|
|
uintptr(unsafe.Pointer(hostname)), uintptr(len(r.HostName)), 0)
|
|
}
|
|
|
|
// SetDomainName
|
|
if domainname != nil {
|
|
syscall.RawSyscall(syscall.SYS_SETDOMAINNAME,
|
|
uintptr(unsafe.Pointer(domainname)), uintptr(len(r.DomainName)), 0)
|
|
}
|
|
|
|
// chdir for child
|
|
if workdir != nil {
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_CHDIR, uintptr(unsafe.Pointer(workdir)), 0, 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocChdir, err1)
|
|
}
|
|
}
|
|
|
|
// Set limit
|
|
for i, rlim = range r.RLimits {
|
|
// prlimit instead of setrlimit to avoid 32-bit limitation (linux > 3.2)
|
|
_, _, err1 = syscall.RawSyscall6(syscall.SYS_PRLIMIT64, 0, uintptr(rlim.Res), uintptr(unsafe.Pointer(&rlim.Rlim)), 0, 0, 0)
|
|
if err1 != 0 {
|
|
childExitErrorWithIndex(pipe, LocSetRlimit, i, err1)
|
|
}
|
|
}
|
|
|
|
// No new privs
|
|
if r.NoNewPrivs || r.Seccomp != nil {
|
|
_, _, err1 = syscall.RawSyscall6(syscall.SYS_PRCTL, unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0, 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocSetNoNewPrivs, err1)
|
|
}
|
|
}
|
|
|
|
// Drop all capabilities
|
|
if (r.Credential != nil || r.DropCaps) && !r.UnshareCgroupAfterSync {
|
|
// make sure the children have no privilege at all
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_SECUREBITS,
|
|
_SECURE_KEEP_CAPS_LOCKED|_SECURE_NO_SETUID_FIXUP|_SECURE_NO_SETUID_FIXUP_LOCKED|_SECURE_NOROOT|_SECURE_NOROOT_LOCKED, 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocDropCapability, err1)
|
|
}
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_CAPSET, uintptr(unsafe.Pointer(&dropCapHeader)), uintptr(unsafe.Pointer(&dropCapData)), 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocSetCap, err1)
|
|
}
|
|
}
|
|
|
|
// Enable Ptrace & sync with parent (since ptrace_me is a blocking operation)
|
|
if r.Ptrace && r.Seccomp != nil {
|
|
{
|
|
if r.SyncFunc != nil {
|
|
r1, _, err1 = syscall.RawSyscall(syscall.SYS_WRITE, uintptr(pipe), uintptr(unsafe.Pointer(&err2)), uintptr(unsafe.Sizeof(err2)))
|
|
if r1 == 0 || err1 != 0 {
|
|
childExitError(pipe, LocSyncWrite, err1)
|
|
}
|
|
|
|
r1, _, err1 = syscall.RawSyscall(syscall.SYS_READ, uintptr(pipe), uintptr(unsafe.Pointer(&err2)), uintptr(unsafe.Sizeof(err2)))
|
|
if r1 == 0 || err1 != 0 {
|
|
childExitError(pipe, LocSyncRead, err1)
|
|
}
|
|
}
|
|
|
|
// unshare cgroup namespace
|
|
if r.UnshareCgroupAfterSync {
|
|
// do not error if unshare fails, it is not critical
|
|
syscall.RawSyscall(syscall.SYS_UNSHARE, uintptr(unix.CLONE_NEWCGROUP), 0, 0)
|
|
|
|
if r.DropCaps || r.Credential != nil {
|
|
// make sure the children have no privilege at all
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_SECUREBITS,
|
|
_SECURE_KEEP_CAPS_LOCKED|_SECURE_NO_SETUID_FIXUP|_SECURE_NO_SETUID_FIXUP_LOCKED|_SECURE_NOROOT|_SECURE_NOROOT_LOCKED, 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocKeepCapability, err1)
|
|
}
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_CAPSET, uintptr(unsafe.Pointer(&dropCapHeader)), uintptr(unsafe.Pointer(&dropCapData)), 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocSetCap, err1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_PTRACE, uintptr(syscall.PTRACE_TRACEME), 0, 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocPtraceMe, err1)
|
|
}
|
|
}
|
|
|
|
// if both seccomp and ptrace is defined, then seccomp filter should have
|
|
// traced execve, thus child need parent attached to it first
|
|
// actually, this is not effective if pid namespace is unshared
|
|
if r.StopBeforeSeccomp || (r.Seccomp != nil && r.Ptrace) {
|
|
// Stop to wait for ptrace tracer
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_KILL, pid, uintptr(syscall.SIGSTOP), 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocStop, err1)
|
|
}
|
|
}
|
|
|
|
// Load seccomp, stop and wait for tracer
|
|
if r.Seccomp != nil && (!r.UnshareCgroupAfterSync || r.Ptrace) {
|
|
// If execve is seccomp trapped, then tracee stop is necessary
|
|
// otherwise execve will fail due to ENOSYS
|
|
// Do getpid and kill to send SYS_KILL to self
|
|
// need to do before seccomp as these might be traced
|
|
|
|
// Load seccomp filter
|
|
_, _, err1 = syscall.RawSyscall(unix.SYS_SECCOMP, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, uintptr(unsafe.Pointer(r.Seccomp)))
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocSeccomp, err1)
|
|
}
|
|
}
|
|
|
|
// Before exec, sync with parent through pipe (configured as close_on_exec)
|
|
if !r.Ptrace || r.Seccomp == nil {
|
|
{
|
|
if r.SyncFunc != nil {
|
|
r1, _, err1 = syscall.RawSyscall(syscall.SYS_WRITE, uintptr(pipe), uintptr(unsafe.Pointer(&err2)), uintptr(unsafe.Sizeof(err2)))
|
|
if r1 == 0 || err1 != 0 {
|
|
childExitError(pipe, LocSyncWrite, err1)
|
|
}
|
|
|
|
r1, _, err1 = syscall.RawSyscall(syscall.SYS_READ, uintptr(pipe), uintptr(unsafe.Pointer(&err2)), uintptr(unsafe.Sizeof(err2)))
|
|
if r1 == 0 || err1 != 0 {
|
|
childExitError(pipe, LocSyncRead, err1)
|
|
}
|
|
}
|
|
|
|
// unshare cgroup namespace
|
|
if r.UnshareCgroupAfterSync {
|
|
// do not error if unshare fails, it is not critical
|
|
syscall.RawSyscall(syscall.SYS_UNSHARE, uintptr(unix.CLONE_NEWCGROUP), 0, 0)
|
|
|
|
if r.DropCaps || r.Credential != nil {
|
|
// make sure the children have no privilege at all
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_SECUREBITS,
|
|
_SECURE_KEEP_CAPS_LOCKED|_SECURE_NO_SETUID_FIXUP|_SECURE_NO_SETUID_FIXUP_LOCKED|_SECURE_NOROOT|_SECURE_NOROOT_LOCKED, 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocKeepCapability, err1)
|
|
}
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_CAPSET, uintptr(unsafe.Pointer(&dropCapHeader)), uintptr(unsafe.Pointer(&dropCapData)), 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocSetCap, err1)
|
|
}
|
|
}
|
|
|
|
if r.Seccomp != nil {
|
|
// Load seccomp filter
|
|
_, _, err1 = syscall.RawSyscall(unix.SYS_SECCOMP, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, uintptr(unsafe.Pointer(r.Seccomp)))
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocSeccomp, err1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enable ptrace if no seccomp is needed
|
|
if r.Ptrace && r.Seccomp == nil {
|
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_PTRACE, uintptr(syscall.PTRACE_TRACEME), 0, 0)
|
|
if err1 != 0 {
|
|
childExitError(pipe, LocPtraceMe, err1)
|
|
}
|
|
}
|
|
|
|
// at this point, runner is successfully attached for seccomp trap filter
|
|
// or execve trapped without seccomp filter
|
|
// time to exec
|
|
// if execfile fd is specified, call fexecve
|
|
if r.ExecFile > 0 {
|
|
_, _, err1 = syscall.RawSyscall6(unix.SYS_EXECVEAT, r.ExecFile,
|
|
uintptr(unsafe.Pointer(&empty[0])), uintptr(unsafe.Pointer(&argv[0])),
|
|
uintptr(unsafe.Pointer(&env[0])), unix.AT_EMPTY_PATH, 0)
|
|
} else {
|
|
_, _, err1 = syscall.RawSyscall(unix.SYS_EXECVE, uintptr(unsafe.Pointer(argv0)),
|
|
uintptr(unsafe.Pointer(&argv[0])), uintptr(unsafe.Pointer(&env[0])))
|
|
}
|
|
// Fix potential ETXTBSY but with caution (max 50 attempt)
|
|
// The ETXTBSY happens when we copy the executable into container, another goroutine
|
|
// forks but not execve yet (time consuming for setting up mounting points), the forked
|
|
// process is still holding the fd of the copied executable fd. However, we don't
|
|
// want to have different logic to lock the container creation
|
|
for range [50]struct{}{} {
|
|
if err1 != syscall.ETXTBSY {
|
|
break
|
|
}
|
|
// wait instead of busy wait
|
|
syscall.RawSyscall(unix.SYS_NANOSLEEP, uintptr(unsafe.Pointer(&etxtbsyRetryInterval)), 0, 0)
|
|
if r.ExecFile > 0 {
|
|
_, _, err1 = syscall.RawSyscall6(unix.SYS_EXECVEAT, r.ExecFile,
|
|
uintptr(unsafe.Pointer(&empty[0])), uintptr(unsafe.Pointer(&argv[0])),
|
|
uintptr(unsafe.Pointer(&env[0])), unix.AT_EMPTY_PATH, 0)
|
|
} else {
|
|
_, _, err1 = syscall.RawSyscall(unix.SYS_EXECVE, uintptr(unsafe.Pointer(argv0)),
|
|
uintptr(unsafe.Pointer(&argv[0])), uintptr(unsafe.Pointer(&env[0])))
|
|
}
|
|
}
|
|
childExitError(pipe, LocExecve, err1)
|
|
return
|
|
}
|
|
|
|
//go:nosplit
|
|
func childExitError(pipe int, loc ErrorLocation, err syscall.Errno) {
|
|
// send error code on pipe
|
|
childError := ChildError{
|
|
Err: err,
|
|
Location: loc,
|
|
}
|
|
|
|
// send error code on pipe
|
|
syscall.RawSyscall(unix.SYS_WRITE, uintptr(pipe), uintptr(unsafe.Pointer(&childError)), unsafe.Sizeof(childError))
|
|
for {
|
|
syscall.RawSyscall(syscall.SYS_EXIT, uintptr(err), 0, 0)
|
|
}
|
|
}
|
|
|
|
//go:nosplit
|
|
func childExitErrorWithIndex(pipe int, loc ErrorLocation, idx int, err syscall.Errno) {
|
|
// send error code on pipe
|
|
childError := ChildError{
|
|
Err: err,
|
|
Location: loc,
|
|
Index: idx,
|
|
}
|
|
|
|
// send error code on pipe
|
|
syscall.RawSyscall(unix.SYS_WRITE, uintptr(pipe), uintptr(unsafe.Pointer(&childError)), unsafe.Sizeof(childError))
|
|
for {
|
|
syscall.RawSyscall(syscall.SYS_EXIT, uintptr(err), 0, 0)
|
|
}
|
|
}
|