Commit df0892cb authored by Ian Lance Taylor's avatar Ian Lance Taylor

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 <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarAustin Clements <austin@google.com>
parent 17ba830f
......@@ -251,3 +251,16 @@ func TestSignalIgnoreSIGTRAP(t *testing.T) {
t.Fatalf("want %s, got %s\n", want, output)
}
}
func TestSignalDuringExec(t *testing.T) {
switch runtime.GOOS {
case "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd":
default:
t.Skipf("skipping test on %s", runtime.GOOS)
}
output := runTestProg(t, "testprognet", "SignalDuringExec")
want := "OK\n"
if output != want {
t.Fatalf("want %s, got %s\n", want, output)
}
}
......@@ -87,6 +87,11 @@ func msigsave(mp *m) {
func msigrestore(sigmask sigset) {
}
//go:nosplit
//go:nowritebarrierrec
func clearSignalHandlers() {
}
//go:nosplit
func sigblock() {
}
......
......@@ -173,6 +173,11 @@ func msigsave(mp *m) {
func msigrestore(sigmask sigset) {
}
//go:nosplit
//go:nowritebarrierrec
func clearSignalHandlers() {
}
func sigblock() {
}
......
......@@ -656,6 +656,11 @@ func msigsave(mp *m) {
func msigrestore(sigmask sigset) {
}
//go:nosplit
//go:nowritebarrierrec
func clearSignalHandlers() {
}
//go:nosplit
func sigblock() {
}
......
......@@ -2804,12 +2804,12 @@ func exitsyscall0(gp *g) {
func beforefork() {
gp := getg().m.curg
// Fork can hang if preempted with signals frequently enough (see issue 5517).
// Ensure that we stay on the same M where we disable profiling.
// Block signals during a fork, so that the child does not run
// a signal handler before exec if a signal is sent to the process
// group. See issue #18600.
gp.m.locks++
if gp.m.profilehz != 0 {
setThreadCPUProfiler(0)
}
msigsave(gp.m)
sigblock()
// This function is called before fork in syscall package.
// Code between fork and exec must not allocate memory nor even try to grow stack.
......@@ -2828,13 +2828,11 @@ func syscall_runtime_BeforeFork() {
func afterfork() {
gp := getg().m.curg
// See the comment in beforefork.
// See the comments in beforefork.
gp.stackguard0 = gp.stack.lo + _StackGuard
hz := sched.profilehz
if hz != 0 {
setThreadCPUProfiler(hz)
}
msigrestore(gp.m.sigmask)
gp.m.locks--
}
......@@ -2845,6 +2843,20 @@ func syscall_runtime_AfterFork() {
systemstack(afterfork)
}
// Called from syscall package after fork in child.
// It resets non-sigignored signals to the default handler, and
// restores the signal mask in preparation for the exec.
//go:linkname syscall_runtime_AfterForkInChild syscall.runtime_AfterForkInChild
//go:nosplit
//go:nowritebarrierrec
func syscall_runtime_AfterForkInChild() {
clearSignalHandlers()
// When we are the child we are the only thread running,
// so we know that nothing else has changed gp.m.sigmask.
msigrestore(getg().m.sigmask)
}
// Allocate a new g, with a stack big enough for stacksize bytes.
func malg(stacksize int32) *g {
newg := new(g)
......
......@@ -204,6 +204,20 @@ func sigignore(sig uint32) {
}
}
// clearSignalHandlers clears all signal handlers that are not ignored
// back to the default. This is called by the child after a fork, so that
// we can enable the signal mask for the exec without worrying about
// running a signal handler in the child.
//go:nosplit
//go:nowritebarrierrec
func clearSignalHandlers() {
for i := uint32(0); i < _NSIG; i++ {
if atomic.Load(&handlingSig[i]) != 0 {
setsig(i, _SIG_DFL)
}
}
}
// setProcessCPUProfiler is called when the profiling timer changes.
// It is called with prof.lock held. hz is the new timer, and is 0 if
// profiling is being disabled. Enable or disable the signal as
......@@ -310,6 +324,11 @@ func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
}
setg(g.m.gsignal)
if g.stackguard0 == stackFork {
signalDuringFork(sig)
}
c := &sigctxt{info, ctx}
c.fixsigcode(sig)
sighandler(sig, info, ctx, g)
......@@ -521,6 +540,16 @@ func sigNotOnStack(sig uint32) {
throw("non-Go code set up signal handler without SA_ONSTACK flag")
}
// signalDuringFork is called if we receive a signal while doing a fork.
// We do not want signals at that time, as a signal sent to the process
// group may be delivered to the child process, causing confusion.
// This should never be called, because we block signals across the fork;
// this function is just a safety check. See issue 18600 for background.
func signalDuringFork(sig uint32) {
println("signal", sig, "received during fork")
throw("signal received during fork")
}
// This runs on a foreign stack, without an m or a g. No stack split.
//go:nosplit
//go:norace
......
// Copyright 2017 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.
// +build darwin dragonfly freebsd linux netbsd openbsd
// This is in testprognet instead of testprog because testprog
// must not import anything (like net, but also like os/signal)
// that kicks off background goroutines during init.
package main
import (
"fmt"
"os"
"os/exec"
"os/signal"
"sync"
"syscall"
"time"
)
func init() {
register("SignalDuringExec", SignalDuringExec)
register("Nop", Nop)
}
func SignalDuringExec() {
pgrp := syscall.Getpgrp()
const tries = 10
var wg sync.WaitGroup
c := make(chan os.Signal, tries)
signal.Notify(c, syscall.SIGWINCH)
wg.Add(1)
go func() {
defer wg.Done()
for range c {
}
}()
for i := 0; i < tries; i++ {
time.Sleep(time.Microsecond)
wg.Add(2)
go func() {
defer wg.Done()
cmd := exec.Command(os.Args[0], "Nop")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Printf("Start failed: %v", err)
}
}()
go func() {
defer wg.Done()
syscall.Kill(-pgrp, syscall.SIGWINCH)
}()
}
signal.Stop(c)
close(c)
wg.Wait()
fmt.Println("OK")
}
func Nop() {
// This is just for SignalDuringExec.
}
......@@ -27,6 +27,7 @@ type SysProcAttr struct {
// Implemented in runtime package.
func runtime_BeforeFork()
func runtime_AfterFork()
func runtime_AfterForkInChild()
// Fork, dup fd onto 0..len(fd), and exec(argv0, argvv, envv) in child.
// 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
// Fork succeeded, now in child.
runtime_AfterForkInChild()
// Enable tracing if requested.
if sys.Ptrace {
_, _, err1 = RawSyscall(SYS_PTRACE, uintptr(PTRACE_TRACEME), 0, 0)
......
......@@ -50,6 +50,7 @@ var (
// Implemented in runtime package.
func runtime_BeforeFork()
func runtime_AfterFork()
func runtime_AfterForkInChild()
// Fork, dup fd onto 0..len(fd), and exec(argv0, argvv, envv) in child.
// 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
// Fork succeeded, now in child.
runtime_AfterForkInChild()
// Wait for User ID/Group ID mappings to be written.
if sys.UidMappings != nil || sys.GidMappings != nil {
if _, _, err1 = RawSyscall(SYS_CLOSE, uintptr(p[1]), 0, 0); err1 != 0 {
......
......@@ -23,6 +23,7 @@ type SysProcAttr struct {
// Implemented in runtime package.
func runtime_BeforeFork()
func runtime_AfterFork()
func runtime_AfterForkInChild()
func chdir(path uintptr) (err Errno)
func chroot1(path uintptr) (err Errno)
......@@ -93,6 +94,8 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
// Fork succeeded, now in child.
runtime_AfterForkInChild()
// Session ID
if sys.Setsid {
_, err1 = setsid()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment