Skip to content

encode: bound nesting depth to prevent stack overflow on deep input#79

Open
martinholovsky wants to merge 1 commit into
hjson:masterfrom
martinholovsky:fix-encoder-max-depth
Open

encode: bound nesting depth to prevent stack overflow on deep input#79
martinholovsky wants to merge 1 commit into
hjson:masterfrom
martinholovsky:fix-encoder-max-depth

Conversation

@martinholovsky

Copy link
Copy Markdown

The decoder gained a maxNestingDepth guard to fix a stack-exhaustion issue (GHSA-5wfc-hjrc-gq87 / GO-2026-5157), but the encoder was left unguarded.

The encoder’s only depth-related logic is cycle detection, which triggers solely on a repeated pointer:

// encode.go
case reflect.Ptr, reflect.Slice, reflect.Map:
    if e.pDepth++; e.pDepth > depthLimit {
        // ... errors only if value.Pointer() was seen before (a cycle)
    }

A deeply nested, non-cyclic value has a distinct pointer at every level, so the cycle check never fires and hjson.Marshal recurses until the goroutine stack overflows — a fatal, unrecoverable crash (a Go stack overflow cannot be recover()-ed).

Reproduce (still applies to v4.6.0):

var v any = "x"
for i := 0; i < 3_000_000; i++ { v = []any{v} }
_, _ = hjson.Marshal(v) // fatal error: stack overflow  (frames in encode.go)

Fix: reuse the existing pDepth counter to also enforce maxNestingDepth on the encode side, returning the same "Exceeded max depth (%d)" error the decoder already returns. No behavior change for normal input (nothing legitimately nests 10000 deep); the cycle check still catches true cycles early. Added a regression test (TestEncodeMaxDepth). go test ./... passes; gofmt clean.

The decoder gained a maxNestingDepth guard to fix a stack-exhaustion issue
(GHSA-5wfc-hjrc-gq87 / GO-2026-5157), but the encoder was left unguarded. Its
only depth-related logic is cycle detection, which triggers solely on a repeated
pointer; a deeply nested, non-cyclic value (distinct pointer at every level)
slips past it, so hjson.Marshal recurses until the goroutine stack overflows — a
fatal, unrecoverable crash (DoS).

Reuse the existing pDepth counter to also enforce maxNestingDepth on the encode
side, returning the same "Exceeded max depth" error the decoder already returns.
No behavior change for normal input (nothing legitimately nests 10000 deep).

Reproduce (pre-fix): var v any = "x"; for i := 0; i < 3_000_000; i++ { v =
[]any{v} }; hjson.Marshal(v) -> fatal error: stack overflow.
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.

1 participant