From: Andrew Gerrand Date: Thu, 12 May 2016 20:55:46 +0000 (-0700) Subject: text/template: detect pathologically recursive template invocations X-Git-Tag: go1.7beta1~228 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=eb69476c66339ca494f98e65a78d315da99a9c79;p=gostls13.git text/template: detect pathologically recursive template invocations Return an error message instead of eating memory and eventually triggering a stack overflow. Fixes #15618 Change-Id: I3dcf1d669104690a17847a20fbfeb6d7e39e8751 Reviewed-on: https://go-review.googlesource.com/23091 Reviewed-by: Rob Pike --- diff --git a/src/text/template/exec.go b/src/text/template/exec.go index 22881c6852..8e5ad93ca6 100644 --- a/src/text/template/exec.go +++ b/src/text/template/exec.go @@ -15,14 +15,21 @@ import ( "text/template/parse" ) +// maxExecDepth specifies the maximum stack depth of templates within +// templates. This limit is only practically reached by accidentally +// recursive template invocations. This limit allows us to return +// an error instead of triggering a stack overflow. +const maxExecDepth = 100000 + // state represents the state of an execution. It's not part of the // template so that multiple executions of the same template // can execute in parallel. type state struct { - tmpl *Template - wr io.Writer - node parse.Node // current node, for errors - vars []variable // push-down stack of variable values. + tmpl *Template + wr io.Writer + node parse.Node // current node, for errors + vars []variable // push-down stack of variable values. + depth int // the height of the stack of executing templates. } // variable holds the dynamic value of a variable such as $, $x etc. @@ -363,9 +370,13 @@ func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) { if tmpl == nil { s.errorf("template %q not defined", t.Name) } + if s.depth == maxExecDepth { + s.errorf("exceeded maximum template depth (%v)", maxExecDepth) + } // Variables declared by the pipeline persist. dot = s.evalPipeline(dot, t.Pipe) newState := *s + newState.depth++ newState.tmpl = tmpl // No dynamic scoping: template invocations inherit no variables. newState.vars = []variable{{"$", dot}} diff --git a/src/text/template/exec_test.go b/src/text/template/exec_test.go index bc2aa683ec..3ef065edcf 100644 --- a/src/text/template/exec_test.go +++ b/src/text/template/exec_test.go @@ -1297,3 +1297,16 @@ func TestMissingFieldOnNil(t *testing.T) { t.Errorf("got error %q, want %q", got, want) } } + +func TestMaxExecDepth(t *testing.T) { + tmpl := Must(New("tmpl").Parse(`{{template "tmpl" .}}`)) + err := tmpl.Execute(ioutil.Discard, nil) + got := "" + if err != nil { + got = err.Error() + } + const want = "exceeded maximum template depth" + if !strings.Contains(got, want) { + t.Errorf("got error %q; want %q", got, want) + } +}