test(fuzz): record reproducible run seeds (#6547)

This commit is contained in:
Zhengchao An
2026-08-25 04:32:41 +08:00
committed by GitHub
parent 40e6decc93
commit 82df9ec4fa
5 changed files with 145 additions and 6 deletions
+1
View File
@@ -34,6 +34,7 @@ script-tests: ## Run shell script tests
./scripts/test_exact_1mib_handoff_abba.sh ./scripts/test_exact_1mib_handoff_abba.sh
./scripts/test_pinned_paired_abba_bench.sh ./scripts/test_pinned_paired_abba_bench.sh
./scripts/test_manual_transition_runbooks.sh ./scripts/test_manual_transition_runbooks.sh
./scripts/test_fuzz_runner.sh
./scripts/check_embedded_secrets.sh --self-test ./scripts/check_embedded_secrets.sh --self-test
python3 ./scripts/check_test_wiring.py --self-test python3 ./scripts/check_test_wiring.py --self-test
python3 ./scripts/check_security_coverage.py --self-test python3 ./scripts/check_security_coverage.py --self-test
+2 -2
View File
@@ -173,7 +173,7 @@ jobs:
path: | path: |
fuzz/artifacts/** fuzz/artifacts/**
fuzz/corpus/${{ matrix.target }}/** fuzz/corpus/${{ matrix.target }}/**
if-no-files-found: ignore if-no-files-found: error
retention-days: 7 retention-days: 7
# ────────────────────────────────────────────────────────────── # ──────────────────────────────────────────────────────────────
@@ -227,7 +227,7 @@ jobs:
path: | path: |
fuzz/artifacts/** fuzz/artifacts/**
fuzz/corpus/${{ matrix.target }}/** fuzz/corpus/${{ matrix.target }}/**
if-no-files-found: ignore if-no-files-found: error
retention-days: 30 retention-days: 30
# ────────────────────────────────────────────────────────────── # ──────────────────────────────────────────────────────────────
+8
View File
@@ -68,10 +68,18 @@ FUZZ_TARGET=path_containment ./scripts/fuzz/run.sh
# Nightly-style: 300s per target # Nightly-style: 300s per target
MAX_TOTAL_TIME=300 ./scripts/fuzz/run.sh MAX_TOTAL_TIME=300 ./scripts/fuzz/run.sh
# Replay a recorded libFuzzer seed
FUZZ_TARGET=path_containment FUZZ_SEED=123456789 ./scripts/fuzz/run.sh
# Skip build (use pre-built harness) # Skip build (use pre-built harness)
SKIP_BUILD=1 FUZZ_TARGET=local_metadata ./scripts/fuzz/run.sh SKIP_BUILD=1 FUZZ_TARGET=local_metadata ./scripts/fuzz/run.sh
``` ```
Each run writes `fuzz/artifacts/<target>/run-manifest.txt` with the target,
libFuzzer seed, time budget, Git revision and dirty state, and runner mode. CI
uploads that manifest with the corpus and any crash input so the exact run can
be replayed.
## CI Workflow ## CI Workflow
The GitHub Actions workflow (`.github/workflows/fuzz.yml`) uses a **build/run separation** pattern: The GitHub Actions workflow (`.github/workflows/fuzz.yml`) uses a **build/run separation** pattern:
+44 -4
View File
@@ -25,6 +25,7 @@
# Environment variables: # Environment variables:
# FUZZ_TARGET — run only this target (default: all smoke targets) # FUZZ_TARGET — run only this target (default: all smoke targets)
# MAX_TOTAL_TIME — seconds to fuzz per target (default: 60) # MAX_TOTAL_TIME — seconds to fuzz per target (default: 60)
# FUZZ_SEED — replay one libFuzzer seed (default: generate and record one per target)
# ARTIFACT_ROOT — artifact output directory (default: artifacts) # ARTIFACT_ROOT — artifact output directory (default: artifacts)
# BUILD_ONLY — set to 1 to skip fuzz runs (default: 0) # BUILD_ONLY — set to 1 to skip fuzz runs (default: 0)
# SKIP_BUILD — set to 1 to skip build phase (default: 0) # SKIP_BUILD — set to 1 to skip build phase (default: 0)
@@ -37,6 +38,7 @@ SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
REPO_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/../.." && pwd) REPO_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/../.." && pwd)
FUZZ_DIR="$REPO_ROOT/fuzz" FUZZ_DIR="$REPO_ROOT/fuzz"
MAX_TOTAL_TIME=${MAX_TOTAL_TIME:-60} MAX_TOTAL_TIME=${MAX_TOTAL_TIME:-60}
FUZZ_SEED=${FUZZ_SEED:-}
ARTIFACT_ROOT=${ARTIFACT_ROOT:-artifacts} ARTIFACT_ROOT=${ARTIFACT_ROOT:-artifacts}
FUZZ_TARGET=${FUZZ_TARGET:-} FUZZ_TARGET=${FUZZ_TARGET:-}
BUILD_ONLY=${BUILD_ONLY:-0} BUILD_ONLY=${BUILD_ONLY:-0}
@@ -44,6 +46,15 @@ SKIP_BUILD=${SKIP_BUILD:-0}
USE_PREBUILT_BINARY=${USE_PREBUILT_BINARY:-0} USE_PREBUILT_BINARY=${USE_PREBUILT_BINARY:-0}
PREBUILT_BINARY_DIR=${PREBUILT_BINARY_DIR:-} PREBUILT_BINARY_DIR=${PREBUILT_BINARY_DIR:-}
if [ -n "$FUZZ_SEED" ]; then
case "$FUZZ_SEED" in
0*|*[!0-9]*)
echo "FUZZ_SEED must be a positive decimal integer without leading zeroes: $FUZZ_SEED" >&2
exit 1
;;
esac
fi
cd "$FUZZ_DIR" cd "$FUZZ_DIR"
mkdir -p "$ARTIFACT_ROOT" mkdir -p "$ARTIFACT_ROOT"
@@ -75,6 +86,35 @@ for target in $targets; do
mkdir -p "$artifact_dir" mkdir -p "$artifact_dir"
mkdir -p "$corpus_dir" mkdir -p "$corpus_dir"
seed="$FUZZ_SEED"
if [ -z "$seed" ]; then
seed=$(printf '%s\n' "${GITHUB_RUN_ID:-local}:${GITHUB_RUN_ATTEMPT:-0}:$target:$(date +%s):$$" | cksum | awk '{print $1}')
if [ "$seed" = "0" ]; then
seed=1
fi
fi
revision=$(git -C "$REPO_ROOT" rev-parse HEAD 2>/dev/null || printf 'unknown')
if [ "$revision" = "unknown" ]; then
git_dirty="unknown"
elif [ -n "$(git -C "$REPO_ROOT" status --porcelain --untracked-files=normal 2>/dev/null)" ]; then
git_dirty="true"
else
git_dirty="false"
fi
if [ "$USE_PREBUILT_BINARY" = "1" ]; then
runner_mode="prebuilt"
else
runner_mode="cargo-fuzz"
fi
{
printf 'target=%s\n' "$target"
printf 'seed=%s\n' "$seed"
printf 'max_total_time=%s\n' "$MAX_TOTAL_TIME"
printf 'git_revision=%s\n' "$revision"
printf 'git_dirty=%s\n' "$git_dirty"
printf 'runner_mode=%s\n' "$runner_mode"
} > "$artifact_dir/run-manifest.txt"
if [ "$USE_PREBUILT_BINARY" = "1" ]; then if [ "$USE_PREBUILT_BINARY" = "1" ]; then
binary_dir="$PREBUILT_BINARY_DIR" binary_dir="$PREBUILT_BINARY_DIR"
if [ -z "$binary_dir" ]; then if [ -z "$binary_dir" ]; then
@@ -89,11 +129,11 @@ for target in $targets; do
echo "Missing executable prebuilt fuzz binary: $binary_path" >&2 echo "Missing executable prebuilt fuzz binary: $binary_path" >&2
exit 1 exit 1
fi fi
echo "==> $binary_path (-max_total_time=$MAX_TOTAL_TIME, -artifact_prefix=$artifact_dir/, corpus=$corpus_dir)" echo "==> $binary_path (-max_total_time=$MAX_TOTAL_TIME, -seed=$seed, -artifact_prefix=$artifact_dir/, corpus=$corpus_dir)"
"$binary_path" -max_total_time="$MAX_TOTAL_TIME" -artifact_prefix="$artifact_dir/" "$corpus_dir" "$binary_path" -max_total_time="$MAX_TOTAL_TIME" -seed="$seed" -artifact_prefix="$artifact_dir/" "$corpus_dir"
continue continue
fi fi
echo "==> cargo +nightly fuzz run $target (-max_total_time=$MAX_TOTAL_TIME, -artifact_prefix=$artifact_dir/)" echo "==> cargo +nightly fuzz run $target (-max_total_time=$MAX_TOTAL_TIME, -seed=$seed, -artifact_prefix=$artifact_dir/)"
cargo +nightly fuzz run "$target" -- -max_total_time="$MAX_TOTAL_TIME" -artifact_prefix="$artifact_dir/" cargo +nightly fuzz run "$target" -- -max_total_time="$MAX_TOTAL_TIME" -seed="$seed" -artifact_prefix="$artifact_dir/"
done done
+90
View File
@@ -0,0 +1,90 @@
#!/bin/sh
set -eu
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
REPO_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
TMP_ROOT=$(mktemp -d)
trap 'rm -rf "$TMP_ROOT"' EXIT HUP INT TERM
mkdir -p "$TMP_ROOT/bin"
cat > "$TMP_ROOT/bin/path_containment" <<'EOF'
#!/bin/sh
printf '%s\n' "$@" > "$FAKE_ARGS_FILE"
EOF
chmod +x "$TMP_ROOT/bin/path_containment"
EXPLICIT_ARTIFACTS="$TMP_ROOT/explicit-artifacts"
FAKE_ARGS_FILE="$TMP_ROOT/explicit-args" \
FUZZ_TARGET=path_containment \
FUZZ_SEED=123456789 \
MAX_TOTAL_TIME=7 \
ARTIFACT_ROOT="$EXPLICIT_ARTIFACTS" \
SKIP_BUILD=1 \
USE_PREBUILT_BINARY=1 \
PREBUILT_BINARY_DIR="$TMP_ROOT/bin" \
"$REPO_ROOT/scripts/fuzz/run.sh"
EXPLICIT_MANIFEST="$EXPLICIT_ARTIFACTS/path_containment/run-manifest.txt"
test -f "$EXPLICIT_MANIFEST"
grep -Fx -- '-seed=123456789' "$TMP_ROOT/explicit-args"
grep -Fx 'target=path_containment' "$EXPLICIT_MANIFEST"
grep -Fx 'seed=123456789' "$EXPLICIT_MANIFEST"
grep -Fx 'max_total_time=7' "$EXPLICIT_MANIFEST"
grep -Fx "git_revision=$(git -C "$REPO_ROOT" rev-parse HEAD)" "$EXPLICIT_MANIFEST"
grep -E '^git_dirty=(true|false)$' "$EXPLICIT_MANIFEST"
grep -Fx 'runner_mode=prebuilt' "$EXPLICIT_MANIFEST"
AUTO_ARTIFACTS="$TMP_ROOT/auto-artifacts"
FAKE_ARGS_FILE="$TMP_ROOT/auto-args" \
FUZZ_TARGET=path_containment \
MAX_TOTAL_TIME=1 \
ARTIFACT_ROOT="$AUTO_ARTIFACTS" \
SKIP_BUILD=1 \
USE_PREBUILT_BINARY=1 \
PREBUILT_BINARY_DIR="$TMP_ROOT/bin" \
"$REPO_ROOT/scripts/fuzz/run.sh"
AUTO_MANIFEST="$AUTO_ARTIFACTS/path_containment/run-manifest.txt"
auto_seed=$(sed -n 's/^seed=//p' "$AUTO_MANIFEST")
case "$auto_seed" in
''|*[!0-9]*)
echo "automatic seed was not recorded as an unsigned decimal integer: $auto_seed" >&2
exit 1
;;
esac
grep -Fx -- "-seed=$auto_seed" "$TMP_ROOT/auto-args"
cat > "$TMP_ROOT/bin/cargo" <<'EOF'
#!/bin/sh
printf '%s\n' "$@" > "$FAKE_CARGO_ARGS_FILE"
EOF
chmod +x "$TMP_ROOT/bin/cargo"
CARGO_ARTIFACTS="$TMP_ROOT/cargo-artifacts"
PATH="$TMP_ROOT/bin:$PATH" \
FAKE_CARGO_ARGS_FILE="$TMP_ROOT/cargo-args" \
FUZZ_TARGET=path_containment \
FUZZ_SEED=987654321 \
MAX_TOTAL_TIME=9 \
ARTIFACT_ROOT="$CARGO_ARTIFACTS" \
SKIP_BUILD=1 \
"$REPO_ROOT/scripts/fuzz/run.sh"
CARGO_MANIFEST="$CARGO_ARTIFACTS/path_containment/run-manifest.txt"
grep -Fx '+nightly' "$TMP_ROOT/cargo-args"
grep -Fx 'fuzz' "$TMP_ROOT/cargo-args"
grep -Fx 'run' "$TMP_ROOT/cargo-args"
grep -Fx 'path_containment' "$TMP_ROOT/cargo-args"
grep -Fx -- '-seed=987654321' "$TMP_ROOT/cargo-args"
grep -Fx 'seed=987654321' "$CARGO_MANIFEST"
grep -Fx 'runner_mode=cargo-fuzz' "$CARGO_MANIFEST"
for invalid_seed in 0 0123 not-a-number; do
if FUZZ_TARGET=path_containment FUZZ_SEED="$invalid_seed" SKIP_BUILD=1 "$REPO_ROOT/scripts/fuzz/run.sh" >/dev/null 2>&1; then
echo "invalid FUZZ_SEED was accepted: $invalid_seed" >&2
exit 1
fi
done
echo "fuzz runner seed manifest tests passed"