Skip to content

Fuzz tree deserialisation in CI - #84

Merged
Amaury Chamayou (achamayou) merged 4 commits into
mainfrom
achamayou-fuzz-tree-deserialisation
Aug 26, 2026
Merged

Fuzz tree deserialisation in CI#84
Amaury Chamayou (achamayou) merged 4 commits into
mainfrom
achamayou-fuzz-tree-deserialisation

Conversation

@achamayou

@achamayou Amaury Chamayou (achamayou) commented Aug 24, 2026

Copy link
Copy Markdown
Member

Summary

  • add an opt-in FUZZING CMake configuration for Clang/libFuzzer
  • fuzz the final stacked implementation of merkle::Tree::deserialise
  • run the bounded fuzz target in the Ubuntu Debug + clang++ + OpenSSL-OFF CI matrix entry

Target surface

LLVMFuzzerTestOneInput copies the arbitrary input bytes into the representation accepted by Tree::deserialise, starts at position zero, and deserialises into a fresh tree. std::runtime_error and std::out_of_range are treated as expected malformed-input rejections; sanitizer findings, process crashes, and unexpected exception types fail the target.

Because this PR is based directly on achamayou-tree-size-validation, the harness exercises the combined bounds, ownership/exception-safety, and representability checks from the lower stack layers.

Structured input generation and corpus

The libFuzzer dictionary contains five structured byte sequences:

  • empty-tree and one-leaf 16-byte count headers
  • maximum leaf count and high flushed-count headers
  • a 32-byte zero hash

These tokens steer mutation toward serialized count and hash fields. There is no checked-in persistent corpus: each bounded run starts from libFuzzer's empty corpus, uses the dictionary to generate structured inputs, and builds an ephemeral in-memory corpus for that run.

Sanitizers

The fuzz executable is compiled and linked with:

-fsanitize=fuzzer,address,undefined -fno-omit-frame-pointer

This combines libFuzzer with AddressSanitizer and UndefinedBehaviorSanitizer. Linux ASan supplies LeakSanitizer support; the local validation below explicitly enabled detect_leaks=1 and configured both sanitizers to halt on the first finding.

Bounded CI configuration

FUZZING is enabled only for the Ubuntu Debug, clang++, OpenSSL-OFF matrix job. CTest invokes the target with:

-runs=200000 -seed=1 -max_len=4096 -timeout=5 \
-dict=test/tree_deserialise_fuzzer.dict

This gives a deterministic mutation seed, exactly 200,000 executions, a 4 KiB input cap, a five-second per-input timeout, and a 60-second CTest test timeout. The surrounding CI CTest invocation also has a 300-second timeout.

Failure artifacts and reproduction

On a crash or sanitizer finding, libFuzzer prints the failing input and writes its usual artifact (for example, crash-*, leak-*, or timeout-*) in the test working directory, build/<configuration>/test. The current workflow preserves the diagnostic in the verbose CTest log but does not upload the artifact separately.

After downloading or reconstructing the reported artifact, reproduce it with the same sanitized binary:

build/fuzz-clang-tidy/test/fuzz_tree_deserialise path/to/crash-*

For a bounded replay with the PR's dictionary and deterministic seed, rerun the CTest command below.

Local validation

Validated on Ubuntu 26.04 under WSL, Clang 21.1.8, and CMake 4.2.3 with the CI-failing Clang-tidy configuration enabled:

cmake -S . -B build/fuzz-clang-tidy \
  -DCMAKE_BUILD_TYPE=Debug \
  -DCMAKE_CXX_COMPILER=clang++ \
  -DOPENSSL=OFF \
  -DFUZZING=ON \
  -DLONG_TESTS=OFF \
  -DCLANG_TIDY=ON \
  -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build/fuzz-clang-tidy \
  --target fuzz_tree_deserialise -j "$(nproc)"
cmake --build build/fuzz-clang-tidy \
  --target unit_tests -j "$(nproc)"
ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 \
UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 \
ctest --test-dir build/fuzz-clang-tidy -VV -C Debug --timeout 300 \
  -R '^fuzz_tree_deserialise$'
ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 \
UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 \
ctest --test-dir build/fuzz-clang-tidy -VV -C Debug --timeout 300 \
  -R '^unit_tests$'

The fuzz target built cleanly under Clang-tidy and completed all 200,000 fixed-seed executions in 13.60 seconds (cov: 456, ft: 1628, ephemeral corpus 55/7726b, peak RSS 201 MB) with no ASan, UBSan, or leak finding. unit_tests passed all 12 test cases and 187 assertions under the same sanitizer and Clang-tidy build.

Stack

Depends on #82, which is stacked on #80 and #78.

@achamayou
Amaury Chamayou (achamayou) force-pushed the achamayou-fuzz-tree-deserialisation branch from b59937f to 8f42b49 Compare August 24, 2026 14:59
@achamayou
Amaury Chamayou (achamayou) force-pushed the achamayou-fuzz-tree-deserialisation branch from 8f42b49 to 6264df0 Compare August 24, 2026 20:48
@achamayou
Amaury Chamayou (achamayou) marked this pull request as ready for review August 25, 2026 12:14
@achamayou
Amaury Chamayou (achamayou) requested a review from a team as a code owner August 25, 2026 12:14
Copilot AI balanced review requested due to automatic review settings August 25, 2026 12:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds opt-in libFuzzer coverage for tree deserialization and runs it in one sanitized Ubuntu CI configuration.

Changes:

  • Adds a tree-deserialization fuzz harness and mutation dictionary.
  • Configures Clang sanitizers and bounded CTest execution.
  • Enables fuzzing for Ubuntu Debug with Clang and OpenSSL disabled.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
CMakeLists.txt Adds the FUZZING option.
test/CMakeLists.txt Builds and registers the fuzz target.
test/fuzz_tree_deserialise.cpp Implements the fuzz harness.
test/tree_deserialise_fuzzer.dict Provides structured dictionary tokens.
.github/workflows/ci.yml Enables fuzzing in the selected CI job.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread test/CMakeLists.txt Outdated
@achamayou
Amaury Chamayou (achamayou) force-pushed the achamayou-fuzz-tree-deserialisation branch 2 times, most recently from 9c4c8e0 to 9c2c337 Compare August 25, 2026 12:57
@achamayou
Amaury Chamayou (achamayou) changed the base branch from achamayou-tree-size-validation to achamayou-tree-deserialise-ownership August 25, 2026 12:59
@achamayou
Amaury Chamayou (achamayou) force-pushed the achamayou-fuzz-tree-deserialisation branch from 9c2c337 to b7cc117 Compare August 25, 2026 15:09
Base automatically changed from achamayou-tree-deserialise-ownership to main August 25, 2026 15:45
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 73572868-1386-49b1-8849-ac9f9543ad3a
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@achamayou
Amaury Chamayou (achamayou) force-pushed the achamayou-fuzz-tree-deserialisation branch from b7cc117 to 860cd95 Compare August 25, 2026 15:45
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@achamayou
Amaury Chamayou (achamayou) merged commit 73bbb41 into main Aug 26, 2026
15 checks passed
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.

3 participants