From b41fa33da53a590e05b66ee4200acdc6a909b54f Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 20 Mar 2026 08:42:11 +0100 Subject: [PATCH 1/2] ci: split fuzz sanity check into separate parallel job The sanity check (cargo test on fuzz targets) doesn't use the restored corpus and was blocking the actual fuzz run. Move it to a separate fuzz_sanity job so both run in parallel. AI tools were used in preparing this commit. --- .github/workflows/build.yml | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b7bae9166f3..f50c9b8d231 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -205,6 +205,21 @@ jobs: - name: Simulate docs.rs build run: ci/check-docsrs.sh + fuzz_sanity: + runs-on: self-hosted + env: + TOOLCHAIN: 1.75 + steps: + - name: Checkout source code + uses: actions/checkout@v4 + - name: Install Rust ${{ env.TOOLCHAIN }} toolchain + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ env.TOOLCHAIN }} + - name: Sanity check fuzz targets on Rust ${{ env.TOOLCHAIN }} + run: | + cd fuzz + cargo test --quiet --color always --lib --bins -j8 + fuzz: runs-on: self-hosted env: @@ -238,11 +253,6 @@ jobs: key: fuzz-corpus-refs/heads/main-${{ github.sha }} restore-keys: | fuzz-corpus-refs/heads/main- - - name: Sanity check fuzz targets on Rust ${{ env.TOOLCHAIN }} - run: | - cd fuzz - cargo test --verbose --color always --lib --bins -j8 - cargo clean - name: Run fuzzers run: cd fuzz && ./ci-fuzz.sh && cd .. - name: Upload honggfuzz corpus @@ -308,7 +318,7 @@ jobs: TOR_PROXY="127.0.0.1:9050" RUSTFLAGS="--cfg=tor" cargo test --verbose --color always -p lightning-net-tokio notify-failure: - needs: [build-workspace, build-features, build-bindings, build-nostd, build-cfg-flags, build-sync, fuzz, linting, rustfmt, check_release, check_docs, benchmark, ext-test, tor-connect, coverage] + needs: [build-workspace, build-features, build-bindings, build-nostd, build-cfg-flags, build-sync, fuzz_sanity, fuzz, linting, rustfmt, check_release, check_docs, benchmark, ext-test, tor-connect, coverage] if: failure() && github.ref == 'refs/heads/main' runs-on: ubuntu-latest permissions: From 24bbb4a632efbf031d53c07a0c6d2ca596589c69 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 20 Mar 2026 15:38:12 +0100 Subject: [PATCH 2/2] fuzz: improve iteration scaling, add minimization and summary table Replace the fixed 30s run_time with iteration counts scaled to 8x corpus size (plus a 1000 baseline) with a 10-minute hard cap per target. This ensures the full corpus is replayed with room for mutations, while small targets finish quickly. On main (and on PRs with the fuzz-minimize label), run honggfuzz corpus minimization after each target to prune inputs that don't contribute unique coverage, keeping the cache size manageable. Print a summary table at the end with per-target stats: iterations, corpus sizes before/after fuzzing and minimization, and run times. Other changes: - Use -q (quiet) to suppress per-iteration status output - Set 3s per-input timeout (-t 3) for all targets - Pass FUZZ_MINIMIZE env var from PR label in workflow - Check for crashes after minimization, not just after fuzzing AI tools were used in preparing this commit. --- .github/workflows/build.yml | 2 + fuzz/ci-fuzz.sh | 76 +++++++++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f50c9b8d231..b68d545ac3e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -255,6 +255,8 @@ jobs: fuzz-corpus-refs/heads/main- - name: Run fuzzers run: cd fuzz && ./ci-fuzz.sh && cd .. + env: + FUZZ_MINIMIZE: ${{ contains(github.event.pull_request.labels.*.name, 'fuzz-minimize') }} - name: Upload honggfuzz corpus uses: actions/upload-artifact@v4 with: diff --git a/fuzz/ci-fuzz.sh b/fuzz/ci-fuzz.sh index d57a5ad78fa..47bf41ba620 100755 --- a/fuzz/ci-fuzz.sh +++ b/fuzz/ci-fuzz.sh @@ -30,20 +30,82 @@ sed -i 's/lto = true//' Cargo.toml export HFUZZ_BUILD_ARGS="--features honggfuzz_fuzz" cargo --color always hfuzz build -j8 + +SUMMARY="" + +check_crash() { + local FILE=$1 + if [ -f "hfuzz_workspace/$FILE/HONGGFUZZ.REPORT.TXT" ]; then + cat "hfuzz_workspace/$FILE/HONGGFUZZ.REPORT.TXT" + for CASE in "hfuzz_workspace/$FILE"/SIG*; do + cat "$CASE" | xxd -p + done + exit 1 + fi +} + for TARGET in src/bin/*.rs; do FILENAME=$(basename $TARGET) FILE="${FILENAME%.*}" - HFUZZ_RUN_ARGS="--exit_upon_crash -v -n8 --run_time 30" + CORPUS_DIR="hfuzz_workspace/$FILE/input" + CORPUS_COUNT=$(find "$CORPUS_DIR" -type f 2>/dev/null | wc -l) + # Run 8x the corpus size plus a baseline, ensuring full corpus replay + # with room for new mutations. The 10-minute hard cap (--run_time 600) + # prevents slow-per-iteration targets from running too long. + ITERATIONS=$((CORPUS_COUNT * 8 + 1000)) + HFUZZ_RUN_ARGS="--exit_upon_crash -q -n8 -t 3 -N $ITERATIONS --run_time 600" if [ "$FILE" = "chanmon_consistency_target" -o "$FILE" = "fs_store_target" ]; then HFUZZ_RUN_ARGS="$HFUZZ_RUN_ARGS -F 64" fi export HFUZZ_RUN_ARGS + FUZZ_START=$(date +%s) cargo --color always hfuzz run $FILE - if [ -f hfuzz_workspace/$FILE/HONGGFUZZ.REPORT.TXT ]; then - cat hfuzz_workspace/$FILE/HONGGFUZZ.REPORT.TXT - for CASE in hfuzz_workspace/$FILE/SIG*; do - cat $CASE | xxd -p - done - exit 1 + FUZZ_END=$(date +%s) + FUZZ_TIME=$((FUZZ_END - FUZZ_START)) + FUZZ_CORPUS_COUNT=$(find "$CORPUS_DIR" -type f 2>/dev/null | wc -l) + check_crash "$FILE" + if [ "$GITHUB_REF" = "refs/heads/main" ] || [ "$FUZZ_MINIMIZE" = "true" ]; then + HFUZZ_RUN_ARGS="-M -q -n8 -t 3" + export HFUZZ_RUN_ARGS + MIN_START=$(date +%s) + cargo --color always hfuzz run $FILE + MIN_END=$(date +%s) + MIN_TIME=$((MIN_END - MIN_START)) + MIN_CORPUS_COUNT=$(find "$CORPUS_DIR" -type f 2>/dev/null | wc -l) + check_crash "$FILE" + SUMMARY="${SUMMARY}${FILE}|${ITERATIONS}|${CORPUS_COUNT}|${FUZZ_CORPUS_COUNT}|${FUZZ_TIME}|${MIN_CORPUS_COUNT}|${MIN_TIME}\n" + else + SUMMARY="${SUMMARY}${FILE}|${ITERATIONS}|${CORPUS_COUNT}|${FUZZ_CORPUS_COUNT}|${FUZZ_TIME}|-|-\n" + fi +done + +fmt_time() { + local secs=$1 + local m=$((secs / 60)) + local s=$((secs % 60)) + if [ "$m" -gt 0 ]; then + printf "%dm %ds" "$m" "$s" + else + printf "%ds" "$s" + fi +} + +# Print summary table +set +x +echo "" +echo "==== Fuzz Summary ====" +HDR="%-40s %7s %7s %-15s %9s %-15s %9s\n" +FMT="%-40s %7s %7s %6s %-9s %9s %6s %-9s %9s\n" +printf "$HDR" "Target" "Iters" "Corpus" " Fuzzed" "Fuzz time" " Minimized" "Min. time" +printf "$HDR" "------" "-----" "------" "---------------" "---------" "---------------" "---------" +echo -e "$SUMMARY" | while IFS='|' read -r name iters orig fuzzed ftime minimized mtime; do + [ -z "$name" ] && continue + fuzz_delta=$((fuzzed - orig)) + if [ "$minimized" = "-" ]; then + printf "$FMT" "$name" "$iters" "$orig" "$fuzzed" "(+$fuzz_delta)" "$(fmt_time "$ftime")" "-" "" "-" + else + min_delta=$((minimized - fuzzed)) + printf "$FMT" "$name" "$iters" "$orig" "$fuzzed" "(+$fuzz_delta)" "$(fmt_time "$ftime")" "$minimized" "($min_delta)" "$(fmt_time "$mtime")" fi done +echo "======================"