Skip to content

Commit 53efbb2

Browse files
committed
fuzz: copy fuzz.sh, cycle.sh and README.md from rust-bitcoin
The README has a few references to rust-bitcoin. I left these alone because all the content of the doc should be the same between the two repos, and it is probably a useful hint to somebody reading the README that the fuzz infrastructure was copied from rust-bitcoin.
1 parent 9b950fd commit 53efbb2

3 files changed

Lines changed: 249 additions & 0 deletions

File tree

fuzz/README.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# Fuzzing
2+
3+
`rust-bitcoin` has fuzzing harnesses setup for use with
4+
`cargo-fuzz`.
5+
6+
To run the fuzz-tests as in CI -- briefly fuzzing every target -- simply
7+
run
8+
9+
```bash
10+
./fuzz.sh
11+
```
12+
13+
in this directory.
14+
15+
By default, `fuzz.sh` runs each target for 100 seconds. Pass
16+
`-max_total_time` to run for longer or shorter:
17+
18+
```bash
19+
./fuzz.sh -max_total_time=300
20+
```
21+
22+
## Fuzzing with weak cryptography
23+
24+
You may wish to replace the hashing and signing code with broken crypto,
25+
which will be faster and enable the fuzzer to do otherwise impossible
26+
things such as forging signatures or finding preimages to hashes.
27+
28+
Doing so may result in spurious bug reports since the broken crypto does
29+
not respect the encoding or algebraic invariants upheld by the real crypto. We
30+
would like to improve this, but it's a nontrivial problem -- though not
31+
beyond the abilities of a motivated student with a few months of time.
32+
Please let us know if you are interested in taking this on!
33+
34+
Meanwhile, to use the broken crypto, simply compile (and run the fuzzing
35+
scripts) with
36+
37+
```bash
38+
RUSTFLAGS="--cfg=hashes_fuzz --cfg=secp256k1_fuzz"
39+
```
40+
41+
which will replace the hashing library with broken hashes, and the
42+
`secp256k1` library with broken cryptography.
43+
44+
Needless to say, NEVER COMPILE REAL CODE WITH THESE FLAGS because if a
45+
fuzzer can break your crypto, so can anybody.
46+
47+
## Long-term fuzzing
48+
49+
To see the full list of targets, the most straightforward way is to run
50+
51+
```bash
52+
cargo fuzz list
53+
```
54+
55+
To run each of them for an hour, run
56+
57+
```bash
58+
./cycle.sh
59+
```
60+
This script uses the `chrt` utility to try to reduce the priority of the
61+
jobs. If you would like to run for longer, the most straightforward way
62+
is to edit `cycle.sh` before starting. To run the fuzz-tests in parallel,
63+
you will need to implement a custom harness.
64+
65+
To run a single fuzztest indefinitely, run
66+
67+
```bash
68+
cargo +nightly fuzz run "<target>"
69+
```
70+
71+
## Adding fuzz tests
72+
73+
All fuzz tests can be found in the `fuzz_target/` directory. Adding a new
74+
one is as simple as copying an existing one and editing the `do_test`
75+
function to do what you want.
76+
77+
If your test clearly belongs to a specific crate, please put it in that
78+
crate's directory. Otherwise, you can put it directly in `fuzz_target/`.
79+
80+
If you need to add dependencies, edit the file `generate-files.sh` to add
81+
it to the generated `Cargo.toml`.
82+
83+
Once you've added a fuzztest, regenerate the `Cargo.toml` and CI job by
84+
running
85+
86+
```bash
87+
./generate-files.sh
88+
```
89+
90+
Then to test your fuzztest, run
91+
92+
```bash
93+
./fuzz.sh <target>
94+
```
95+
96+
If it is working, you will see a rapid stream of data for many seconds
97+
(you can hit Ctrl+C to stop it early) that looks something like this:
98+
```text
99+
INFO: Running with entropic power schedule (0xFF, 100).
100+
INFO: Seed: 2953319389
101+
INFO: Loaded 1 modules (9121 inline 8-bit counters): 9121 [0x104132ea0, 0x104135241),
102+
INFO: Loaded 1 PC tables (9121 PCs): 9121 [0x104135248,0x104158c58),
103+
INFO: 0 files found in /some/path/to/rust-bitcoin/fuzz/corpus/units_arbitrary_weight
104+
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
105+
INFO: A corpus is not provided, starting from an empty corpus
106+
#2 INITED cov: 42 ft: 42 corp: 1/1b exec/s: 0 rss: 36Mb
107+
#411 NEW cov: 43 ft: 43 corp: 2/9b lim: 8 exec/s: 0 rss: 37Mb L: 8/8 MS: 4 ChangeBinInt-ShuffleBytes-ShuffleBytes-InsertRepeatedBytes-
108+
#1329 NEW cov: 43 ft: 44 corp: 3/26b lim: 17 exec/s: 0 rss: 37Mb L: 17/17 MS: 3 InsertRepeatedBytes-CMP-CopyPart- DE: "\001\000\000\000"-
109+
#1357 REDUCE cov: 43 ft: 44 corp: 3/25b lim: 17 exec/s: 0 rss: 37Mb L: 16/16 MS: 3 CopyPart-CMP-EraseBytes- DE: "\000\000\000\000\000\000\000\000"-
110+
...
111+
```
112+
If you don't see this, you should quickly see an error.
113+
114+
## Reproducing Failures
115+
116+
If a fuzztest fails, it will exit with a summary which looks something like
117+
```text
118+
...
119+
thread '<unnamed>' (3001874) panicked at units/src/weight.rs:103:25:
120+
attempt to multiply with overflow
121+
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
122+
==66478== ERROR: libFuzzer: deadly signal
123+
#0 0x0001049fd3c4 in __sanitizer_print_stack_trace+0x28 (librustc-nightly_rt.asan.dylib:arm64+0x5d3c4)
124+
#1 0x000104078b90 in fuzzer::PrintStackTrace()+0x30 (units_arbitrary_weight:arm64+0x100070b90)
125+
#2 0x00010406d074 in fuzzer::Fuzzer::CrashCallback()+0x54 (units_arbitrary_weight:arm64+0x100065074)
126+
#3 0x000180d26740 in _sigtramp+0x34 (libsystem_platform.dylib:arm64+0x3740)
127+
...
128+
```
129+
This will tell you where the test failed and is followed by information about how to reproduce the crash.
130+
It will look something like this:
131+
132+
```text
133+
...
134+
NOTE: libFuzzer has rudimentary signal handlers.
135+
Combine libFuzzer with AddressSanitizer or similar for better crash reports.
136+
SUMMARY: libFuzzer: deadly signal
137+
MS: 2 ChangeByte-CopyPart-; base unit: 25058c6b0d02cd1d71a030ad61c46b7396ddcdb9
138+
0x5e,0x5e,0x5e,0x5e,0x5e,0x44,0x0,0x0,0x0,0x0,0x0,0x5d,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x5e,0xa,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0xa5,0x1,0x1,0x1,
139+
^^^^^D\000\000\000\000\000]\001\000\000\000\000\000\000^\012\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\245\001\001\001
140+
artifact_prefix='/some/path/to/rust-bitcoin/fuzz/artifacts/units_arbitrary_weight/'; Test unit written to /some/path/to/rust-bitcoin/fuzz/artifacts/units_arbitrary_weight/crash-1b454523d38a6c3f45d453dfea4099f3cb574822
141+
Base64: Xl5eXl5EAAAAAABdAQAAAAAAAF4KAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBpQEBAQ==
142+
────────────────────────────────────────────────────────────────────────────────
143+
144+
Failing input:
145+
146+
fuzz/artifacts/units_arbitrary_weight/crash-1b454523d38a6c3f45d453dfea4099f3cb574822
147+
148+
Output of `std::fmt::Debug`:
149+
150+
[94, 94, 94, 94, 94, 68, 0, 0, 0, 0, 0, 93, 1, 0, 0, 0, 0, 0, 0, 94, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 165, 1, 1, 1]
151+
152+
Reproduce with:
153+
154+
cargo fuzz run units_arbitrary_weight fuzz/artifacts/units_arbitrary_weight/crash-1b454523d38a6c3f45d453dfea4099f3cb574822
155+
156+
Minimize test case with:
157+
158+
cargo fuzz tmin units_arbitrary_weight fuzz/artifacts/units_arbitrary_weight/crash-1b454523d38a6c3f45d453dfea4099f3cb574822
159+
160+
────────────────────────────────────────────────────────────────────────────────
161+
```

