Skip to content

fix(bb): fix out-of-bounds read behind the nightly debug build failure - #25129

Draft
AztecBot wants to merge 1 commit into
merge-train/barretenbergfrom
cb/46ca1d3d8af7
Draft

fix(bb): fix out-of-bounds read behind the nightly debug build failure#25129
AztecBot wants to merge 1 commit into
merge-train/barretenbergfrom
cb/46ca1d3d8af7

Conversation

@AztecBot

@AztecBot AztecBot commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

The Nightly Debug Build fails on AcirComponentsCheckTest.DetectsUnconstrainedWitnesses with SIGABRT (exit 134) — run #170, CI log:

Error: attempt to subscript container with out-of-bounds index 10, but container only holds 9 elements.

Root cause

The test fabricated an "unconstrained witness" by truncating the builder's index table:

builder.real_variable_index.resize(9);

create_circuit sizes the builder from max_witness_index (9 here), so ACIR witnesses 0–9 become variables 0–9 and the builder's internal constants start at 10 (zero_idx() == 10; 19 variables in total). Truncating to 9 entries leaves the builder's own gates wired to variables past the end of that table, and ComponentsChecker hands the builder to cdg::StaticAnalyzer_, which resolves every gate wire through real_variable_index. Gate 0 of the arithmetic block wires zero_idx, so the walk read index 10 of a 9-element vector.

Under the debug preset's -D_GLIBCXX_DEBUG the checked vector aborts; in every other build it is a silent past-the-end read (UB), which is why the test passed everywhere else. Any truncation small enough to hide witness 9 also drops variables the builder's gates reference, so the corruption could never be made consistent — it describes a builder state that cannot exist.

Changes

  1. The test no longer corrupts the builder. It expresses "this ACIR component has no circuit-side placement" the way the checker actually supports it: the ACIR handed to ComponentsChecker links a second pair of witnesses (w1000/w1001) that create_circuit never allocated variables for. That exercises the existing witness >= real_variable_index.size() guard in build_circuit_component_map and yields exactly one UNCONSTRAINED error, with the builder left self-consistent.

  2. StaticAnalyzer_::to_real bounds-checks the index. Every gate wire is resolved through it, so it is where an inconsistent builder surfaces. A BB_ASSERT_LT reports the problem instead of reading past the end of the table, in release builds as well as debug. The two other raw real_variable_index[...] subscripts in graph.cpp now go through the same accessor, so there is a single checked path.

  3. ComponentsChecker_::check asserts the builder invariant up front, so a caller passing a builder whose index table does not cover its variables gets an actionable message at the API boundary rather than a failure deep inside the analyzer. ACIR witnesses beyond the builder's variables remain a legitimate input — those are reported as UNCONSTRAINED, which is the checker's job.

The result: the invalid state is no longer created, and if it is ever created again the failure is a legible assertion rather than an out-of-bounds read. With the old resize(9) reintroduced on top of this change, the test now fails cleanly with:

Assertion failed: (builder_.real_variable_index.size() == builder_.get_num_variables())
  Actual  : 9
  Expected: 19
  Reason  : Builder does not have a real variable index for every variable.

Verification

All with the debug preset (cmake --preset debug, i.e. -D_GLIBCXX_DEBUG), on the same commit the nightly failed at:

  • acir_components_check_tests — SIGABRT before, all 10 tests pass after.
  • boomerang_value_detection_tests — all 96 tests pass, so the new to_real assertion does not fire on any real analyzer input.
  • Full debug tree (919 targets) compiles clean, covering the analyzer's other consumers (goblin_tests, stdlib_honk_verifier_tests, stdlib_translator_vm_verifier_tests, vm2_tests).

Note that the nightly's test phase fail-fasts, so it aborted 14s in at this failure; other debug-only failures may still be hiding behind it.


Created by claudebox · group: slackbot · requested by ludamad (@ludamad) · Slack thread

@AztecBot AztecBot added ci-draft Run CI on draft PRs. ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure claudebox Owned by claudebox. it can push to this PR. labels Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-draft Run CI on draft PRs. ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure claudebox Owned by claudebox. it can push to this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant