set pgid on MacOS

This commit is contained in:
criyle 2020-05-15 19:01:10 -04:00
parent b6036eb830
commit d664e1eae8
12 changed files with 173 additions and 109 deletions

View File

@ -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...)
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -1,4 +1,4 @@
package darwin
package forkexec
import (
"io/ioutil"

View File

@ -1,4 +1,4 @@
package darwin
package forkexec
import (
"errors"

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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 {