fuzz/cycle.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env bash
2+
3+
# Continuously cycle over fuzz targets running each for 1 hour.
4+
# It uses chrt SCHED_IDLE so that other process takes priority.
5+
#
6+
# For cargo-fuzz usage see https://github.com/rust-fuzz/cargo-fuzz?tab=readme-ov-file#usage
7+
8+
set -euo pipefail
9+
10+
REPO_DIR=$(git rev-parse --show-toplevel)
11+
# can't find the file because of the ENV var
12+
# shellcheck source=/dev/null
13+
source "$REPO_DIR/fuzz/fuzz-util.sh"
14+
15+
while :
16+
do
17+
for targetFile in $(listTargetFiles); do
18+
targetName=$(targetFileToName "$targetFile")
19+
echo "Fuzzing target $targetName ($targetFile)"
20+
21+
# fuzz for one hour
22+
chrt -i 0 cargo +nightly fuzz run "$targetName" -- -max_total_time=3600
23+
cargo +nightly fuzz cmin "$targetName"
24+
done
25+
done
26+

fuzz/fuzz.sh

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env bash
2+
# This script is used to briefly fuzz every target when no target is provided. Otherwise, it will briefly fuzz the
3+
# provided target
4+
5+
set -euox pipefail
6+
7+
REPO_DIR=$(git rev-parse --show-toplevel)
8+
9+
# can't find the file because of the ENV var
10+
# shellcheck source=/dev/null
11+
source "$REPO_DIR/fuzz/fuzz-util.sh"
12+
13+
target=
14+
max_total_time=100
15+
16+
for arg in "$@"; do
17+
case "$arg" in
18+
-max_total_time=*)
19+
max_total_time="${arg#-max_total_time=}"
20+
;;
21+
-*)
22+
echo "Unknown option: $arg"
23+
exit 2
24+
;;
25+
*)
26+
if [ -n "$target" ]; then
27+
echo "Unexpected argument: $arg"
28+
exit 2
29+
fi
30+
target="$arg"
31+
;;
32+
esac
33+
done
34+
35+
case "$max_total_time" in
36+
''|*[!0-9]*)
37+
echo "-max_total_time must be a non-negative integer number of seconds"
38+
exit 2
39+
;;
40+
esac
41+
42+
# Check that input files are correct Windows file names
43+
checkWindowsFiles
44+
45+
if [ -z "$target" ]; then
46+
targetFiles="$(listTargetFiles)"
47+
else
48+
targetFiles=fuzz_targets/"$target".rs
49+
fi
50+
51+
cargo --version
52+
rustc --version
53+
54+
# Testing
55+
cargo install --force --locked --version 0.12.0 cargo-fuzz
56+
for targetFile in $targetFiles; do
57+
targetName=$(targetFileToName "$targetFile")
58+
echo "Fuzzing target $targetName ($targetFile) for $max_total_time seconds"
59+
# cargo-fuzz will check for the corpus at fuzz/corpus/<target>
60+
cargo +nightly fuzz run "$targetName" -- -max_total_time="$max_total_time"
61+
checkReport "$targetName"
62+
done

0 commit comments

Comments
 (0)