Skip to content

fix(json): bound JSON reader nesting depth to prevent stack overflow (FB-2936) - #42

Merged
lorenzhs merged 1 commit into
release-24.0.0from
moshap/fb-2936-json-nesting-depth-guard
Aug 17, 2026
Merged

fix(json): bound JSON reader nesting depth to prevent stack overflow (FB-2936)#42
lorenzhs merged 1 commit into
release-24.0.0from
moshap/fb-2936-json-nesting-depth-guard

Conversation

@moshap-firebolt

@moshap-firebolt moshap-firebolt commented Aug 13, 2026

Copy link
Copy Markdown

Summary

Fixes FB-2936 — an unauthenticated remote crash (stack-overflow SIGSEGV) in the Arrow C++ JSON reader on deeply nested JSON. Firebolt's read_json() (and JSON external tables / COPY FROM) reach this via schema inference; any read-only query over an attacker-supplied JSON file can take down the engine process.

Root cause

arrow::json's block parser runs RapidJSON iteratively (kParseIterativeFlag), so parsing deep JSON is stack-safe. But the array/struct builders are finalized recursivelyRawArrayBuilder<Kind::kArray|kObject>::FinishRawBuilderSet::Finish recurse one native frame per nesting level with no depth guard. Deeply nested input ({"a":{"a":{…}}} or [[[…]]], ~20k levels) overflows the thread stack during finalization, before any limit can fire. ParseOptions exposes no depth cap (checked against upstream main too).

Fix

Reject input nested deeper than kMaxNestingDepth (1000) in HandlerBase::StartNested() — the single point where built (non-skipped) object/array nesting deepens, shared by every handler (InferType/Ignore/Error) via StartObjectImpl/StartArrayImpl. builder_stack_.size() is the live depth; on exceed we return Status::Invalid, which aborts the (already-iterative) parse cleanly. Skipped subtrees don't build and can't drive the Finish recursion, so guarding built nesting is sufficient. This mirrors the existing fix(thrift): restore skip() recursion-depth guard patch.

Three lines of behavior change at one choke point; ~1 comparison per nested container (negligible).

Validation

Built Firebolt with this patch and its packdb-side guard removed, then ran the JSON inference SQL suite:

  • A 2000-level document is now rejected gracefully (JSON nesting depth exceeds the maximum of 1000) instead of SIGSEGV.
  • read_json, read_files, read_relative_path, and external-table JSON tests all pass unchanged.

Notes

  • 1000 is a hardcoded constant here (consistent with Firebolt's other JSON depth limits; simdjson's default is 1024). If we want it tunable, it can be promoted to a ParseOptions::max_nesting_depth field — that's also the form to propose upstream to apache/arrow, which has the same gap.

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

🤖 Generated with Claude Code


Note

Cursor Bugbot is generating a summary for commit 83b978e. Configure here.

…(FB-2936)

The JSON block parser parses iteratively (kParseIterativeFlag) and cannot
overflow, but the array/struct builders are finalized recursively
(RawArrayBuilder<kArray|kObject>::Finish / RawBuilderSet::Finish), so deeply
nested input (e.g. {"a":{"a":{...}}} or [[[...]]]) overflows the native stack and
crashes the process before any limit fires.

Reject input nested deeper than kMaxNestingDepth (1000) in StartNested() — the
single point where built (non-skipped) object/array nesting deepens for every
handler — so the pathological case is unreachable by construction. Mirrors the
existing thrift skip() recursion-depth guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@moshap-firebolt
moshap-firebolt force-pushed the moshap/fb-2936-json-nesting-depth-guard branch from 83b978e to c438631 Compare August 14, 2026 04:41
@lorenzhs
lorenzhs merged commit f897f9c into release-24.0.0 Aug 17, 2026
10 checks passed
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