Skip to content

Commit e496e61

Browse files
beneschdvyukov
authored andcommitted
runtime: never call into race detector with retaken P
cgocall could previously invoke the race detector on an M whose P had been retaken. The race detector would attempt to use the P-local state from this stale P, racing with the thread that was actually wired to that P. The result was memory corruption of ThreadSanitizer's internal data structures that presented as hard-to-understand assertion failures and segfaults. Reorder cgocall so that it always acquires a P before invoking the race detector, and add a test that stresses the interaction between cgo and the race detector to protect against future bugs of this kind. Fixes golang#27660. Change-Id: Ide93f96a23490314d6647547140e0a412a97f0d4 Reviewed-on: https://go-review.googlesource.com/c/148717 Run-TryBot: Dmitry Vyukov <[email protected]> Reviewed-by: Dmitry Vyukov <[email protected]>
1 parent 06be7cb commit e496e61

File tree

3 files changed

+68
-15
lines changed

3 files changed

+68
-15
lines changed

misc/cgo/test/cgo_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ func Test25143(t *testing.T) { test25143(t) }
9292
func Test23356(t *testing.T) { test23356(t) }
9393
func Test26066(t *testing.T) { test26066(t) }
9494
func Test26213(t *testing.T) { test26213(t) }
95+
func Test27660(t *testing.T) { test27660(t) }
9596

9697
func BenchmarkCgoCall(b *testing.B) { benchCgoCall(b) }
9798
func BenchmarkGoString(b *testing.B) { benchGoString(b) }

misc/cgo/test/test27660.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2018 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+
// Stress the interaction between the race detector and cgo in an
6+
// attempt to reproduce the memory corruption described in #27660.
7+
// The bug was very timing sensitive; at the time of writing this
8+
// test would only trigger the bug about once out of every five runs.
9+
10+
package cgotest
11+
12+
// #include <unistd.h>
13+
import "C"
14+
15+
import (
16+
"context"
17+
"math/rand"
18+
"runtime"
19+
"sync"
20+
"testing"
21+
"time"
22+
)
23+
24+
func test27660(t *testing.T) {
25+
ctx, cancel := context.WithCancel(context.Background())
26+
defer cancel()
27+
ints := make([]int, 100)
28+
locks := make([]sync.Mutex, 100)
29+
// Slowly create threads so that ThreadSanitizer is forced to
30+
// frequently resize its SyncClocks.
31+
for i := 0; i < 100; i++ {
32+
go func() {
33+
for ctx.Err() == nil {
34+
// Sleep in C for long enough that it is likely that the runtime
35+
// will retake this goroutine's currently wired P.
36+
C.usleep(1000 /* 1ms */)
37+
runtime.Gosched() // avoid starvation (see #28701)
38+
}
39+
}()
40+
go func() {
41+
// Trigger lots of synchronization and memory reads/writes to
42+
// increase the likelihood that the race described in #27660
43+
// results in corruption of ThreadSanitizer's internal state
44+
// and thus an assertion failure or segfault.
45+
for ctx.Err() == nil {
46+
j := rand.Intn(100)
47+
locks[j].Lock()
48+
ints[j]++
49+
locks[j].Unlock()
50+
}
51+
}()
52+
time.Sleep(time.Millisecond)
53+
}
54+
}

src/runtime/cgocall.go

+13-15
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,19 @@ func cgocall(fn, arg unsafe.Pointer) int32 {
130130
mp.incgo = true
131131
errno := asmcgocall(fn, arg)
132132

133-
// Call endcgo before exitsyscall because exitsyscall may
133+
// Update accounting before exitsyscall because exitsyscall may
134134
// reschedule us on to a different M.
135-
endcgo(mp)
135+
mp.incgo = false
136+
mp.ncgo--
136137

137138
exitsyscall()
138139

140+
// Note that raceacquire must be called only after exitsyscall has
141+
// wired this M to a P.
142+
if raceenabled {
143+
raceacquire(unsafe.Pointer(&racecgosync))
144+
}
145+
139146
// From the garbage collector's perspective, time can move
140147
// backwards in the sequence above. If there's a callback into
141148
// Go code, GC will see this function at the call to
@@ -153,16 +160,6 @@ func cgocall(fn, arg unsafe.Pointer) int32 {
153160
return errno
154161
}
155162

156-
//go:nosplit
157-
func endcgo(mp *m) {
158-
mp.incgo = false
159-
mp.ncgo--
160-
161-
if raceenabled {
162-
raceacquire(unsafe.Pointer(&racecgosync))
163-
}
164-
}
165-
166163
// Call from C back to Go.
167164
//go:nosplit
168165
func cgocallbackg(ctxt uintptr) {
@@ -347,13 +344,14 @@ func unwindm(restore *bool) {
347344
sched.sp = *(*uintptr)(unsafe.Pointer(sched.sp + 16))
348345
}
349346

350-
// Call endcgo to do the accounting that cgocall will not have a
351-
// chance to do during an unwind.
347+
// Do the accounting that cgocall will not have a chance to do
348+
// during an unwind.
352349
//
353350
// In the case where a Go call originates from C, ncgo is 0
354351
// and there is no matching cgocall to end.
355352
if mp.ncgo > 0 {
356-
endcgo(mp)
353+
mp.incgo = false
354+
mp.ncgo--
357355
}
358356

359357
releasem(mp)

0 commit comments

Comments
 (0)