From 974764364aa09a34cad2d74a6b7c52c12a136ea3 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Tue, 24 Mar 2026 22:18:47 -0700 Subject: runtime: unix: sane exit in dieFromSignal for pid 1 A curious bug was reported to kubernetes[1] and runc[2] recently: sometimes runc init reports exit status of 2. Turns out, Go runtime assumes that on any UNIX system signals such as SIGTERM (or any other that has _sigKill flag set in sigtable) with no signal handler set up, will result in kernel terminating the program. This is true, except for PID 1 which gets a custom treatment from the kernel. As a result, when a Go program that runs as PID 1 (which is easy to achieve in Linux by using a new PID namespace) receives such a signal, Go runtime calls dieFromSignal which falls through all the way to exit(2), which is very confusing to a user. This issue can be worked around by the program by adding custom handlers for SIGTERM/SIGINT/SIGHUP, but that requires a goroutine to handle those signals, which, in case of runc, unnecessarily raises its NPROC/pid.max requirement (see discussion at [2]). Since practically exit(2) in dieFromSignal can only happen when the process is running as PID 1, replace it with exit(128+sig) to mimic the shell convention when a child is terminated by a signal. Add a test case which demonstrates the issue and validates the fix (albeit only on Linux). [An earlier version of this patch used to do nothing in dieFromSignal for PID 1 case, but such behavior might be a breaking change for a Go program running in a Linux container as PID 1.] Fixes #78442 [1]: https://github.com/kubernetes/kubernetes/issues/135713 [2]: https://github.com/opencontainers/runc/pull/5189 Change-Id: I196e09e4b5ce84ce2c747a0c2d1fc6e9cf3a6131 Reviewed-on: https://go-review.googlesource.com/c/go/+/759040 LUCI-TryBot-Result: Go LUCI Reviewed-by: Ian Lance Taylor Reviewed-by: Junyang Shao Reviewed-by: Dmitri Shuralyov --- src/runtime/testdata/testprog/signal_pid1.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/runtime/testdata/testprog/signal_pid1.go (limited to 'src/runtime/testdata') diff --git a/src/runtime/testdata/testprog/signal_pid1.go b/src/runtime/testdata/testprog/signal_pid1.go new file mode 100644 index 0000000000..7446056e2d --- /dev/null +++ b/src/runtime/testdata/testprog/signal_pid1.go @@ -0,0 +1,26 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "os" + "time" +) + +func init() { + register("SignalPid1", SignalPid1) +} + +// SignalPid1 is a helper for TestSignalPid1. +func SignalPid1() { + if os.Getpid() != 1 { + fmt.Fprintln(os.Stderr, "I am not PID 1") + return + } + fmt.Println("ready") + + time.Sleep(time.Hour) +} -- cgit v1.3-5-g9baa