Skip to content

Commit f7e51c1

Browse files
runtime: adjust gsignal stack to current signal stack
If non-Go code calls sigaltstack before a signal is received, use sigaltstack to determine the current signal stack and set the gsignal stack to use it. This makes the Go runtime more robust in the face of non-Go code. We still can't handle a disabled signal stack or a signal triggered with SA_ONSTACK clear, but we now give clear errors for those cases. Fixes #7227. Update #9896. Change-Id: Icb1607e01fd6461019b6d77d940e59b3aed4d258 Reviewed-on: https://go-review.googlesource.com/18102 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Mikio Hara <[email protected]>
1 parent e4dcf5c commit f7e51c1

23 files changed

+419
-327
lines changed

misc/cgo/test/cgo_unix_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2015 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 !windows
6+
7+
package cgotest
8+
9+
import "testing"
10+
11+
func TestSigaltstack(t *testing.T) { testSigaltstack(t) }

misc/cgo/test/sigaltstack.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2015 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 !windows
6+
7+
// Test that the Go runtime still works if C code changes the signal stack.
8+
9+
package cgotest
10+
11+
/*
12+
#include <signal.h>
13+
#include <stdio.h>
14+
#include <stdlib.h>
15+
#include <string.h>
16+
17+
static stack_t oss;
18+
static char signalStack[SIGSTKSZ];
19+
20+
static void changeSignalStack() {
21+
stack_t ss;
22+
memset(&ss, 0, sizeof ss);
23+
ss.ss_sp = signalStack;
24+
ss.ss_flags = 0;
25+
ss.ss_size = SIGSTKSZ;
26+
if (sigaltstack(&ss, &oss) < 0) {
27+
perror("sigaltstack");
28+
abort();
29+
}
30+
}
31+
32+
static void restoreSignalStack() {
33+
#if defined(__x86_64__) && defined(__APPLE__)
34+
// The Darwin C library enforces a minimum that the kernel does not.
35+
// This is OK since we allocated this much space in mpreinit,
36+
// it was just removed from the buffer by stackalloc.
37+
oss.ss_size = MINSIGSTKSZ;
38+
#endif
39+
if (sigaltstack(&oss, NULL) < 0) {
40+
perror("sigaltstack restore");
41+
abort();
42+
}
43+
}
44+
45+
static int zero() {
46+
return 0;
47+
}
48+
*/
49+
import "C"
50+
51+
import (
52+
"runtime"
53+
"testing"
54+
)
55+
56+
func testSigaltstack(t *testing.T) {
57+
switch {
58+
case runtime.GOOS == "solaris", runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64"):
59+
t.Skipf("switching signal stack not implemented on %s/s", runtime.GOOS, runtime.GOARCH)
60+
}
61+
62+
C.changeSignalStack()
63+
defer C.restoreSignalStack()
64+
defer func() {
65+
if recover() == nil {
66+
t.Error("did not see expected panic")
67+
}
68+
}()
69+
v := 1 / int(C.zero())
70+
t.Errorf("unexpected success of division by zero == %d", v)
71+
}

src/runtime/signal1_unix.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,19 @@ func ensureSigM() {
249249
}
250250
}()
251251
}
252+
253+
// This is called when we receive a signal when there is no signal stack.
254+
// This can only happen if non-Go code calls sigaltstack to disable the
255+
// signal stack. This is called via cgocallback to establish a stack.
256+
func noSignalStack(sig uint32) {
257+
println("signal", sig, "received on thread with no signal stack")
258+
throw("non-Go code disabled sigaltstack")
259+
}
260+
261+
// This is called if we receive a signal when there is a signal stack
262+
// but we are not on it. This can only happen if non-Go code called
263+
// sigaction without setting the SS_ONSTACK flag.
264+
func sigNotOnStack(sig uint32) {
265+
println("signal", sig, "received but handler not on signal stack")
266+
throw("non-Go code set up signal handler without SA_ONSTACK flag")
267+
}

src/runtime/signal2_unix.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// +build darwin linux
5+
// +build darwin dragonfly freebsd linux netbsd openbsd
66

77
package runtime
88

src/runtime/signal_darwin.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,29 @@ func sigtrampgo(fn uintptr, infostyle, sig uint32, info *siginfo, ctx unsafe.Poi
6161
sigreturn(ctx, infostyle)
6262
return
6363
}
64+
65+
// If some non-Go code called sigaltstack, adjust.
66+
sp := uintptr(unsafe.Pointer(&sig))
67+
if sp < g.m.gsignal.stack.lo || sp >= g.m.gsignal.stack.hi {
68+
var st stackt
69+
sigaltstack(nil, &st)
70+
if st.ss_flags&_SS_DISABLE != 0 {
71+
setg(nil)
72+
cgocallback(unsafe.Pointer(funcPC(noSignalStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
73+
}
74+
stsp := uintptr(unsafe.Pointer(st.ss_sp))
75+
if sp < stsp || sp >= stsp+st.ss_size {
76+
setg(nil)
77+
cgocallback(unsafe.Pointer(funcPC(sigNotOnStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
78+
}
79+
g.m.gsignal.stack.lo = stsp
80+
g.m.gsignal.stack.hi = stsp + st.ss_size
81+
g.m.gsignal.stackguard0 = stsp + _StackGuard
82+
g.m.gsignal.stackguard1 = stsp + _StackGuard
83+
g.m.gsignal.stackAlloc = st.ss_size
84+
g.m.gsignal.stktopsp = getcallersp(unsafe.Pointer(&sig))
85+
}
86+
6487
setg(g.m.gsignal)
6588
sighandler(sig, info, ctx, g)
6689
setg(g)

src/runtime/signal_freebsd.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package runtime
66

7+
import "unsafe"
8+
79
type sigTabT struct {
810
flags int32
911
name string
@@ -44,3 +46,41 @@ var sigtable = [...]sigTabT{
4446
/* 31 */ {_SigNotify, "SIGUSR2: user-defined signal 2"},
4547
/* 32 */ {_SigNotify, "SIGTHR: reserved"},
4648
}
49+
50+
//go:nosplit
51+
func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
52+
if sigfwdgo(sig, info, ctx) {
53+
return
54+
}
55+
g := getg()
56+
if g == nil {
57+
badsignal(uintptr(sig))
58+
return
59+
}
60+
61+
// If some non-Go code called sigaltstack, adjust.
62+
sp := uintptr(unsafe.Pointer(&sig))
63+
if sp < g.m.gsignal.stack.lo || sp >= g.m.gsignal.stack.hi {
64+
var st stackt
65+
sigaltstack(nil, &st)
66+
if st.ss_flags&_SS_DISABLE != 0 {
67+
setg(nil)
68+
cgocallback(unsafe.Pointer(funcPC(noSignalStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
69+
}
70+
stsp := uintptr(unsafe.Pointer(st.ss_sp))
71+
if sp < stsp || sp >= stsp+st.ss_size {
72+
setg(nil)
73+
cgocallback(unsafe.Pointer(funcPC(sigNotOnStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
74+
}
75+
g.m.gsignal.stack.lo = stsp
76+
g.m.gsignal.stack.hi = stsp + st.ss_size
77+
g.m.gsignal.stackguard0 = stsp + _StackGuard
78+
g.m.gsignal.stackguard1 = stsp + _StackGuard
79+
g.m.gsignal.stackAlloc = st.ss_size
80+
g.m.gsignal.stktopsp = getcallersp(unsafe.Pointer(&sig))
81+
}
82+
83+
setg(g.m.gsignal)
84+
sighandler(sig, info, ctx, g)
85+
setg(g)
86+
}

src/runtime/signal_linux.go

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/runtime/signal_openbsd.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package runtime
66

7+
import "unsafe"
8+
79
type sigTabT struct {
810
flags int32
911
name string
@@ -44,3 +46,41 @@ var sigtable = [...]sigTabT{
4446
/* 31 */ {_SigNotify, "SIGUSR2: user-defined signal 2"},
4547
/* 32 */ {_SigNotify, "SIGTHR: reserved"},
4648
}
49+
50+
//go:nosplit
51+
func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
52+
if sigfwdgo(sig, info, ctx) {
53+
return
54+
}
55+
g := getg()
56+
if g == nil {
57+
badsignal(uintptr(sig))
58+
return
59+
}
60+
61+
// If some non-Go code called sigaltstack, adjust.
62+
sp := uintptr(unsafe.Pointer(&sig))
63+
if sp < g.m.gsignal.stack.lo || sp >= g.m.gsignal.stack.hi {
64+
var st stackt
65+
sigaltstack(nil, &st)
66+
if st.ss_flags&_SS_DISABLE != 0 {
67+
setg(nil)
68+
cgocallback(unsafe.Pointer(funcPC(noSignalStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
69+
}
70+
stsp := uintptr(unsafe.Pointer(st.ss_sp))
71+
if sp < stsp || sp >= stsp+st.ss_size {
72+
setg(nil)
73+
cgocallback(unsafe.Pointer(funcPC(sigNotOnStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
74+
}
75+
g.m.gsignal.stack.lo = stsp
76+
g.m.gsignal.stack.hi = stsp + st.ss_size
77+
g.m.gsignal.stackguard0 = stsp + _StackGuard
78+
g.m.gsignal.stackguard1 = stsp + _StackGuard
79+
g.m.gsignal.stackAlloc = st.ss_size
80+
g.m.gsignal.stktopsp = getcallersp(unsafe.Pointer(&sig))
81+
}
82+
83+
setg(g.m.gsignal)
84+
sighandler(sig, info, ctx, g)
85+
setg(g)
86+
}

src/runtime/signal_sigtramp.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2009 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 dragonfly linux netbsd
6+
7+
package runtime
8+
9+
import "unsafe"
10+
11+
// Continuation of the (assembly) sigtramp() logic.
12+
//go:nosplit
13+
func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
14+
if sigfwdgo(sig, info, ctx) {
15+
return
16+
}
17+
g := getg()
18+
if g == nil {
19+
badsignal(uintptr(sig))
20+
return
21+
}
22+
23+
// If some non-Go code called sigaltstack, adjust.
24+
sp := uintptr(unsafe.Pointer(&sig))
25+
if sp < g.m.gsignal.stack.lo || sp >= g.m.gsignal.stack.hi {
26+
var st sigaltstackt
27+
sigaltstack(nil, &st)
28+
if st.ss_flags&_SS_DISABLE != 0 {
29+
setg(nil)
30+
cgocallback(unsafe.Pointer(funcPC(noSignalStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
31+
}
32+
stsp := uintptr(unsafe.Pointer(st.ss_sp))
33+
if sp < stsp || sp >= stsp+st.ss_size {
34+
setg(nil)
35+
cgocallback(unsafe.Pointer(funcPC(sigNotOnStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
36+
}
37+
g.m.gsignal.stack.lo = stsp
38+
g.m.gsignal.stack.hi = stsp + st.ss_size
39+
g.m.gsignal.stackguard0 = stsp + _StackGuard
40+
g.m.gsignal.stackguard1 = stsp + _StackGuard
41+
g.m.gsignal.stackAlloc = st.ss_size
42+
g.m.gsignal.stktopsp = getcallersp(unsafe.Pointer(&sig))
43+
}
44+
45+
setg(g.m.gsignal)
46+
sighandler(sig, info, ctx, g)
47+
setg(g)
48+
}

0 commit comments

Comments
 (0)