diff --git a/README.md b/README.md index 4cb57e6..b532170 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ New Features: 4. Allow pipes as input / output files 5. Use Linux Namespace to isolate file access (elimilate ptrace) 6. Use Linux Control Groups to limit & acct CPU & memory (elimilate wait4.rusage) +7. Container tech with execveat memfd, sethostname, setdomainname Default file access action: @@ -71,4 +72,3 @@ It seems unshare net or ipc takes time, maybe limits action by seccomp instead. TODO: 1. Add ability to pre-fork container deamons -2. Add fork-exec with execve memfd (execveat), sethostname, setdomainname, prepare devices (mknod) diff --git a/cmd/run_program/main.go b/cmd/run_program/main.go index a78c3d0..682d5a5 100644 --- a/cmd/run_program/main.go +++ b/cmd/run_program/main.go @@ -8,6 +8,7 @@ import ( "time" "github.com/criyle/go-judger/cgroup" + "github.com/criyle/go-judger/memfd" "github.com/criyle/go-judger/runconfig" "github.com/criyle/go-judger/runprogram" "github.com/criyle/go-judger/rununshared" @@ -33,13 +34,15 @@ func printUsage() { func main() { var ( addReadable, addWritable, addRawReadable, addRawWritable arrayFlags - allowProc, unsafe, showDetails, namespace, useCGroup bool + allowProc, unsafe, showDetails, namespace, useCGroup, memfile bool pType, result string timeLimit, realTimeLimit, memoryLimit, outputLimit, stackLimit uint64 inputFileName, outputFileName, errorFileName, workPath string - runner Runner - cg *cgroup.CGroup - err error + + runner Runner + cg *cgroup.CGroup + err error + execFile uintptr ) flag.Usage = printUsage @@ -63,6 +66,7 @@ func main() { flag.Var(&addRawWritable, "add-writable-raw", "Add a writable file (don't transform to its real path)") flag.BoolVar(&namespace, "ns", false, "Use namespace to restrict file accesses") flag.BoolVar(&useCGroup, "cgroup", false, "Use cgroup to colloct resource usage") + flag.BoolVar(&memfile, "memfd", false, "Use memfd as exec file") flag.Parse() args := flag.Args() @@ -110,6 +114,21 @@ func main() { return nil } + if memfile { + fin, err := os.Open(args[0]) + if err != nil { + panic(fmt.Sprintln("filed to open args[0]", err)) + } + execf, err := memfd.DupToMemfd("run_program", fin) + if err != nil { + panic(fmt.Sprintln("dup to memfd failed", err)) + } + fin.Close() + defer execf.Close() + execFile = execf.Fd() + println("memfd: ", execFile) + } + // open input / output / err files files, err := prepareFiles(inputFileName, outputFileName, errorFileName) if err != nil { @@ -142,11 +161,12 @@ func main() { panic("cannot make temp root for new namespace") } runner = &rununshared.RunUnshared{ - Args: h.Args, - Env: []string{pathEnv}, - WorkDir: "/w", - Files: fds, - RLimits: rlims, + Args: h.Args, + Env: []string{pathEnv}, + ExecFile: execFile, + WorkDir: "/w", + Files: fds, + RLimits: rlims, ResLimits: specs.ResLimit{ TimeLimit: timeLimit * 1e3, RealTimeLimit: realTimeLimit * 1e3, @@ -162,13 +182,16 @@ func main() { }), ShowDetails: showDetails, SyncFunc: syncFunc, + HostName: "run_program", + DomainName: "run_program", } } else { runner = &runprogram.RunProgram{ - Args: h.Args, - Env: []string{pathEnv}, - WorkDir: workPath, - RLimits: rlims, + Args: h.Args, + Env: []string{pathEnv}, + ExecFile: execFile, + WorkDir: workPath, + RLimits: rlims, TraceLimit: specs.ResLimit{ TimeLimit: timeLimit * 1e3, RealTimeLimit: realTimeLimit * 1e3, diff --git a/forkexec/fork.go b/forkexec/fork.go index 87ef54a..f21f6b1 100644 --- a/forkexec/fork.go +++ b/forkexec/fork.go @@ -41,6 +41,18 @@ func (r *Runner) Start() (int, error) { return 0, err } + // prepare hostname + hostname, err := syscallStringFromString(r.HostName) + if err != nil { + return 0, err + } + + // prepare domainname + domainname, err := syscallStringFromString(r.DomainName) + if err != nil { + return 0, err + } + // prepare pivot_root param pivotRoot, oldRoot, err := preparePivotRoot(r.PivotRoot) if err != nil { @@ -172,6 +184,14 @@ func (r *Runner) Start() (int, error) { pipe = nextfd nextfd++ } + if r.ExecFile > 0 && int(r.ExecFile) < nextfd { + _, _, err1 = syscall.RawSyscall(syscall.SYS_DUP3, r.ExecFile, uintptr(nextfd), syscall.FD_CLOEXEC) + if err1 != 0 { + goto childerror + } + r.ExecFile = uintptr(nextfd) + nextfd++ + } for i := 0; i < len(fd); i++ { if fd[i] >= 0 && fd[i] < int(i) { _, _, err1 = syscall.RawSyscall(syscall.SYS_DUP3, uintptr(fd[i]), uintptr(nextfd), syscall.FD_CLOEXEC) @@ -300,6 +320,18 @@ func (r *Runner) Start() (int, error) { } } + // SetHostName + if hostname != nil { + _, _, err1 = syscall.RawSyscall(syscall.SYS_SETHOSTNAME, + uintptr(unsafe.Pointer(hostname)), uintptr(len(r.HostName)), 0) + } + + // SetDomainName + if domainname != nil { + _, _, err1 = 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) @@ -402,10 +434,18 @@ func (r *Runner) Start() (int, error) { // at this point, runner is successfully attached for seccomp trap filter // or execve traped without seccomp filter // time to exec - _, _, err1 = syscall.RawSyscall(syscall.SYS_EXECVE, - uintptr(unsafe.Pointer(argv0)), - uintptr(unsafe.Pointer(&argv[0])), - uintptr(unsafe.Pointer(&envv[0]))) + // 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(&envv[0])), unix.AT_EMPTY_PATH, 0) + } else { + _, _, err1 = syscall.RawSyscall6(unix.SYS_EXECVEAT, uintptr(_AT_FDCWD), + uintptr(unsafe.Pointer(argv0)), + uintptr(unsafe.Pointer(&argv[0])), + uintptr(unsafe.Pointer(&envv[0])), 0, 0) + } childerror: syscall.RawSyscall(syscall.SYS_EXIT, uintptr(err1), 0, 0) diff --git a/forkexec/runner.go b/forkexec/runner.go index 3de1cff..266425e 100644 --- a/forkexec/runner.go +++ b/forkexec/runner.go @@ -17,6 +17,9 @@ type Runner struct { Args []string Env []string + // if exec_fd is defined, then at the end, fd_execve is called + ExecFile uintptr + // POSIX Resource limit set by set rlimit RLimits []rlimit.RLimit @@ -50,11 +53,14 @@ type Runner struct { UnshareFlags uintptr // mounts defines the mount syscalls after unshare mount namespace - // need CAP_ADMIN inside the namespace (e.g. unshare user namespace) + // need CAP_SYS_ADMIN inside the namespace (e.g. unshare user namespace) // if pivot root is provided, relative target is better for chdir-mount meta // and pivot root will mount as tmpfs before any mount Mounts []*mount.Mount + // HostName and DomainName to be set after unshare UTS & user (CAP_SYS_ADMIN) + HostName, DomainName string + // pivot_root defines a readonly new root after unshare mount namespace // it should be a directory in absolute path // Before mounts, tt will call: diff --git a/runconfig/config.go b/runconfig/config.go index cddf98d..e39e2c7 100644 --- a/runconfig/config.go +++ b/runconfig/config.go @@ -78,7 +78,7 @@ var ( // default syscalls to trace defaultSyscallTraces = []string{ // should be traced - "execve", + "execveat", // file open "open", diff --git a/runprogram/runprogram.go b/runprogram/runprogram.go index 83ba96a..caf196e 100644 --- a/runprogram/runprogram.go +++ b/runprogram/runprogram.go @@ -15,6 +15,9 @@ type RunProgram struct { Env []string WorkDir string + // fexecve + ExecFile uintptr + // file disriptors for new process, from 0 to len - 1 Files []uintptr diff --git a/runprogram/runprogram_run.go b/runprogram/runprogram_run.go index 960c86b..c18cb2d 100644 --- a/runprogram/runprogram_run.go +++ b/runprogram/runprogram_run.go @@ -28,6 +28,7 @@ func (r *RunProgram) Start() (rt specs.TraceResult, err error) { ch := &forkexec.Runner{ Args: r.Args, Env: r.Env, + ExecFile: r.ExecFile, RLimits: r.RLimits.PrepareRLimit(), Files: r.Files, WorkDir: r.WorkDir, diff --git a/rununshared/run.go b/rununshared/run.go index 3685dac..3092830 100644 --- a/rununshared/run.go +++ b/rununshared/run.go @@ -36,6 +36,7 @@ func (r *RunUnshared) Start() (rt specs.TraceResult, err error) { ch := &forkexec.Runner{ Args: r.Args, Env: r.Env, + ExecFile: r.ExecFile, RLimits: r.RLimits.PrepareRLimit(), Files: r.Files, WorkDir: r.WorkDir, @@ -44,6 +45,8 @@ func (r *RunUnshared) Start() (rt specs.TraceResult, err error) { StopBeforeSeccomp: false, UnshareFlags: UnshareFlags, Mounts: r.Mounts, + HostName: r.HostName, + DomainName: r.DomainName, PivotRoot: r.Root, DropCaps: true, SyncFunc: r.SyncFunc, diff --git a/rununshared/rununshared.go b/rununshared/rununshared.go index 1a48a1c..b29a8ed 100644 --- a/rununshared/rununshared.go +++ b/rununshared/rununshared.go @@ -12,6 +12,9 @@ type RunUnshared struct { Args []string Env []string + // fexecve param + ExecFile uintptr + // workdir is the current dir after unshare mount namespaces WorkDir string @@ -33,6 +36,9 @@ type RunUnshared struct { // Mount syscalls Mounts []*mount.Mount + // hostname & domainname + HostName, DomainName string + // Show Details ShowDetails bool