Skip to content

Commit

Permalink
runtime: adjust gsignal stack to current signal stack
Browse files Browse the repository at this point in the history
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 <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Mikio Hara <mikioh.mikioh@gmail.com>
  • Loading branch information
ianlancetaylor committed Dec 24, 2015
1 parent e4dcf5c commit f7e51c1
Show file tree
Hide file tree
Showing 23 changed files with 419 additions and 327 deletions.
11 changes: 11 additions & 0 deletions misc/cgo/test/cgo_unix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2015 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 !windows

package cgotest

import "testing"

func TestSigaltstack(t *testing.T) { testSigaltstack(t) }
71 changes: 71 additions & 0 deletions misc/cgo/test/sigaltstack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2015 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 !windows

// Test that the Go runtime still works if C code changes the signal stack.

package cgotest

/*
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static stack_t oss;
static char signalStack[SIGSTKSZ];
static void changeSignalStack() {
stack_t ss;
memset(&ss, 0, sizeof ss);
ss.ss_sp = signalStack;
ss.ss_flags = 0;
ss.ss_size = SIGSTKSZ;
if (sigaltstack(&ss, &oss) < 0) {
perror("sigaltstack");
abort();
}
}
static void restoreSignalStack() {
#if defined(__x86_64__) && defined(__APPLE__)
// The Darwin C library enforces a minimum that the kernel does not.
// This is OK since we allocated this much space in mpreinit,
// it was just removed from the buffer by stackalloc.
oss.ss_size = MINSIGSTKSZ;
#endif
if (sigaltstack(&oss, NULL) < 0) {
perror("sigaltstack restore");
abort();
}
}
static int zero() {
return 0;
}
*/
import "C"

import (
"runtime"
"testing"
)

func testSigaltstack(t *testing.T) {
switch {
case runtime.GOOS == "solaris", runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64"):
t.Skipf("switching signal stack not implemented on %s/s", runtime.GOOS, runtime.GOARCH)
}

C.changeSignalStack()
defer C.restoreSignalStack()
defer func() {
if recover() == nil {
t.Error("did not see expected panic")
}
}()
v := 1 / int(C.zero())
t.Errorf("unexpected success of division by zero == %d", v)
}
16 changes: 16 additions & 0 deletions src/runtime/signal1_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,19 @@ func ensureSigM() {
}
}()
}

// This is called when we receive a signal when there is no signal stack.
// This can only happen if non-Go code calls sigaltstack to disable the
// signal stack. This is called via cgocallback to establish a stack.
func noSignalStack(sig uint32) {
println("signal", sig, "received on thread with no signal stack")
throw("non-Go code disabled sigaltstack")
}

// This is called if we receive a signal when there is a signal stack
// but we are not on it. This can only happen if non-Go code called
// sigaction without setting the SS_ONSTACK flag.
func sigNotOnStack(sig uint32) {
println("signal", sig, "received but handler not on signal stack")
throw("non-Go code set up signal handler without SA_ONSTACK flag")
}
2 changes: 1 addition & 1 deletion src/runtime/signal2_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin linux
// +build darwin dragonfly freebsd linux netbsd openbsd

package runtime

Expand Down
23 changes: 23 additions & 0 deletions src/runtime/signal_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,29 @@ func sigtrampgo(fn uintptr, infostyle, sig uint32, info *siginfo, ctx unsafe.Poi
sigreturn(ctx, infostyle)
return
}

// If some non-Go code called sigaltstack, adjust.
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)
cgocallback(unsafe.Pointer(funcPC(noSignalStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
}
stsp := uintptr(unsafe.Pointer(st.ss_sp))
if sp < stsp || sp >= stsp+st.ss_size {
setg(nil)
cgocallback(unsafe.Pointer(funcPC(sigNotOnStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
}
g.m.gsignal.stack.lo = stsp
g.m.gsignal.stack.hi = stsp + st.ss_size
g.m.gsignal.stackguard0 = stsp + _StackGuard
g.m.gsignal.stackguard1 = stsp + _StackGuard
g.m.gsignal.stackAlloc = st.ss_size
g.m.gsignal.stktopsp = getcallersp(unsafe.Pointer(&sig))
}

setg(g.m.gsignal)
sighandler(sig, info, ctx, g)
setg(g)
Expand Down
40 changes: 40 additions & 0 deletions src/runtime/signal_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

package runtime

import "unsafe"

type sigTabT struct {
flags int32
name string
Expand Down Expand Up @@ -44,3 +46,41 @@ var sigtable = [...]sigTabT{
/* 31 */ {_SigNotify, "SIGUSR2: user-defined signal 2"},
/* 32 */ {_SigNotify, "SIGTHR: reserved"},
}

//go:nosplit
func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
if sigfwdgo(sig, info, ctx) {
return
}
g := getg()
if g == nil {
badsignal(uintptr(sig))
return
}

// If some non-Go code called sigaltstack, adjust.
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)
cgocallback(unsafe.Pointer(funcPC(noSignalStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
}
stsp := uintptr(unsafe.Pointer(st.ss_sp))
if sp < stsp || sp >= stsp+st.ss_size {
setg(nil)
cgocallback(unsafe.Pointer(funcPC(sigNotOnStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
}
g.m.gsignal.stack.lo = stsp
g.m.gsignal.stack.hi = stsp + st.ss_size
g.m.gsignal.stackguard0 = stsp + _StackGuard
g.m.gsignal.stackguard1 = stsp + _StackGuard
g.m.gsignal.stackAlloc = st.ss_size
g.m.gsignal.stktopsp = getcallersp(unsafe.Pointer(&sig))
}

setg(g.m.gsignal)
sighandler(sig, info, ctx, g)
setg(g)
}
23 changes: 0 additions & 23 deletions src/runtime/signal_linux.go

This file was deleted.

40 changes: 40 additions & 0 deletions src/runtime/signal_openbsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

package runtime

import "unsafe"

type sigTabT struct {
flags int32
name string
Expand Down Expand Up @@ -44,3 +46,41 @@ var sigtable = [...]sigTabT{
/* 31 */ {_SigNotify, "SIGUSR2: user-defined signal 2"},
/* 32 */ {_SigNotify, "SIGTHR: reserved"},
}

//go:nosplit
func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
if sigfwdgo(sig, info, ctx) {
return
}
g := getg()
if g == nil {
badsignal(uintptr(sig))
return
}

// If some non-Go code called sigaltstack, adjust.
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)
cgocallback(unsafe.Pointer(funcPC(noSignalStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
}
stsp := uintptr(unsafe.Pointer(st.ss_sp))
if sp < stsp || sp >= stsp+st.ss_size {
setg(nil)
cgocallback(unsafe.Pointer(funcPC(sigNotOnStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
}
g.m.gsignal.stack.lo = stsp
g.m.gsignal.stack.hi = stsp + st.ss_size
g.m.gsignal.stackguard0 = stsp + _StackGuard
g.m.gsignal.stackguard1 = stsp + _StackGuard
g.m.gsignal.stackAlloc = st.ss_size
g.m.gsignal.stktopsp = getcallersp(unsafe.Pointer(&sig))
}

setg(g.m.gsignal)
sighandler(sig, info, ctx, g)
setg(g)
}
48 changes: 48 additions & 0 deletions src/runtime/signal_sigtramp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2009 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 dragonfly linux netbsd

package runtime

import "unsafe"

// Continuation of the (assembly) sigtramp() logic.
//go:nosplit
func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
if sigfwdgo(sig, info, ctx) {
return
}
g := getg()
if g == nil {
badsignal(uintptr(sig))
return
}

// If some non-Go code called sigaltstack, adjust.
sp := uintptr(unsafe.Pointer(&sig))
if sp < g.m.gsignal.stack.lo || sp >= g.m.gsignal.stack.hi {
var st sigaltstackt
sigaltstack(nil, &st)
if st.ss_flags&_SS_DISABLE != 0 {
setg(nil)
cgocallback(unsafe.Pointer(funcPC(noSignalStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
}
stsp := uintptr(unsafe.Pointer(st.ss_sp))
if sp < stsp || sp >= stsp+st.ss_size {
setg(nil)
cgocallback(unsafe.Pointer(funcPC(sigNotOnStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig))
}
g.m.gsignal.stack.lo = stsp
g.m.gsignal.stack.hi = stsp + st.ss_size
g.m.gsignal.stackguard0 = stsp + _StackGuard
g.m.gsignal.stackguard1 = stsp + _StackGuard
g.m.gsignal.stackAlloc = st.ss_size
g.m.gsignal.stktopsp = getcallersp(unsafe.Pointer(&sig))
}

setg(g.m.gsignal)
sighandler(sig, info, ctx, g)
setg(g)
}
Loading

0 comments on commit f7e51c1

Please sign in to comment.