Skip to content

Commit 74f0f69

Browse files
cherrymuibradfitz
authored andcommitted
[release-branch.go1.12] runtime: scan gp._panic in stack scan
In runtime.gopanic, the _panic object p is stack allocated and referenced from gp._panic. With stack objects, p on stack is dead at the point preprintpanics runs. gp._panic points to p, but stack scan doesn't look at gp. Heap scan of gp does look at gp._panic, but it stops and ignores the pointer as it points to the stack. So whatever p points to may be collected and clobbered. We need to scan gp._panic explicitly during stack scan. To test it reliably, we introduce a GODEBUG mode "clobberfree", which clobbers the memory content when the GC frees an object. Fixes #30150. Change-Id: I11128298f03a89f817faa221421a9d332b41dced Reviewed-on: https://go-review.googlesource.com/c/161778 Run-TryBot: Cherry Zhang <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Keith Randall <[email protected]> Reviewed-by: Austin Clements <[email protected]> (cherry picked from commit af8f406) Reviewed-on: https://go-review.googlesource.com/c/162358 Run-TryBot: Brad Fitzpatrick <[email protected]> Reviewed-by: Cherry Zhang <[email protected]>
1 parent 7ab5e0c commit 74f0f69

File tree

6 files changed

+58
-1
lines changed

6 files changed

+58
-1
lines changed

src/runtime/crash_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,3 +728,15 @@ func TestG0StackOverflow(t *testing.T) {
728728

729729
runtime.G0StackOverflow()
730730
}
731+
732+
// Test that panic message is not clobbered.
733+
// See issue 30150.
734+
func TestDoublePanic(t *testing.T) {
735+
output := runTestProg(t, "testprog", "DoublePanic", "GODEBUG=clobberfree=1")
736+
wants := []string{"panic: XXX", "panic: YYY"}
737+
for _, want := range wants {
738+
if !strings.Contains(output, want) {
739+
t.Errorf("output:\n%s\n\nwant output containing: %s", output, want)
740+
}
741+
}
742+
}

src/runtime/extern.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ It is a comma-separated list of name=val pairs setting these named variables:
2727
allocfreetrace: setting allocfreetrace=1 causes every allocation to be
2828
profiled and a stack trace printed on each object's allocation and free.
2929
30+
clobberfree: setting clobberfree=1 causes the garbage collector to
31+
clobber the memory content of an object with bad content when it frees
32+
the object.
33+
3034
cgocheck: setting cgocheck=0 disables all checks for packages
3135
using cgo to incorrectly pass Go pointers to non-Go code.
3236
Setting cgocheck=1 (the default) enables relatively cheap

src/runtime/mgcmark.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,13 @@ func scanstack(gp *g, gcw *gcWork) {
709709
return true
710710
}
711711
gentraceback(^uintptr(0), ^uintptr(0), 0, gp, 0, nil, 0x7fffffff, scanframe, nil, 0)
712+
713+
// Find additional pointers that point into the stack from the heap.
714+
// Currently this includes defers and panics. See also function copystack.
712715
tracebackdefers(gp, scanframe, nil)
716+
if gp._panic != nil {
717+
state.putPtr(uintptr(unsafe.Pointer(gp._panic)))
718+
}
713719

714720
// Find and scan all reachable stack objects.
715721
state.buildIndex()

src/runtime/mgcsweep.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ func (s *mspan) sweep(preserve bool) bool {
291291
}
292292
}
293293

294-
if debug.allocfreetrace != 0 || raceenabled || msanenabled {
294+
if debug.allocfreetrace != 0 || debug.clobberfree != 0 || raceenabled || msanenabled {
295295
// Find all newly freed objects. This doesn't have to
296296
// efficient; allocfreetrace has massive overhead.
297297
mbits := s.markBitsForBase()
@@ -302,6 +302,9 @@ func (s *mspan) sweep(preserve bool) bool {
302302
if debug.allocfreetrace != 0 {
303303
tracefree(unsafe.Pointer(x), size)
304304
}
305+
if debug.clobberfree != 0 {
306+
clobberfree(unsafe.Pointer(x), size)
307+
}
305308
if raceenabled {
306309
racefree(unsafe.Pointer(x), size)
307310
}
@@ -446,3 +449,12 @@ retry:
446449
traceGCSweepDone()
447450
}
448451
}
452+
453+
// clobberfree sets the memory content at x to bad content, for debugging
454+
// purposes.
455+
func clobberfree(x unsafe.Pointer, size uintptr) {
456+
// size (span.elemsize) is always a multiple of 4.
457+
for i := uintptr(0); i < size; i += 4 {
458+
*(*uint32)(add(x, i)) = 0xdeadbeef
459+
}
460+
}

src/runtime/runtime1.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ type dbgVar struct {
301301
var debug struct {
302302
allocfreetrace int32
303303
cgocheck int32
304+
clobberfree int32
304305
efence int32
305306
gccheckmark int32
306307
gcpacertrace int32
@@ -318,6 +319,7 @@ var debug struct {
318319

319320
var dbgvars = []dbgVar{
320321
{"allocfreetrace", &debug.allocfreetrace},
322+
{"clobberfree", &debug.clobberfree},
321323
{"cgocheck", &debug.cgocheck},
322324
{"efence", &debug.efence},
323325
{"gccheckmark", &debug.gccheckmark},

src/runtime/testdata/testprog/crash.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
func init() {
1313
register("Crash", Crash)
14+
register("DoublePanic", DoublePanic)
1415
}
1516

1617
func test(name string) {
@@ -43,3 +44,23 @@ func Crash() {
4344
testInNewThread("second-new-thread")
4445
test("main-again")
4546
}
47+
48+
type P string
49+
50+
func (p P) String() string {
51+
// Try to free the "YYY" string header when the "XXX"
52+
// panic is stringified.
53+
runtime.GC()
54+
runtime.GC()
55+
runtime.GC()
56+
return string(p)
57+
}
58+
59+
// Test that panic message is not clobbered.
60+
// See issue 30150.
61+
func DoublePanic() {
62+
defer func() {
63+
panic(P("YYY"))
64+
}()
65+
panic(P("XXX"))
66+
}

0 commit comments

Comments
 (0)