]> Cypherpunks repositories - gostls13.git/commitdiff
text/template: detect pathologically recursive template invocations
authorAndrew Gerrand <adg@golang.org>
Thu, 12 May 2016 20:55:46 +0000 (13:55 -0700)
committerAndrew Gerrand <adg@golang.org>
Thu, 12 May 2016 22:32:30 +0000 (22:32 +0000)
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 <r@golang.org>
src/text/template/exec.go
src/text/template/exec_test.go

index 22881c685279f6a94561e6c69867aedbe151f52e..8e5ad93ca6b5b881d87b73a6bbeaf808bd368904 100644 (file)
@@ -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}}
index bc2aa683ec9a0695f0f995e3a2a40545305f7b78..3ef065edcfdebefd07065d3ca56d3b9073a10240 100644 (file)
@@ -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 := "<nil>"
+       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)
+       }
+}