mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-11-04 14:49:53 +08:00
add ability to forkexec to unshare namespaces
This commit is contained in:
parent
0c79e83a9f
commit
148937fdc2
@ -34,7 +34,8 @@ Default file access action:
|
|||||||
Packages:
|
Packages:
|
||||||
|
|
||||||
- seccomp: provides utility function that wrappers libseccomp
|
- seccomp: provides utility function that wrappers libseccomp
|
||||||
- forkexec: fork-exec provides ptrace and seccomp before exec
|
- mount: provides utility function that wrappers mount syscall
|
||||||
|
- forkexec: fork-exec provides unshare, ptrace, seccomp before exec
|
||||||
- tracer: ptrace tracer and provides syscall trap filter context
|
- tracer: ptrace tracer and provides syscall trap filter context
|
||||||
- runprogram: wrapper to call forkexec and trecer
|
- runprogram: wrapper to call forkexec and trecer
|
||||||
- runconfig: defines arch & language specified trace condition
|
- runconfig: defines arch & language specified trace condition
|
||||||
|
|||||||
23
forkexec/consts.go
Normal file
23
forkexec/consts.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package forkexec
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
// defines missing consts from syscall package
|
||||||
|
const (
|
||||||
|
SECCOMP_SET_MODE_STRICT = 0
|
||||||
|
SECCOMP_SET_MODE_FILTER = 1
|
||||||
|
SECCOMP_FILTER_FLAG_TSYNC = 1
|
||||||
|
|
||||||
|
// CLONE_NEWCGOUP is not included
|
||||||
|
UnshareFlags = syscall.CLONE_NEWIPC | syscall.CLONE_NEWNET | syscall.CLONE_NEWNS |
|
||||||
|
syscall.CLONE_NEWPID | syscall.CLONE_NEWUSER | syscall.CLONE_NEWUTS
|
||||||
|
)
|
||||||
|
|
||||||
|
// used by unshare remount / to private
|
||||||
|
var (
|
||||||
|
none = [...]byte{'n', 'o', 'n', 'e', 0}
|
||||||
|
slash = [...]byte{'/', 0}
|
||||||
|
|
||||||
|
// tmp dir made by pivot_root
|
||||||
|
OldRoot = "old_root"
|
||||||
|
)
|
||||||
195
forkexec/fork.go
195
forkexec/fork.go
@ -25,44 +25,33 @@ func afterForkInChild()
|
|||||||
func (r *Runner) Start() (int, error) {
|
func (r *Runner) Start() (int, error) {
|
||||||
var (
|
var (
|
||||||
err1 syscall.Errno
|
err1 syscall.Errno
|
||||||
workdir *byte
|
|
||||||
nextfd int
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// make exec args0
|
argv0, argv, envv, err := prepareExec(r.Args, r.Env)
|
||||||
argv0, err := syscall.BytePtrFromString(r.Args[0])
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
// make exec args
|
|
||||||
argv, err := syscall.SlicePtrFromStrings(r.Args)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
// make env
|
|
||||||
envv, err := syscall.SlicePtrFromStrings(r.Env)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// make work dir
|
// prepare work dir
|
||||||
if r.WorkDir != "" {
|
workdir, err := syscallStringFromString(r.WorkDir)
|
||||||
workdir, err = syscall.BytePtrFromString(r.WorkDir)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// prepare pivot_root param
|
||||||
|
pivotRoot, oldRoot, err := preparePivotRoot(r.PivotRoot)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare mount param
|
||||||
|
mountParams, dirsToMake, err := prepareMounts(r.Mounts)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// similar to exec_linux, avoid side effect by shuffling around
|
// similar to exec_linux, avoid side effect by shuffling around
|
||||||
fd := make([]int, len(r.Files))
|
fd, nextfd := prepareFds(r.Files)
|
||||||
nextfd = len(r.Files)
|
|
||||||
for i, ufd := range r.Files {
|
|
||||||
if nextfd < int(ufd) {
|
|
||||||
nextfd = int(ufd)
|
|
||||||
}
|
|
||||||
fd[i] = int(ufd)
|
|
||||||
}
|
|
||||||
nextfd++
|
|
||||||
|
|
||||||
// Acquire the fork lock so that no other threads
|
// Acquire the fork lock so that no other threads
|
||||||
// create new fds that are not yet close-on-exec
|
// create new fds that are not yet close-on-exec
|
||||||
@ -73,7 +62,8 @@ func (r *Runner) Start() (int, error) {
|
|||||||
// No more allocation or calls of non-assembly functions.
|
// No more allocation or calls of non-assembly functions.
|
||||||
beforeFork()
|
beforeFork()
|
||||||
|
|
||||||
pid, _, err1 := syscall.RawSyscall6(syscall.SYS_CLONE, uintptr(syscall.SIGCHLD), 0, 0, 0, 0, 0)
|
// UnshareFlags (new namespaces) is activated by clone syscall
|
||||||
|
pid, _, err1 := syscall.RawSyscall6(syscall.SYS_CLONE, uintptr(syscall.SIGCHLD)|(r.UnshareFlags&UnshareFlags), 0, 0, 0, 0, 0)
|
||||||
if err1 != 0 || pid != 0 {
|
if err1 != 0 || pid != 0 {
|
||||||
// restore all signals
|
// restore all signals
|
||||||
afterFork()
|
afterFork()
|
||||||
@ -89,30 +79,13 @@ func (r *Runner) Start() (int, error) {
|
|||||||
afterForkInChild()
|
afterForkInChild()
|
||||||
// Notice: cannot call any GO functions beyond this point
|
// Notice: cannot call any GO functions beyond this point
|
||||||
|
|
||||||
// Set the pgid, so that the wait operation can apply to only certain
|
// Get pid of child
|
||||||
// subgroup of processes
|
pid, _, err1 = syscall.RawSyscall(syscall.SYS_GETPID, 0, 0, 0)
|
||||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_SETPGID, 0, 0, 0)
|
|
||||||
if err1 != 0 {
|
if err1 != 0 {
|
||||||
goto childerror
|
goto childerror
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set limit
|
// Pass 1 & pass 2 assigns fds for child process
|
||||||
for _, 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 {
|
|
||||||
goto childerror
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chdir if needed
|
|
||||||
if workdir != nil {
|
|
||||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_CHDIR, uintptr(unsafe.Pointer(workdir)), 0, 0)
|
|
||||||
if err1 != 0 {
|
|
||||||
goto childerror
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass 1: fd[i] < i => nextfd
|
// Pass 1: fd[i] < i => nextfd
|
||||||
for i := 0; i < len(fd); i++ {
|
for i := 0; i < len(fd); i++ {
|
||||||
if fd[i] >= 0 && fd[i] < int(i) {
|
if fd[i] >= 0 && fd[i] < int(i) {
|
||||||
@ -147,54 +120,142 @@ func (r *Runner) Start() (int, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable Ptrace
|
// Set the pgid, so that the wait operation can apply to only certain
|
||||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_PTRACE, uintptr(syscall.PTRACE_TRACEME), 0, 0)
|
// subgroup of processes
|
||||||
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_SETPGID, 0, 0, 0)
|
||||||
if err1 != 0 {
|
if err1 != 0 {
|
||||||
goto childerror
|
goto childerror
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load seccomp, stop and wait for tracer
|
// If mount point is unshared, mark root as private to avoid propagate
|
||||||
if r.BPF != nil {
|
// outside to the original mount namespace
|
||||||
// Check if support
|
if r.UnshareFlags&syscall.CLONE_NEWNS == syscall.CLONE_NEWNS {
|
||||||
// SECCOMP_SET_MODE_STRICT = 0, args = 1 for invalid operation
|
_, _, err1 = syscall.RawSyscall6(syscall.SYS_MOUNT, uintptr(unsafe.Pointer(&none[0])),
|
||||||
_, _, err1 = syscall.RawSyscall(unix.SYS_SECCOMP, 0, 1, 0)
|
uintptr(unsafe.Pointer(&slash[0])), 0, syscall.MS_REC|syscall.MS_PRIVATE, 0, 0)
|
||||||
if err1 != syscall.EINVAL {
|
if err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// chdir to new root before performing mounts
|
||||||
|
if pivotRoot != nil {
|
||||||
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_CHDIR, uintptr(unsafe.Pointer(pivotRoot)), 0, 0)
|
||||||
|
if err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// performing mounts
|
||||||
|
for i, m := range mountParams {
|
||||||
|
// mkdirs(target)
|
||||||
|
for _, p := range dirsToMake[i] {
|
||||||
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_MKDIR, uintptr(unsafe.Pointer(p)), 0755, 0)
|
||||||
|
if err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pivit_root
|
||||||
|
if pivotRoot != nil {
|
||||||
|
// mkdir(root/old_root)
|
||||||
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_MKDIR, uintptr(unsafe.Pointer(oldRoot)), 0755, 0)
|
||||||
|
if err1 != 0 {
|
||||||
goto childerror
|
goto childerror
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the filter manually
|
// pivot_root(root, root/old_root)
|
||||||
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_MKDIR, uintptr(unsafe.Pointer(pivotRoot)), uintptr(unsafe.Pointer(oldRoot)), 0)
|
||||||
|
if err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
|
||||||
|
// umount(root/old, MNT_DETACH)
|
||||||
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_UMOUNT2, uintptr(unsafe.Pointer(oldRoot)), syscall.MNT_DETACH, 0)
|
||||||
|
if err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
|
||||||
|
// rmdir(root/old_root)
|
||||||
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_RMDIR, uintptr(unsafe.Pointer(oldRoot)), 0755, 0)
|
||||||
|
if err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// chdir for child
|
||||||
|
if workdir != nil {
|
||||||
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_CHDIR, uintptr(unsafe.Pointer(workdir)), 0, 0)
|
||||||
|
if err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set limit
|
||||||
|
for _, 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 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// No new privs
|
// 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)
|
_, _, err1 = syscall.RawSyscall6(syscall.SYS_PRCTL, unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0, 0)
|
||||||
if err1 != 0 {
|
if err1 != 0 {
|
||||||
goto childerror
|
goto childerror
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If execve is seccomp trapped, then tracee stop is necessary
|
// Enable Ptrace
|
||||||
// otherwise execve will fail due to ENOSYS
|
if r.Ptrace {
|
||||||
// Do getpid and kill to send SYS_KILL to self
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_PTRACE, uintptr(syscall.PTRACE_TRACEME), 0, 0)
|
||||||
// need to do before seccomp as these might be traced
|
|
||||||
// Get pid of child
|
|
||||||
pid, _, err1 = syscall.RawSyscall(syscall.SYS_GETPID, 0, 0, 0)
|
|
||||||
if err1 != 0 {
|
if err1 != 0 {
|
||||||
goto childerror
|
goto childerror
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stop to wait for tracer
|
// if both seccomp and ptrace is defined, then seccomp filter should have
|
||||||
|
// traced execve, thus child need parent attached to it first
|
||||||
|
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)
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_KILL, pid, uintptr(syscall.SIGSTOP), 0)
|
||||||
if err1 != 0 {
|
if err1 != 0 {
|
||||||
goto childerror
|
goto childerror
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load seccomp, stop and wait for tracer
|
||||||
|
if r.Seccomp != nil {
|
||||||
|
// 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
|
// Load seccomp filter
|
||||||
// SECCOMP_SET_MODE_FILTER = 1
|
_, _, err1 = syscall.RawSyscall(unix.SYS_SECCOMP, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, uintptr(unsafe.Pointer(r.Seccomp)))
|
||||||
// SECCOMP_FILTER_FLAG_TSYNC = 1
|
|
||||||
_, _, err1 = syscall.RawSyscall(unix.SYS_SECCOMP, 1, 1, uintptr(unsafe.Pointer(r.BPF)))
|
|
||||||
if err1 != 0 {
|
if err1 != 0 {
|
||||||
goto childerror
|
goto childerror
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// at this point, tracer is successfully attached for seccomp trap filter
|
// stop before execve syscall
|
||||||
|
if r.StopBeforeExec {
|
||||||
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_KILL, pid, uintptr(syscall.SIGSTOP), 0)
|
||||||
|
if err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// at this point, runner is successfully attached for seccomp trap filter
|
||||||
// or execve traped without seccomp filter
|
// or execve traped without seccomp filter
|
||||||
// time to exec
|
// time to exec
|
||||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_EXECVE,
|
_, _, err1 = syscall.RawSyscall(syscall.SYS_EXECVE,
|
||||||
|
|||||||
98
forkexec/fork_util.go
Normal file
98
forkexec/fork_util.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package forkexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/criyle/go-judger/mount"
|
||||||
|
)
|
||||||
|
|
||||||
|
// prepareExec prepares execve parameters
|
||||||
|
func prepareExec(Args, Env []string) (*byte, []*byte, []*byte, error) {
|
||||||
|
// make exec args0
|
||||||
|
argv0, err := syscall.BytePtrFromString(Args[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
// make exec args
|
||||||
|
argv, err := syscall.SlicePtrFromStrings(Args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
// make env
|
||||||
|
envv, err := syscall.SlicePtrFromStrings(Env)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
return argv0, argv, envv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareFds prapares fd array
|
||||||
|
func prepareFds(files []uintptr) ([]int, int) {
|
||||||
|
fd := make([]int, len(files))
|
||||||
|
nextfd := len(files)
|
||||||
|
for i, ufd := range files {
|
||||||
|
if nextfd < int(ufd) {
|
||||||
|
nextfd = int(ufd)
|
||||||
|
}
|
||||||
|
fd[i] = int(ufd)
|
||||||
|
}
|
||||||
|
nextfd++
|
||||||
|
return fd, nextfd
|
||||||
|
}
|
||||||
|
|
||||||
|
// syscallStringFromString prepares *byte if string is not empty, other wise nil
|
||||||
|
func syscallStringFromString(str string) (*byte, error) {
|
||||||
|
if str != "" {
|
||||||
|
return syscall.BytePtrFromString(str)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// preparePivotRoot prepares pivot root parameters
|
||||||
|
func preparePivotRoot(r string) (*byte, *byte, error) {
|
||||||
|
if r == "" {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
or := path.Join(r, OldRoot)
|
||||||
|
root, err := syscall.BytePtrFromString(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
oldRoot, err := syscall.BytePtrFromString(or)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return root, oldRoot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareMounts prepare mkdir and mount syscall params
|
||||||
|
func prepareMounts(ms []*mount.Mount) ([]*mount.SyscallParams, [][]*byte, error) {
|
||||||
|
params, err := mount.ToSyscalls(ms)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
// evaluate paths that need to be created
|
||||||
|
pathsToCreate := make([][]*byte, 0, len(ms))
|
||||||
|
for _, m := range ms {
|
||||||
|
prefix := pathPrefix(m.Target)
|
||||||
|
paths, err := syscall.SlicePtrFromStrings(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
pathsToCreate = append(pathsToCreate, paths)
|
||||||
|
}
|
||||||
|
return params, pathsToCreate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pathPrefix get all components from path
|
||||||
|
func pathPrefix(path string) []string {
|
||||||
|
ret := make([]string, 0)
|
||||||
|
for i := 1; i < len(path); i++ {
|
||||||
|
if path[i] == '/' {
|
||||||
|
ret = append(ret, path[:i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret = append(ret, path)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
11
forkexec/rlimit.go
Normal file
11
forkexec/rlimit.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package forkexec
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
// RLimit is the resource limits defined by Linux setrlimit
|
||||||
|
type RLimit struct {
|
||||||
|
// Res is the resource type (e.g. syscall.RLIMIT_CPU)
|
||||||
|
Res int
|
||||||
|
// Rlim is the limit applied to that resource
|
||||||
|
Rlim syscall.Rlimit
|
||||||
|
}
|
||||||
@ -2,12 +2,17 @@
|
|||||||
// executable and ptraced
|
// executable and ptraced
|
||||||
package forkexec
|
package forkexec
|
||||||
|
|
||||||
import "syscall"
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/criyle/go-judger/mount"
|
||||||
|
)
|
||||||
|
|
||||||
// Runner is the RunProgramConfig including the exec path, argv
|
// Runner is the RunProgramConfig including the exec path, argv
|
||||||
// and resource limits. It creates tracee for ptrace-based tracer.
|
// and resource limits. It creates tracee for ptrace-based tracer.
|
||||||
|
// It can also create unshared process in another namespace
|
||||||
type Runner struct {
|
type Runner struct {
|
||||||
// argv and env for the child process
|
// argv and env for execve syscall for the child process
|
||||||
Args []string
|
Args []string
|
||||||
Env []string
|
Env []string
|
||||||
|
|
||||||
@ -17,17 +22,53 @@ type Runner struct {
|
|||||||
// file disriptors for new process, from 0 to len - 1
|
// file disriptors for new process, from 0 to len - 1
|
||||||
Files []uintptr
|
Files []uintptr
|
||||||
|
|
||||||
// work path set by setcwd (current working directory for child)
|
// work path set by chdir(dir) (current working directory for child)
|
||||||
|
// if pivot_root is defined, this will execute after changed to new root
|
||||||
WorkDir string
|
WorkDir string
|
||||||
|
|
||||||
// BPF syscall filter applied to child
|
// seccomp syscall filter applied to child
|
||||||
BPF *syscall.SockFprog
|
Seccomp *syscall.SockFprog
|
||||||
}
|
|
||||||
|
|
||||||
// RLimit is the resource limits defined by Linux setrlimit
|
// ptrace controls child process to call ptrace(PTRACE_TRACEME)
|
||||||
type RLimit struct {
|
// runtime.LockOSThread is required for tracer to call ptrace syscalls
|
||||||
// Res is the resource type (e.g. syscall.RLIMIT_CPU)
|
Ptrace bool
|
||||||
Res int
|
|
||||||
// Rlim is the limit applied to that resource
|
// no_new_privs calls ptctl(PR_SET_NO_NEW_PRIVS) to 0 to disable calls to
|
||||||
Rlim syscall.Rlimit
|
// setuid processes. It is automatically enabled when seccomp filter is provided
|
||||||
|
NoNewPrivs bool
|
||||||
|
|
||||||
|
// stop before seccomp calls kill(getpid(), SIGSTOP) to wait for tracer to continue
|
||||||
|
// right before the calls to seccomp. It is automatically enabled when seccomp
|
||||||
|
// filter and ptrace are provided since kill might not be avaliable after
|
||||||
|
// seccomp and execve might be traced by ptrace
|
||||||
|
StopBeforeSeccomp bool
|
||||||
|
|
||||||
|
// stop before exec calls kill(getpid(), SIGSTOP) to wait for tracer to continue
|
||||||
|
// right before the final calls to execve. It acts as a synchronize
|
||||||
|
// for parent process
|
||||||
|
StopBeforeExec bool
|
||||||
|
|
||||||
|
// unshare flag to create linux namespace, effective when clone child
|
||||||
|
// since unshare syscall does not join the new pid group
|
||||||
|
UnshareFlags uintptr
|
||||||
|
|
||||||
|
// mounts defines the mount syscalls after unshare mount namespace
|
||||||
|
// need CAP_ADMIN inside the namespace (e.g. unshare user namespace)
|
||||||
|
// if pivot root is provided, relative target will based on PivotRoot directory
|
||||||
|
Mounts []*mount.Mount
|
||||||
|
|
||||||
|
// pivot_root defines the new root after unshare mount namespace
|
||||||
|
// root need to be a mount point (e.g. defined in Mounts) and it should
|
||||||
|
// be a absolute path
|
||||||
|
// It will call:
|
||||||
|
// mkdir(root/old_root)
|
||||||
|
// pivot_root(root, root/old_root)
|
||||||
|
// umount(root/old, MNT_DETACH)
|
||||||
|
// rmdir(root/old_root)
|
||||||
|
PivotRoot string
|
||||||
|
|
||||||
|
// drop_caps calls cap_set(self, 0) to drop all capabilities
|
||||||
|
// from effective, permitted, inheritable capability sets before execve
|
||||||
|
// it should avoid calls to set
|
||||||
|
DropCaps bool
|
||||||
}
|
}
|
||||||
|
|||||||
1
go.sum
1
go.sum
@ -1,3 +1,4 @@
|
|||||||
|
github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo=
|
||||||
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
|
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
|
||||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|||||||
81
mount/mount.go
Normal file
81
mount/mount.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package mount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mount defines syscall for mount points
|
||||||
|
type Mount struct {
|
||||||
|
Source, Target, FsType, Data string
|
||||||
|
Flags uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyscallParams defines the raw syscall arguments to mount
|
||||||
|
type SyscallParams struct {
|
||||||
|
Source, Target, FsType, Data *byte
|
||||||
|
Flags uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToSyscall convert Mount to SyscallPrams
|
||||||
|
func (m *Mount) ToSyscall() (*SyscallParams, error) {
|
||||||
|
var data *byte
|
||||||
|
source, err := syscall.BytePtrFromString(m.Source)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
target, err := syscall.BytePtrFromString(m.Target)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fsType, err := syscall.BytePtrFromString(m.FsType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if m.Data != "" {
|
||||||
|
data, err = syscall.BytePtrFromString(m.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &SyscallParams{
|
||||||
|
Source: source,
|
||||||
|
Target: target,
|
||||||
|
FsType: fsType,
|
||||||
|
Flags: m.Flags,
|
||||||
|
Data: data,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount calls mount syscall
|
||||||
|
func (m *Mount) Mount() error {
|
||||||
|
err := os.MkdirAll(m.Target, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return syscall.Mount(m.Source, m.Target, m.FsType, m.Flags, m.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToSyscalls converts arrays of Mounts into SyscallParams
|
||||||
|
func ToSyscalls(ms []*Mount) ([]*SyscallParams, error) {
|
||||||
|
ret := make([]*SyscallParams, 0, len(ms))
|
||||||
|
for _, m := range ms {
|
||||||
|
sp, err := m.ToSyscall()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret = append(ret, sp)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mounts calls multiple mount syscalls
|
||||||
|
func Mounts(ms []*Mount) error {
|
||||||
|
for _, m := range ms {
|
||||||
|
err := m.Mount()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -30,7 +30,8 @@ func (r *RunProgram) Start() (rt tracer.TraceResult, err error) {
|
|||||||
RLimits: r.RLimits.prepareRLimit(),
|
RLimits: r.RLimits.prepareRLimit(),
|
||||||
Files: r.Files,
|
Files: r.Files,
|
||||||
WorkDir: r.WorkDir,
|
WorkDir: r.WorkDir,
|
||||||
BPF: bpf,
|
Seccomp: bpf,
|
||||||
|
Ptrace: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
th := &tracerHandler{
|
th := &tracerHandler{
|
||||||
|
|||||||
@ -35,6 +35,7 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
|||||||
pgid, err := runner.Start()
|
pgid, err := runner.Start()
|
||||||
handler.Debug("tracer started: ", pgid, err)
|
handler.Debug("tracer started: ", pgid, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
handler.Debug("start tracee failed: ", err)
|
||||||
result.TraceStatus = TraceCodeRE
|
result.TraceStatus = TraceCodeRE
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user