[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 <cherryyz@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
(cherry picked from commit af8f4062c24cb36af4dc24fbaffd23aa7f7bde36)
Reviewed-on: https://go-review.googlesource.com/c/162358
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
diff --git a/src/runtime/crash_test.go b/src/runtime/crash_test.go
index 6fba4dd..03ebf02 100644
--- a/src/runtime/crash_test.go
+++ b/src/runtime/crash_test.go
@@ -728,3 +728,15 @@
 
 	runtime.G0StackOverflow()
 }
+
+// Test that panic message is not clobbered.
+// See issue 30150.
+func TestDoublePanic(t *testing.T) {
+	output := runTestProg(t, "testprog", "DoublePanic", "GODEBUG=clobberfree=1")
+	wants := []string{"panic: XXX", "panic: YYY"}
+	for _, want := range wants {
+		if !strings.Contains(output, want) {
+			t.Errorf("output:\n%s\n\nwant output containing: %s", output, want)
+		}
+	}
+}
diff --git a/src/runtime/extern.go b/src/runtime/extern.go
index af858a3..437406d 100644
--- a/src/runtime/extern.go
+++ b/src/runtime/extern.go
@@ -27,6 +27,10 @@
 	allocfreetrace: setting allocfreetrace=1 causes every allocation to be
 	profiled and a stack trace printed on each object's allocation and free.
 
+	clobberfree: setting clobberfree=1 causes the garbage collector to
+	clobber the memory content of an object with bad content when it frees
+	the object.
+
 	cgocheck: setting cgocheck=0 disables all checks for packages
 	using cgo to incorrectly pass Go pointers to non-Go code.
 	Setting cgocheck=1 (the default) enables relatively cheap
diff --git a/src/runtime/mgcmark.go b/src/runtime/mgcmark.go
index 86416ca..022cc8d 100644
--- a/src/runtime/mgcmark.go
+++ b/src/runtime/mgcmark.go
@@ -709,7 +709,13 @@
 		return true
 	}
 	gentraceback(^uintptr(0), ^uintptr(0), 0, gp, 0, nil, 0x7fffffff, scanframe, nil, 0)
+
+	// Find additional pointers that point into the stack from the heap.
+	// Currently this includes defers and panics. See also function copystack.
 	tracebackdefers(gp, scanframe, nil)
+	if gp._panic != nil {
+		state.putPtr(uintptr(unsafe.Pointer(gp._panic)))
+	}
 
 	// Find and scan all reachable stack objects.
 	state.buildIndex()
diff --git a/src/runtime/mgcsweep.go b/src/runtime/mgcsweep.go
index edb9fca..6ac3b03 100644
--- a/src/runtime/mgcsweep.go
+++ b/src/runtime/mgcsweep.go
@@ -291,7 +291,7 @@
 		}
 	}
 
-	if debug.allocfreetrace != 0 || raceenabled || msanenabled {
+	if debug.allocfreetrace != 0 || debug.clobberfree != 0 || raceenabled || msanenabled {
 		// Find all newly freed objects. This doesn't have to
 		// efficient; allocfreetrace has massive overhead.
 		mbits := s.markBitsForBase()
@@ -302,6 +302,9 @@
 				if debug.allocfreetrace != 0 {
 					tracefree(unsafe.Pointer(x), size)
 				}
+				if debug.clobberfree != 0 {
+					clobberfree(unsafe.Pointer(x), size)
+				}
 				if raceenabled {
 					racefree(unsafe.Pointer(x), size)
 				}
@@ -446,3 +449,12 @@
 		traceGCSweepDone()
 	}
 }
+
+// clobberfree sets the memory content at x to bad content, for debugging
+// purposes.
+func clobberfree(x unsafe.Pointer, size uintptr) {
+	// size (span.elemsize) is always a multiple of 4.
+	for i := uintptr(0); i < size; i += 4 {
+		*(*uint32)(add(x, i)) = 0xdeadbeef
+	}
+}
diff --git a/src/runtime/runtime1.go b/src/runtime/runtime1.go
index c5667e7..0c0a31e 100644
--- a/src/runtime/runtime1.go
+++ b/src/runtime/runtime1.go
@@ -301,6 +301,7 @@
 var debug struct {
 	allocfreetrace     int32
 	cgocheck           int32
+	clobberfree        int32
 	efence             int32
 	gccheckmark        int32
 	gcpacertrace       int32
@@ -318,6 +319,7 @@
 
 var dbgvars = []dbgVar{
 	{"allocfreetrace", &debug.allocfreetrace},
+	{"clobberfree", &debug.clobberfree},
 	{"cgocheck", &debug.cgocheck},
 	{"efence", &debug.efence},
 	{"gccheckmark", &debug.gccheckmark},
diff --git a/src/runtime/testdata/testprog/crash.go b/src/runtime/testdata/testprog/crash.go
index 4d83132..c4990cd 100644
--- a/src/runtime/testdata/testprog/crash.go
+++ b/src/runtime/testdata/testprog/crash.go
@@ -11,6 +11,7 @@
 
 func init() {
 	register("Crash", Crash)
+	register("DoublePanic", DoublePanic)
 }
 
 func test(name string) {
@@ -43,3 +44,23 @@
 	testInNewThread("second-new-thread")
 	test("main-again")
 }
+
+type P string
+
+func (p P) String() string {
+	// Try to free the "YYY" string header when the "XXX"
+	// panic is stringified.
+	runtime.GC()
+	runtime.GC()
+	runtime.GC()
+	return string(p)
+}
+
+// Test that panic message is not clobbered.
+// See issue 30150.
+func DoublePanic() {
+	defer func() {
+		panic(P("YYY"))
+	}()
+	panic(P("XXX"))
+}