Skip to content

Commit

Permalink
fixes golang#32912
Browse files Browse the repository at this point in the history
The crash occurs when go runtime calls a VDSO function (say
__vdso_clock_gettime) and a signal arrives to that thread.
Since VDSO functions temporarily destroy the G register (R10),
Go functions asynchronously executed in that thread (i.e. Go's signal
handler) can try to load data from the destroyed G, which causes
segmentation fault.
  • Loading branch information
nyuichi committed Sep 11, 2019
1 parent 5d04e76 commit 28ce42c
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/runtime/crash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ func buildTestProg(t *testing.T, binary string, flags ...string) (string, error)
}

func TestVDSO(t *testing.T) {
t.Parallel()
output := runTestProg(t, "testprog", "SignalInVDSO")
want := "success\n"
if output != want {
Expand Down
27 changes: 21 additions & 6 deletions src/runtime/signal_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,21 @@ func sigpipe() {
dieFromSignal(_SIGPIPE)
}

// sigFetchG fetches the value of G safely when running in a signal handler.
// On some architectures, the g value may be clobbered when running in a VDSO.
// See issue #32912.
//
//go:nosplit
func sigFetchG(c *sigctxt) *g {
switch GOARCH {
case "arm", "arm64", "ppc64", "ppc64le":
if inVDSOPage(c.sigpc()) {
return nil
}
}
return getg()
}

// sigtrampgo is called from the signal handler function, sigtramp,
// written in assembly code.
// This is called by the signal handler, and the world may be stopped.
Expand All @@ -289,9 +304,9 @@ func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
if sigfwdgo(sig, info, ctx) {
return
}
g := getg()
c := &sigctxt{info, ctx}
g := sigFetchG(c)
if g == nil {
c := &sigctxt{info, ctx}
if sig == _SIGPROF {
sigprofNonGoPC(c.sigpc())
return
Expand Down Expand Up @@ -347,7 +362,6 @@ func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
signalDuringFork(sig)
}

c := &sigctxt{info, ctx}
c.fixsigcode(sig)
sighandler(sig, info, ctx, g)
setg(g)
Expand Down Expand Up @@ -657,9 +671,10 @@ func sigfwdgo(sig uint32, info *siginfo, ctx unsafe.Pointer) bool {
return false
}
// Determine if the signal occurred inside Go code. We test that:
// (1) we were in a goroutine (i.e., m.curg != nil), and
// (2) we weren't in CGO.
g := getg()
// (1) we weren't in VDSO page,
// (2) we were in a goroutine (i.e., m.curg != nil), and
// (3) we weren't in CGO.
g := sigFetchG(c)
if g != nil && g.m != nil && g.m.curg != nil && !g.m.incgo {
return false
}
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/testdata/testprog/vdso.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ func signalInVDSO() {
t0 := time.Now()
t1 := t0
// We should get a profiling signal 100 times a second,
// so running for 10 seconds should be sufficient.
for t1.Sub(t0) < 10*time.Second {
// so running for 1 second should be sufficient.
for t1.Sub(t0) < time.Second {
t1 = time.Now()
}

Expand Down
1 change: 1 addition & 0 deletions src/runtime/vdso_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ func vdsoauxv(tag, val uintptr) {
}

// vdsoMarker reports whether PC is on the VDSO page.
//go:nosplit
func inVDSOPage(pc uintptr) bool {
for _, k := range vdsoSymbolKeys {
if *k.ptr != 0 {
Expand Down

0 comments on commit 28ce42c

Please sign in to comment.