Skip to content

Prevent stack overflow from deeply nested sections - #96

Merged
cbroglie merged 1 commit into
cbroglie:masterfrom
ChrisJr404:fix/section-nesting-stack-overflow
Aug 27, 2026
Merged

Prevent stack overflow from deeply nested sections#96
cbroglie merged 1 commit into
cbroglie:masterfrom
ChrisJr404:fix/section-nesting-stack-overflow

Conversation

@ChrisJr404

Copy link
Copy Markdown

Parsing a template recurses once per open section tag: parseSection calls itself for every {{#...}} / {{^...}} it encounters. There is no bound on that recursion, so a template made up of a large number of section openers drives the parser to recurse until the goroutine stack is exhausted.

When that happens Go aborts with fatal error: stack overflow, which is not a normal panic and cannot be caught with recover() — it takes down the whole process. Any program that parses untrusted templates through the documented entry points (ParseString, Render, and friends) can therefore be crashed by a single crafted string.

Minimal reproducer:

package main

import (
    "strings"

    "github.com/cbroglie/mustache"
)

func main() {
    // no closing tags needed; the crash happens during parsing
    mustache.ParseString(strings.Repeat("{{#a}}", 4_000_000))
}
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
...
github.com/cbroglie/mustache.(*Template).parseSection(...)
github.com/cbroglie/mustache.(*Template).parseSection(...)
...

Fix

Thread a depth counter through parseSection and return an ordinary parse error (ErrNestingTooDeep) once the nesting exceeds a generous limit (10000). Real templates never approach that depth, so well-formed input — including deeply-but-reasonably nested sections — is unaffected, while a hostile template now yields a normal error instead of crashing the process.

Added tests cover both sides: excessive nesting now returns ErrNestingTooDeep, and ordinary nested (properly closed) sections still parse. The full suite, including the upstream mustache spec conformance tests, passes.

parseSection recurses once per open section tag, so a template consisting
of a large number of section openers (for example many "{{#a}}") drives the
parser to recurse until the goroutine stack is exhausted. Go reports this as
an unrecoverable "fatal error: stack overflow" that aborts the whole process
and cannot be caught with recover(), so any code that parses untrusted
templates via ParseString/Render can be crashed by a single crafted string.

Bound the section nesting depth and return an ordinary parse error once it is
exceeded. The limit (10000) is far above anything a real template needs, so
well-formed templates are unaffected.
@cbroglie
cbroglie merged commit bc8a53e into cbroglie:master Aug 27, 2026
1 check passed
@cbroglie

Copy link
Copy Markdown
Owner

Thanks @ChrisJr404!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants