diff --git a/internal/app/shim-signal.c b/internal/app/shim-signal.c index b6120f1..fc00534 100644 --- a/internal/app/shim-signal.c +++ b/internal/app/shim-signal.c @@ -6,27 +6,43 @@ #include static pid_t hakurei_shim_param_ppid = -1; +static int hakurei_shim_fd = -1; -// this cannot unblock hlog since Go code is not async-signal-safe +static ssize_t hakurei_shim_write(const void *buf, size_t count) { + int savedErrno = errno; + ssize_t ret = write(hakurei_shim_fd, buf, count); + if (ret == -1 && errno != EAGAIN) + exit(EXIT_FAILURE); + errno = savedErrno; + return ret; +} + +/* see shim_linux.go for handling of the value */ static void hakurei_shim_sigaction(int sig, siginfo_t *si, void *ucontext) { if (sig != SIGCONT || si == NULL) { - // unreachable - fprintf(stderr, "sigaction: sa_sigaction got invalid siginfo\n"); + /* unreachable */ + hakurei_shim_write("\2", 1); return; } - // monitor requests shim exit - if (si->si_pid == hakurei_shim_param_ppid) - exit(254); + if (si->si_pid == hakurei_shim_param_ppid) { + /* monitor requests shim exit */ + hakurei_shim_write("\0", 1); + return; + } - fprintf(stderr, "sigaction: got SIGCONT from process %d\n", si->si_pid); + /* unexpected si_pid */ + hakurei_shim_write("\3", 1); - // shim orphaned before monitor delivers a signal if (getppid() != hakurei_shim_param_ppid) - exit(3); + /* shim orphaned before monitor delivers a signal */ + hakurei_shim_write("\1", 1); } -void hakurei_shim_setup_cont_signal(pid_t ppid) { +void hakurei_shim_setup_cont_signal(pid_t ppid, int fd) { + if (hakurei_shim_param_ppid != -1 || hakurei_shim_fd != -1) + *(int *)NULL = 0; /* unreachable */ + struct sigaction new_action = {0}, old_action = {0}; if (sigaction(SIGCONT, NULL, &old_action) != 0) return; @@ -45,4 +61,5 @@ void hakurei_shim_setup_cont_signal(pid_t ppid) { errno = 0; hakurei_shim_param_ppid = ppid; + hakurei_shim_fd = fd; } diff --git a/internal/app/shim-signal.h b/internal/app/shim-signal.h index 28b80ed..bdeae3b 100644 --- a/internal/app/shim-signal.h +++ b/internal/app/shim-signal.h @@ -1,3 +1,3 @@ #include -void hakurei_shim_setup_cont_signal(pid_t ppid); +void hakurei_shim_setup_cont_signal(pid_t ppid, int fd); diff --git a/internal/app/shim_linux.go b/internal/app/shim_linux.go index 67e5fdb..066f51d 100644 --- a/internal/app/shim_linux.go +++ b/internal/app/shim_linux.go @@ -3,10 +3,12 @@ package app import ( "context" "errors" + "io" "log" "os" "os/exec" "os/signal" + "runtime" "syscall" "time" @@ -34,6 +36,13 @@ type shimParams struct { Verbose bool } +const ( + // ShimExitRequest is returned when the monitor process requests shim exit. + ShimExitRequest = 254 + // ShimExitOrphan is returned when the shim is orphaned before monitor delivers a signal. + ShimExitOrphan = 3 +) + // ShimMain is the main function of the shim process and runs as the unconstrained target user. func ShimMain() { hlog.Prepare("shim") @@ -58,18 +67,55 @@ func ShimMain() { } else { internal.InstallOutput(params.Verbose) closeSetup = f - - // the Go runtime does not expose siginfo_t so SIGCONT is handled in C to check si_pid - if _, err = C.hakurei_shim_setup_cont_signal(C.pid_t(params.Monitor)); err != nil { - log.Fatalf("cannot install SIGCONT handler: %v", err) - } - - // pdeath_signal delivery is checked as if the dying process called kill(2), see kernel/exit.c - if _, _, errno := syscall.Syscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(syscall.SIGCONT), 0); errno != 0 { - log.Fatalf("cannot set parent-death signal: %v", errno) - } } + var signalPipe io.ReadCloser + // the Go runtime does not expose siginfo_t so SIGCONT is handled in C to check si_pid + if r, w, err := os.Pipe(); err != nil { + log.Fatalf("cannot pipe: %v", err) + } else if _, err = C.hakurei_shim_setup_cont_signal(C.pid_t(params.Monitor), C.int(w.Fd())); err != nil { + log.Fatalf("cannot install SIGCONT handler: %v", err) + } else { + defer runtime.KeepAlive(w) + signalPipe = r + } + + // pdeath_signal delivery is checked as if the dying process called kill(2), see kernel/exit.c + if _, _, errno := syscall.Syscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(syscall.SIGCONT), 0); errno != 0 { + log.Fatalf("cannot set parent-death signal: %v", errno) + } + + // signal handler outcome + go func() { + buf := make([]byte, 1) + for { + if _, err := signalPipe.Read(buf); err != nil { + log.Fatalf("cannot read from signal pipe: %v", err) + } + + switch buf[0] { + case 0: + hlog.Resume() + os.Exit(ShimExitRequest) + return + + case 1: + hlog.BeforeExit() + os.Exit(ShimExitOrphan) + return + + case 2: + log.Println("sa_sigaction got invalid siginfo") + + case 3: + log.Println("got SIGCONT from unexpected process") + + default: + log.Fatalf("got invalid message %d from signal handler", buf[0]) + } + } + }() + if params.Container == nil || params.Container.Ops == nil { log.Fatal("invalid container params") } diff --git a/test/test.py b/test/test.py index 1636164..9e15039 100644 --- a/test/test.py +++ b/test/test.py @@ -181,6 +181,15 @@ interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code")) if interrupt_exit_code != 254: raise Exception(f"unexpected exit code {interrupt_exit_code}") +# Check shim SIGCONT from unexpected process behaviour: +swaymsg("exec sh -c 'ne-foot &> /tmp/shim-cont-unexpected-pid'") +wait_for_window(f"u0_a{aid(0)}@machine") +machine.succeed("pkill -CONT -f 'hakurei shim'") +machine.succeed("pkill -INT -f 'hakurei -v app '") +machine.wait_until_fails("pgrep foot", timeout=5) +machine.wait_for_file("/tmp/shim-cont-unexpected-pid") +print(machine.succeed('grep "shim: got SIGCONT from unexpected process$" /tmp/shim-cont-unexpected-pid')) + # Start app (foot) with Wayland enablement: swaymsg("exec ne-foot") wait_for_window(f"u0_a{aid(0)}@machine")