Commit 904f046e authored by Yuichi Nishiwaki's avatar Yuichi Nishiwaki Committed by Ian Lance Taylor

runtime: fix crash during VDSO calls on arm

As discussed in #32912, a 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.

To fix the issue a guard is inserted in front of sigtrampgo, so that the control escapes from
signal handlers without touching G in case the signal occurred in the VDSO context.
The test case included in the patch is take from discussion in a relevant thread on github:
https://github.com/golang/go/issues/32912#issuecomment-517874531.
This patch not only fixes the issue on AArch64 but also that on 32bit ARM.

Fixes #32912

Change-Id: I657472e54b7aa3c617fabc5019ce63aa4105624a
GitHub-Last-Rev: 28ce42c4a02a060f08c1b0dd1c9a392123fd2ee9
GitHub-Pull-Request: golang/go#34030
Reviewed-on: https://go-review.googlesource.com/c/go/+/192937
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
parent 8ef6d6a8
...@@ -143,6 +143,15 @@ func buildTestProg(t *testing.T, binary string, flags ...string) (string, error) ...@@ -143,6 +143,15 @@ func buildTestProg(t *testing.T, binary string, flags ...string) (string, error)
return exe, nil return exe, nil
} }
func TestVDSO(t *testing.T) {
t.Parallel()
output := runTestProg(t, "testprog", "SignalInVDSO")
want := "success\n"
if output != want {
t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want);
}
}
var ( var (
staleRuntimeOnce sync.Once // guards init of staleRuntimeErr staleRuntimeOnce sync.Once // guards init of staleRuntimeErr
staleRuntimeErr error staleRuntimeErr error
......
...@@ -274,6 +274,21 @@ func sigpipe() { ...@@ -274,6 +274,21 @@ func sigpipe() {
dieFromSignal(_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, // sigtrampgo is called from the signal handler function, sigtramp,
// written in assembly code. // written in assembly code.
// This is called by the signal handler, and the world may be stopped. // This is called by the signal handler, and the world may be stopped.
...@@ -289,9 +304,9 @@ func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) { ...@@ -289,9 +304,9 @@ func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
if sigfwdgo(sig, info, ctx) { if sigfwdgo(sig, info, ctx) {
return return
} }
g := getg() c := &sigctxt{info, ctx}
g := sigFetchG(c)
if g == nil { if g == nil {
c := &sigctxt{info, ctx}
if sig == _SIGPROF { if sig == _SIGPROF {
sigprofNonGoPC(c.sigpc()) sigprofNonGoPC(c.sigpc())
return return
...@@ -347,7 +362,6 @@ func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) { ...@@ -347,7 +362,6 @@ func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
signalDuringFork(sig) signalDuringFork(sig)
} }
c := &sigctxt{info, ctx}
c.fixsigcode(sig) c.fixsigcode(sig)
sighandler(sig, info, ctx, g) sighandler(sig, info, ctx, g)
setg(g) setg(g)
...@@ -657,9 +671,10 @@ func sigfwdgo(sig uint32, info *siginfo, ctx unsafe.Pointer) bool { ...@@ -657,9 +671,10 @@ func sigfwdgo(sig uint32, info *siginfo, ctx unsafe.Pointer) bool {
return false return false
} }
// Determine if the signal occurred inside Go code. We test that: // Determine if the signal occurred inside Go code. We test that:
// (1) we were in a goroutine (i.e., m.curg != nil), and // (1) we weren't in VDSO page,
// (2) we weren't in CGO. // (2) we were in a goroutine (i.e., m.curg != nil), and
g := getg() // (3) we weren't in CGO.
g := sigFetchG(c)
if g != nil && g.m != nil && g.m.curg != nil && !g.m.incgo { if g != nil && g.m != nil && g.m.curg != nil && !g.m.incgo {
return false return false
} }
......
// Copyright 2019 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.
// Invoke signal hander in the VDSO context (see issue 32912).
package main
import (
"fmt"
"io/ioutil"
"os"
"runtime/pprof"
"time"
)
func init() {
register("SignalInVDSO", signalInVDSO)
}
func signalInVDSO() {
f, err := ioutil.TempFile("", "timeprofnow")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
if err := pprof.StartCPUProfile(f); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
t0 := time.Now()
t1 := t0
// We should get a profiling signal 100 times a second,
// so running for 1 second should be sufficient.
for t1.Sub(t0) < time.Second {
t1 = time.Now()
}
pprof.StopCPUProfile()
name := f.Name()
if err := f.Close(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
if err := os.Remove(name); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
fmt.Println("success");
}
...@@ -281,6 +281,7 @@ func vdsoauxv(tag, val uintptr) { ...@@ -281,6 +281,7 @@ func vdsoauxv(tag, val uintptr) {
} }
// vdsoMarker reports whether PC is on the VDSO page. // vdsoMarker reports whether PC is on the VDSO page.
//go:nosplit
func inVDSOPage(pc uintptr) bool { func inVDSOPage(pc uintptr) bool {
for _, k := range vdsoSymbolKeys { for _, k := range vdsoSymbolKeys {
if *k.ptr != 0 { if *k.ptr != 0 {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment