#36098 fuzz: add corpus reachability checks

full analysis

https://github.com/bitcoin/bitcoin/pull/36098 · HowHsu · +274/-18 in 14 files, 13 commits · labels: Fuzzing

Goal

  • Detect when fuzz targets running against static corpora silently lose coverage of critical paths
  • Enable fuzz runs to fail explicitly if registered reachability goals are never triggered

This pull request introduces a reachability tracking utility (`ReachabilityGoal`) for fuzz targets. Targets explicitly register named goals during initialization and observe whether those conditions are satisfied across processed inputs. When `FUZZ_ENFORCE_REACHABILITY=1` is set, the fuzz binary fails on exit if any registered goal was never reached, enabling automated corpus CI to detect lost coverage in high-value paths.

Problem: Static fuzz corpora can silently lose coverage of deep execution paths after refactors or test harness changes without triggering errors. Developers and CI runners have no automated way to ensure that a corpus replay exercises critical functionality such as mempool acceptance or P2P handshakes.

Category: Test infrastructure (#4 of 45)

P3 · test coverage

  • P3 because it detects silent loss of coverage on critical paths like mempool acceptance or P2P handshakes
  • Helps maintainers ensure static corpora remain effective across harness changes

P3 because it adds a valuable check to prevent regressions in fuzz corpus coverage, helping ensure that fuzzers exercise deep logic such as P2P handshakes and package acceptance. In the tests rubric, capabilities that safeguard coverage of high-risk code paths rank well, though it is reasonably deferrable as an internal test framework enhancement.

Membership: Modifies fuzzing test infrastructure in src/test/fuzz/util/reachability.{h,cpp} and adds coverage reachability assertions to multiple fuzz targets.

Factors: security/stability 1, bug 0, performance 0, user value 0, leverage 1

Reviewability: Ready

  • Ready for review, core infrastructure is in place and passing CI

The patch is clean, compiles, and passes CI. Open comments are minor refinements to specific targets and termination mechanics that do not invalidate review of the framework.

Author status: Active, recently stated intention to address the mempool load check granularity.

Open concerns:

  • maflcko suggested that checking successful mempool load in validation_load_mempool is too coarse and should check specific features like unbroadcast sets and fee deltas; author agreed to update.
  • Crypt-iQ questioned the use of `std::_Exit` instead of `std::exit` or letting destruction complete normally.

Resolved concerns:

  • dergoegge suggested adding messages to reachability checks for verbose failures; author added descriptions and source locations.
  • Author switched from lazy registration to a two-phase register and observe protocol so early returns in fuzz targets do not conceal unreached goals.

Agreement: Mild

  • Strong support for adding reachability checks to fuzz testing
  • Approved, noting it provides value while wondering about higher-level formulations (maflcko)
  • Believes what is in the PR already provides value (dergoegge)
  • Concept approval after testing application to coinselection fuzzing (brunoerg)
  • Concept approval without stated reasons (nervana21)

Mild: maflcko noted validation_load_mempool checks could pass on empty files and author agreed to refine them; Crypt-iQ asked about std::_Exit.

Multiple contributors and maintainers support the feature. Two nonblocking implementation questions remain open, one of which the author agreed to update in an upcoming push.

  • maflcko approved on 2026-08-27: 'lgtm. I wonder if reachability goals can be formulated at a higher level.'
  • dergoegge commented on 2026-08-27: 'I think what\'s in this PR already provides value.'
  • brunoerg gave Concept ACK on 2026-08-27.
  • HowHsu stated on 2026-09-17: 'I\'ll update this soon' regarding validation_load_mempool coverage.

Objections:

ReviewerKindHarmStatusBlockingAuthor repliedQuote
maflckocorrectnessThe validation_load_mempool check could pass trivially on empty files without verifying that mempool features like unbroadcast sets and fee deltas were loaded.opennoyes2026-08-31: "Instead of a plain 'success' (which probably happens on an empty file), it may be better to ensure all features are covered: txs, unbroadcast set, fee deltas... etc?"
Crypt-iQmaintenanceUsing std::_Exit circumvents normal destruction and resource cleanup on exit.opennono2026-09-03: "Why std::_Exit which according to https://en.cppreference.com/cpp/utility/program/_Exit does not clean up resources compared to std::exit?"

Support:

  • maflcko: Co-authored and approved the reachability tracking mechanism to catch lost path coverage in corpora.
  • dergoegge: Affirmed that the reachability assertions provide clear value for fuzz testing.
  • brunoerg: Concept ACK. [not substantive]
  • nervana21: Concept ACK. [not substantive]

Participants: maflcko (objection), dergoegge (support), Crypt-iQ (objection), brunoerg (support), nervana21 (support)

State derived from the lists: nonblocking objection open (maflcko, Crypt-iQ)

Review verdicts (DrahtBot): 0

Dependencies

Enables:

  • Automated corpus verification in qa-assets nightly CI runs

Files

292 lines under test/bench/ci.

  • src/test/fuzz/util/reachability.cpp +106/-0
  • src/test/fuzz/util/reachability.h +34/-0
  • src/test/fuzz/package_eval.cpp +25/-2
  • src/test/fuzz/tx_pool.cpp +25/-2
  • src/test/fuzz/p2p_transport_serialization.cpp +15/-4
  • src/test/fuzz/validation_load_mempool.cpp +9/-4
  • src/test/fuzz/process_message.cpp +10/-2
  • src/test/fuzz/process_messages.cpp +10/-2
  • src/test/fuzz/utxo_snapshot.cpp +10/-1
  • src/test/fuzz/p2p_handshake.cpp +8/-0
  • src/test/fuzz/policy_estimator_io.cpp +7/-1
  • src/test/fuzz/cmpctblock.cpp +7/-0
  • src/test/fuzz/partially_downloaded_block.cpp +7/-0
  • src/test/fuzz/util/CMakeLists.txt +1/-0

Card

This PR adds a reachability tracking framework to Bitcoin Core's fuzz tests, allowing targets to register conditions that must be hit across corpus inputs. Setting FUZZ_ENFORCE_REACHABILITY=1 causes fuzz binaries to exit with failure if any goal is missed, preventing silent coverage regressions in corpus CI. It instruments 11 critical fuzz targets including package evaluation, mempool acceptance, and P2P message processing. The PR has strong concept support from fuzzing maintainers with minor open feedback on check granularity and exit handling.

Data

dossier JSON · extract JSON · model openrouter/google/gemini-3.8-flash, generated 2026-09-17T21:49, confidence high, input hash a7ca7883e8cb6dee