Skip to content

Commit 491c1e7

Browse files
prattmicgopherbot
authored andcommitted
[release-branch.go1.21] runtime: allow update of system stack bounds on callback from C thread
[This cherry-pick combines CL 527715, CL 527775, CL 527797, and CL 529216.] [This is a redo of CL 525455 with the test fixed on darwin by defining _XOPEN_SOURCE, and disabled with android, musl, and openbsd, which do not provide getcontext.] Since CL 495855, Ms are cached for C threads calling into Go, including the stack bounds of the system stack. Some C libraries (e.g., coroutine libraries) do manual stack management and may change stacks between calls to Go on the same thread. Changing the stack if there is more Go up the stack would be problematic. But if the calls are completely independent there is no particular reason for Go to care about the changing stack boundary. Thus, this CL allows the stack bounds to change in such cases. The primary downside here (besides additional complexity) is that normal systems that do not manipulate the stack may not notice unintentional stack corruption as quickly as before. Note that callbackUpdateSystemStack is written to be usable for the initial setup in needm as well as updating the stack in cgocallbackg. For #62440. For #62130. For #63209. Change-Id: I0fe0134f865932bbaff1fc0da377c35c013bd768 Reviewed-on: https://go-review.googlesource.com/c/go/+/527715 Run-TryBot: Michael Pratt <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Auto-Submit: Michael Pratt <[email protected]> Reviewed-by: Cherry Mui <[email protected]> (cherry picked from commit 4f9fe6d) (cherry picked from commit e8ba057) (cherry picked from commit a843991) (cherry picked from commit d110d7c) Reviewed-on: https://go-review.googlesource.com/c/go/+/530480 Auto-Submit: Dmitri Shuralyov <[email protected]> TryBot-Bypass: Michael Pratt <[email protected]>
1 parent caafb50 commit 491c1e7

File tree

5 files changed

+245
-24
lines changed

5 files changed

+245
-24
lines changed

src/runtime/cgocall.go

+70
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,73 @@ func cgocall(fn, arg unsafe.Pointer) int32 {
206206
return errno
207207
}
208208

209+
// Set or reset the system stack bounds for a callback on sp.
210+
//
211+
// Must be nosplit because it is called by needm prior to fully initializing
212+
// the M.
213+
//
214+
//go:nosplit
215+
func callbackUpdateSystemStack(mp *m, sp uintptr, signal bool) {
216+
g0 := mp.g0
217+
if sp > g0.stack.lo && sp <= g0.stack.hi {
218+
// Stack already in bounds, nothing to do.
219+
return
220+
}
221+
222+
if mp.ncgo > 0 {
223+
// ncgo > 0 indicates that this M was in Go further up the stack
224+
// (it called C and is now receiving a callback). It is not
225+
// safe for the C call to change the stack out from under us.
226+
227+
// Note that this case isn't possible for signal == true, as
228+
// that is always passing a new M from needm.
229+
230+
// Stack is bogus, but reset the bounds anyway so we can print.
231+
hi := g0.stack.hi
232+
lo := g0.stack.lo
233+
g0.stack.hi = sp + 1024
234+
g0.stack.lo = sp - 32*1024
235+
g0.stackguard0 = g0.stack.lo + stackGuard
236+
237+
print("M ", mp.id, " procid ", mp.procid, " runtime: cgocallback with sp=", hex(sp), " out of bounds [", hex(lo), ", ", hex(hi), "]")
238+
print("\n")
239+
exit(2)
240+
}
241+
242+
// This M does not have Go further up the stack. However, it may have
243+
// previously called into Go, initializing the stack bounds. Between
244+
// that call returning and now the stack may have changed (perhaps the
245+
// C thread is running a coroutine library). We need to update the
246+
// stack bounds for this case.
247+
//
248+
// Set the stack bounds to match the current stack. If we don't
249+
// actually know how big the stack is, like we don't know how big any
250+
// scheduling stack is, but we assume there's at least 32 kB. If we
251+
// can get a more accurate stack bound from pthread, use that, provided
252+
// it actually contains SP..
253+
g0.stack.hi = sp + 1024
254+
g0.stack.lo = sp - 32*1024
255+
if !signal && _cgo_getstackbound != nil {
256+
// Don't adjust if called from the signal handler.
257+
// We are on the signal stack, not the pthread stack.
258+
// (We could get the stack bounds from sigaltstack, but
259+
// we're getting out of the signal handler very soon
260+
// anyway. Not worth it.)
261+
var bounds [2]uintptr
262+
asmcgocall(_cgo_getstackbound, unsafe.Pointer(&bounds))
263+
// getstackbound is an unsupported no-op on Windows.
264+
//
265+
// Don't use these bounds if they don't contain SP. Perhaps we
266+
// were called by something not using the standard thread
267+
// stack.
268+
if bounds[0] != 0 && sp > bounds[0] && sp <= bounds[1] {
269+
g0.stack.lo = bounds[0]
270+
g0.stack.hi = bounds[1]
271+
}
272+
}
273+
g0.stackguard0 = g0.stack.lo + stackGuard
274+
}
275+
209276
// Call from C back to Go. fn must point to an ABIInternal Go entry-point.
210277
//
211278
//go:nosplit
@@ -216,6 +283,9 @@ func cgocallbackg(fn, frame unsafe.Pointer, ctxt uintptr) {
216283
exit(2)
217284
}
218285

286+
sp := gp.m.g0.sched.sp // system sp saved by cgocallback.
287+
callbackUpdateSystemStack(gp.m, sp, false)
288+
219289
// The call from C is on gp.m's g0 stack, so we must ensure
220290
// that we stay on that M. We have to do this before calling
221291
// exitsyscall, since it would otherwise be free to move us to

src/runtime/crash_cgo_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -853,3 +853,20 @@ func TestEnsureBindM(t *testing.T) {
853853
t.Errorf("expected %q, got %v", want, got)
854854
}
855855
}
856+
857+
func TestStackSwitchCallback(t *testing.T) {
858+
t.Parallel()
859+
switch runtime.GOOS {
860+
case "windows", "plan9", "android", "ios", "openbsd": // no getcontext
861+
t.Skipf("skipping test on %s", runtime.GOOS)
862+
}
863+
got := runTestProg(t, "testprogcgo", "StackSwitchCallback")
864+
skip := "SKIP\n"
865+
if got == skip {
866+
t.Skip("skipping on musl/bionic libc")
867+
}
868+
want := "OK\n"
869+
if got != want {
870+
t.Errorf("expected %q, got %v", want, got)
871+
}
872+
}

src/runtime/proc.go

+9-24
Original file line numberDiff line numberDiff line change
@@ -2037,30 +2037,10 @@ func needm(signal bool) {
20372037
osSetupTLS(mp)
20382038

20392039
// Install g (= m->g0) and set the stack bounds
2040-
// to match the current stack. If we don't actually know
2041-
// how big the stack is, like we don't know how big any
2042-
// scheduling stack is, but we assume there's at least 32 kB.
2043-
// If we can get a more accurate stack bound from pthread,
2044-
// use that.
2040+
// to match the current stack.
20452041
setg(mp.g0)
2046-
gp := getg()
2047-
gp.stack.hi = getcallersp() + 1024
2048-
gp.stack.lo = getcallersp() - 32*1024
2049-
if !signal && _cgo_getstackbound != nil {
2050-
// Don't adjust if called from the signal handler.
2051-
// We are on the signal stack, not the pthread stack.
2052-
// (We could get the stack bounds from sigaltstack, but
2053-
// we're getting out of the signal handler very soon
2054-
// anyway. Not worth it.)
2055-
var bounds [2]uintptr
2056-
asmcgocall(_cgo_getstackbound, unsafe.Pointer(&bounds))
2057-
// getstackbound is an unsupported no-op on Windows.
2058-
if bounds[0] != 0 {
2059-
gp.stack.lo = bounds[0]
2060-
gp.stack.hi = bounds[1]
2061-
}
2062-
}
2063-
gp.stackguard0 = gp.stack.lo + stackGuard
2042+
sp := getcallersp()
2043+
callbackUpdateSystemStack(mp, sp, signal)
20642044

20652045
// Should mark we are already in Go now.
20662046
// Otherwise, we may call needm again when we get a signal, before cgocallbackg1,
@@ -2177,9 +2157,14 @@ func oneNewExtraM() {
21772157
// So that the destructor would invoke dropm while the non-Go thread is exiting.
21782158
// This is much faster since it avoids expensive signal-related syscalls.
21792159
//
2180-
// NOTE: this always runs without a P, so, nowritebarrierrec required.
2160+
// This always runs without a P, so //go:nowritebarrierrec is required.
2161+
//
2162+
// This may run with a different stack than was recorded in g0 (there is no
2163+
// call to callbackUpdateSystemStack prior to dropm), so this must be
2164+
// //go:nosplit to avoid the stack bounds check.
21812165
//
21822166
//go:nowritebarrierrec
2167+
//go:nosplit
21832168
func dropm() {
21842169
// Clear m and g, and return m to the extra list.
21852170
// After the call to setg we can only call nosplit functions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright 2023 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+
//go:build unix && !android && !openbsd
6+
7+
// Required for darwin ucontext.
8+
#define _XOPEN_SOURCE
9+
// Required for netbsd stack_t if _XOPEN_SOURCE is set.
10+
#define _XOPEN_SOURCE_EXTENDED 1
11+
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
12+
13+
#include <assert.h>
14+
#include <pthread.h>
15+
#include <stddef.h>
16+
#include <stdio.h>
17+
#include <stdlib.h>
18+
#include <ucontext.h>
19+
20+
// musl libc does not provide getcontext, etc. Skip the test there.
21+
//
22+
// musl libc doesn't provide any direct detection mechanism. So assume any
23+
// non-glibc linux is using musl.
24+
//
25+
// Note that bionic does not provide getcontext either, but that is skipped via
26+
// the android build tag.
27+
#if defined(__linux__) && !defined(__GLIBC__)
28+
#define MUSL 1
29+
#endif
30+
#if defined(MUSL)
31+
void callStackSwitchCallbackFromThread(void) {
32+
printf("SKIP\n");
33+
exit(0);
34+
}
35+
#else
36+
37+
// Use a stack size larger than the 32kb estimate in
38+
// runtime.callbackUpdateSystemStack. This ensures that a second stack
39+
// allocation won't accidentally count as in bounds of the first stack
40+
#define STACK_SIZE (64ull << 10)
41+
42+
static ucontext_t uctx_save, uctx_switch;
43+
44+
extern void stackSwitchCallback(void);
45+
46+
static void *stackSwitchThread(void *arg) {
47+
// Simple test: callback works from the normal system stack.
48+
stackSwitchCallback();
49+
50+
// Next, verify that switching stacks doesn't break callbacks.
51+
52+
char *stack1 = malloc(STACK_SIZE);
53+
if (stack1 == NULL) {
54+
perror("malloc");
55+
exit(1);
56+
}
57+
58+
// Allocate the second stack before freeing the first to ensure we don't get
59+
// the same address from malloc.
60+
char *stack2 = malloc(STACK_SIZE);
61+
if (stack1 == NULL) {
62+
perror("malloc");
63+
exit(1);
64+
}
65+
66+
if (getcontext(&uctx_switch) == -1) {
67+
perror("getcontext");
68+
exit(1);
69+
}
70+
uctx_switch.uc_stack.ss_sp = stack1;
71+
uctx_switch.uc_stack.ss_size = STACK_SIZE;
72+
uctx_switch.uc_link = &uctx_save;
73+
makecontext(&uctx_switch, stackSwitchCallback, 0);
74+
75+
if (swapcontext(&uctx_save, &uctx_switch) == -1) {
76+
perror("swapcontext");
77+
exit(1);
78+
}
79+
80+
if (getcontext(&uctx_switch) == -1) {
81+
perror("getcontext");
82+
exit(1);
83+
}
84+
uctx_switch.uc_stack.ss_sp = stack2;
85+
uctx_switch.uc_stack.ss_size = STACK_SIZE;
86+
uctx_switch.uc_link = &uctx_save;
87+
makecontext(&uctx_switch, stackSwitchCallback, 0);
88+
89+
if (swapcontext(&uctx_save, &uctx_switch) == -1) {
90+
perror("swapcontext");
91+
exit(1);
92+
}
93+
94+
free(stack1);
95+
free(stack2);
96+
97+
return NULL;
98+
}
99+
100+
void callStackSwitchCallbackFromThread(void) {
101+
pthread_t thread;
102+
assert(pthread_create(&thread, NULL, stackSwitchThread, NULL) == 0);
103+
assert(pthread_join(thread, NULL) == 0);
104+
}
105+
106+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2023 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+
//go:build unix && !android && !openbsd
6+
7+
package main
8+
9+
/*
10+
void callStackSwitchCallbackFromThread(void);
11+
*/
12+
import "C"
13+
14+
import (
15+
"fmt"
16+
"runtime/debug"
17+
)
18+
19+
func init() {
20+
register("StackSwitchCallback", StackSwitchCallback)
21+
}
22+
23+
//export stackSwitchCallback
24+
func stackSwitchCallback() {
25+
// We want to trigger a bounds check on the g0 stack. To do this, we
26+
// need to call a splittable function through systemstack().
27+
// SetGCPercent contains such a systemstack call.
28+
gogc := debug.SetGCPercent(100)
29+
debug.SetGCPercent(gogc)
30+
}
31+
32+
33+
// Regression test for https://go.dev/issue/62440. It should be possible for C
34+
// threads to call into Go from different stacks without crashing due to g0
35+
// stack bounds checks.
36+
//
37+
// N.B. This is only OK for threads created in C. Threads with Go frames up the
38+
// stack must not change the stack out from under us.
39+
func StackSwitchCallback() {
40+
C.callStackSwitchCallbackFromThread();
41+
42+
fmt.Printf("OK\n")
43+
}

0 commit comments

Comments
 (0)