aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2016-12-08 17:39:00 -0800
committerIan Lance Taylor <iant@golang.org>2016-12-12 19:19:59 +0000
commitfded5dbb2f51d1c24d1541076b143929cdc988af (patch)
treea21887446a0c447b5b2da51673e0fe46be57e518
parent265e547658ac6e91df308326041ea462593053fc (diff)
downloadgo-fded5dbb2f51d1c24d1541076b143929cdc988af.tar.gz
go-fded5dbb2f51d1c24d1541076b143929cdc988af.zip
runtime: don't crash if signal delivered on g0 stack
Also, if we changed the gsignal stack to match the stack we are executing on, restore it when returning from the signal handler, for safety. Fixes #18255. Change-Id: Ic289b36e4e38a56f8a6d4b5d74f68121c242e81a Reviewed-on: https://go-review.googlesource.com/34239 Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Bryan Mills <bcmills@google.com> Reviewed-by: David Crawshaw <crawshaw@golang.org>
-rwxr-xr-xmisc/cgo/testsanitizers/test.bash1
-rw-r--r--misc/cgo/testsanitizers/tsan9.go60
-rw-r--r--src/runtime/signal_unix.go88
3 files changed, 131 insertions, 18 deletions
diff --git a/misc/cgo/testsanitizers/test.bash b/misc/cgo/testsanitizers/test.bash
index 01cce956b8..9853875c7e 100755
--- a/misc/cgo/testsanitizers/test.bash
+++ b/misc/cgo/testsanitizers/test.bash
@@ -145,6 +145,7 @@ if test "$tsan" = "yes"; then
testtsan tsan3.go
testtsan tsan4.go
testtsan tsan8.go
+ testtsan tsan9.go
# These tests are only reliable using clang or GCC version 7 or later.
# Otherwise runtime/cgo/libcgo.h can't tell whether TSAN is in use.
diff --git a/misc/cgo/testsanitizers/tsan9.go b/misc/cgo/testsanitizers/tsan9.go
new file mode 100644
index 0000000000..7cd0ac7dd6
--- /dev/null
+++ b/misc/cgo/testsanitizers/tsan9.go
@@ -0,0 +1,60 @@
+// Copyright 2016 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
+
+// This program failed when run under the C/C++ ThreadSanitizer. The
+// TSAN library was not keeping track of whether signals should be
+// delivered on the alternate signal stack.
+
+/*
+#cgo CFLAGS: -g -fsanitize=thread
+#cgo LDFLAGS: -g -fsanitize=thread
+
+#include <stdlib.h>
+#include <sys/time.h>
+
+void spin() {
+ size_t n;
+ struct timeval tvstart, tvnow;
+ int diff;
+
+ gettimeofday(&tvstart, NULL);
+ for (n = 0; n < 1<<20; n++) {
+ free(malloc(n));
+ gettimeofday(&tvnow, NULL);
+ diff = (tvnow.tv_sec - tvstart.tv_sec) * 1000 * 1000 + (tvnow.tv_usec - tvstart.tv_usec);
+
+ // Profile frequency is 100Hz so we should definitely
+ // get a signal in 50 milliseconds.
+ if (diff > 50 * 1000) {
+ break;
+ }
+ }
+}
+*/
+import "C"
+
+import (
+ "io/ioutil"
+ "runtime/pprof"
+ "time"
+)
+
+func goSpin() {
+ start := time.Now()
+ for n := 0; n < 1<<20; n++ {
+ _ = make([]byte, n)
+ if time.Since(start) > 50*time.Millisecond {
+ break
+ }
+ }
+}
+
+func main() {
+ pprof.StartCPUProfile(ioutil.Discard)
+ go C.spin()
+ goSpin()
+ pprof.StopCPUProfile()
+}
diff --git a/src/runtime/signal_unix.go b/src/runtime/signal_unix.go
index 19173ac211..49c7579f27 100644
--- a/src/runtime/signal_unix.go
+++ b/src/runtime/signal_unix.go
@@ -212,25 +212,43 @@ func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
}
// If some non-Go code called sigaltstack, adjust.
+ setStack := false
+ var gsignalStack gsignalStack
sp := uintptr(unsafe.Pointer(&sig))
if sp < g.m.gsignal.stack.lo || sp >= g.m.gsignal.stack.hi {
- var st stackt
- sigaltstack(nil, &st)
- if st.ss_flags&_SS_DISABLE != 0 {
- setg(nil)
- needm(0)
- noSignalStack(sig)
- dropm()
- }
- stsp := uintptr(unsafe.Pointer(st.ss_sp))
- if sp < stsp || sp >= stsp+st.ss_size {
- setg(nil)
- needm(0)
- sigNotOnStack(sig)
- dropm()
+ if sp >= g.m.g0.stack.lo && sp < g.m.g0.stack.hi {
+ // The signal was delivered on the g0 stack.
+ // This can happen when linked with C code
+ // using the thread sanitizer, which collects
+ // signals then delivers them itself by calling
+ // the signal handler directly when C code,
+ // including C code called via cgo, calls a
+ // TSAN-intercepted function such as malloc.
+ st := stackt{ss_size: g.m.g0.stack.hi - g.m.g0.stack.lo}
+ setSignalstackSP(&st, g.m.g0.stack.lo)
+ setGsignalStack(&st, &gsignalStack)
+ g.m.gsignal.stktopsp = getcallersp(unsafe.Pointer(&sig))
+ setStack = true
+ } else {
+ var st stackt
+ sigaltstack(nil, &st)
+ if st.ss_flags&_SS_DISABLE != 0 {
+ setg(nil)
+ needm(0)
+ noSignalStack(sig)
+ dropm()
+ }
+ stsp := uintptr(unsafe.Pointer(st.ss_sp))
+ if sp < stsp || sp >= stsp+st.ss_size {
+ setg(nil)
+ needm(0)
+ sigNotOnStack(sig)
+ dropm()
+ }
+ setGsignalStack(&st, &gsignalStack)
+ g.m.gsignal.stktopsp = getcallersp(unsafe.Pointer(&sig))
+ setStack = true
}
- setGsignalStack(&st)
- g.m.gsignal.stktopsp = getcallersp(unsafe.Pointer(&sig))
}
setg(g.m.gsignal)
@@ -238,6 +256,9 @@ func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
c.fixsigcode(sig)
sighandler(sig, info, ctx, g)
setg(g)
+ if setStack {
+ restoreGsignalStack(&gsignalStack)
+ }
}
// sigpanic turns a synchronous signal into a run-time panic.
@@ -585,7 +606,7 @@ func minitSignalStack() {
signalstack(&_g_.m.gsignal.stack)
_g_.m.newSigstack = true
} else {
- setGsignalStack(&st)
+ setGsignalStack(&st, nil)
_g_.m.newSigstack = false
}
}
@@ -618,14 +639,32 @@ func unminitSignals() {
}
}
+// gsignalStack saves the fields of the gsignal stack changed by
+// setGsignalStack.
+type gsignalStack struct {
+ stack stack
+ stackguard0 uintptr
+ stackguard1 uintptr
+ stackAlloc uintptr
+ stktopsp uintptr
+}
+
// setGsignalStack sets the gsignal stack of the current m to an
// alternate signal stack returned from the sigaltstack system call.
+// It saves the old values in *old for use by restoreGsignalStack.
// This is used when handling a signal if non-Go code has set the
// alternate signal stack.
//go:nosplit
//go:nowritebarrierrec
-func setGsignalStack(st *stackt) {
+func setGsignalStack(st *stackt, old *gsignalStack) {
g := getg()
+ if old != nil {
+ old.stack = g.m.gsignal.stack
+ old.stackguard0 = g.m.gsignal.stackguard0
+ old.stackguard1 = g.m.gsignal.stackguard1
+ old.stackAlloc = g.m.gsignal.stackAlloc
+ old.stktopsp = g.m.gsignal.stktopsp
+ }
stsp := uintptr(unsafe.Pointer(st.ss_sp))
g.m.gsignal.stack.lo = stsp
g.m.gsignal.stack.hi = stsp + st.ss_size
@@ -634,6 +673,19 @@ func setGsignalStack(st *stackt) {
g.m.gsignal.stackAlloc = st.ss_size
}
+// restoreGsignalStack restores the gsignal stack to the value it had
+// before entering the signal handler.
+//go:nosplit
+//go:nowritebarrierrec
+func restoreGsignalStack(st *gsignalStack) {
+ gp := getg().m.gsignal
+ gp.stack = st.stack
+ gp.stackguard0 = st.stackguard0
+ gp.stackguard1 = st.stackguard1
+ gp.stackAlloc = st.stackAlloc
+ gp.stktopsp = st.stktopsp
+}
+
// signalstack sets the current thread's alternate signal stack to s.
//go:nosplit
func signalstack(s *stack) {