diff options
| author | Kir Kolyshkin <kolyshkin@gmail.com> | 2026-03-24 22:18:47 -0700 |
|---|---|---|
| committer | Kirill Kolyshkin <kolyshkin@gmail.com> | 2026-03-31 12:38:51 -0700 |
| commit | 974764364aa09a34cad2d74a6b7c52c12a136ea3 (patch) | |
| tree | 39a8c03db84a8dc82f3ac56f3bef9cbd220d02b5 /src/runtime/testdata | |
| parent | 2000e27ea6a644ea3623db201d8ba2818e8f5838 (diff) | |
| download | go-974764364aa09a34cad2d74a6b7c52c12a136ea3.tar.xz | |
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 <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Diffstat (limited to 'src/runtime/testdata')
| -rw-r--r-- | src/runtime/testdata/testprog/signal_pid1.go | 26 |
1 files changed, 26 insertions, 0 deletions
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) +} |
