Skip to content

Commit df0892c

Browse files
runtime, syscall: reset signal handlers to default in child
Block all signals during a fork. In the parent process, after the fork, restore the signal mask. In the child process, reset all currently handled signals to the default handler, and then restore the signal mask. The effect of this is that the child will be operating using the same signal regime as the program it is about to exec, as exec resets all non-ignored signals to the default, and preserves the signal mask. We do this so that in the case of a signal sent to the process group, the child process will not try to run a signal handler while in the precarious state after a fork. Fixes #18600. Change-Id: I9f39aaa3884035908d687ee323c975f349d5faaa Reviewed-on: https://go-review.googlesource.com/45471 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Austin Clements <[email protected]>
1 parent 17ba830 commit df0892c

File tree

10 files changed

+158
-10
lines changed

10 files changed

+158
-10
lines changed

src/runtime/crash_unix_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,3 +251,16 @@ func TestSignalIgnoreSIGTRAP(t *testing.T) {
251251
t.Fatalf("want %s, got %s\n", want, output)
252252
}
253253
}
254+
255+
func TestSignalDuringExec(t *testing.T) {
256+
switch runtime.GOOS {
257+
case "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd":
258+
default:
259+
t.Skipf("skipping test on %s", runtime.GOOS)
260+
}
261+
output := runTestProg(t, "testprognet", "SignalDuringExec")
262+
want := "OK\n"
263+
if output != want {
264+
t.Fatalf("want %s, got %s\n", want, output)
265+
}
266+
}

