Commit 757e0de8 authored by Russ Cox's avatar Russ Cox

runtime: impose stack size limit

The goal is to stop only those programs that would keep
going and run the machine out of memory, but before they do that.
1 GB on 64-bit, 250 MB on 32-bit.
That seems implausibly large, and it can be adjusted.

Fixes #2556.
Fixes #4494.
Fixes #5173.

R=khr, r, dvyukov
CC=golang-dev
https://golang.org/cl/12541052
parent 205329aa
...@@ -74,10 +74,10 @@ func testCrashHandler(t *testing.T, cgo bool) { ...@@ -74,10 +74,10 @@ func testCrashHandler(t *testing.T, cgo bool) {
type crashTest struct { type crashTest struct {
Cgo bool Cgo bool
} }
got := executeTest(t, crashSource, &crashTest{Cgo: cgo}) output := executeTest(t, crashSource, &crashTest{Cgo: cgo})
want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n" want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n"
if got != want { if output != want {
t.Fatalf("expected %q, but got %q", want, got) t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
} }
} }
...@@ -86,10 +86,10 @@ func TestCrashHandler(t *testing.T) { ...@@ -86,10 +86,10 @@ func TestCrashHandler(t *testing.T) {
} }
func testDeadlock(t *testing.T, source string) { func testDeadlock(t *testing.T, source string) {
got := executeTest(t, source, nil) output := executeTest(t, source, nil)
want := "fatal error: all goroutines are asleep - deadlock!\n" want := "fatal error: all goroutines are asleep - deadlock!\n"
if !strings.HasPrefix(got, want) { if !strings.HasPrefix(output, want) {
t.Fatalf("expected %q, but got %q", want, got) t.Fatalf("output does not start with %q:\n%s", want, output)
} }
} }
...@@ -110,10 +110,18 @@ func TestLockedDeadlock2(t *testing.T) { ...@@ -110,10 +110,18 @@ func TestLockedDeadlock2(t *testing.T) {
} }
func TestGoexitDeadlock(t *testing.T) { func TestGoexitDeadlock(t *testing.T) {
got := executeTest(t, goexitDeadlockSource, nil) output := executeTest(t, goexitDeadlockSource, nil)
want := "" want := ""
if got != want { if output != "" {
t.Fatalf("expected %q, but got %q", want, got) t.Fatalf("expected no output:\n%s", want, output)
}
}
func TestStackOverflow(t *testing.T) {
output := executeTest(t, stackOverflowSource, nil)
want := "runtime: goroutine stack exceeds 4194304-byte limit\nfatal error: stack overflow"
if !strings.HasPrefix(output, want) {
t.Fatalf("output does not start with %q:\n%s", want, output)
} }
} }
...@@ -219,3 +227,19 @@ func main() { ...@@ -219,3 +227,19 @@ func main() {
runtime.Goexit() runtime.Goexit()
} }
` `
const stackOverflowSource = `
package main
import "runtime/debug"
func main() {
debug.SetMaxStack(4<<20)
f(make([]byte, 10))
}
func f(x []byte) byte {
var buf [64<<10]byte
return x[0] + f(buf[:])
}
`
...@@ -24,6 +24,7 @@ func readGCStats(*[]time.Duration) ...@@ -24,6 +24,7 @@ func readGCStats(*[]time.Duration)
func enableGC(bool) bool func enableGC(bool) bool
func setGCPercent(int) int func setGCPercent(int) int
func freeOSMemory() func freeOSMemory()
func setMaxStack(int) int
// ReadGCStats reads statistics about garbage collection into stats. // ReadGCStats reads statistics about garbage collection into stats.
// The number of entries in the pause history is system-dependent; // The number of entries in the pause history is system-dependent;
...@@ -99,3 +100,17 @@ func SetGCPercent(percent int) int { ...@@ -99,3 +100,17 @@ func SetGCPercent(percent int) int {
func FreeOSMemory() { func FreeOSMemory() {
freeOSMemory() freeOSMemory()
} }
// SetMaxStack sets the maximum amount of memory that
// can be used by a single goroutine stack.
// If any goroutine exceeds this limit while growing its stack,
// the program crashes.
// SetMaxStack returns the previous setting.
// The initial setting is 1 GB on 64-bit systems, 250 MB on 32-bit systems.
//
// SetMaxStack is useful mainly for limiting the damage done by
// goroutines that enter an infinite recursion. It only limits future
// stack growth.
func SetMaxStack(bytes int) int {
return setMaxStack(bytes)
}
...@@ -320,8 +320,10 @@ runtime·unwindstack(G *gp, byte *sp) ...@@ -320,8 +320,10 @@ runtime·unwindstack(G *gp, byte *sp)
gp->stackbase = top->stackbase; gp->stackbase = top->stackbase;
gp->stackguard = top->stackguard; gp->stackguard = top->stackguard;
gp->stackguard0 = gp->stackguard; gp->stackguard0 = gp->stackguard;
if(top->free != 0) if(top->free != 0) {
gp->stacksize -= top->free;
runtime·stackfree(stk, top->free); runtime·stackfree(stk, top->free);
}
} }
if(sp != nil && (sp < (byte*)gp->stackguard - StackGuard || (byte*)gp->stackbase < sp)) { if(sp != nil && (sp < (byte*)gp->stackguard - StackGuard || (byte*)gp->stackbase < sp)) {
......
...@@ -168,6 +168,14 @@ void ...@@ -168,6 +168,14 @@ void
runtime·main(void) runtime·main(void)
{ {
Defer d; Defer d;
// Max stack size is 1 GB on 64-bit, 250 MB on 32-bit.
// Using decimal instead of binary GB and MB because
// they look nicer in the stack overflow failure message.
if(sizeof(void*) == 8)
runtime·maxstacksize = 1000000000;
else
runtime·maxstacksize = 250000000;
newm(sysmon, nil); newm(sysmon, nil);
...@@ -1668,6 +1676,7 @@ runtime·malg(int32 stacksize) ...@@ -1668,6 +1676,7 @@ runtime·malg(int32 stacksize)
stk = g->param; stk = g->param;
g->param = nil; g->param = nil;
} }
g->stacksize = StackSystem + stacksize;
newg->stack0 = (uintptr)stk; newg->stack0 = (uintptr)stk;
newg->stackguard = (uintptr)stk + StackGuard; newg->stackguard = (uintptr)stk + StackGuard;
newg->stackguard0 = newg->stackguard; newg->stackguard0 = newg->stackguard;
......
...@@ -259,6 +259,7 @@ struct G ...@@ -259,6 +259,7 @@ struct G
uintptr syscallguard; // if status==Gsyscall, syscallguard = stackguard to use during gc uintptr syscallguard; // if status==Gsyscall, syscallguard = stackguard to use during gc
uintptr stackguard; // same as stackguard0, but not set to StackPreempt uintptr stackguard; // same as stackguard0, but not set to StackPreempt
uintptr stack0; uintptr stack0;
uintptr stacksize;
G* alllink; // on allg G* alllink; // on allg
void* param; // passed parameter on wakeup void* param; // passed parameter on wakeup
int16 status; int16 status;
...@@ -713,6 +714,7 @@ extern uint32 runtime·Hchansize; ...@@ -713,6 +714,7 @@ extern uint32 runtime·Hchansize;
extern uint32 runtime·cpuid_ecx; extern uint32 runtime·cpuid_ecx;
extern uint32 runtime·cpuid_edx; extern uint32 runtime·cpuid_edx;
extern DebugVars runtime·debug; extern DebugVars runtime·debug;
extern uintptr runtime·maxstacksize;
/* /*
* common functions and data * common functions and data
......
...@@ -176,13 +176,17 @@ runtime·oldstack(void) ...@@ -176,13 +176,17 @@ runtime·oldstack(void)
gp->stackguard = top->stackguard; gp->stackguard = top->stackguard;
gp->stackguard0 = gp->stackguard; gp->stackguard0 = gp->stackguard;
if(top->free != 0) if(top->free != 0) {
gp->stacksize -= top->free;
runtime·stackfree(old, top->free); runtime·stackfree(old, top->free);
}
gp->status = oldstatus; gp->status = oldstatus;
runtime·gogo(&gp->sched); runtime·gogo(&gp->sched);
} }
uintptr runtime·maxstacksize = 1<<20; // enough until runtime.main sets it for real
// Called from runtime·newstackcall or from runtime·morestack when a new // Called from runtime·newstackcall or from runtime·morestack when a new
// stack segment is needed. Allocate a new stack big enough for // stack segment is needed. Allocate a new stack big enough for
// m->moreframesize bytes, copy m->moreargsize bytes to the new frame, // m->moreframesize bytes, copy m->moreargsize bytes to the new frame,
...@@ -285,6 +289,11 @@ runtime·newstack(void) ...@@ -285,6 +289,11 @@ runtime·newstack(void)
if(framesize < StackMin) if(framesize < StackMin)
framesize = StackMin; framesize = StackMin;
framesize += StackSystem; framesize += StackSystem;
gp->stacksize += framesize;
if(gp->stacksize > runtime·maxstacksize) {
runtime·printf("runtime: goroutine stack exceeds %D-byte limit\n", (uint64)runtime·maxstacksize);
runtime·throw("stack overflow");
}
stk = runtime·stackalloc(framesize); stk = runtime·stackalloc(framesize);
top = (Stktop*)(stk+framesize-sizeof(*top)); top = (Stktop*)(stk+framesize-sizeof(*top));
free = framesize; free = framesize;
...@@ -353,3 +362,11 @@ runtime·gostartcallfn(Gobuf *gobuf, FuncVal *fv) ...@@ -353,3 +362,11 @@ runtime·gostartcallfn(Gobuf *gobuf, FuncVal *fv)
{ {
runtime·gostartcall(gobuf, fv->fn, fv); runtime·gostartcall(gobuf, fv->fn, fv);
} }
void
runtimedebug·setMaxStack(intgo in, intgo out)
{
out = runtime·maxstacksize;
runtime·maxstacksize = in;
FLUSH(&out);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment