8000 fuzz: btree_page target never reaches B+tree node decoding · Issue #375 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content

fuzz: btree_page target never reaches B+tree node decoding #375

Closed

Description

@adityamukho

Summary

The btree_page fuzz target (fuzz/fuzz_targets/btree_page.rs) never decodes the fuzzer-controlled page as a B+tree node, so OnDiskIndexReader / range_scan / leaf-entry decoding (read_leaf_entries::<EavtKey> etc.) get no fuzz coverage.

Why

The target writes a synthetic header plus the fuzzed bytes at page 2, sets eavt_root_page = 2, opens the file and runs a query. But PersistentFactStorage::load() never trusts that root:

  • fact_page_count is left 0, so num_fact_pages comes from the fallback eavt_root_page.saturating_sub(1) = 1.
  • With num_fact_pages != 0 and eavt_root_page != 0, load() compares the stored index_checksum (left 0) with the CRC32 over pages 1..page_count. They never match, so needs_rebuild is true.
  • The rebuild re-indexes from fact page 1 (all zeros), writes fresh empty B+trees, and overwrites the header roots. Page 2 is never read.

So the target's result does not depend on its input. On main it was vacuous for a different reason: page_count = 0 failed with STG-007. The v8 header change in the #371 branch moved it past that point but not into node decoding. A reviewer confirmed this with an out-of-tree probe that copies load()'s branch logic: the query returned an empty result without touching page 2.

fact_page.rs is fine: it now reaches packed-fact-page decoding.

Proposed fix

Build a header that sends load() into its "trust the index roots" branch:

  • Set fact_page_count explicitly (e.g. 1, with page 1 a valid empty packed fact page) so the fallback isn't used.
  • Compute index_checksum as compute_page_checksum over pages 1..page_count, after writing the fuzzed page, and set header_checksum to match (or leave it 0 if a zero checksum is still skipped).
  • Keep eavt_root_page (and optionally the other roots) pointing at the fuzzed page, then run queries that range-scan each index (entity-bound, attribute-bound, value-bound, ref-bound) so every key type's decode path runs.
  • The target must still never assert success. Errors from malformed nodes are expected; only panics, hangs and OOMs are findings.
  • Add a seed to fuzz/corpus/btree_page/ containing a valid v8 leaf node and a valid internal node.

Acceptance: running the target with a known-bad node (e.g. a truncated slot directory) reaches btree_v6 decode code (checked with coverage or a temporary log), and a well-formed leaf seed returns its entries from a query.

Context

Found during the final review of the #371/#287 branch (fix/371-index-value-collision, file format v8). Deferred there so as not to widen that PR.

Activity

  1. added this to the v3.0.0 milestone on Sep 25, 2026
  2. added a commit that references this issue on Oct 9, 2026
  3. adityamukho commented on Oct 10, 2026

    @adityamukho
    CollaboratorAuthor

    Done on v3 in #498 (merged 2026-10-09). btree_page now builds a real v8 file through the public API, overwrites one leaf or internal node of any of the five trees (optionally with the type byte flipped), recomputes that page's CRC, and runs one input-chosen probe (EAVT/AEVT/AVET/VAET/DICT scans, :valid-at, :as-of, verify()), read-only or read-write.

    Acceptance, checked with a temporary trace in storage/node.rs: the valid leaf and internal seeds (fuzz/corpus/btree_page/seed_v8_leaf*.bin, seed_v8_internal*.bin) decode the fuzzed page and return the full answer, and verify() is clean. The truncated-slot and type-flipped seeds decode it and fail with INT-049. The first v3 nightly run (37987855944) fuzzed btree_page for its full time with no findings.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0