ReviewOS

zig-utils/zig-js

JSON: reject hostile nesting before native stack overflow

#515
Closed chris opened this 9 days ago ·
9 days ago

Problem

JSON.parse uses mutually recursive JsonParser.parseValue / parseArray / parseObject calls without entering the engine call-depth guard. A valid 50,000-level array currently terminates the process with SIGSEGV (exit 139) instead of producing a catchable resource error.

The public entry points also catch every parser error as invalid syntax, which can mask non-syntax failures such as the depth guard or allocator failure as SyntaxError.

This is a denial-of-service and failure-classification boundary for untrusted JSON.

Required outcome

  • Apply the existing interpreter stack-depth contract to every recursively parsed JSON value before the native stack can overflow.
  • Preserve error.Invalid as SyntaxError, but propagate resource/host failures without relabeling them as invalid JSON.
  • Cover deeply nested valid arrays, objects, and mixed containers in normal and TSan builds.
  • Verify shallow boundary values still parse, invalid syntax remains SyntaxError, reviver/source-text semantics remain unchanged, OOM stays failure-atomic, and no input-specific depth shortcut is introduced.
  • Add a deterministic subprocess witness proving hostile depth cannot crash or hang the engine.

Relationships

Child of #473 and contributes to #493. Related to the bounded-resource requirements in #480.

9 days ago

Completed on main in e7d430f7 (fix(json): guard recursive parsing depth (#515)).

The parser now charges only recursive object/array containers to the existing logical/native stack guard, preserving the flat-scalar fast path. JSON.parse and JSON.rawJSON convert only error.Invalid to SyntaxError; allocation, existing throw, and internal control failures propagate unchanged. The recursive reviver walk uses the same stack boundary.

A production-module subprocess gate covers 50,000-level arrays, 50,000-level objects, 20,000 mixed containers, deep JSON.rawJSON, reviver traversal, a valid 96-level boundary, and malformed-syntax classification. CI runs the gate normally and under TSan.

Evidence:

  • before: the 50,000-array witness exited with SIGSEGV / 139
  • after: the exact external ReleaseFast witness returns OK 42
  • Debug, ReleaseSafe, and TSan runtime gates pass
  • full sharded unit gate: 1,544 passed, 1 expected platform skip, 0 failed, 0 leaked
  • focused CI command, GC relocation inventory, and optimizer release inventory pass
  • ReleaseFast test262 test/built-ins/JSON/parse: 77/77 before and 77/77 after; flips 0, regressions 0

This completes the denial-of-service and failure-classification boundary without input-specific shortcuts or a reduced JSON grammar. It advances #473, #493, and #480.

Sign in to comment on this issue.