// waitStatusToStateUpdate converts syscall.WaitStatus to a StateUpdate. func waitStatusToStateUpdate(ws syscall.WaitStatus) *stateUpdate { switch { case ws.Exited(): es := ws.ExitStatus() if es == 0 { return newExitedStateUpdate(ok) } return newExitedStateUpdate(newFailure(fmt.Sprint(es))) case ws.Signaled(): msg := fmt.Sprintf("signaled %v", ws.Signal()) if ws.CoreDump() { msg += " (core dumped)" } return newUnexitedStateUpdate(msg) case ws.Stopped(): msg := fmt.Sprintf("stopped %v", ws.StopSignal()) trap := ws.TrapCause() if trap != -1 { msg += fmt.Sprintf(" (trapped %v)", trap) } return newUnexitedStateUpdate(msg) case ws.Continued(): return newUnexitedStateUpdate("continued") default: return newUnexitedStateUpdate(fmt.Sprint("unknown status", ws)) } }
func babySit(process *os.Process) int { // Forward all signals to the app sigchan := make(chan os.Signal, 1) sigutil.CatchAll(sigchan) go func() { for sig := range sigchan { if sig == syscall.SIGCHLD { continue } process.Signal(sig) } }() // Wait for the app to exit. Also, as pid 1 it's our job to reap all // orphaned zombies. var wstatus syscall.WaitStatus for { pid, err := syscall.Wait4(-1, &wstatus, 0, nil) if err == nil && pid == process.Pid { break } } if wstatus.Signaled() { return 0 } return wstatus.ExitStatus() }
func waitForContainerToExit(dir string, containerPid int, signals chan os.Signal) (exitCode int) { for range signals { for { var status syscall.WaitStatus var rusage syscall.Rusage wpid, err := syscall.Wait4(-1, &status, syscall.WNOHANG, &rusage) if err != nil || wpid <= 0 { break // wait for next SIGCHLD } if wpid == containerPid { exitCode = status.ExitStatus() if status.Signaled() { exitCode = 128 + int(status.Signal()) } ioWg.Wait() // wait for full output to be collected check(ioutil.WriteFile(filepath.Join(dir, "exitcode"), []byte(strconv.Itoa(exitCode)), 0700)) return exitCode } } } panic("ran out of signals") // cant happen }
func ChildWaitingFunc(pid int, sig chan *ChildWaitData) { var status syscall.WaitStatus var rusage syscall.Rusage result := &ChildWaitData{} for { wpid, err := syscall.Wait4(pid, &status, syscall.WUNTRACED|syscall.WCONTINUED, &rusage) if wpid != pid { continue } if status.Exited() { result.ExitCode = uint32(status.ExitStatus()) break } if status.Stopped() { result.SuccessCode |= EF_STOPPED result.StopSignal = uint32(status.StopSignal()) syscall.Kill(pid, syscall.SIGKILL) } if status.Signaled() { result.SuccessCode |= EF_KILLED_BY_OTHER result.KillSignal = uint32(status.Signal()) break } if err != nil { break } } result.RusageCpuUser = time.Nanosecond * time.Duration(rusage.Utime.Nano()) result.RusageCpuKernel = time.Nanosecond * time.Duration(rusage.Stime.Nano()) sig <- result close(sig) }
// waitStatusToError converts syscall.WaitStatus to an Error. func waitStatusToError(ws syscall.WaitStatus) error { switch { case ws.Exited(): es := ws.ExitStatus() if es == 0 { return nil } return errors.New(fmt.Sprint(es)) case ws.Signaled(): msg := fmt.Sprintf("signaled %v", ws.Signal()) if ws.CoreDump() { msg += " (core dumped)" } return errors.New(msg) case ws.Stopped(): msg := fmt.Sprintf("stopped %v", ws.StopSignal()) trap := ws.TrapCause() if trap != -1 { msg += fmt.Sprintf(" (trapped %v)", trap) } return errors.New(msg) /* case ws.Continued(): return newUnexitedStateUpdate("continued") */ default: return fmt.Errorf("unknown WaitStatus", ws) } }
func handleSigchld(mpid int) *resultPack { for { var status syscall.WaitStatus var spid int var err error spid, err = syscall.Wait4(-mpid, &status, syscall.WNOHANG|syscall.WALL, nil) if err != nil { poePanic(err, "wait4 failed") } else if spid == 0 { return nil } if spid == mpid && status.Exited() { return &resultPack{POE_SUCCESS, status.ExitStatus(), ""} } else if spid == mpid && status.Signaled() { return &resultPack{POE_SIGNALED, -1, fmt.Sprintf("Program terminated with signal %d (%s)", int(status.Signal()), status.Signal().String())} } else if status.Stopped() { e := status >> 16 & 0xff switch e { case PTRACE_EVENT_SECCOMP: if res := handleSyscall(spid); res != nil { return res } case syscall.PTRACE_EVENT_CLONE, syscall.PTRACE_EVENT_FORK, syscall.PTRACE_EVENT_VFORK: syscall.PtraceCont(spid, 0) default: syscall.PtraceCont(spid, int(status.StopSignal())) } } } }
func babySit(process *os.Process) int { log := logger.New("fn", "babySit") // Forward all signals to the app sigchan := make(chan os.Signal, 1) sigutil.CatchAll(sigchan) go func() { for sig := range sigchan { log.Info("received signal", "type", sig) if sig == syscall.SIGCHLD { continue } log.Info("forwarding signal to command", "type", sig) process.Signal(sig) } }() // Wait for the app to exit. Also, as pid 1 it's our job to reap all // orphaned zombies. var wstatus syscall.WaitStatus for { pid, err := syscall.Wait4(-1, &wstatus, 0, nil) if err == nil && pid == process.Pid { break } } if wstatus.Signaled() { log.Info("command exited due to signal") return 0 } return wstatus.ExitStatus() }
func monitor(active chan bool, notify chan notification) { for { monitoring := <-active for monitoring { var rusage syscall.Rusage var status syscall.WaitStatus options := syscall.WUNTRACED pid, err := syscall.Wait4(-1, &status, options, &rusage) if err != nil { println("Wait4:", err.Error()) } if pid <= 0 { break } if status.Stopped() { if pid == task0.Job.Group { incoming <- syscall.SIGTSTP } continue } if status.Signaled() { if status.Signal() == syscall.SIGINT && pid == task0.Job.Group { incoming <- syscall.SIGINT } status += 128 } notify <- notification{pid, status} monitoring = <-active } } }
func printStatus(ws syscall.WaitStatus) string { switch { case ws.Exited(): es := ws.ExitStatus() if es == 0 { return "" } return fmt.Sprintf("exited %v", es) case ws.Signaled(): msg := fmt.Sprintf("signaled %v", ws.Signal()) if ws.CoreDump() { msg += " (core dumped)" } return msg case ws.Stopped(): msg := fmt.Sprintf("stopped %v", ws.StopSignal()) trap := ws.TrapCause() if trap != -1 { msg += fmt.Sprintf(" (trapped %v)", trap) } return msg case ws.Continued(): return "continued" default: return fmt.Sprintf("unknown status %v", ws) } }
func (self *server) Execute(req *pb.ExecutionRequest, resp pb.Builder_ExecuteServer) error { if len(req.Args) == 0 { return fmt.Errorf("Request has no command to execute.") } var args []string if len(req.Args) > 1 { args = req.Args[1:] } if req.BuildEnv == nil { return fmt.Errorf("No build environment present") } else if err := sig.VerifyEnv(req.BuildEnv); err != nil { return fmt.Errorf("Failure verifying build environment: %s", err) } cmd := exec.Command(req.Args[0], args...) cmd.Env = convertEnv(req.GetEnv()) cmd.Stdin = bytes.NewReader(req.Stdin) cmd.Dir = req.BuildEnv.Path glog.V(1).Infof("Commands to execute %v (build dir: %s)", req, cmd.Dir) stdoutPipe, err := cmd.StdoutPipe() if err != nil { return err } stderrPipe, err := cmd.StderrPipe() if err != nil { return err } if err = cmd.Start(); err != nil { return err } streamReader(stdoutPipe, stderrPipe, resp) err = cmd.Wait() var status syscall.WaitStatus if err != nil { if _, ok := err.(*exec.ExitError); ok { status = err.(*exec.ExitError).Sys().(syscall.WaitStatus) } else { return err } } else if cmd.ProcessState != nil { status = cmd.ProcessState.Sys().(syscall.WaitStatus) } s := &pb.Status{ CoreDump: status.CoreDump(), Exited: status.Exited(), ExitStatus: int32(status.ExitStatus()), Signaled: status.Signaled(), Signal: int32(status.Signal()), } resp.Send(&pb.ExecutionResponse{Status: s}) return nil }
func (t *PTracer) handleStopped(pid int, status syscall.WaitStatus) { signal := syscall.Signal(0) target, err := t.thread(pid) if err != nil { log.Printf("thread failed: %v", err) return } if !target.attached { target.attached = true err = syscall.PtraceSetOptions(pid, ptraceOptions) if err != nil { log.Printf("SetOptions failed, pid=%d, err=%v", pid, err) return } } else if status.Stopped() && status.StopSignal() == syscall.SIGTRAP|ptraceTracesysgoodBit { // pid entered Syscall-enter-stop or syscall-exit-stop target.syscallStopped() } else if status.Stopped() && status.StopSignal() == syscall.SIGTRAP { // pid entered PTRACE_EVENT stop switch status.TrapCause() { case syscall.PTRACE_EVENT_CLONE: err := target.handleClone(pid) if err != nil { log.Printf("clone failed: %v", err) return } default: log.Printf("Unknown PTRACE_EVENT %d for pid %d", status.TrapCause(), pid) } } else if status.Exited() || status.Signaled() { // "tracer can safely assume pid will exit" t.threadExited(target) return } else if status.Stopped() { // tracee received a non-trace related signal signal = status.StopSignal() if signal == syscall.SIGSTOP && target.process.detaching { t.detachThread(target) return } } else { // unknown stop - shouldn't happen! log.Printf("Pid %d random stop with status %x", pid, status) } // Restart stopped caller in syscall trap mode. // log.Printf("Restarting pid %d with signal %d", pid, int(signal)) err = syscall.PtraceSyscall(pid, int(signal)) if err != nil { log.Printf("PtraceSyscall failed, pid=%d, err=%v", pid, err) } }
func toGuardMode(args []string) { _, pName := path.Split(os.Args[0]) for { pid, err := syscall.ForkExec(args[0], args, nil) if err != nil { log.Fatalf("Error - %s fork failed! [%s]", pName, err.Error()) os.Exit(1) } childPid = pid var ws syscall.WaitStatus _, err = syscall.Wait4(childPid, &ws, 0, nil) for err == syscall.EINTR { _, err = syscall.Wait4(childPid, &ws, 0, nil) } // Log lf, fErr := os.OpenFile("debug.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if fErr != nil { log.Fatalf("Error - open debug.log failed! [%s]", fErr.Error()) } defer lf.Close() l := log.New(lf, "", os.O_APPEND) if ws.Exited() { l.Printf("[%s] Exited - %s exit status %d", sTime(), pName, ws.ExitStatus()) os.Exit(1) } if ws.Signaled() { l.Printf("[%s] Exited - %s catch signal %d", sTime(), pName, ws.Signal()) } time.Sleep(time.Second * 2) // After release os resouce } }
// ExitStatus returns the correct exit status for a process based on if it // was signaled or exited cleanly func ExitStatus(status syscall.WaitStatus) int { if status.Signaled() { return exitSignalOffset + int(status.Signal()) } return status.ExitStatus() }
func Tracer() { p := new(oz.Profile) if err := json.NewDecoder(os.Stdin).Decode(&p); err != nil { log.Error("unable to decode profile data: %v", err) os.Exit(1) } var proc_attr syscall.ProcAttr var sys_attr syscall.SysProcAttr sys_attr.Ptrace = true done := false proc_attr.Sys = &sys_attr cmd := os.Args[1] cmdArgs := os.Args[2:] log.Info("Tracer running command (%v) arguments (%v)\n", cmd, cmdArgs) c := exec.Command(cmd) c.SysProcAttr = &syscall.SysProcAttr{Ptrace: true} c.Env = os.Environ() c.Args = append(c.Args, cmdArgs...) pi, err := c.StdinPipe() if err != nil { fmt.Errorf("error creating stdin pipe for tracer process: %v", err) os.Exit(1) } jdata, err := json.Marshal(p) if err != nil { fmt.Errorf("Unable to marshal seccomp state: %+v", err) os.Exit(1) } io.Copy(pi, bytes.NewBuffer(jdata)) log.Info(string(jdata)) pi.Close() children := make(map[int]bool) renderFunctions := getRenderingFunctions() if err := c.Start(); err == nil { children[c.Process.Pid] = true var s syscall.WaitStatus pid, err := syscall.Wait4(-1, &s, syscall.WALL, nil) children[pid] = true if err != nil { log.Error("Error (wait4) here first: %v %i", err, pid) } log.Info("Tracing child pid: %v\n", pid) for done == false { syscall.PtraceSetOptions(pid, unix.PTRACE_O_TRACESECCOMP|unix.PTRACE_O_TRACEFORK|unix.PTRACE_O_TRACEVFORK|unix.PTRACE_O_TRACECLONE) syscall.PtraceCont(pid, 0) pid, err = syscall.Wait4(-1, &s, syscall.WALL, nil) if err != nil { log.Error("Error (wait4) here: %v %i %v\n", err, pid, children) if len(children) == 0 { done = true } continue } children[pid] = true if s.Exited() == true { delete(children, pid) log.Info("Child pid %v finished.\n", pid) if len(children) == 0 { done = true } continue } if s.Signaled() == true { log.Error("Other pid signalled %v %v", pid, s) delete(children, pid) continue } switch uint32(s) >> 8 { case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_SECCOMP << 8): if err != nil { log.Error("Error (ptrace): %v", err) continue } var regs syscall.PtraceRegs err = syscall.PtraceGetRegs(pid, ®s) if err != nil { log.Error("Error (ptrace): %v", err) } systemcall, err := syscallByNum(getSyscallNumber(regs)) if err != nil { log.Error("Error: %v", err) continue } /* Render the system call invocation */ r := getSyscallRegisterArgs(regs) call := "" if f, ok := renderFunctions[getSyscallNumber(regs)]; ok { call, err = f(pid, r) if err != nil { log.Info("%v", err) continue } } else { call = renderSyscallBasic(pid, systemcall, regs) } log.Info("==============================================\nseccomp hit on sandbox pid %v (%v) syscall %v (%v):\n\n%s\nI ==============================================\n\n", pid, getProcessCmdLine(pid), systemcall.name, systemcall.num, call) continue case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_EXIT << 8): log.Error("Ptrace exit event detected pid %v (%s)", pid, getProcessCmdLine(pid)) case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_CLONE << 8): log.Error("Ptrace clone event detected pid %v (%s)", pid, getProcessCmdLine(pid)) continue case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_FORK << 8): log.Error("PTrace fork event detected pid %v (%s)", pid, getProcessCmdLine(pid)) continue case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_VFORK << 8): log.Error("Ptrace vfork event detected pid %v (%s)", pid, getProcessCmdLine(pid)) continue case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_VFORK_DONE << 8): log.Error("Ptrace vfork done event detected pid %v (%s)", pid, getProcessCmdLine(pid)) continue case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_EXEC << 8): log.Error("Ptrace exec event detected pid %v (%s)", pid, getProcessCmdLine(pid)) continue case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_STOP << 8): log.Error("Ptrace stop event detected pid %v (%s)", pid, getProcessCmdLine(pid)) continue case uint32(unix.SIGTRAP): log.Error("SIGTRAP detected in pid %v (%s)", pid, getProcessCmdLine(pid)) continue case uint32(unix.SIGCHLD): log.Error("SIGCHLD detected pid %v (%s)", pid, getProcessCmdLine(pid)) continue case uint32(unix.SIGSTOP): log.Error("SIGSTOP detected pid %v (%s)", pid, getProcessCmdLine(pid)) continue default: y := s.StopSignal() log.Error("Child stopped for unknown reasons pid %v status %v signal %i (%s)", pid, s, y, getProcessCmdLine(pid)) continue } } } }
// childReaper is used to handle events from child processes, including child exit. // If running as pid=1 then this means it handles zombie process reaping for orphaned children // as well as direct child processes. func (t *tether) childReaper() error { signal.Notify(t.incoming, syscall.SIGCHLD) /* PR_SET_CHILD_SUBREAPER (since Linux 3.4) If arg2 is nonzero, set the "child subreaper" attribute of the calling process; if arg2 is zero, unset the attribute. When a process is marked as a child subreaper, all of the children that it creates, and their descendants, will be marked as having a subreaper. In effect, a subreaper fulfills the role of init(1) for its descendant processes. Upon termination of a process that is orphaned (i.e., its immediate parent has already terminated) and marked as having a subreaper, the nearest still living ancestor subreaper will receive a SIGCHLD signal and be able to wait(2) on the process to discover its termination status. */ if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, SetChildSubreaper, uintptr(1), 0); err != 0 { return err } log.Info("Started reaping child processes") go func() { for _ = range t.incoming { var status syscall.WaitStatus func() { // general resiliency defer recover() // reap until no more children to process for { log.Debugf("Inspecting children with status change") pid, err := syscall.Wait4(-1, &status, syscall.WNOHANG, nil) if pid == 0 || err == syscall.ECHILD { log.Debug("No more child processes to reap") break } if err == nil { if !status.Exited() && !status.Signaled() { log.Debugf("Received notifcation about non-exit status change for %d: %d", pid, status) // no reaping or exit handling required continue } log.Debugf("Reaped process %d, return code: %d", pid, status.ExitStatus()) session, ok := t.removeChildPid(pid) if ok { // will be -1 if terminated due to signal session.ExitStatus = status.ExitStatus() t.handleSessionExit(session) } else { // This is an adopted zombie. The Wait4 call // already clean it up from the kernel log.Infof("Reaped zombie process PID %d\n", pid) } } else { log.Warnf("Wait4 got error: %v\n", err) } } }() } }() return nil }
func babySit(init *ContainerInit, hbs []discoverd.Heartbeater) int { log := logger.New() var shutdownOnce sync.Once hbDone := make(chan struct{}) closeHBs := func() { for _, hb := range hbs { if err := hb.Close(); err != nil { log.Error("error deregistering service", "addr", hb.Addr(), "err", err) } else { log.Info("service deregistered", "addr", hb.Addr()) } } close(hbDone) } // Close the heartbeaters if requested to do so go func() { <-init.deregister log.Info("received deregister request") shutdownOnce.Do(closeHBs) }() // Forward all signals to the app sigchan := make(chan os.Signal, 1) sigutil.CatchAll(sigchan) go func() { for sig := range sigchan { log.Info("received signal", "type", sig) if sig == syscall.SIGCHLD { continue } if sig == syscall.SIGTERM || sig == syscall.SIGINT { shutdownOnce.Do(closeHBs) } log.Info("forwarding signal to job", "type", sig) init.process.Signal(sig) } }() // Wait for the app to exit. Also, as pid 1 it's our job to reap all // orphaned zombies. var wstatus syscall.WaitStatus for { pid, err := syscall.Wait4(-1, &wstatus, 0, nil) if err == nil && pid == init.process.Pid { break } } // Ensure that the heartbeaters are closed even if the app wasn't signaled shutdownOnce.Do(closeHBs) select { case <-hbDone: case <-time.After(5 * time.Second): log.Error("timed out waiting for services to be deregistered") } if wstatus.Signaled() { log.Debug("job exited due to signal") return 0 } return wstatus.ExitStatus() }
func Run(startChan <-chan int, stopChan chan struct{}, appName string, appArgs []string, dirName string) <-chan *report.PtMonitorReport { log.Info("ptmon: starting...") sysInfo := system.GetSystemInfo() archName := system.MachineToArchName(sysInfo.Machine) syscallResolver := system.CallNumberResolver(archName) reportChan := make(chan *report.PtMonitorReport, 1) go func() { ptReport := &report.PtMonitorReport{ ArchName: string(archName), SyscallStats: map[string]report.SyscallStatInfo{}, } syscallStats := map[int16]uint64{} eventChan := make(chan syscallEvent) doneMonitoring := make(chan int) var app *exec.Cmd go func() { //Ptrace is not pretty... and it requires that you do all ptrace calls from the same thread runtime.LockOSThread() var err error app, err = target.Start(appName, appArgs, dirName, true) utils.FailOn(err) targetPid := app.Process.Pid log.Debugf("ptmon: target PID ==> %d\n", targetPid) var wstat syscall.WaitStatus _, err = syscall.Wait4(targetPid, &wstat, 0, nil) if err != nil { log.Warnf("ptmon: error waiting for %d: %v\n", targetPid, err) doneMonitoring <- 1 } log.Debugln("ptmon: initial process status =>", wstat) if wstat.Exited() { log.Warn("ptmon: app exited (unexpected)") doneMonitoring <- 2 } if wstat.Signaled() { log.Warn("ptmon: app signalled (unexpected)") doneMonitoring <- 3 } syscallReturn := false gotCallNum := false gotRetVal := false var callNum uint64 var retVal uint64 for wstat.Stopped() { var regs syscall.PtraceRegs switch syscallReturn { case false: if err := syscall.PtraceGetRegs(targetPid, ®s); err != nil { log.Fatalf("ptmon: PtraceGetRegs(call): %v", err) } callNum = regs.Orig_rax syscallReturn = true gotCallNum = true case true: if err := syscall.PtraceGetRegs(targetPid, ®s); err != nil { log.Fatalf("ptmon: PtraceGetRegs(return): %v", err) } retVal = regs.Rax syscallReturn = false gotRetVal = true } err = syscall.PtraceSyscall(targetPid, 0) if err != nil { log.Warnf("ptmon: PtraceSyscall error: %v\n", err) break } _, err = syscall.Wait4(targetPid, &wstat, 0, nil) if err != nil { log.Warnf("ptmon: error waiting 4 %d: %v\n", targetPid, err) break } if gotCallNum && gotRetVal { gotCallNum = false gotRetVal = false eventChan <- syscallEvent{ callNum: int16(callNum), retVal: retVal, } } } log.Infoln("ptmon: monitor is exiting... status=", wstat) doneMonitoring <- 0 }() done: for { select { case rc := <-doneMonitoring: log.Info("ptmon: done =>", rc) break done case <-stopChan: log.Info("ptmon: stopping...") //NOTE: need a better way to stop the target app... if err := app.Process.Signal(syscall.SIGTERM); err != nil { log.Warnln("ptmon: error stopping target app =>", err) if err := app.Process.Kill(); err != nil { log.Warnln("ptmon: error killing target app =>", err) } } break done case e := <-eventChan: ptReport.SyscallCount++ if _, ok := syscallStats[e.callNum]; ok { syscallStats[e.callNum]++ } else { syscallStats[e.callNum] = 1 } } } log.Debugf("ptmon: executed syscall count = %d\n", ptReport.SyscallCount) log.Debugf("ptmon: number of syscalls: %v\n", len(syscallStats)) for scNum, scCount := range syscallStats { log.Debugf("[%v] %v = %v", scNum, syscallResolver(scNum), scCount) ptReport.SyscallStats[strconv.FormatInt(int64(scNum), 10)] = report.SyscallStatInfo{ Number: scNum, Name: syscallResolver(scNum), Count: scCount, } } ptReport.SyscallNum = uint32(len(ptReport.SyscallStats)) reportChan <- ptReport }() return reportChan }
// childReaper is used to handle events from child processes, including child exit. // If running as pid=1 then this means it handles zombie process reaping for orphaned children // as well as direct child processes. func (t *tether) childReaper() error { signal.Notify(t.incoming, syscall.SIGCHLD) /* PR_SET_CHILD_SUBREAPER (since Linux 3.4) If arg2 is nonzero, set the "child subreaper" attribute of the calling process; if arg2 is zero, unset the attribute. When a process is marked as a child subreaper, all of the children that it creates, and their descendants, will be marked as having a subreaper. In effect, a subreaper fulfills the role of init(1) for its descendant processes. Upon termination of a process that is orphaned (i.e., its immediate parent has already terminated) and marked as having a subreaper, the nearest still living ancestor subreaper will receive a SIGCHLD signal and be able to wait(2) on the process to discover its termination status. */ if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, SetChildSubreaper, uintptr(1), 0); err != 0 { return err } log.Info("Started reaping child processes") go func() { var status syscall.WaitStatus flag := syscall.WNOHANG | syscall.WUNTRACED | syscall.WCONTINUED for range t.incoming { func() { // general resiliency defer func() { if r := recover(); r != nil { fmt.Fprintf(os.Stderr, "Recovered in childReaper %s", debug.Stack()) } }() // reap until no more children to process for { log.Debugf("Inspecting children with status change") select { case <-t.ctx.Done(): log.Warnf("Someone called shutdown, returning from child reaper") return default: } pid, err := syscall.Wait4(-1, &status, flag, nil) // pid 0 means no processes wish to report status if pid == 0 || err == syscall.ECHILD { log.Debug("No more child processes to reap") break } if err != nil { log.Warnf("Wait4 got error: %v\n", err) break } if !status.Exited() && !status.Signaled() { log.Debugf("Received notifcation about non-exit status change for %d: %d", pid, status) // no reaping or exit handling required continue } log.Debugf("Reaped process %d, return code: %d", pid, status.ExitStatus()) session, ok := t.removeChildPid(pid) log.Debugf("Remove child pid: %d session: %#+v ok: %t", pid, session, ok) if ok { session.Lock() session.ExitStatus = status.ExitStatus() session.Unlock() t.handleSessionExit(session) } else { // This is an adopted zombie. The Wait4 call already clean it up from the kernel log.Warnf("Reaped zombie process PID %d", pid) } } }() } log.Info("Stopped reaping child processes") }() return nil }
func Tracer() { var train = false var cmd string var cmdArgs []string var p *oz.Profile var noprofile = flag.Bool("train", false, "Training mode") var debug = flag.Bool("debug", false, "Debug") var appendpolicy = flag.Bool("append", false, "Append to existing policy if exists") var trainingoutput = flag.String("output", "", "Training policy output file") flag.Parse() var args = flag.Args() if *noprofile == true { train = true // TODO: remove hardcoded path and read prefix from /etc/oz.conf cmd = "/usr/bin/oz-seccomp" cmdArgs = append([]string{"-mode=train"}, args...) } else { p = new(oz.Profile) if err := json.NewDecoder(os.Stdin).Decode(&p); err != nil { log.Error("unable to decode profile data: %v", err) os.Exit(1) } if p.Seccomp.Mode == oz.PROFILE_SECCOMP_TRAIN { train = true } *debug = p.Seccomp.Debug cmd = args[0] cmdArgs = args[1:] } var cpid = 0 done := false log.Info("Tracer running command (%v) arguments (%v)\n", cmd, cmdArgs) c := exec.Command(cmd) c.SysProcAttr = &syscall.SysProcAttr{Ptrace: true} c.Env = os.Environ() c.Args = append(c.Args, cmdArgs...) if *noprofile == false { pi, err := c.StdinPipe() if err != nil { fmt.Errorf("error creating stdin pipe for tracer process: %v", err) os.Exit(1) } jdata, err := json.Marshal(p) if err != nil { fmt.Errorf("Unable to marshal seccomp state: %+v", err) os.Exit(1) } io.Copy(pi, bytes.NewBuffer(jdata)) pi.Close() } children := make(map[int]bool) renderFunctions := getRenderingFunctions() trainingset := make(map[int]bool) trainingargs := make(map[int]map[int][]uint) if err := c.Start(); err == nil { cpid = c.Process.Pid children[c.Process.Pid] = true var s syscall.WaitStatus pid, err := syscall.Wait4(-1, &s, syscall.WALL, nil) children[pid] = true if err != nil { log.Error("Error (wait4) err:%v pid:%i", err, pid) } log.Info("Tracing child pid: %v\n", pid) for done == false { pflags := unix.PTRACE_O_TRACESECCOMP pflags |= unix.PTRACE_O_TRACEFORK pflags |= unix.PTRACE_O_TRACEVFORK pflags |= unix.PTRACE_O_TRACECLONE pflags |= C.PTRACE_O_EXITKILL syscall.PtraceSetOptions(pid, pflags) syscall.PtraceCont(pid, 0) pid, err = syscall.Wait4(-1, &s, syscall.WALL, nil) if err != nil { log.Error("Error (wait4) err:%v pid:%i children:%v\n", err, pid, children) done = true continue } children[pid] = true if s.Exited() == true { delete(children, pid) log.Info("Child pid %v finished.\n", pid) if len(children) == 0 { done = true } continue } if s.Signaled() == true { log.Error("Pid signaled, pid: %v signal: %v", pid, s) delete(children, pid) continue } switch uint32(s) >> 8 { case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_SECCOMP << 8): var regs syscall.PtraceRegs err = syscall.PtraceGetRegs(pid, ®s) if err != nil { log.Error("Error (ptrace): %v", err) } systemcall, err := syscallByNum(getSyscallNumber(regs)) if err != nil { log.Error("Error: %v", err) continue } /* Render the system call invocation */ r := getSyscallRegisterArgs(regs) call := "" if train == true { trainingset[getSyscallNumber(regs)] = true if systemcall.captureArgs != nil { for c, i := range systemcall.captureArgs { if i == 1 { if trainingargs[getSyscallNumber(regs)] == nil { trainingargs[getSyscallNumber(regs)] = make(map[int][]uint) } if contains(trainingargs[getSyscallNumber(regs)][c], uint(r[c])) == false { trainingargs[getSyscallNumber(regs)][c] = append(trainingargs[getSyscallNumber(regs)][c], uint(r[c])) } } } } } if f, ok := renderFunctions[getSyscallNumber(regs)]; ok { call, err = f(pid, r) if err != nil { log.Info("%v", err) continue } if *debug == true { call += "\n " + renderSyscallBasic(pid, systemcall, regs) } } else { call = renderSyscallBasic(pid, systemcall, regs) } log.Info("seccomp hit on sandbox pid %v (%v) syscall %v (%v):\n %s", pid, getProcessCmdLine(pid), systemcall.name, systemcall.num, call) continue case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_EXIT << 8): if *debug == true { log.Error("Ptrace exit event detected pid %v (%s)", pid, getProcessCmdLine(pid)) } delete(children, pid) continue case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_CLONE << 8): newpid, err := syscall.PtraceGetEventMsg(pid) if err != nil { log.Error("PTrace event message retrieval failed: %v", err) } children[int(newpid)] = true if *debug == true { log.Error("Ptrace clone event detected pid %v (%s)", pid, getProcessCmdLine(pid)) } continue case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_FORK << 8): if *debug == true { log.Error("PTrace fork event detected pid %v (%s)", pid, getProcessCmdLine(pid)) } newpid, err := syscall.PtraceGetEventMsg(pid) if err != nil { log.Error("PTrace event message retrieval failed: %v", err) } children[int(newpid)] = true continue case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_VFORK << 8): if *debug == true { log.Error("Ptrace vfork event detected pid %v (%s)", pid, getProcessCmdLine(pid)) } newpid, err := syscall.PtraceGetEventMsg(pid) if err != nil { log.Error("PTrace event message retrieval failed: %v", err) } children[int(newpid)] = true continue case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_VFORK_DONE << 8): if *debug == true { log.Error("Ptrace vfork done event detected pid %v (%s)", pid, getProcessCmdLine(pid)) } newpid, err := syscall.PtraceGetEventMsg(pid) if err != nil { log.Error("PTrace event message retrieval failed: %v", err) } children[int(newpid)] = true continue case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_EXEC << 8): if *debug == true { log.Error("Ptrace exec event detected pid %v (%s)", pid, getProcessCmdLine(pid)) } continue case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_STOP << 8): if *debug == true { log.Error("Ptrace stop event detected pid %v (%s)", pid, getProcessCmdLine(pid)) } continue case uint32(unix.SIGTRAP): if *debug == true { log.Error("SIGTRAP detected in pid %v (%s)", pid, getProcessCmdLine(pid)) } continue case uint32(unix.SIGCHLD): if *debug == true { log.Error("SIGCHLD detected pid %v (%s)", pid, getProcessCmdLine(pid)) } continue case uint32(unix.SIGSTOP): if *debug == true { log.Error("SIGSTOP detected pid %v (%s)", pid, getProcessCmdLine(pid)) } continue case uint32(unix.SIGSEGV): if *debug == true { log.Error("SIGSEGV detected pid %v (%s)", pid, getProcessCmdLine(pid)) } err = syscall.Kill(pid, 9) if err != nil { log.Error("kill: %v", err) os.Exit(1) } delete(children, pid) continue default: y := s.StopSignal() if *debug == true { log.Error("Child stopped for unknown reasons pid %v status %v signal %i (%s)", pid, s, y, getProcessCmdLine(pid)) } continue } } if train == true { var u *user.User var e error u, e = user.Current() var resolvedpath = "" if e != nil { log.Error("user.Current(): %v", e) } if *trainingoutput != "" { resolvedpath = *trainingoutput } else { if *noprofile == false { resolvedpath, e = fs.ResolvePathNoGlob(p.Seccomp.TrainOutput, u) if e != nil { log.Error("resolveVars(): %v", e) } } else { s := fmt.Sprintf("${HOME}/%s-%d.seccomp", fname(os.Args[2]), cpid) resolvedpath, e = fs.ResolvePathNoGlob(s, u) } } policyout := "execve:1\n" for call := range trainingset { done := false for c := range trainingargs { if c == call { for a, v := range trainingargs[c] { sc, _ := syscallByNum(call) policyout += fmt.Sprintf("%s:%s\n", sc.name, genArgs(uint(a), (v))) done = true } } } if done == false { sc, _ := syscallByNum(call) policyout += fmt.Sprintf("%s:1\n", sc.name) } } if *appendpolicy == true { log.Error("Not yet implemented.") } f, err := os.OpenFile(resolvedpath, os.O_CREATE|os.O_RDWR, 0600) if err == nil { _, err := f.WriteString(policyout) if err != nil { log.Error("Error writing policy file: %v", err) } err = f.Close() if err != nil { log.Error("Error closing policy file: %v", err) } } else { log.Error("Error opening policy file \"%s\": %v", resolvedpath, err) } } } }
func resolveExitCode(w syscall.WaitStatus) int { if w.Signaled() { return int(w) | 0x80 } return w.ExitStatus() }