1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
name: fuzz
# Coverage-guided fuzzing (TODO.complete/36): runs every target in
# fuzz/fuzz_targets/ for a bounded wall-clock budget weekly. The
# proptest suite samples well-formed inputs; libFuzzer explores the
# adversarial shapes samplers never emit — unterminated directives,
# malformed PHC strings, pathological separator pairs, 0xFF-split
# merge inputs.
#
# Seeds (fuzz/seeds/<target>/) and dictionaries (fuzz/dicts/) are
# committed; grown corpora (fuzz/corpus/) are cached across runs so
# each week's budget extends the last exploration instead of
# restarting from the seeds. A crash fails the job and the
# minimizing input is uploaded as an artifact — reproduce locally:
# cargo fuzz run <target> fuzz/artifacts/<target>/<file>
#
# One job builds all six targets once (the sanitizer build of the
# C stack dominates; a per-target matrix would rebuild it 6x) and
# loops the targets sequentially.
on:
schedule:
- cron: '0 6 * * 3' # Wednesdays 06:00 UTC
workflow_dispatch:
inputs:
seconds_per_target:
description: 'libFuzzer -max_total_time per target (seconds)'
default: '300'
permissions:
contents: read
concurrency:
group: fuzz
cancel-in-progress: true
env:
RUST_BACKTRACE: full
BOTAN_VERSION: 3.7.0
PREFIX: /usr
CARGO_FUZZ_VERSION: 0.13.2
# rust-toolchain.toml pins `stable` for the repo and rustup override
# files outrank the action's default — cargo-fuzz's -Zsanitizer
# flags need nightly, so force it via the env var that outranks
# the override file.
RUSTUP_TOOLCHAIN: nightly
jobs:
fuzz:
name: libFuzzer all targets
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@v7
- name: Install rust nightly
uses: dtolnay/rust-toolchain@nightly
- name: Cache cargo-fuzz
uses: actions/cache@v4
with:
path: ~/.cargo-fuzz
key: cargo-fuzz-${{ env.CARGO_FUZZ_VERSION }}-ubuntu
- name: Install cargo-fuzz
run: |
if [ ! -x "$HOME/.cargo-fuzz/bin/cargo-fuzz" ]; then
cargo install cargo-fuzz --version "$CARGO_FUZZ_VERSION" --locked --root "$HOME/.cargo-fuzz"
fi
echo "$HOME/.cargo-fuzz/bin" >> "$GITHUB_PATH"
- name: Install Botan
run: ./ci/install.sh
- name: Restore fuzz corpora
uses: actions/cache@v4
with:
path: fuzz/corpus
key: fuzz-corpus-${{ github.run_id }}
restore-keys: |
fuzz-corpus-
- name: Build all targets
run: cargo fuzz build --features vendored-rnp -O
- name: Fuzz each target
run: |
set -u
fails=0
for t in parse_no_panic conflict_well_formed parse_extfields \
merge_driver cas_hash_validation separator_round_trip; do
mkdir -p "fuzz/corpus/$t" "fuzz/seeds/$t"
dict=()
if [ -f "fuzz/dicts/$t.dict" ]; then
dict=(-dict="fuzz/dicts/$t.dict")
fi
echo "::group::$t"
set +e
cargo fuzz run --features vendored-rnp -O "$t" \
"fuzz/corpus/$t" "fuzz/seeds/$t" -- \
-max_total_time="${{ inputs.seconds_per_target || 300 }}" \
-timeout=25 -rss_limit_mb=4096 -max_len=16384 "${dict[@]}"
rc=$?
set -e
echo "::endgroup::"
if [ "$rc" -ne 0 ]; then
echo "::error::$t exited $rc — crash artifact in fuzz/artifacts/$t"
fails=1
fi
done
exit "$fails"
- name: Upload crash artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: fuzz-crashes
path: fuzz/artifacts/
if-no-files-found: ignore