From 82ba1f781e50557e1ed99b3d8b43d1754b3b5df2 Mon Sep 17 00:00:00 2001 From: Junjun Zhang Date: Sun, 13 Sep 2026 12:26:40 +0800 Subject: [PATCH 1/2] fix(util): signal-0 EPERM means alive on Unix (#2190) kill(pid, 0) returning EPERM means the process EXISTS but we lack permission to signal it (POSIX) - IsProcessAlive treated it as dead, the exact Unix twin of the #1723 Windows bug (ACCESS_DENIED was made "alive" there because false-dead deletes PID files and forks second daemons / triggers concurrent crash recovery - run_journal.go's own comments record the #1490 incident on that path). Only a real error (ESRCH et al.) means dead now; the EPERM branch itself needs a cross-privilege process pair, which unit tests cannot construct, so the test pins the surrounding self-alive / reaped-child-dead semantics (sandboxed environments skip the fork). Co-Authored-By: ggcode Co-Authored-By: ggcode --- internal/util/process.go | 9 ++++++- internal/util/zz_issue2190_test.go | 39 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 internal/util/zz_issue2190_test.go diff --git a/internal/util/process.go b/internal/util/process.go index 38d9ef37b..6f0150e82 100644 --- a/internal/util/process.go +++ b/internal/util/process.go @@ -3,6 +3,7 @@ package util import ( + "errors" "os" "strings" "syscall" @@ -23,7 +24,13 @@ func IsProcessAlive(pid int) bool { if err != nil { return false } - if proc.Signal(syscall.Signal(0)) != nil { + // #2190: kill(pid, 0) EPERM means the process EXISTS but we lack + // permission to signal it (POSIX) - treating it as dead mirrors the + // exact bug Windows fixed in #1723 (a privileged daemon probed by an + // unprivileged caller: false-dead deletes the PID file and forks a + // second instance, or triggers concurrent crash recovery - the Unix + // twin of #1490). Only a real error (ESRCH et al.) means dead. + if err := proc.Signal(syscall.Signal(0)); err != nil && !errors.Is(err, syscall.EPERM) { return false } return !isZombieUnix(pid) diff --git a/internal/util/zz_issue2190_test.go b/internal/util/zz_issue2190_test.go new file mode 100644 index 000000000..c8006d9a4 --- /dev/null +++ b/internal/util/zz_issue2190_test.go @@ -0,0 +1,39 @@ +package util + +// #2190 regression: kill(pid,0) EPERM means the process EXISTS but is +// not signalable by us (POSIX) - it was returned as dead, the Unix twin +// of the #1723 Windows bug (false-dead deletes PID files and forks +// second daemons / concurrent crash recovery). The EPERM branch itself +// needs a cross-privilege process (not unit-testable); these pin the +// surrounding semantics that must not regress alongside it. + +import ( + "os" + "syscall" + "testing" +) + +func TestIsProcessAliveBasicSemantics(t *testing.T) { + if !IsProcessAlive(os.Getpid()) { + t.Fatal("self must be alive") + } + // A definitely-dead PID: spawn and reap one. + r, w, err := os.Pipe() + if err != nil { + t.Skipf("pipe: %v", err) + } + defer r.Close() + defer w.Close() + pid, err := syscall.ForkExec("/bin/true", []string{"/bin/true"}, &syscall.ProcAttr{Files: []uintptr{r.Fd(), w.Fd(), w.Fd()}}) + if err != nil { + t.Skipf("forkexec: %v", err) + } + // Wait for exit; probe until dead (bounded). + for i := 0; i < 100; i++ { + if !IsProcessAlive(pid) { + return // dead, as expected + } + syscall.Wait4(pid, nil, syscall.WNOHANG, nil) + } + t.Fatal("reaped child must be reported dead") +} From 050bac63584f3996811a129be631f2add8d7dd99 Mon Sep 17 00:00:00 2001 From: Junjun Zhang Date: Sun, 13 Sep 2026 12:39:37 +0800 Subject: [PATCH 2/2] fix(util): build-tag the EPERM regression test off Windows (#2190) The Windows 'process liveness' CI job compiles the util test package; ForkExec/Wait4/WNOHANG are Unix-only syscalls (process.go itself is already !windows). Tag the test to match. Co-Authored-By: ggcode --- internal/util/zz_issue2190_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/util/zz_issue2190_test.go b/internal/util/zz_issue2190_test.go index c8006d9a4..068a08a38 100644 --- a/internal/util/zz_issue2190_test.go +++ b/internal/util/zz_issue2190_test.go @@ -1,3 +1,5 @@ +//go:build !windows + package util // #2190 regression: kill(pid,0) EPERM means the process EXISTS but is