diff --git a/cmd/runprog/main.go b/cmd/runprog/main.go index 5c41f1a..ae90850 100644 --- a/cmd/runprog/main.go +++ b/cmd/runprog/main.go @@ -4,6 +4,8 @@ import ( "flag" "fmt" "os" + + "github.com/criyle/go-sandbox/runner" ) const ( @@ -15,3 +17,45 @@ func printUsage() { flag.PrintDefaults() os.Exit(2) } + +// Status defines uoj/run_program constants +type Status int + +// UOJ run_program constants +const ( + StatusNormal Status = iota // 0 + StatusInvalid // 1 + StatusRE // 2 + StatusMLE // 3 + StatusTLE // 4 + StatusOLE // 5 + StatusBan // 6 + StatusFatal // 7 +) + +func getStatus(s runner.Status) int { + switch s { + case runner.StatusNormal: + return int(StatusNormal) + case runner.StatusInvalid: + return int(StatusInvalid) + case runner.StatusTimeLimitExceeded: + return int(StatusTLE) + case runner.StatusMemoryLimitExceeded: + return int(StatusMLE) + case runner.StatusOutputLimitExceeded: + return int(StatusOLE) + case runner.StatusDisallowedSyscall: + return int(StatusBan) + case runner.StatusSignalled, runner.StatusNonzeroExitStatus: + return int(StatusRE) + default: + return int(StatusFatal) + } +} + +func debug(v ...interface{}) { + if showDetails { + fmt.Fprintln(os.Stderr, v...) + } +} diff --git a/cmd/runprog/main_darwin.go b/cmd/runprog/main_darwin.go index efe40fb..279ebce 100644 --- a/cmd/runprog/main_darwin.go +++ b/cmd/runprog/main_darwin.go @@ -5,7 +5,6 @@ import ( "flag" "fmt" "io/ioutil" - "log" "os" "syscall" "time" @@ -13,13 +12,15 @@ import ( "github.com/criyle/go-sandbox/pkg/forkexec" "github.com/criyle/go-sandbox/pkg/rlimit" "github.com/criyle/go-sandbox/runner" + "golang.org/x/sys/unix" ) var ( timeLimit, realTimeLimit, memoryLimit, outputLimit, stackLimit uint64 inputFileName, outputFileName, errorFileName, workPath string - profilePath string + profilePath, result string + showDetails bool args []string ) @@ -36,6 +37,8 @@ func main() { flag.StringVar(&errorFileName, "err", "", "Set error file name") flag.StringVar(&workPath, "work-path", "", "Set the work path of the program") flag.StringVar(&profilePath, "p", "", "sandbox profile") + flag.BoolVar(&showDetails, "show-trace-details", false, "Show trace details") + flag.StringVar(&result, "res", "stdout", "Set the file name for output the result") flag.Parse() args = flag.Args() @@ -53,8 +56,50 @@ func main() { workPath, _ = os.Getwd() } - ret, err := start() - log.Println(ret, err) + var ( + f *os.File + err error + ) + if result == "stdout" { + f = os.Stdout + } else if result == "stderr" { + f = os.Stderr + } else { + f, err = os.Create(result) + if err != nil { + debug("Failed to open result file:", err) + return + } + defer f.Close() + } + + rt, err := start() + debug(rt, err) + + if rt == nil { + rt = &runner.Result{ + Status: runner.StatusRunnerError, + } + } + if err == nil && rt.Status != runner.StatusNormal { + err = rt.Status + } + debug("setupTime: ", rt.SetUpTime) + debug("runningTime: ", rt.RunningTime) + if err != nil { + debug(err) + c, ok := err.(runner.Status) + if !ok { + c = runner.StatusRunnerError + } + // Handle fatal error from trace + fmt.Fprintf(f, "%d %d %d %d\n", getStatus(c), int(rt.Time/time.Millisecond), uint64(rt.Memory)>>10, rt.ExitStatus) + if c == runner.StatusRunnerError { + os.Exit(1) + } + } else { + fmt.Fprintf(f, "%d %d %d %d\n", 0, int(rt.Time/time.Millisecond), uint64(rt.Memory)>>10, rt.ExitStatus) + } } func start() (*runner.Result, error) { @@ -94,8 +139,8 @@ func start() (*runner.Result, error) { Stack: stackLimit << 20, } - log.Println(rlims) - log.Println(args) + debug(rlims) + debug(args) r := forkexec.Runner{ Args: args, @@ -113,46 +158,75 @@ func start() (*runner.Result, error) { if err != nil { return nil, err } + + defer func() { + killAll(pid) + collectZombie(pid) + }() + var ( wstatus syscall.WaitStatus rusage syscall.Rusage ) - _, err = syscall.Wait4(pid, &wstatus, 0, &rusage) - for err == syscall.EINTR { + for { _, err = syscall.Wait4(pid, &wstatus, 0, &rusage) - } - fTime = time.Now() - if err != nil { - return nil, err - } - result := runner.Result{ - Status: runner.StatusNormal, - Time: time.Duration(rusage.Utime.Nano()), - Memory: runner.Size(rusage.Maxrss), - SetUpTime: mTime.Sub(sTime), - RunningTime: fTime.Sub(mTime), - } - switch { - case wstatus.Exited(): - if status := wstatus.ExitStatus(); status != 0 { - result.Status = runner.StatusNonzeroExitStatus - return &result, nil + if err == syscall.EINTR { + continue + } + fTime = time.Now() + if err != nil { + return nil, err + } + result := runner.Result{ + Status: runner.StatusNormal, + Time: time.Duration(rusage.Utime.Nano()), + Memory: runner.Size(rusage.Maxrss), // seems MacOS uses bytes instead of kb + SetUpTime: mTime.Sub(sTime), + RunningTime: fTime.Sub(mTime), + } + if uint64(result.Time) > timeLimit*1e9 { + result.Status = runner.StatusTimeLimitExceeded + } + if uint64(result.Memory) > memoryLimit<<20 { + result.Status = runner.StatusMemoryLimitExceeded } - case wstatus.Signaled(): - result.Status = runner.StatusSignalled - result.ExitStatus = int(wstatus.Signal()) - return &result, nil + switch { + case wstatus.Exited(): + if status := wstatus.ExitStatus(); status != 0 { + result.Status = runner.StatusNonzeroExitStatus + return &result, nil + } - default: + case wstatus.Signaled(): + sig := wstatus.Signal() + switch sig { + case unix.SIGXCPU, unix.SIGKILL: + result.Status = runner.StatusTimeLimitExceeded + case unix.SIGXFSZ: + result.Status = runner.StatusOutputLimitExceeded + case unix.SIGSYS: + result.Status = runner.StatusDisallowedSyscall + default: + result.Status = runner.StatusSignalled + } + result.ExitStatus = int(sig) + return &result, nil + } + } +} + +// kill all tracee according to pids +func killAll(pgid int) { + unix.Kill(-pgid, unix.SIGKILL) +} + +// collect died child processes +func collectZombie(pgid int) { + var wstatus unix.WaitStatus + for { + if _, err := unix.Wait4(-pgid, &wstatus, unix.WNOHANG, nil); err != unix.EINTR && err != nil { + break + } } - - if uint64(result.Time) > timeLimit*1e9 { - result.Status = runner.StatusTimeLimitExceeded - } - if uint64(result.Memory) > memoryLimit<<20 { - result.Status = runner.StatusMemoryLimitExceeded - } - - return &result, nil } diff --git a/cmd/runprog/main_linux.go b/cmd/runprog/main_linux.go index a910432..e59d3bd 100644 --- a/cmd/runprog/main_linux.go +++ b/cmd/runprog/main_linux.go @@ -400,48 +400,6 @@ func start() (*runner.Result, error) { return &rt, nil } -func debug(v ...interface{}) { - if showDetails { - fmt.Fprintln(os.Stderr, v...) - } -} - -// Status defines uoj/run_program constants -type Status int - -// UOJ run_program constants -const ( - StatusNormal Status = iota // 0 - StatusInvalid // 1 - StatusRE // 2 - StatusMLE // 3 - StatusTLE // 4 - StatusOLE // 5 - StatusBan // 6 - StatusFatal // 7 -) - -func getStatus(s runner.Status) int { - switch s { - case runner.StatusNormal: - return int(StatusNormal) - case runner.StatusInvalid: - return int(StatusInvalid) - case runner.StatusTimeLimitExceeded: - return int(StatusTLE) - case runner.StatusMemoryLimitExceeded: - return int(StatusMLE) - case runner.StatusOutputLimitExceeded: - return int(StatusOLE) - case runner.StatusDisallowedSyscall: - return int(StatusBan) - case runner.StatusSignalled, runner.StatusNonzeroExitStatus: - return int(StatusRE) - default: - return int(StatusFatal) - } -} - type credGen struct { cur uint32 } diff --git a/pkg/forkexec/fork_child_darwin.go b/pkg/forkexec/fork_child_darwin.go index 66c6b76..cb6b27e 100644 --- a/pkg/forkexec/fork_child_darwin.go +++ b/pkg/forkexec/fork_child_darwin.go @@ -37,6 +37,12 @@ func forkAndExecInChild(r *Runner, argv0 *byte, argv, env []*byte, workdir, prof goto childerror } + // Set pg id + _, _, err1 = rawSyscall(funcPC(libc_setpgid_trampoline), 0, 0, 0) + if err1 != 0 { + goto childerror + } + // Pass 1 & pass 2 assigns fds for child process // Pass 1: fd[i] < i => nextfd if pipe < nextfd { diff --git a/pkg/darwin/sandbox_darwin_test.go b/pkg/forkexec/sandbox_darwin_test.go similarity index 97% rename from pkg/darwin/sandbox_darwin_test.go rename to pkg/forkexec/sandbox_darwin_test.go index 5b31772..6d598f7 100644 --- a/pkg/darwin/sandbox_darwin_test.go +++ b/pkg/forkexec/sandbox_darwin_test.go @@ -1,4 +1,4 @@ -package darwin +package forkexec import ( "io/ioutil" diff --git a/pkg/darwin/sandbox_load_darwin.go b/pkg/forkexec/sandbox_load_darwin.go similarity index 97% rename from pkg/darwin/sandbox_load_darwin.go rename to pkg/forkexec/sandbox_load_darwin.go index 203e315..144ed8e 100644 --- a/pkg/darwin/sandbox_load_darwin.go +++ b/pkg/forkexec/sandbox_load_darwin.go @@ -1,4 +1,4 @@ -package darwin +package forkexec import ( "errors" diff --git a/pkg/forkexec/syscall_darwin.go b/pkg/forkexec/syscall_darwin.go index 319ba4b..1b97cb9 100644 --- a/pkg/forkexec/syscall_darwin.go +++ b/pkg/forkexec/syscall_darwin.go @@ -3,12 +3,8 @@ package forkexec import ( "syscall" _ "unsafe" // use go:linkname - - "github.com/criyle/go-sandbox/pkg/darwin" // use sandbox_init ) -var _ = darwin.SandboxInit - //go:linkname funcPC syscall.funcPC func funcPC(f func()) uintptr @@ -51,11 +47,8 @@ func libc_execve_trampoline() //go:linkname libc_exit_trampoline syscall.libc_exit_trampoline func libc_exit_trampoline() -//go:linkname libc_sandbox_init_trampoline github.com/criyle/go-sandbox/pkg/darwin.libc_sandbox_init_trampoline -func libc_sandbox_init_trampoline() - -//go:linkname libc_sandbox_free_error_trampoline github.com/criyle/go-sandbox/pkg/darwin.libc_sandbox_free_error_trampoline -func libc_sandbox_free_error_trampoline() +//go:linkname libc_setpgid_trampoline syscall.libc_setpgid_trampoline +func libc_setpgid_trampoline() //go:linkname fcntl syscall.fcntl func fcntl(fd int, cmd int, arg int) (val int, err error) diff --git a/pkg/darwin/test.sb b/pkg/forkexec/test.sb similarity index 100% rename from pkg/darwin/test.sb rename to pkg/forkexec/test.sb diff --git a/pkg/darwin/syscall_darwin.go b/pkg/forkexec/zsyscall_darwin.go similarity index 82% rename from pkg/darwin/syscall_darwin.go rename to pkg/forkexec/zsyscall_darwin.go index 5626caf..243e0c4 100644 --- a/pkg/darwin/syscall_darwin.go +++ b/pkg/forkexec/zsyscall_darwin.go @@ -1,4 +1,4 @@ -package darwin +package forkexec import ( "syscall" @@ -31,9 +31,3 @@ func libc_sandbox_free_error_trampoline() //go:linkname libc_sandbox_free_error libc_sandbox_free_error //go:cgo_import_dynamic libc_sandbox_free_error sandbox_free_error "/usr/lib/libSystem.B.dylib" - -//go:linkname syscall3 syscall.syscall -func syscall3(fn, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) - -//go:linkname funcPC syscall.funcPC -func funcPC(f func()) uintptr diff --git a/pkg/darwin/zsyscall_darwin.s b/pkg/forkexec/zsyscall_darwin.s similarity index 100% rename from pkg/darwin/zsyscall_darwin.s rename to pkg/forkexec/zsyscall_darwin.s diff --git a/pkg/seccomp/filter.go b/pkg/seccomp/filter.go deleted file mode 100644 index c22bf55..0000000 --- a/pkg/seccomp/filter.go +++ /dev/null @@ -1,7 +0,0 @@ -// Package seccomp provides a generated filter format for seccomp filter -package seccomp - -import "syscall" - -// Filter is the BPF seccomp filter value -type Filter []syscall.SockFilter diff --git a/pkg/seccomp/filter_linux.go b/pkg/seccomp/filter_linux.go index 1f8060c..71685d4 100644 --- a/pkg/seccomp/filter_linux.go +++ b/pkg/seccomp/filter_linux.go @@ -1,8 +1,10 @@ +// Package seccomp provides a generated filter format for seccomp filter package seccomp -import ( - "syscall" -) +import "syscall" + +// Filter is the BPF seccomp filter value +type Filter []syscall.SockFilter // SockFprog converts Filter to SockFprog for seccomp syscall func (f Filter) SockFprog() *syscall.SockFprog {