src/runtime/os_nacl.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ func msigsave(mp *m) {
8787
func msigrestore(sigmask sigset) {
8888
}
8989

90+
//go:nosplit
91+
//go:nowritebarrierrec
92+
func clearSignalHandlers() {
93+
}
94+
9095
//go:nosplit
9196
func sigblock() {
9297
}

src/runtime/os_plan9.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,11 @@ func msigsave(mp *m) {
173173
func msigrestore(sigmask sigset) {
174174
}
175175

176+
//go:nosplit
177+
//go:nowritebarrierrec
178+
func clearSignalHandlers() {
179+
}
180+
176181
func sigblock() {
177182
}
178183

src/runtime/os_windows.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,11 @@ func msigsave(mp *m) {
656656
func msigrestore(sigmask sigset) {
657657
}
658658

659+
//go:nosplit
660+
//go:nowritebarrierrec
661+
func clearSignalHandlers() {
662+
}
663+
659664
//go:nosplit
660665
func sigblock() {
661666
}

src/runtime/proc.go

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2804,12 +2804,12 @@ func exitsyscall0(gp *g) {
28042804
func beforefork() {
28052805
gp := getg().m.curg
28062806

2807-
// Fork can hang if preempted with signals frequently enough (see issue 5517).
2808-
// Ensure that we stay on the same M where we disable profiling.
2807+
// Block signals during a fork, so that the child does not run
2808+
// a signal handler before exec if a signal is sent to the process
2809+
// group. See issue #18600.
28092810
gp.m.locks++
2810-
if gp.m.profilehz != 0 {
2811-
setThreadCPUProfiler(0)
2812-
}
2811+
msigsave(gp.m)
2812+
sigblock()
28132813

28142814
// This function is called before fork in syscall package.
28152815
// Code between fork and exec must not allocate memory nor even try to grow stack.
@@ -2828,13 +2828,11 @@ func syscall_runtime_BeforeFork() {
28282828
func afterfork() {
28292829
gp := getg().m.curg
28302830

2831-
// See the comment in beforefork.
2831+
// See the comments in beforefork.
28322832
gp.stackguard0 = gp.stack.lo + _StackGuard
28332833

2834-
hz := sched.profilehz
2835-
if hz != 0 {
2836-
setThreadCPUProfiler(hz)
2837-
}
2834+
msigrestore(gp.m.sigmask)
2835+
28382836
gp.m.locks--
28392837
}
28402838

@@ -2845,6 +2843,20 @@ func syscall_runtime_AfterFork() {
28452843
systemstack(afterfork)
28462844
}
28472845

2846+
// Called from syscall package after fork in child.
2847+
// It resets non-sigignored signals to the default handler, and
2848+
// restores the signal mask in preparation for the exec.
2849+
//go:linkname syscall_runtime_AfterForkInChild syscall.runtime_AfterForkInChild
2850+
//go:nosplit
2851+
//go:nowritebarrierrec
2852+
func syscall_runtime_AfterForkInChild() {
2853+
clearSignalHandlers()
2854+
2855+
// When we are the child we are the only thread running,
2856+
// so we know that nothing else has changed gp.m.sigmask.
2857+
msigrestore(getg().m.sigmask)
2858+
}
2859+
28482860
// Allocate a new g, with a stack big enough for stacksize bytes.
28492861
func malg(stacksize int32) *g {
28502862
newg := new(g)

src/runtime/signal_unix.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,20 @@ func sigignore(sig uint32) {
204204
}
205205
}
206206

207+
// clearSignalHandlers clears all signal handlers that are not ignored
208+
// back to the default. This is called by the child after a fork, so that
209+
// we can enable the signal mask for the exec without worrying about
210+
// running a signal handler in the child.
211+
//go:nosplit
212+
//go:nowritebarrierrec
213+
func clearSignalHandlers() {
214+
for i := uint32(0); i < _NSIG; i++ {
215+
if atomic.Load(&handlingSig[i]) != 0 {
216+
setsig(i, _SIG_DFL)
217+
}
218+
}
219+
}
220+
207221
// setProcessCPUProfiler is called when the profiling timer changes.
208222
// It is called with prof.lock held. hz is the new timer, and is 0 if
209223
// profiling is being disabled. Enable or disable the signal as
@@ -310,6 +324,11 @@ func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
310324
}
311325

312326
setg(g.m.gsignal)
327+
328+
if g.stackguard0 == stackFork {
329+
signalDuringFork(sig)
330+
}
331+
313332
c := &sigctxt{info, ctx}
314333
c.fixsigcode(sig)
315334
sighandler(sig, info, ctx, g)
@@ -521,6 +540,16 @@ func sigNotOnStack(sig uint32) {
521540
throw("non-Go code set up signal handler without SA_ONSTACK flag")
522541
}
523542

543+
// signalDuringFork is called if we receive a signal while doing a fork.
544+
// We do not want signals at that time, as a signal sent to the process
545+
// group may be delivered to the child process, causing confusion.
546+
// This should never be called, because we block signals across the fork;
547+
// this function is just a safety check. See issue 18600 for background.
548+
func signalDuringFork(sig uint32) {
549+
println("signal", sig, "received during fork")
550+
throw("signal received during fork")
551+
}
552+
524553
// This runs on a foreign stack, without an m or a g. No stack split.
525554
//go:nosplit
526555
//go:norace
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build darwin dragonfly freebsd linux netbsd openbsd
6+
7+
// This is in testprognet instead of testprog because testprog
8+
// must not import anything (like net, but also like os/signal)
9+
// that kicks off background goroutines during init.
10+
11+
package main
12+
13+
import (
14+
"fmt"
15+
"os"
16+
"os/exec"
17+
"os/signal"
18+
"sync"
19+
"syscall"
20+
"time"
21+
)
22+
23+
func init() {
24+
register("SignalDuringExec", SignalDuringExec)
25+
register("Nop", Nop)
26+
}
27+
28+
func SignalDuringExec() {
29+
pgrp := syscall.Getpgrp()
30+
31+
const tries = 10
32+
33+
var wg sync.WaitGroup
34+
c := make(chan os.Signal, tries)
35+
signal.Notify(c, syscall.SIGWINCH)
36+
wg.Add(1)
37+
go func() {
38+
defer wg.Done()
39+
for range c {
40+
}
41+
}()
42+
43+
for i := 0; i < tries; i++ {
44+
time.Sleep(time.Microsecond)
45+
wg.Add(2)
46+
go func() {
47+
defer wg.Done()
48+
cmd := exec.Command(os.Args[0], "Nop")
49+
cmd.Stdout = os.Stdout
50+
cmd.Stderr = os.Stderr
51+
if err := cmd.Run(); err != nil {
52+
fmt.Printf("Start failed: %v", err)
53+
}
54+
}()
55+
go func() {
56+
defer wg.Done()
57+
syscall.Kill(-pgrp, syscall.SIGWINCH)
58+
}()
59+
}
60+
61+
signal.Stop(c)
62+
close(c)
63+
wg.Wait()
64+
65+
fmt.Println("OK")
66+
}
67+
68+
func Nop() {
69+
// This is just for SignalDuringExec.
70+
}

src/syscall/exec_bsd.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type SysProcAttr struct {
2727
// Implemented in runtime package.
2828
func runtime_BeforeFork()
2929
func runtime_AfterFork()
30+
func runtime_AfterForkInChild()
3031

3132
// Fork, dup fd onto 0..len(fd), and exec(argv0, argvv, envv) in child.
3233
// If a dup or exec fails, write the errno error to pipe.
@@ -88,6 +89,8 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
8889

8990
// Fork succeeded, now in child.
9091

92+
runtime_AfterForkInChild()
93+
9194
// Enable tracing if requested.
9295
if sys.Ptrace {
9396
_, _, err1 = RawSyscall(SYS_PTRACE, uintptr(PTRACE_TRACEME), 0, 0)

src/syscall/exec_linux.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ var (
5050
// Implemented in runtime package.
5151
func runtime_BeforeFork()
5252
func runtime_AfterFork()
53+
func runtime_AfterForkInChild()
5354

5455
// Fork, dup fd onto 0..len(fd), and exec(argv0, argvv, envv) in child.
5556
// If a dup or exec fails, write the errno error to pipe.
@@ -133,6 +134,8 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
133134

134135
// Fork succeeded, now in child.
135136

137+
runtime_AfterForkInChild()
138+
136139
// Wait for User ID/Group ID mappings to be written.
137140
if sys.UidMappings != nil || sys.GidMappings != nil {
138141
if _, _, err1 = RawSyscall(SYS_CLOSE, uintptr(p[1]), 0, 0); err1 != 0 {

src/syscall/exec_solaris.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type SysProcAttr struct {
2323
// Implemented in runtime package.
2424
func runtime_BeforeFork()
2525
func runtime_AfterFork()
26+
func runtime_AfterForkInChild()
2627

2728
func chdir(path uintptr) (err Errno)
2829
func chroot1(path uintptr) (err Errno)
@@ -93,6 +94,8 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
9394

9495
// Fork succeeded, now in child.
9596

97+
runtime_AfterForkInChild()
98+
9699
// Session ID
97100
if sys.Setsid {
98101
_, err1 = setsid()

0 commit comments

Comments
 (0)