github.com/criyle/go-sandbox@v0.10.3/cmd/runprog/main_darwin.go (about) 1 // Command runprog executes program defined restricted environment including seccomp-ptraced, namespaced and containerized. 2 package main 3 4 import ( 5 "flag" 6 "fmt" 7 "os" 8 "syscall" 9 "time" 10 11 "github.com/criyle/go-sandbox/pkg/forkexec" 12 "github.com/criyle/go-sandbox/pkg/rlimit" 13 "github.com/criyle/go-sandbox/runner" 14 "golang.org/x/sys/unix" 15 ) 16 17 var ( 18 timeLimit, realTimeLimit, memoryLimit, outputLimit, stackLimit uint64 19 inputFileName, outputFileName, errorFileName, workPath string 20 21 profilePath, result string 22 showDetails bool 23 24 args []string 25 ) 26 27 func main() { 28 flag.Usage = printUsage 29 flag.Uint64Var(&timeLimit, "tl", 1, "Set time limit (in second)") 30 flag.Uint64Var(&realTimeLimit, "rtl", 0, "Set real time limit (in second)") 31 flag.Uint64Var(&memoryLimit, "ml", 256, "Set memory limit (in mb)") 32 flag.Uint64Var(&outputLimit, "ol", 64, "Set output limit (in mb)") 33 flag.Uint64Var(&stackLimit, "sl", 32, "Set stack limit (in mb)") 34 flag.StringVar(&inputFileName, "in", "", "Set input file name") 35 flag.StringVar(&outputFileName, "out", "", "Set output file name") 36 flag.StringVar(&errorFileName, "err", "", "Set error file name") 37 flag.StringVar(&workPath, "work-path", "", "Set the work path of the program") 38 flag.StringVar(&profilePath, "p", "", "sandbox profile") 39 flag.BoolVar(&showDetails, "show-trace-details", false, "Show trace details") 40 flag.StringVar(&result, "res", "stdout", "Set the file name for output the result") 41 flag.Parse() 42 43 args = flag.Args() 44 if len(args) == 0 { 45 printUsage() 46 } 47 48 if realTimeLimit < timeLimit { 49 realTimeLimit = timeLimit + 2 50 } 51 if stackLimit > memoryLimit { 52 stackLimit = memoryLimit 53 } 54 if workPath == "" { 55 workPath, _ = os.Getwd() 56 } 57 58 var ( 59 f *os.File 60 err error 61 ) 62 if result == "stdout" { 63 f = os.Stdout 64 } else if result == "stderr" { 65 f = os.Stderr 66 } else { 67 f, err = os.Create(result) 68 if err != nil { 69 debug("Failed to open result file:", err) 70 return 71 } 72 defer f.Close() 73 } 74 75 rt, err := start() 76 debug(rt, err) 77 if e, ok := err.(syscall.Errno); ok { 78 debug("errno", int(e)) 79 } 80 81 if rt == nil { 82 rt = &runner.Result{ 83 Status: runner.StatusRunnerError, 84 } 85 } 86 if err == nil && rt.Status != runner.StatusNormal { 87 err = rt.Status 88 } 89 debug("setupTime: ", rt.SetUpTime) 90 debug("runningTime: ", rt.RunningTime) 91 if err != nil { 92 debug(err) 93 c, ok := err.(runner.Status) 94 if !ok { 95 c = runner.StatusRunnerError 96 } 97 // Handle fatal error from trace 98 fmt.Fprintf(f, "%d %d %d %d\n", getStatus(c), int(rt.Time/time.Millisecond), uint64(rt.Memory)>>10, rt.ExitStatus) 99 if c == runner.StatusRunnerError { 100 os.Exit(1) 101 } 102 } else { 103 fmt.Fprintf(f, "%d %d %d %d\n", 0, int(rt.Time/time.Millisecond), uint64(rt.Memory)>>10, rt.ExitStatus) 104 } 105 } 106 107 func start() (*runner.Result, error) { 108 var sTime, mTime, fTime time.Time 109 sTime = time.Now() 110 files, err := prepareFiles(inputFileName, outputFileName, errorFileName) 111 if err != nil { 112 return nil, err 113 } 114 defer closeFiles(files) 115 116 var profile string 117 if profilePath != "" { 118 c, err := os.ReadFile(profilePath) 119 if err != nil { 120 return nil, fmt.Errorf("profile: %v", err) 121 } 122 profile = string(c) 123 } 124 125 // if not defined, then use the original value 126 fds := make([]uintptr, len(files)) 127 for i, f := range files { 128 if f != nil { 129 fds[i] = f.Fd() 130 } else { 131 fds[i] = uintptr(i) 132 } 133 } 134 135 rlims := rlimit.RLimits{ 136 CPU: timeLimit, 137 CPUHard: realTimeLimit, 138 FileSize: outputLimit << 20, 139 Data: memoryLimit << 20, 140 AddressSpace: memoryLimit << 20, 141 Stack: stackLimit << 20, 142 } 143 144 debug(rlims) 145 debug(args) 146 147 r := forkexec.Runner{ 148 Args: args, 149 Env: []string{pathEnv}, 150 RLimits: rlims.PrepareRLimit(), 151 Files: fds, 152 WorkDir: workPath, 153 SandboxProfile: profile, 154 SyncFunc: func(pid int) error { 155 mTime = time.Now() 156 return nil 157 }, 158 } 159 pid, err := r.Start() 160 if err != nil { 161 return nil, err 162 } 163 164 defer func() { 165 killAll(pid) 166 collectZombie(pid) 167 }() 168 169 var ( 170 wstatus syscall.WaitStatus 171 rusage syscall.Rusage 172 ) 173 for { 174 _, err = syscall.Wait4(pid, &wstatus, 0, &rusage) 175 if err == syscall.EINTR { 176 continue 177 } 178 fTime = time.Now() 179 if err != nil { 180 return nil, err 181 } 182 result := runner.Result{ 183 Status: runner.StatusNormal, 184 Time: time.Duration(rusage.Utime.Nano()), 185 Memory: runner.Size(rusage.Maxrss), // seems MacOS uses bytes instead of kb 186 SetUpTime: mTime.Sub(sTime), 187 RunningTime: fTime.Sub(mTime), 188 } 189 if uint64(result.Time) > timeLimit*1e9 { 190 result.Status = runner.StatusTimeLimitExceeded 191 } 192 if uint64(result.Memory) > memoryLimit<<20 { 193 result.Status = runner.StatusMemoryLimitExceeded 194 } 195 196 switch { 197 case wstatus.Exited(): 198 if status := wstatus.ExitStatus(); status != 0 { 199 result.Status = runner.StatusNonzeroExitStatus 200 } 201 return &result, nil 202 203 case wstatus.Signaled(): 204 sig := wstatus.Signal() 205 switch sig { 206 case unix.SIGXCPU, unix.SIGKILL: 207 result.Status = runner.StatusTimeLimitExceeded 208 case unix.SIGXFSZ: 209 result.Status = runner.StatusOutputLimitExceeded 210 case unix.SIGSYS: 211 result.Status = runner.StatusDisallowedSyscall 212 default: 213 result.Status = runner.StatusSignalled 214 } 215 result.ExitStatus = int(sig) 216 return &result, nil 217 } 218 } 219 } 220 221 // kill all tracee according to pids 222 func killAll(pgid int) { 223 unix.Kill(-pgid, unix.SIGKILL) 224 } 225 226 // collect died child processes 227 func collectZombie(pgid int) { 228 var wstatus unix.WaitStatus 229 for { 230 if _, err := unix.Wait4(-pgid, &wstatus, unix.WNOHANG, nil); err != unix.EINTR && err != nil { 231 break 232 } 233 } 234 }