Skip to content

Commit 69e75c8

Browse files
adonovangopherbot
authored andcommitted
runtime: properly frame panic values in tracebacks
This CL causes the printing of panic values to ensure that all newlines in the output are immediately followed by a tab, so that there is no way for a maliciously crafted panic value to fool a program attempting to parse the traceback into thinking that the panic value is in fact a goroutine stack. See #64590 (comment) + release note Updates #64590 Updates #63455 Change-Id: I5142acb777383c0c122779d984e73879567dc627 Reviewed-on: https://go-review.googlesource.com/c/go/+/581215 Auto-Submit: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Pratt <[email protected]>
1 parent 4513f1a commit 69e75c8

File tree

8 files changed

+56
-22
lines changed

8 files changed

+56
-22
lines changed

doc/next/4-runtime.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
## Runtime {#runtime}
2+
3+
The traceback printed by the runtime after an unhandled panic or other
4+
fatal error now indents the second and subsequent lines of the error
5+
message (for example, the argument to panic) by a single tab, so that
6+
it can be unambiguously distinguished from the stack trace of the
7+
first goroutine. See [#64590](/issue/64590) for discussion.

src/runtime/crash_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -966,11 +966,11 @@ func TestPanicWhilePanicking(t *testing.T) {
966966
Func string
967967
}{
968968
{
969-
"panic while printing panic value: important error message",
969+
"panic while printing panic value: important multi-line\n\terror message",
970970
"ErrorPanic",
971971
},
972972
{
973-
"panic while printing panic value: important stringer message",
973+
"panic while printing panic value: important multi-line\n\tstringer message",
974974
"StringerPanic",
975975
},
976976
{
@@ -986,7 +986,7 @@ func TestPanicWhilePanicking(t *testing.T) {
986986
"CircularPanic",
987987
},
988988
{
989-
"important string message",
989+
"important multi-line\n\tstring message",
990990
"StringPanic",
991991
},
992992
{

src/runtime/error.go

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,16 @@ type stringer interface {
211211
String() string
212212
}
213213

214-
// printany prints an argument passed to panic.
214+
// printpanicval prints an argument passed to panic.
215215
// If panic is called with a value that has a String or Error method,
216216
// it has already been converted into a string by preprintpanics.
217-
func printany(i any) {
218-
switch v := i.(type) {
217+
//
218+
// To ensure that the traceback can be unambiguously parsed even when
219+
// the panic value contains "\ngoroutine" and other stack-like
220+
// strings, newlines in the string representation of v are replaced by
221+
// "\n\t".
222+
func printpanicval(v any) {
223+
switch v := v.(type) {
219224
case nil:
220225
print("nil")
221226
case bool:
@@ -251,19 +256,22 @@ func printany(i any) {
251256
case complex128:
252257
print(v)
253258
case string:
254-
print(v)
259+
printindented(v)
255260
default:
256-
printanycustomtype(i)
261+
printanycustomtype(v)
257262
}
258263
}
259264

265+
// Invariant: each newline in the string representation is followed by a tab.
260266
func printanycustomtype(i any) {
261267
eface := efaceOf(&i)
262268
typestring := toRType(eface._type).string()
263269

264270
switch eface._type.Kind_ {
265271
case abi.String:
266-
print(typestring, `("`, *(*string)(eface.data), `")`)
272+
print(typestring, `("`)
273+
printindented(*(*string)(eface.data))
274+
print(`")`)
267275
case abi.Bool:
268276
print(typestring, "(", *(*bool)(eface.data), ")")
269277
case abi.Int:
@@ -301,6 +309,21 @@ func printanycustomtype(i any) {
301309
}
302310
}
303311

312+
// printindented prints s, replacing "\n" with "\n\t".
313+
func printindented(s string) {
314+
for {
315+
i := bytealg.IndexByteString(s, '\n')
316+
if i < 0 {
317+
break
318+
}
319+
i += len("\n")
320+
print(s[:i])
321+
print("\t")
322+
s = s[i:]
323+
}
324+
print(s)
325+
}
326+
304327
// panicwrap generates a panic for a call to a wrapped value method
305328
// with a nil pointer receiver.
306329
//

src/runtime/panic.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,7 @@ func printpanics(p *_panic) {
656656
return
657657
}
658658
print("panic: ")
659-
printany(p.arg)
659+
printpanicval(p.arg)
660660
if p.recovered {
661661
print(" [recovered]")
662662
}
@@ -718,20 +718,20 @@ func gopanic(e any) {
718718
gp := getg()
719719
if gp.m.curg != gp {
720720
print("panic: ")
721-
printany(e)
721+
printpanicval(e)
722722
print("\n")
723723
throw("panic on system stack")
724724
}
725725

726726
if gp.m.mallocing != 0 {
727727
print("panic: ")
728-
printany(e)
728+
printpanicval(e)
729729
print("\n")
730730
throw("panic during malloc")
731731
}
732732
if gp.m.preemptoff != "" {
733733
print("panic: ")
734-
printany(e)
734+
printpanicval(e)
735735
print("\n")
736736
print("preempt off reason: ")
737737
print(gp.m.preemptoff)
@@ -740,7 +740,7 @@ func gopanic(e any) {
740740
}
741741
if gp.m.locks != 0 {
742742
print("panic: ")
743-
printany(e)
743+
printpanicval(e)
744744
print("\n")
745745
throw("panic holding locks")
746746
}
@@ -1015,7 +1015,9 @@ func throw(s string) {
10151015
// Everything throw does should be recursively nosplit so it
10161016
// can be called even when it's unsafe to grow the stack.
10171017
systemstack(func() {
1018-
print("fatal error: ", s, "\n")
1018+
print("fatal error: ")
1019+
printpanicval(s)
1020+
print("\n")
10191021
})
10201022

10211023
fatalthrow(throwTypeRuntime)
@@ -1034,7 +1036,9 @@ func fatal(s string) {
10341036
// Everything fatal does should be recursively nosplit so it
10351037
// can be called even when it's unsafe to grow the stack.
10361038
systemstack(func() {
1037-
print("fatal error: ", s, "\n")
1039+
print("fatal error: ")
1040+
printpanicval(s)
1041+
print("\n")
10381042
})
10391043

10401044
fatalthrow(throwTypeUser)

src/runtime/panic_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func TestPanicWithDirectlyPrintableCustomTypes(t *testing.T) {
2727
{"panicCustomInt16", `panic: main.MyInt16(93)`},
2828
{"panicCustomInt32", `panic: main.MyInt32(93)`},
2929
{"panicCustomInt64", `panic: main.MyInt64(93)`},
30-
{"panicCustomString", `panic: main.MyString("Panic")`},
30+
{"panicCustomString", `panic: main.MyString("Panic` + "\n\t" + `line two")`},
3131
{"panicCustomUint", `panic: main.MyUint(93)`},
3232
{"panicCustomUint8", `panic: main.MyUint8(93)`},
3333
{"panicCustomUint16", `panic: main.MyUint16(93)`},

src/runtime/testdata/testprog/crash.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func DoublePanic() {
7777
type exampleError struct{}
7878

7979
func (e exampleError) Error() string {
80-
panic("important error message")
80+
panic("important multi-line\nerror message")
8181
}
8282

8383
func ErrorPanic() {
@@ -97,7 +97,7 @@ func DoubleErrorPanic() {
9797
type exampleStringer struct{}
9898

9999
func (s exampleStringer) String() string {
100-
panic("important stringer message")
100+
panic("important multi-line\nstringer message")
101101
}
102102

103103
func StringerPanic() {
@@ -115,7 +115,7 @@ func DoubleStringerPanic() {
115115
}
116116

117117
func StringPanic() {
118-
panic("important string message")
118+
panic("important multi-line\nstring message")
119119
}
120120

121121
func NilPanic() {

src/runtime/testdata/testprog/panicprint.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func panicCustomComplex128() {
3131
}
3232

3333
func panicCustomString() {
34-
panic(MyString("Panic"))
34+
panic(MyString("Panic\nline two"))
3535
}
3636

3737
func panicCustomBool() {

src/testing/testing_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,8 @@ func parseRunningTests(out []byte) (runningTests []string, ok bool) {
762762
inRunningTests := false
763763
for _, line := range strings.Split(string(out), "\n") {
764764
if inRunningTests {
765-
if trimmed, ok := strings.CutPrefix(line, "\t"); ok {
765+
// Package testing adds one tab, the panic printer adds another.
766+
if trimmed, ok := strings.CutPrefix(line, "\t\t"); ok {
766767
if name, _, ok := strings.Cut(trimmed, " "); ok {
767768
runningTests = append(runningTests, name)
768769
continue

0 commit comments

Comments
 (0)