Commit 8174f7fb authored by Austin Clements's avatar Austin Clements

runtime: mlock top of signal stack on Linux 5.2–5.4.1

Linux 5.2 introduced a bug that can corrupt vector registers on return
from a signal if the signal stack isn't faulted in:
https://bugzilla.kernel.org/show_bug.cgi?id=205663

This CL works around this by mlocking the top page of all Go signal
stacks on the affected kernels.

Fixes #35326, #35777

Change-Id: I77c80a2baa4780827633f92f464486caa222295d
Reviewed-on: https://go-review.googlesource.com/c/go/+/209899
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarCherry Zhang <cherryyz@google.com>
Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
Reviewed-by: default avatarDavid Chase <drchase@google.com>
parent fa3a121a
...@@ -263,3 +263,14 @@ type sockaddr_un struct { ...@@ -263,3 +263,14 @@ type sockaddr_un struct {
family uint16 family uint16
path [108]byte path [108]byte
} }
const __NEW_UTS_LEN = 64
type new_utsname struct {
sysname [__NEW_UTS_LEN + 1]byte
nodename [__NEW_UTS_LEN + 1]byte
release [__NEW_UTS_LEN + 1]byte
version [__NEW_UTS_LEN + 1]byte
machine [__NEW_UTS_LEN + 1]byte
domainname [__NEW_UTS_LEN + 1]byte
}
...@@ -289,6 +289,7 @@ func getHugePageSize() uintptr { ...@@ -289,6 +289,7 @@ func getHugePageSize() uintptr {
func osinit() { func osinit() {
ncpu = getproccount() ncpu = getproccount()
physHugePageSize = getHugePageSize() physHugePageSize = getHugePageSize()
osArchInit()
} }
var urandom_dev = []byte("/dev/urandom\x00") var urandom_dev = []byte("/dev/urandom\x00")
...@@ -318,11 +319,20 @@ func libpreinit() { ...@@ -318,11 +319,20 @@ func libpreinit() {
initsig(true) initsig(true)
} }
// gsignalInitQuirk, if non-nil, is called for every allocated gsignal G.
//
// TODO(austin): Remove this after Go 1.15 when we remove the
// mlockGsignal workaround.
var gsignalInitQuirk func(gsignal *g)
// Called to initialize a new m (including the bootstrap m). // Called to initialize a new m (including the bootstrap m).
// Called on the parent thread (main thread in case of bootstrap), can allocate memory. // Called on the parent thread (main thread in case of bootstrap), can allocate memory.
func mpreinit(mp *m) { func mpreinit(mp *m) {
mp.gsignal = malg(32 * 1024) // Linux wants >= 2K mp.gsignal = malg(32 * 1024) // Linux wants >= 2K
mp.gsignal.m = mp mp.gsignal.m = mp
if gsignalInitQuirk != nil {
gsignalInitQuirk(mp.gsignal)
}
} }
func gettid() uint32 func gettid() uint32
......
// 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.
package runtime
func osArchInit() {}
// 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.
package runtime
//go:noescape
func uname(utsname *new_utsname) int
func mlock(addr, len uintptr) int
func osArchInit() {
// Linux 5.2 introduced a bug that can corrupt vector
// registers on return from a signal if the signal stack isn't
// faulted in:
// https://bugzilla.kernel.org/show_bug.cgi?id=205663
//
// It was fixed in 5.3.15, 5.4.2, and all 5.5 and later
// kernels.
//
// If we're on an affected kernel, work around this issue by
// mlocking the top page of every signal stack. This doesn't
// help for signal stacks created in C, but there's not much
// we can do about that.
//
// TODO(austin): Remove this in Go 1.15, at which point it
// will be unlikely to encounter any of the affected kernels
// in the wild.
var uts new_utsname
if uname(&uts) < 0 {
throw("uname failed")
}
// Check for null terminator to ensure gostringnocopy doesn't
// walk off the end of the release string.
found := false
for _, b := range uts.release {
if b == 0 {
found = true
break
}
}
if !found {
return
}
rel := gostringnocopy(&uts.release[0])
major, minor, patch, ok := parseRelease(rel)
if !ok {
return
}
if major == 5 && (minor == 2 || minor == 3 && patch < 15 || minor == 4 && patch < 2) {
gsignalInitQuirk = mlockGsignal
if m0.gsignal != nil {
throw("gsignal quirk too late")
}
}
}
func mlockGsignal(gsignal *g) {
mlock(gsignal.stack.hi-physPageSize, physPageSize)
}
...@@ -39,6 +39,8 @@ func archauxv(tag, val uintptr) { ...@@ -39,6 +39,8 @@ func archauxv(tag, val uintptr) {
} }
} }
func osArchInit() {}
//go:nosplit //go:nosplit
func cputicks() int64 { func cputicks() int64 {
// Currently cputicks() is used in blocking profiler and to seed fastrand(). // Currently cputicks() is used in blocking profiler and to seed fastrand().
......
...@@ -27,6 +27,8 @@ func archauxv(tag, val uintptr) { ...@@ -27,6 +27,8 @@ func archauxv(tag, val uintptr) {
} }
} }
func osArchInit() {}
//go:nosplit //go:nosplit
func cputicks() int64 { func cputicks() int64 {
// Currently cputicks() is used in blocking profiler and to seed fastrand(). // Currently cputicks() is used in blocking profiler and to seed fastrand().
......
...@@ -10,6 +10,8 @@ package runtime ...@@ -10,6 +10,8 @@ package runtime
func archauxv(tag, val uintptr) { func archauxv(tag, val uintptr) {
} }
func osArchInit() {}
//go:nosplit //go:nosplit
func cputicks() int64 { func cputicks() int64 {
// Currently cputicks() is used in blocking profiler and to seed fastrand(). // Currently cputicks() is used in blocking profiler and to seed fastrand().
......
...@@ -10,6 +10,8 @@ package runtime ...@@ -10,6 +10,8 @@ package runtime
func archauxv(tag, val uintptr) { func archauxv(tag, val uintptr) {
} }
func osArchInit() {}
//go:nosplit //go:nosplit
func cputicks() int64 { func cputicks() int64 {
// Currently cputicks() is used in blocking profiler and to seed fastrand(). // Currently cputicks() is used in blocking profiler and to seed fastrand().
......
...@@ -20,3 +20,5 @@ func archauxv(tag, val uintptr) { ...@@ -20,3 +20,5 @@ func archauxv(tag, val uintptr) {
cpu.HWCap2 = uint(val) cpu.HWCap2 = uint(val)
} }
} }
func osArchInit() {}
...@@ -17,3 +17,5 @@ func archauxv(tag, val uintptr) { ...@@ -17,3 +17,5 @@ func archauxv(tag, val uintptr) {
cpu.S390X.HasVX = val&_HWCAP_S390_VX != 0 cpu.S390X.HasVX = val&_HWCAP_S390_VX != 0
} }
} }
func osArchInit() {}
...@@ -33,8 +33,10 @@ ...@@ -33,8 +33,10 @@
#define SYS_clone 56 #define SYS_clone 56
#define SYS_exit 60 #define SYS_exit 60
#define SYS_kill 62 #define SYS_kill 62
#define SYS_uname 63
#define SYS_fcntl 72 #define SYS_fcntl 72
#define SYS_sigaltstack 131 #define SYS_sigaltstack 131
#define SYS_mlock 149
#define SYS_arch_prctl 158 #define SYS_arch_prctl 158
#define SYS_gettid 186 #define SYS_gettid 186
#define SYS_futex 202 #define SYS_futex 202
...@@ -764,3 +766,20 @@ TEXT runtime·sbrk0(SB),NOSPLIT,$0-8 ...@@ -764,3 +766,20 @@ TEXT runtime·sbrk0(SB),NOSPLIT,$0-8
SYSCALL SYSCALL
MOVQ AX, ret+0(FP) MOVQ AX, ret+0(FP)
RET RET
// func uname(utsname *new_utsname) int
TEXT ·uname(SB),NOSPLIT,$0-16
MOVQ utsname+0(FP), DI
MOVL $SYS_uname, AX
SYSCALL
MOVQ AX, ret+8(FP)
RET
// func mlock(addr, len uintptr) int
TEXT ·mlock(SB),NOSPLIT,$0-24
MOVQ addr+0(FP), DI
MOVQ len+8(FP), SI
MOVL $SYS_mlock, AX
SYSCALL
MOVQ AX, ret+16(FP)
RET
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