Fuzz testing for bundle headers by benjaminy · Pull Request #7433 · libgit2/libgit2 · GitHub
Skip to content

Fuzz testing for bundle headers - #7433

Open
benjaminy wants to merge 5 commits into
libgit2:mainfrom
benjaminy:bundle-fuzzer
Open

benjaminy wants to merge 5 commits into
libgit2:mainfrom
benjaminy:bundle-fuzzer

Conversation

@benjaminy

Copy link
Copy Markdown
Contributor

Follow-up to #7338. Add fuzz coverage for the internal bundle header parser, including replacement of an owned result, cleanup after malformed input, and reuse after supported or unsupported headers. This changes no parser behavior and adds no public API.

Changes

  • Add bundle_fuzzer, using the existing fuzzer discovery and standalone corpus-replay infrastructure. Each input is parsed through both the memory entry point and a callback returning at most seven bytes per read, without seeking. Both parsers initially hold a valid header, and their return codes and retained results must agree field by field.
  • Check source release, cleared results on hard failures, and the two retained states for unsupported input. Complete headers must have a recognized version and object format, matching OID types, and a pack offset immediately after the blank separator with 17 <= pack_offset <= input size. Reparse a known header to check recovery, then dispose each parser twice.
  • Add 35 seeds covering v2/v3, SHA-1/SHA-256, unsupported capabilities, invalid syntax and ordering, byte handling, and the 8192-byte read boundary. Mark the corpus -text -whitespace to preserve intentional byte sequences and malformed whitespace across checkouts.
  • Add a deterministic test accepting a header line of exactly 1 MiB and rejecting one byte more. This checks the limit independently of the fuzzer's structural invariants.
  • Correct the bundle.h comment: an unknown object format returns GIT_ENOTSUPPORTED before the remaining header is validated.
  • Fix two directly relevant instructions in docs/fuzzing.md: standalone fuzzers can coexist with tests and examples, and libFuzzer builds need -fsanitize=fuzzer-no-link in CFLAGS to instrument the library objects.

Validation

Mutation campaigns ran with libFuzzer, ASan, UBSan, and LeakSanitizer using Debian Clang 19.1.7 on aarch64 Linux in Docker. A deliberately leaking test program first confirmed that leak detection worked. The campaign builds included -fsanitize=fuzzer-no-link in CFLAGS to instrument parser code.

Duration Starting corpus Maximum input length Executions Result
300 s Seed corpus 16 KiB 2.0 million Clean
300 s Corpus from the first run 1.2 MB 2.4 million Clean
120 s Final committed seeds and harness 16 KiB 0.84 million Clean

No crashes, leaks, or timeouts were reported. The final run produced no artifacts. Mutation did not grow inputs close to the 1 MiB line limit, so boundary inputs were exercised separately.

  • Under ASan and LeakSanitizer, the 35 committed seeds and the 1,130-input generated corpus replayed cleanly; all nine fuzzer replay tests and libgit2_tests -sbundle::parse passed.
  • On macOS, 362 generated inputs passed under UBSan: empty input, every truncation of representative v2 and v3 SHA-256 headers, signature variants, newlines at offsets 8186–8199, and prerequisite/ref lines of 1 MiB minus one byte, exactly 1 MiB, 1 MiB plus one byte, and 2 MiB, with and without a final newline.
  • Coverage inspection identified five paths reached only by mutation; corresponding seeds were added. Uncovered parser paths include read, seek, and allocation failures, the descriptor entry point, and dispose(NULL). The harness does not inject source or allocation failures.
  • Standalone fuzzers and tests build with ENABLE_WERROR=ON using Apple Clang 17. A final local recheck passed all nine fuzzer replay tests and all 29 bundle::parse tests. git diff --check passes.

ASan hangs at startup on the macOS host, so ASan validation ran on Linux. Windows/MSVC validation is left to CI.

benjaminy and others added 5 commits October 17, 2026 06:28
An unknown object format is reported as soon as the capability is
seen, before the rest of the header is read, so the claim that
GIT_ENOTSUPPORTED is only returned after the whole header has been
validated is not quite true.  Describe the exception and what the
retained header holds in that case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fuzz the internal bundle header parser, including how it replaces,
releases, and reuses the header that it owns.  Each input is parsed
through both the memory backing and a callback that returns a few
bytes at a time, into parsers that already hold a valid header; the
two results must agree, satisfy a few structural invariants, and leave
the parsers reusable and cleanly disposable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add small seeds for the bundle header fuzzer covering supported
headers (v2, v3 with SHA-1 and SHA-256), unsupported capabilities and
object formats, ordering and syntax errors, byte handling (NUL, CRLF,
trailing pack bytes), and a header line that crosses the 8192-byte
internal read chunk.

Bundle headers are byte-exact, so mark the corpus as not text;
otherwise line-ending conversion on checkout would turn the valid
seeds into malformed ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A header line of exactly the maximum length must be accepted and one
byte more rejected.  Neither boundary was tested, and the fuzz target
cannot tell an off-by-one in the limit from correct behavior.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant