Skip to content

verifier: propagate max_depth and max_tables budgets across VerifyNestedFlatBuffer - #9418

Open
professor-moody wants to merge 2 commits into
google:masterfrom
professor-moody:fix-nested-flatbuffer-verifier-budget
Open

professor-moody wants to merge 2 commits into
google:masterfrom
professor-moody:fix-nested-flatbuffer-verifier-budget

Conversation

@professor-moody

@professor-moody professor-moody commented Oct 18, 2026 •

Copy link
Copy Markdown

Summary

This PR propagates the active max_depth and max_tables verifier budgets across VerifierTemplate::VerifyNestedFlatBuffer (include/flatbuffers/verifier.h) and wires regression coverage into the main test suite (tests/monster_test.h, tests/monster_test.cpp, tests/test.cpp):

  1. Propagate max_depth and max_tables across VerifyNestedFlatBuffer (include/flatbuffers/verifier.h):

    • Previously, VerifyNestedFlatBuffer<T> constructed a fresh VerifierTemplate<TrackVerifierBufferSize> with the caller's unmodified opts_ (max_depth = 64, max_tables = 1000000) and did not fold nested_verifier.num_tables_ back into the outer verifier. For schemas with nested_flatbuffer fields (such as MyGame::Example::Monster::testnestedflatbuffer), nested FlatBuffers reset depth_ = 0 and num_tables_ = 0 at every hop, bypassing opts_.max_depth on deep chains and opts_.max_tables across both chained and sibling nested buffers.
    • VerifyNestedFlatBuffer now checks depth_ < opts_.max_depth and num_tables_ <= opts_.max_tables, initializes nested_opts.max_depth = opts_.max_depth - depth_ and nested_opts.max_tables = opts_.max_tables - num_tables_, and after successful nested verification accumulates num_tables_ += nested_verifier.num_tables_ and checks num_tables_ <= opts_.max_tables.
  2. Regression Test (tests/monster_test.h, tests/monster_test.cpp, tests/test.cpp):

    • Declares flatbuffers::tests::NestedVerifierBudgetExhausted() in tests/monster_test.h, implements it in tests/monster_test.cpp, and invokes it from FlatBufferTests in tests/test.cpp.
    • Verifies that a 2-level nested Monster control passes, a 32-level chain is rejected when opts.max_depth = 16 (while passing under default max_depth = 64), a 12-level chain is rejected when opts.max_tables = 8, and 3 sibling nested Monster buffers under testarrayoftables (13 cumulative tables, max single-branch table count 7) are rejected when opts.max_tables = 8.

Verification

  • Built and ran flattests against master (b8431fbcd7a5c71817f314e18b332c0648554efa, cleanly mergeable with f58d5768f1fd0df9f71aa6ae5f63645e2637c31f):
    • Without the include/flatbuffers/verifier.h fix, flattests fails all three budget assertions (depth_limited, table_limited, and sibling_limited) in NestedVerifierBudgetExhausted().
    • With the fix applied, flattests passes (ALL TESTS PASSED) in both Release and ASan+UBSan builds.

…tedFlatBuffer

## Summary

This PR propagates the active `max_depth` and `max_tables` verifier budgets across `VerifierTemplate::VerifyNestedFlatBuffer` (`include/flatbuffers/verifier.h`) and adds regression coverage in `tests/monster_test.cpp` (`Tests/NestedVerifierBudgetExhausted`):

1. **Propagate `max_depth` and `max_tables` across `VerifyNestedFlatBuffer` (`include/flatbuffers/verifier.h`)**:
   - Previously, `VerifyNestedFlatBuffer<T>` constructed a fresh `VerifierTemplate<TrackVerifierBufferSize>` with the caller's unmodified `opts_` (`max_depth = 64`, `max_tables = 1000000`) and did not fold `nested_verifier.num_tables_` back into the outer verifier. For schemas with self-referential `nested_flatbuffer` fields (such as `MyGame::Example::Monster::testnestedflatbuffer`), a chain of nested FlatBuffers reset `depth_ = 0` and `num_tables_ = 0` at every hop, bypassing `opts_.max_depth` and `opts_.max_tables` and allowing unbounded recursion (`depth >= 52,000` in ASan, `100,000` in Release `-O3`) that terminates in stack exhaustion (`SIGSEGV`) inside `VerifyMonsterBuffer()`.
   - `VerifyNestedFlatBuffer` now checks `depth_ < opts_.max_depth` and `num_tables_ <= opts_.max_tables`, initializes `nested_opts.max_depth = opts_.max_depth - depth_` and `nested_opts.max_tables = opts_.max_tables - num_tables_`, and accumulates `num_tables_ += nested_verifier.num_tables_` on return.

2. **Regression Test (`tests/monster_test.cpp`)**:
   - Adds `Tests/NestedVerifierBudgetExhausted` (`NestedVerifierBudgetExhausted()`), verifying that a 2-level nested `Monster` buffer passes verification while nested chains exceeding `opts.max_depth` or cumulative `opts.max_tables` are cleanly rejected.

## Verification

- Built and tested against `master` (`b8431fbcd7a5c71817f314e18b332c0648554efa`):
  - `ctest` (`flattests`) `1/1` passed in both Release (`-O3 -DNDEBUG`) and ASan+UBSan (`-O1 -g -fsanitize=address,undefined`).
  - Confirmed that deeply nested `testnestedflatbuffer` chains (52,000 levels under ASan; 100,000 levels in Release `-O3`) that previously caused stack overflow (`SIGSEGV`) in `VerifyMonsterBuffer()` are now cleanly rejected (`VerifyMonsterBuffer() == false`) once the cumulative depth or table budget is reached.

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant