Skip to content

Commit bd1f53b

Browse files
committed
text/template: init depth limit
Address feedback received through review. Depth limit is now set at init() to an architecture specific value. WASM is a special case. Tests use the same approach to set the limit to a much lower value, hopefully bringing more clarity. Removed a helper function for generating test expressions. Minor doc update.
1 parent 9c1530d commit bd1f53b

File tree

3 files changed

+28
-30
lines changed

3 files changed

+28
-30
lines changed

src/text/template/doc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ by a period '.' and called "dot", to the value at the current location in the
1616
structure as execution proceeds.
1717
1818
The security model used by this package assumes that template authors are
19-
trusted. text/template does not auto-escape output, so injecting code into
19+
trusted. The package does not auto-escape output, so injecting code into
2020
a template can lead to arbitrary code execution if the template is executed
2121
by an untrusted source.
2222

src/text/template/parse/parse.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type Tree struct {
3232
treeSet map[string]*Tree
3333
actionLine int // line of left delim starting action
3434
rangeDepth int
35-
parenDepth int // current depth of nested parenthesized expressions
35+
parenDepth int // depth of nested parenthesized expressions
3636
}
3737

3838
// A mode value is a set of flags (or 0). Modes control parser behavior.
@@ -43,10 +43,19 @@ const (
4343
SkipFuncCheck // do not check that functions are defined
4444
)
4545

46-
// maxExpressionParenDepth is the maximum depth of nested parenthesized expressions.
47-
// It is used to prevent stack overflows from deep finite recursion in the parser.
48-
const maxExpressionParenDepth = 10000
49-
const maxExpressionParenDepthWasm = 1000 // Lower limit for WASM environments
46+
// maxExpressionParenDepth is the maximum depth permitted for nested
47+
// parenthesized expressions.
48+
var maxExpressionParenDepth int
49+
50+
// init sets up the maximum expression parenthesis depth based on the architecture.
51+
// WebAssembly has a smaller stack size and is more prone to stack overflow.
52+
func init() {
53+
if runtime.GOARCH == "wasm" {
54+
maxExpressionParenDepth = 1000
55+
} else {
56+
maxExpressionParenDepth = 10000
57+
}
58+
}
5059

5160
// Copy returns a copy of the [Tree]. Any parsing state is discarded.
5261
func (t *Tree) Copy() *Tree {
@@ -794,8 +803,7 @@ func (t *Tree) term() Node {
794803
}
795804
return number
796805
case itemLeftParen:
797-
if t.parenDepth >= maxExpressionParenDepth ||
798-
runtime.GOARCH == "wasm" && t.parenDepth >= maxExpressionParenDepthWasm {
806+
if t.parenDepth >= maxExpressionParenDepth {
799807
t.errorf("max expression depth exceeded")
800808
}
801809
t.parenDepth++

src/text/template/parse/parse_test.go

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package parse
77
import (
88
"flag"
99
"fmt"
10-
"runtime"
1110
"strings"
1211
"testing"
1312
)
@@ -87,6 +86,11 @@ var numberTests = []numberTest{
8786
{"0xef", true, true, true, false, 0xef, 0xef, 0xef, 0},
8887
}
8988

89+
func init() {
90+
// Use a small depth limit for testing to avoid creating huge expressions.
91+
maxExpressionParenDepth = 3
92+
}
93+
9094
func TestNumberParse(t *testing.T) {
9195
for _, test := range numberTests {
9296
// If fmt.Sscan thinks it's complex, it's complex. We can't trust the output
@@ -330,13 +334,13 @@ var parseTests = []parseTest{
330334
{"block definition", `{{block "foo"}}hello{{end}}`, hasError, ""},
331335

332336
// Parenthesis nesting depth tests
333-
{"paren nesting normal", "{{( ( ( ( (1) ) ) ) )}}", noError, "{{(((((1)))))}}"},
334-
{"paren nesting at limit", "{{" + buildNestedParenExpression(getMaxParenDepth(), "1") + "}}", noError, "{{" + buildNestedParenExpression(getMaxParenDepth(), "1") + "}}"},
335-
{"paren nesting exceeds limit", "{{" + buildNestedParenExpression(getMaxParenDepth()+1, "1") + "}}", hasError, "template: test:1: max expression depth exceeded"},
336-
{"paren nesting in pipeline", "{{ ( ( ( ( (1) ) ) ) ) | printf }}", noError, "{{(((((1))))) | printf}}"},
337-
{"paren nesting in pipeline exceeds limit", "{{ " + buildNestedParenExpression(getMaxParenDepth()+1, "1") + " | printf }}", hasError, "template: test:1: max expression depth exceeded"},
338-
{"paren nesting with other constructs", "{{if " + buildNestedParenExpression(5, "true") + "}}YES{{end}}", noError, "{{if " + buildNestedParenExpression(5, "true") + "}}\"YES\"{{end}}"},
339-
{"paren nesting with other constructs exceeds limit", "{{if " + buildNestedParenExpression(getMaxParenDepth()+1, "true") + "}}YES{{end}}", hasError, "template: test:1: max expression depth exceeded"},
337+
{"paren nesting normal", "{{ (( 1 )) }}", noError, "{{((1))}}"},
338+
{"paren nesting at limit", "{{ ((( 1 ))) }}", noError, "{{(((1)))}}"},
339+
{"paren nesting exceeds limit", "{{ (((( 1 )))) }}", hasError, "template: test:1: max expression depth exceeded"},
340+
{"paren nesting in pipeline", "{{ ((( 1 ))) | printf }}", noError, "{{(((1))) | printf}}"},
341+
{"paren nesting in pipeline exceeds limit", "{{ (((( 1 )))) | printf }}", hasError, "template: test:1: max expression depth exceeded"},
342+
{"paren nesting with other constructs", "{{ if ((( true ))) }}YES{{ end }}", noError, "{{if (((true)))}}\"YES\"{{end}}"},
343+
{"paren nesting with other constructs exceeds limit", "{{ if (((( true )))) }}YES{{ end }}", hasError, "template: test:1: max expression depth exceeded"},
340344
}
341345

342346
var builtins = map[string]any{
@@ -726,17 +730,3 @@ func BenchmarkListString(b *testing.B) {
726730
b.Fatal("Benchmark was not run")
727731
}
728732
}
729-
730-
// buildNestedParenExpression is a helper for testing parenthesis depth.
731-
func buildNestedParenExpression(depth int, content string) string {
732-
return strings.Repeat("(", depth) + content + strings.Repeat(")", depth)
733-
}
734-
735-
// getMaxParenDepth returns the appropriate parenthesis nesting depth limit
736-
// based on the current architecture.
737-
func getMaxParenDepth() int {
738-
if runtime.GOARCH == "wasm" {
739-
return maxExpressionParenDepthWasm
740-
}
741-
return maxExpressionParenDepth
742-
}

0 commit comments

Comments
 (0)