runtime: if we don't handle a signal on a non-Go thread, raise it

In the past badsignal would crash the program.  In
https://golang.org/cl/10757044 badsignal was changed to call sigsend,
to fix issue #3250.  The effect of this was that when a non-Go thread
received a signal, and os/signal.Notify was not being used to check
for occurrences of the signal, the signal was ignored.

This changes the code so that if os/signal.Notify is not being used,
then the signal handler is reset to what it was, and the signal is
raised again.  This lets non-Go threads handle the signal as they
wish.  In particular, it means that a segmentation violation in a
non-Go thread will ordinarily crash the process, as it should.

Fixes #10139.
Update #11794.

Change-Id: I2109444aaada9d963ad03b1d071ec667760515e5
Reviewed-on: https://go-review.googlesource.com/12503
Reviewed-by: Russ Cox <rsc@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
diff --git a/src/runtime/crash_cgo_test.go b/src/runtime/crash_cgo_test.go
index 9ff95e6..d2847b0 100644
--- a/src/runtime/crash_cgo_test.go
+++ b/src/runtime/crash_cgo_test.go
@@ -82,6 +82,19 @@
 	}
 }
 
+func TestCgoExternalThreadSignal(t *testing.T) {
+	// issue 10139
+	switch runtime.GOOS {
+	case "plan9", "windows":
+		t.Skipf("no pthreads on %s", runtime.GOOS)
+	}
+	got := executeTest(t, cgoExternalThreadSignalSource, nil)
+	want := "OK\n"
+	if got != want {
+		t.Fatalf("expected %q, but got %q", want, got)
+	}
+}
+
 func TestCgoDLLImports(t *testing.T) {
 	// test issue 9356
 	if runtime.GOOS != "windows" {
@@ -282,6 +295,60 @@
 }
 `
 
+const cgoExternalThreadSignalSource = `
+package main
+
+/*
+#include <pthread.h>
+
+void **nullptr;
+
+void *crash(void *p) {
+	*nullptr = p;
+	return 0;
+}
+
+int start_crashing_thread(void) {
+	pthread_t tid;
+	return pthread_create(&tid, 0, crash, 0);
+}
+*/
+import "C"
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"time"
+)
+
+func main() {
+	if len(os.Args) > 1 && os.Args[1] == "crash" {
+		i := C.start_crashing_thread()
+		if i != 0 {
+			fmt.Println("pthread_create failed:", i)
+			// Exit with 0 because parent expects us to crash.
+			return
+		}
+
+		// We should crash immediately, but give it plenty of
+		// time before failing (by exiting 0) in case we are
+		// running on a slow system.
+		time.Sleep(5 * time.Second)
+		return
+	}
+
+	out, err := exec.Command(os.Args[0], "crash").CombinedOutput()
+	if err == nil {
+		fmt.Println("C signal did not crash as expected\n")
+		fmt.Printf("%s\n", out)
+		os.Exit(1)
+	}
+
+	fmt.Println("OK")
+}
+`
+
 const cgoDLLImportsMainSource = `
 package main
 
diff --git a/src/runtime/os1_darwin.go b/src/runtime/os1_darwin.go
index 06bc2c7..08ec611 100644
--- a/src/runtime/os1_darwin.go
+++ b/src/runtime/os1_darwin.go
@@ -469,3 +469,8 @@
 func updatesigmask(m sigmask) {
 	sigprocmask(_SIG_SETMASK, &m[0], nil)
 }
+
+func unblocksig(sig int32) {
+	mask := uint32(1) << (uint32(sig) - 1)
+	sigprocmask(_SIG_UNBLOCK, &mask, nil)
+}
diff --git a/src/runtime/os1_dragonfly.go b/src/runtime/os1_dragonfly.go
index a4c11d4..f96c78c 100644
--- a/src/runtime/os1_dragonfly.go
+++ b/src/runtime/os1_dragonfly.go
@@ -78,7 +78,7 @@
 	}
 
 	var oset sigset
-	sigprocmask(&sigset_all, &oset)
+	sigprocmask(_SIG_SETMASK, &sigset_all, &oset)
 
 	params := lwpparams{
 		start_func: funcPC(lwp_start),
@@ -91,7 +91,7 @@
 	mp.tls[0] = uintptr(mp.id) // XXX so 386 asm can find it
 
 	lwp_create(&params)
-	sigprocmask(&oset, nil)
+	sigprocmask(_SIG_SETMASK, &oset, nil)
 }
 
 func osinit() {
@@ -124,7 +124,7 @@
 	if unsafe.Sizeof(*smask) > unsafe.Sizeof(mp.sigmask) {
 		throw("insufficient storage for signal mask")
 	}
-	sigprocmask(nil, smask)
+	sigprocmask(_SIG_SETMASK, nil, smask)
 }
 
 // Called to initialize a new m (including the bootstrap m).
@@ -145,14 +145,14 @@
 			nmask.__bits[(i-1)/32] &^= 1 << ((uint32(i) - 1) & 31)
 		}
 	}
-	sigprocmask(&nmask, nil)
+	sigprocmask(_SIG_SETMASK, &nmask, nil)
 }
 
 // Called from dropm to undo the effect of an minit.
 func unminit() {
 	_g_ := getg()
 	smask := (*sigset)(unsafe.Pointer(&_g_.m.sigmask))
-	sigprocmask(smask, nil)
+	sigprocmask(_SIG_SETMASK, smask, nil)
 	signalstack(nil)
 }
 
@@ -237,5 +237,11 @@
 func updatesigmask(m sigmask) {
 	var mask sigset
 	copy(mask.__bits[:], m[:])
-	sigprocmask(&mask, nil)
+	sigprocmask(_SIG_SETMASK, &mask, nil)
+}
+
+func unblocksig(sig int32) {
+	var mask sigset
+	mask.__bits[(sig-1)/32] |= 1 << ((uint32(sig) - 1) & 31)
+	sigprocmask(_SIG_UNBLOCK, &mask, nil)
 }
diff --git a/src/runtime/os1_freebsd.go b/src/runtime/os1_freebsd.go
index 6dbf829..f3519f3 100644
--- a/src/runtime/os1_freebsd.go
+++ b/src/runtime/os1_freebsd.go
@@ -88,9 +88,9 @@
 	mp.tls[0] = uintptr(mp.id) // so 386 asm can find it
 
 	var oset sigset
-	sigprocmask(&sigset_all, &oset)
+	sigprocmask(_SIG_SETMASK, &sigset_all, &oset)
 	thr_new(&param, int32(unsafe.Sizeof(param)))
-	sigprocmask(&oset, nil)
+	sigprocmask(_SIG_SETMASK, &oset, nil)
 }
 
 func osinit() {
@@ -123,7 +123,7 @@
 	if unsafe.Sizeof(*smask) > unsafe.Sizeof(mp.sigmask) {
 		throw("insufficient storage for signal mask")
 	}
-	sigprocmask(nil, smask)
+	sigprocmask(_SIG_SETMASK, nil, smask)
 }
 
 // Called to initialize a new m (including the bootstrap m).
@@ -147,14 +147,14 @@
 			nmask.__bits[(i-1)/32] &^= 1 << ((uint32(i) - 1) & 31)
 		}
 	}
-	sigprocmask(&nmask, nil)
+	sigprocmask(_SIG_SETMASK, &nmask, nil)
 }
 
 // Called from dropm to undo the effect of an minit.
 func unminit() {
 	_g_ := getg()
 	smask := (*sigset)(unsafe.Pointer(&_g_.m.sigmask))
-	sigprocmask(smask, nil)
+	sigprocmask(_SIG_SETMASK, smask, nil)
 	signalstack(nil)
 }
 
@@ -239,5 +239,11 @@
 func updatesigmask(m [(_NSIG + 31) / 32]uint32) {
 	var mask sigset
 	copy(mask.__bits[:], m[:])
-	sigprocmask(&mask, nil)
+	sigprocmask(_SIG_SETMASK, &mask, nil)
+}
+
+func unblocksig(sig int32) {
+	var mask sigset
+	mask.__bits[(sig-1)/32] |= 1 << ((uint32(sig) - 1) & 31)
+	sigprocmask(_SIG_UNBLOCK, &mask, nil)
 }
diff --git a/src/runtime/os1_linux.go b/src/runtime/os1_linux.go
index e6942a9..dd64afc 100644
--- a/src/runtime/os1_linux.go
+++ b/src/runtime/os1_linux.go
@@ -333,3 +333,9 @@
 	copy(mask[:], m[:])
 	rtsigprocmask(_SIG_SETMASK, &mask, nil, int32(unsafe.Sizeof(mask)))
 }
+
+func unblocksig(sig int32) {
+	var mask sigset
+	mask[(sig-1)/32] |= 1 << ((uint32(sig) - 1) & 31)
+	rtsigprocmask(_SIG_UNBLOCK, &mask, nil, int32(unsafe.Sizeof(mask)))
+}
diff --git a/src/runtime/os1_nacl.go b/src/runtime/os1_nacl.go
index 66e60f8..143752a 100644
--- a/src/runtime/os1_nacl.go
+++ b/src/runtime/os1_nacl.go
@@ -170,6 +170,10 @@
 
 var badsignal1 = []byte("runtime: signal received on thread not created by Go.\n")
 
+func raisebadsignal(sig int32) {
+	badsignal2()
+}
+
 func madvise(addr unsafe.Pointer, n uintptr, flags int32) {}
 func munmap(addr unsafe.Pointer, n uintptr)               {}
 func resetcpuprofiler(hz int32)                           {}
diff --git a/src/runtime/os1_netbsd.go b/src/runtime/os1_netbsd.go
index 2a579b8..cacd606 100644
--- a/src/runtime/os1_netbsd.go
+++ b/src/runtime/os1_netbsd.go
@@ -230,3 +230,9 @@
 	copy(mask.__bits[:], m[:])
 	sigprocmask(_SIG_SETMASK, &mask, nil)
 }
+
+func unblocksig(sig int32) {
+	var mask sigset
+	mask.__bits[(sig-1)/32] |= 1 << ((uint32(sig) - 1) & 31)
+	sigprocmask(_SIG_UNBLOCK, &mask, nil)
+}
diff --git a/src/runtime/os1_openbsd.go b/src/runtime/os1_openbsd.go
index c07cd24..24a095b 100644
--- a/src/runtime/os1_openbsd.go
+++ b/src/runtime/os1_openbsd.go
@@ -239,3 +239,8 @@
 func updatesigmask(m sigmask) {
 	sigprocmask(_SIG_SETMASK, m[0])
 }
+
+func unblocksig(sig int32) {
+	mask := uint32(1) << (uint32(sig) - 1)
+	sigprocmask(_SIG_UNBLOCK, mask)
+}
diff --git a/src/runtime/os1_plan9.go b/src/runtime/os1_plan9.go
index bda7057..9615b6d 100644
--- a/src/runtime/os1_plan9.go
+++ b/src/runtime/os1_plan9.go
@@ -254,6 +254,10 @@
 	exits(&_badsignal[0])
 }
 
+func raisebadsignal(sig int32) {
+	badsignal2()
+}
+
 func _atoi(b []byte) int {
 	n := 0
 	for len(b) > 0 && '0' <= b[0] && b[0] <= '9' {
diff --git a/src/runtime/os2_dragonfly.go b/src/runtime/os2_dragonfly.go
index 0a20ed4..ccad82f 100644
--- a/src/runtime/os2_dragonfly.go
+++ b/src/runtime/os2_dragonfly.go
@@ -5,8 +5,11 @@
 package runtime
 
 const (
-	_NSIG       = 33
-	_SI_USER    = 0x10001
-	_SS_DISABLE = 4
-	_RLIMIT_AS  = 10
+	_NSIG        = 33
+	_SI_USER     = 0x10001
+	_SS_DISABLE  = 4
+	_RLIMIT_AS   = 10
+	_SIG_BLOCK   = 1
+	_SIG_UNBLOCK = 2
+	_SIG_SETMASK = 3
 )
diff --git a/src/runtime/os2_freebsd.go b/src/runtime/os2_freebsd.go
index f67211f..84ab715 100644
--- a/src/runtime/os2_freebsd.go
+++ b/src/runtime/os2_freebsd.go
@@ -5,8 +5,11 @@
 package runtime
 
 const (
-	_SS_DISABLE = 4
-	_NSIG       = 33
-	_SI_USER    = 0x10001
-	_RLIMIT_AS  = 10
+	_SS_DISABLE  = 4
+	_NSIG        = 33
+	_SI_USER     = 0x10001
+	_RLIMIT_AS   = 10
+	_SIG_BLOCK   = 1
+	_SIG_UNBLOCK = 2
+	_SIG_SETMASK = 3
 )
diff --git a/src/runtime/os2_linux.go b/src/runtime/os2_linux.go
index eaa9f0e8..71f36eb 100644
--- a/src/runtime/os2_linux.go
+++ b/src/runtime/os2_linux.go
@@ -8,6 +8,8 @@
 	_SS_DISABLE  = 2
 	_NSIG        = 65
 	_SI_USER     = 0
+	_SIG_BLOCK   = 0
+	_SIG_UNBLOCK = 1
 	_SIG_SETMASK = 2
 	_RLIMIT_AS   = 9
 )
diff --git a/src/runtime/os2_solaris.go b/src/runtime/os2_solaris.go
index 26ca15f..f5c0c83 100644
--- a/src/runtime/os2_solaris.go
+++ b/src/runtime/os2_solaris.go
@@ -6,6 +6,7 @@
 
 const (
 	_SS_DISABLE  = 2
+	_SIG_UNBLOCK = 2
 	_SIG_SETMASK = 3
 	_NSIG        = 73 /* number of signals in sigtable array */
 	_SI_USER     = 0
diff --git a/src/runtime/os3_solaris.go b/src/runtime/os3_solaris.go
index 53d7b96..7caa72e 100644
--- a/src/runtime/os3_solaris.go
+++ b/src/runtime/os3_solaris.go
@@ -304,6 +304,12 @@
 	sigprocmask(_SIG_SETMASK, &mask, nil)
 }
 
+func unblocksig(sig int32) {
+	var mask sigset
+	mask.__sigbits[(sig-1)/32] |= 1 << ((uint32(sig) - 1) & 31)
+	sigprocmask(_SIG_UNBLOCK, &mask, nil)
+}
+
 //go:nosplit
 func semacreate() uintptr {
 	var sem *semt
diff --git a/src/runtime/os_darwin.go b/src/runtime/os_darwin.go
index c432c99..3deafd5 100644
--- a/src/runtime/os_darwin.go
+++ b/src/runtime/os_darwin.go
@@ -20,7 +20,7 @@
 func sysctl(mib *uint32, miblen uint32, out *byte, size *uintptr, dst *byte, ndst uintptr) int32
 
 //go:noescape
-func sigprocmask(sig uint32, new, old *uint32)
+func sigprocmask(how uint32, new, old *uint32)
 
 //go:noescape
 func sigaction(mode uint32, new, old *sigactiont)
diff --git a/src/runtime/os_dragonfly.go b/src/runtime/os_dragonfly.go
index 60234bb..b19270a 100644
--- a/src/runtime/os_dragonfly.go
+++ b/src/runtime/os_dragonfly.go
@@ -19,7 +19,7 @@
 func sigaction(sig int32, new, old *sigactiont)
 
 //go:noescape
-func sigprocmask(new, old *sigset)
+func sigprocmask(how int32, new, old *sigset)
 
 //go:noescape
 func setitimer(mode int32, new, old *itimerval)
diff --git a/src/runtime/os_freebsd.go b/src/runtime/os_freebsd.go
index b2b5cd1..8c8a106 100644
--- a/src/runtime/os_freebsd.go
+++ b/src/runtime/os_freebsd.go
@@ -19,7 +19,7 @@
 func sigaction(sig int32, new, old *sigactiont)
 
 //go:noescape
-func sigprocmask(new, old *sigset)
+func sigprocmask(how int32, new, old *sigset)
 
 //go:noescape
 func setitimer(mode int32, new, old *itimerval)
diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go
index 523d28b..bd492f5 100644
--- a/src/runtime/os_linux.go
+++ b/src/runtime/os_linux.go
@@ -29,8 +29,8 @@
 
 //go:noescape
 func getrlimit(kind int32, limit unsafe.Pointer) int32
-func raise(sig uint32)
-func raiseproc(sig uint32)
+func raise(sig int32)
+func raiseproc(sig int32)
 
 //go:noescape
 func sched_getaffinity(pid, len uintptr, buf *uintptr) int32
diff --git a/src/runtime/signal1_unix.go b/src/runtime/signal1_unix.go
index d3e9dac..56d9755 100644
--- a/src/runtime/signal1_unix.go
+++ b/src/runtime/signal1_unix.go
@@ -140,6 +140,43 @@
 	raise(_SIGPIPE)
 }
 
+// raisebadsignal is called when a signal is received on a non-Go
+// thread, and the Go program does not want to handle it (that is, the
+// program has not called os/signal.Notify for the signal).
+func raisebadsignal(sig int32) {
+	if sig == _SIGPROF {
+		// Ignore profiling signals that arrive on non-Go threads.
+		return
+	}
+
+	var handler uintptr
+	if sig >= _NSIG {
+		handler = _SIG_DFL
+	} else {
+		handler = fwdSig[sig]
+	}
+
+	// Reset the signal handler and raise the signal.
+	// We are currently running inside a signal handler, so the
+	// signal is blocked.  We need to unblock it before raising the
+	// signal, or the signal we raise will be ignored until we return
+	// from the signal handler.  We know that the signal was unblocked
+	// before entering the handler, or else we would not have received
+	// it.  That means that we don't have to worry about blocking it
+	// again.
+	unblocksig(sig)
+	setsig(sig, handler, false)
+	raise(sig)
+
+	// If the signal didn't cause the program to exit, restore the
+	// Go signal handler and carry on.
+	//
+	// We may receive another instance of the signal before we
+	// restore the Go handler, but that is not so bad: we know
+	// that the Go program has been ignoring the signal.
+	setsig(sig, funcPC(sighandler), true)
+}
+
 func crash() {
 	if GOOS == "darwin" {
 		// OS X core dumps are linear dumps of the mapped memory,
diff --git a/src/runtime/signal_windows.go b/src/runtime/signal_windows.go
index 5e17f74..d80cc97 100644
--- a/src/runtime/signal_windows.go
+++ b/src/runtime/signal_windows.go
@@ -203,6 +203,12 @@
 func sigignore(sig uint32) {
 }
 
+func badsignal2()
+
+func raisebadsignal(sig int32) {
+	badsignal2()
+}
+
 func crash() {
 	// TODO: This routine should do whatever is needed
 	// to make the Windows program abort/crash as it
diff --git a/src/runtime/sigqueue.go b/src/runtime/sigqueue.go
index 9cfe259..e078bfa 100644
--- a/src/runtime/sigqueue.go
+++ b/src/runtime/sigqueue.go
@@ -165,5 +165,13 @@
 // This runs on a foreign stack, without an m or a g.  No stack split.
 //go:nosplit
 func badsignal(sig uintptr) {
-	cgocallback(unsafe.Pointer(funcPC(sigsend)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
+	cgocallback(unsafe.Pointer(funcPC(badsignalgo)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
+}
+
+func badsignalgo(sig uintptr) {
+	if !sigsend(uint32(sig)) {
+		// A foreign thread received the signal sig, and the
+		// Go code does not want to handle it.
+		raisebadsignal(int32(sig))
+	}
 }
diff --git a/src/runtime/sys_dragonfly_amd64.s b/src/runtime/sys_dragonfly_amd64.s
index efda432..3dae2a7 100644
--- a/src/runtime/sys_dragonfly_amd64.s
+++ b/src/runtime/sys_dragonfly_amd64.s
@@ -307,9 +307,9 @@
 	RET
 
 TEXT runtime·sigprocmask(SB),NOSPLIT,$0
-	MOVL	$3, DI			// arg 1 - how (SIG_SETMASK)
-	MOVQ	new+0(FP), SI		// arg 2 - set
-	MOVQ	old+8(FP), DX		// arg 3 - oset
+	MOVL	how+0(FP), DI		// arg 1 - how
+	MOVQ	new+8(FP), SI		// arg 2 - set
+	MOVQ	old+16(FP), DX		// arg 3 - oset
 	MOVL	$340, AX		// sys_sigprocmask
 	SYSCALL
 	JAE	2(PC)
diff --git a/src/runtime/sys_freebsd_386.s b/src/runtime/sys_freebsd_386.s
index 94b8d95..be20808 100644
--- a/src/runtime/sys_freebsd_386.s
+++ b/src/runtime/sys_freebsd_386.s
@@ -355,10 +355,11 @@
 
 TEXT runtime·sigprocmask(SB),NOSPLIT,$16
 	MOVL	$0, 0(SP)		// syscall gap
-	MOVL	$3, 4(SP)		// arg 1 - how (SIG_SETMASK)
-	MOVL	new+0(FP), AX
+	MOVL	how+0(FP), AX		// arg 1 - how
+	MOVL	AX, 4(SP)
+	MOVL	new+4(FP), AX
 	MOVL	AX, 8(SP)		// arg 2 - set
-	MOVL	old+4(FP), AX
+	MOVL	old+8(FP), AX
 	MOVL	AX, 12(SP)		// arg 3 - oset
 	MOVL	$340, AX		// sys_sigprocmask
 	INT	$0x80
diff --git a/src/runtime/sys_freebsd_amd64.s b/src/runtime/sys_freebsd_amd64.s
index a9a621b..8ef0458 100644
--- a/src/runtime/sys_freebsd_amd64.s
+++ b/src/runtime/sys_freebsd_amd64.s
@@ -295,9 +295,9 @@
 	RET
 
 TEXT runtime·sigprocmask(SB),NOSPLIT,$0
-	MOVL	$3, DI			// arg 1 - how (SIG_SETMASK)
-	MOVQ	new+0(FP), SI		// arg 2 - set
-	MOVQ	old+8(FP), DX		// arg 3 - oset
+	MOVL	how+0(FP), DI		// arg 1 - how
+	MOVQ	new+8(FP), SI		// arg 2 - set
+	MOVQ	old+16(FP), DX		// arg 3 - oset
 	MOVL	$340, AX		// sys_sigprocmask
 	SYSCALL
 	JAE	2(PC)
diff --git a/src/runtime/sys_freebsd_arm.s b/src/runtime/sys_freebsd_arm.s
index 3dd04cf..298900c 100644
--- a/src/runtime/sys_freebsd_arm.s
+++ b/src/runtime/sys_freebsd_arm.s
@@ -327,9 +327,9 @@
 	RET
 
 TEXT runtime·sigprocmask(SB),NOSPLIT,$0
-	MOVW $3, R0	// arg 1 - how (SIG_SETMASK)
-	MOVW new+0(FP), R1	// arg 2 - set
-	MOVW old+4(FP), R2	// arg 3 - oset
+	MOVW how+0(FP), R0	// arg 1 - how
+	MOVW new+4(FP), R1	// arg 2 - set
+	MOVW old+8(FP), R2	// arg 3 - oset
 	MOVW $SYS_sigprocmask, R7
 	SWI $0
 	MOVW.CS $0, R8 // crash on syscall failure
diff --git a/src/runtime/sys_linux_amd64.s b/src/runtime/sys_linux_amd64.s
index 8644a0b..59c21c5 100644
--- a/src/runtime/sys_linux_amd64.s
+++ b/src/runtime/sys_linux_amd64.s
@@ -219,7 +219,7 @@
 	RET
 
 TEXT runtime·sigfwd(SB),NOSPLIT,$0-32
-	MOVQ	sig+8(FP), DI
+	MOVL	sig+8(FP), DI
 	MOVQ	info+16(FP), SI
 	MOVQ	ctx+24(FP), DX
 	MOVQ	fn+0(FP), AX