//go:build linux package activator import ( "errors" "os" "syscall" "golang.org/x/sys/unix" "github.com/carlosframework/platform/internal/hostbox" ) // pinRootProcess returns the process root granted at pid, pinned by a pidfd // that was opened before its start was compared with start. The pin, not the // pid, is what the watcher follows, so a reused pid can never stand in for // the grant's process. A pid that is already gone (ESRCH) is the grant's // process having exited and returns a process that reads as exited. A pidfd // that cannot be opened for any other reason is an error, never an exit. func pinRootProcess(pid int, start string) (*os.Process, error) { process, err := os.FindProcess(pid) if err != nil { return nil, err } pinned := false err = process.WithHandle(func(fd uintptr) { got, e := roleProcessStart(pid) // The start was read through the pid. It is the pinned process's // only if that process had not exited by the time it was read. pinned = e == nil && got == start && !pidfdExited(int(fd)) }) switch { case processGone(process, err): return process, nil case err != nil: _ = process.Release() return nil, errors.Join(hostbox.ErrChanged, err) case !pinned: _ = process.Release() return nil, hostbox.ErrChanged } return process, nil } // processGone reports whether err, from process's WithHandle, means the // process has exited. A process found after its pid was gone (ESRCH) has no // handle and answers Signal without a pidfd, so its pid is asked directly. func processGone(process *os.Process, err error) bool { if errors.Is(err, os.ErrNoHandle) { err = process.Signal(syscall.Signal(0)) } return errors.Is(err, os.ErrProcessDone) } // pidfdExited reports whether the process behind fd has exited, without // waiting. func pidfdExited(fd int) bool { fds := []unix.PollFd{{Fd: int32(fd), Events: unix.POLLIN}} for { n, err := unix.Poll(fds, 0) if errors.Is(err, unix.EINTR) { continue } return err != nil || n > 0 } } // watchRootProcess closes done when process exits. The agent is not the // process's parent, so it polls the pidfd process was pinned by rather than // waiting. A process with no pinned pidfd cannot be watched, and done then // stays open: an unwatchable process is never reported as exited. func watchRootProcess(process *os.Process, done chan struct{}) { go func() { exited := false err := process.WithHandle(func(fd uintptr) { fds := []unix.PollFd{{Fd: int32(fd), Events: unix.POLLIN}} for { n, err := unix.Poll(fds, -1) if errors.Is(err, unix.EINTR) { continue } exited = err == nil && n > 0 return } }) if exited || processGone(process, err) { close(done) } }() }