Skip to content

Commit be27637

Browse files
committed
Merge #547: More fuzztest cleanups
a6405f8 fuzz: replace README.md (Andrew Poelstra) 7b04860 fuzz: delete old ./travis-fuzz.sh (Andrew Poelstra) 8e8dcb4 fuzz: remove afl support, run clippy and fmt on fuzz targets (Andrew Poelstra) 14ceccd fuzz: remove existing CI; run fuzz/generate-files.sh to make new one (Andrew Poelstra) 6fea17f fuzz: copy *.sh files from rust-bitcoin; tweak generate-files.sh (Andrew Poelstra) Pull request description: Followup to #538. Copies the new fuzzing scripts from rust-bitcoin and cleans some things up. Confirmed that I can run all these fuzztests in my local nix-based CI. ACKs for top commit: sanket1729: utACK a6405f8 Tree-SHA512: f19fa7a62e244aaebf3ac0a0bcfe1686392eca3679d71edd8d6504888bf1a5004a7527fa46e8ee6296be4a27dfc20408a8ea258569f6337a73f41118d751a451
2 parents bf43cd3 + a6405f8 commit be27637

18 files changed

+425
-219
lines changed

.github/workflows/fuzz.yml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Automatically generated by fuzz/generate-files.sh
2+
name: Fuzz
3+
4+
on:
5+
push:
6+
branches:
7+
- master
8+
- 'test-ci/**'
9+
pull_request:
10+
11+
jobs:
12+
fuzz:
13+
if: ${{ !github.event.act }}
14+
runs-on: ubuntu-20.04
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
fuzz_target: [
19+
roundtrip_miniscript_str,
20+
roundtrip_miniscript_script,
21+
parse_descriptor,
22+
roundtrip_semantic,
23+
parse_descriptor_secret,
24+
roundtrip_descriptor,
25+
roundtrip_concrete,
26+
compile_descriptor,
27+
]
28+
steps:
29+
- name: Install test dependencies
30+
run: sudo apt-get update -y && sudo apt-get install -y binutils-dev libunwind8-dev libcurl4-openssl-dev libelf-dev libdw-dev cmake gcc libiberty-dev
31+
- uses: actions/checkout@v2
32+
- uses: actions/cache@v2
33+
id: cache-fuzz
34+
with:
35+
path: |
36+
~/.cargo/bin
37+
fuzz/target
38+
target
39+
key: cache-${{ matrix.target }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
40+
- uses: actions-rs/toolchain@v1
41+
with:
42+
toolchain: 1.58
43+
override: true
44+
profile: minimal
45+
- name: fuzz
46+
run: cd fuzz && ./fuzz.sh "${{ matrix.fuzz_target }}"
47+
- run: echo "${{ matrix.fuzz_target }}" >executed_${{ matrix.fuzz_target }}
48+
- uses: actions/upload-artifact@v2
49+
with:
50+
name: executed_${{ matrix.fuzz_target }}
51+
path: executed_${{ matrix.fuzz_target }}
52+
53+
verify-execution:
54+
if: ${{ !github.event.act }}
55+
needs: fuzz
56+
runs-on: ubuntu-latest
57+
steps:
58+
- uses: actions/checkout@v2
59+
- uses: actions/download-artifact@v2
60+
- name: Display structure of downloaded files
61+
run: ls -R
62+
- run: find executed_* -type f -exec cat {} + | sort > executed
63+
- run: source ./fuzz/fuzz-util.sh && listTargetNames | sort | diff - executed

.github/workflows/rust.yml

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,6 @@ on: [push, pull_request]
33
name: Continuous integration
44

55
jobs:
6-
Fuzz:
7-
name: Fuzz
8-
runs-on: ubuntu-latest
9-
steps:
10-
- name: Checkout Crate
11-
uses: actions/checkout@v2
12-
- name: Install hongfuzz dependencies
13-
run: sudo apt update && sudo apt install build-essential binutils-dev libunwind-dev libblocksruntime-dev liblzma-dev
14-
- name: Checkout Toolchain
15-
uses: actions-rs/toolchain@v1
16-
with:
17-
profile: minimal
18-
toolchain: 1.58.0
19-
override: true
20-
- name: Running fuzzer
21-
env:
22-
DO_FUZZ: true
23-
run: ./contrib/test.sh
24-
256
Nightly:
267
name: Nightly - Bench + Docs + Fmt
278
runs-on: ubuntu-latest

contrib/test.sh

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,6 @@ if cargo --version | grep "1\.47\.0"; then
2626
cargo update -p serde --precise 1.0.156
2727
fi
2828

29-
# Fuzz if told to
30-
if [ "$DO_FUZZ" = true ]
31-
then
32-
cd fuzz
33-
cargo test --verbose
34-
./travis-fuzz.sh
35-
36-
# Exit out of the fuzzer, do not run other tests.
37-
exit 0
38-
fi
39-
4029
# Test bitcoind integration tests if told to (this only works with the stable toolchain)
4130
if [ "$DO_BITCOIND_TESTS" = true ]; then
4231
cd bitcoind-tests

fuzz/Cargo.toml

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,47 @@
11
[package]
22
name = "descriptor-fuzz"
3+
edition = "2018"
34
version = "0.0.1"
4-
authors = ["Automatically generated"]
5+
authors = ["Generated by fuzz/generate-files.sh"]
56
publish = false
67

78
[package.metadata]
89
cargo-fuzz = true
910

10-
[features]
11-
afl_fuzz = ["afl"]
12-
honggfuzz_fuzz = ["honggfuzz"]
13-
1411
[dependencies]
15-
honggfuzz = { version = "0.5.55", default-features = false, optional = true }
16-
afl = { version = "0.8", optional = true }
17-
regex = { version = "1.4"}
18-
miniscript = { path = "..", features = ["compiler"] }
12+
honggfuzz = { version = "0.5.55", default-features = false }
13+
miniscript = { path = "..", features = [ "compiler" ] }
14+
15+
regex = "1.4"
1916

2017
[[bin]]
21-
name = "roundtrip_descriptor"
22-
path = "fuzz_targets/roundtrip_descriptor.rs"
18+
name = "roundtrip_miniscript_str"
19+
path = "fuzz_targets/roundtrip_miniscript_str.rs"
2320

2421
[[bin]]
2522
name = "roundtrip_miniscript_script"
2623
path = "fuzz_targets/roundtrip_miniscript_script.rs"
2724

2825
[[bin]]
29-
name = "roundtrip_miniscript_str"
30-
path = "fuzz_targets/roundtrip_miniscript_str.rs"
31-
32-
[[bin]]
33-
name = "roundtrip_concrete"
34-
path = "fuzz_targets/roundtrip_concrete.rs"
26+
name = "parse_descriptor"
27+
path = "fuzz_targets/parse_descriptor.rs"
3528

3629
[[bin]]
3730
name = "roundtrip_semantic"
3831
path = "fuzz_targets/roundtrip_semantic.rs"
3932

4033
[[bin]]
41-
name = "compile_descriptor"
42-
path = "fuzz_targets/compile_descriptor.rs"
34+
name = "parse_descriptor_secret"
35+
path = "fuzz_targets/parse_descriptor_secret.rs"
4336

4437
[[bin]]
45-
name = "parse_descriptor"
46-
path = "fuzz_targets/parse_descriptor.rs"
38+
name = "roundtrip_descriptor"
39+
path = "fuzz_targets/roundtrip_descriptor.rs"
4740

4841
[[bin]]
49-
name = "parse_descriptor_secret"
50-
path = "fuzz_targets/parse_descriptor_secret.rs"
42+
name = "roundtrip_concrete"
43+
path = "fuzz_targets/roundtrip_concrete.rs"
44+
45+
[[bin]]
46+
name = "compile_descriptor"
47+
path = "fuzz_targets/compile_descriptor.rs"

fuzz/README.md

Lines changed: 113 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,119 @@
1-
# Fuzz Tests
1+
# Fuzzing
22

3-
Repository for fuzz testing Miniscript.
3+
`miniscript` has a fuzzing harness setup for use with honggfuzz.
44

5-
## How to reproduce crashes?
5+
To run the fuzz-tests as in CI -- briefly fuzzing every target -- simply
6+
run
67

7-
Travis should output a offending hex("048531e80700ae6400670000af5168" in the example)
8-
which you can use as shown. Copy and paste the following code lines into file reporting crashes and
9-
replace the hex with the offending hex.
10-
Refer to file [roundtrip_concrete.rs](./fuzz_targets/roundtrip_concrete.rs) for an example.
8+
./fuzz.sh
9+
10+
in this directory.
11+
12+
To build honggfuzz, you must have libunwind on your system, as well as
13+
libopcodes and libbfd from binutils **2.38** on your system. The most
14+
recently-released binutils 2.39 has changed their API in a breaking way.
15+
16+
On Nix, you can obtain these libraries by running
17+
18+
nix-shell -p libopcodes_2_38 -p libunwind
19+
20+
and then run fuzz.sh as above.
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+
RUSTFLAGS="--cfg=hashes_fuzz --cfg=secp256k1_fuzz"
38+
39+
which will replace the hashing library with broken hashes, and the
40+
secp256k1 library with broken cryptography.
41+
42+
Needless to say, NEVER COMPILE REAL CODE WITH THESE FLAGS because if a
43+
fuzzer can break your crypto, so can anybody.
44+
45+
# Long-term fuzzing
46+
47+
To see the full list of targets, the most straightforward way is to run
48+
49+
source ./fuzz-util.sh
50+
listTargetNames
51+
52+
To run each of them for an hour, run
53+
54+
./cycle.sh
55+
56+
To run a single fuzztest indefinitely, run
57+
58+
cargo hfuzz run <target>
59+
60+
`cycle.sh` 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+
# Adding fuzz tests
66+
67+
All fuzz tests can be found in the `fuzz_target/` directory. Adding a new
68+
one is as simple as copying an existing one and editing the `do_test`
69+
function to do what you want.
70+
71+
If your test clearly belongs to a specific crate, please put it in that
72+
crate's directory. Otherwise you can put it directly in `fuzz_target/`.
73+
74+
If you need to add dependencies, edit the file `generate-files.sh` to add
75+
it to the generated `Cargo.toml`.
76+
77+
Once you've added a fuzztest, regenerate the `Cargo.toml` and CI job by
78+
running
79+
80+
./generate-files.sh
81+
82+
Then to test your fuzztest, run
83+
84+
./fuzz.sh <target>
85+
86+
If it is working, you will see a rapid stream of data for many seconds
87+
(you can hit Ctrl+C to stop it early). If not, you should quickly see
88+
an error.
89+
90+
# Reproducing Failures
91+
92+
If a fuzztest fails, it will exit with a summary which looks something like
93+
94+
```
95+
...
96+
fuzzTarget : hfuzz_target/x86_64-unknown-linux-gnu/release/hashes_sha256
97+
CRASH:
98+
DESCRIPTION:
99+
ORIG_FNAME: 00000000000000000000000000000000.00000000.honggfuzz.cov
100+
FUZZ_FNAME: hfuzz_workspace/hashes_sha256/SIGABRT.PC.7ffff7c8abc7.STACK.18826d9b64.CODE.-6.ADDR.0.INSTR.mov____%eax,%ebp.fuzz
101+
...
102+
=====================================================================
103+
fff400610004
104+
```
105+
106+
The final line is a hex-encoded version of the input that caused the crash. You
107+
can test this directly by editing the `duplicate_crash` test to copy/paste the
108+
hex output into the call to `extend_vec_from_hex`. Then run the test with
109+
110+
cargo test
111+
112+
Note that if you set your `RUSTFLAGS` while fuzzing (see above) you must make
113+
sure they are set the same way when running `cargo test`.
114+
115+
If the `duplicate_crash` function is not present, please add it. A template is
116+
as follows:
11117

12118
```
13119
#[cfg(test)]

fuzz/cycle.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env bash
2+
3+
# Continuosly cycle over fuzz targets running each for 1 hour.
4+
# It uses chrt SCHED_IDLE so that other process takes priority.
5+
#
6+
# For hfuzz options see https://github.com/google/honggfuzz/blob/master/docs/USAGE.md
7+
8+
set -e
9+
REPO_DIR=$(git rev-parse --show-toplevel)
10+
# shellcheck source=./fuzz-util.sh
11+
source "$REPO_DIR/fuzz/fuzz-util.sh"
12+
13+
while :
14+
do
15+
for targetFile in $(listTargetFiles); do
16+
targetName=$(targetFileToName "$targetFile")
17+
echo "Fuzzing target $targetName ($targetFile)"
18+
19+
# fuzz for one hour
20+
HFUZZ_RUN_ARGS='--run_time 3600' chrt -i 0 cargo hfuzz run "$targetName"
21+
# minimize the corpus
22+
HFUZZ_RUN_ARGS="-i hfuzz_workspace/$targetName/input/ -P -M" chrt -i 0 cargo hfuzz run "$targetName"
23+
done
24+
done
25+

fuzz/fuzz-util.sh

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/env bash
2+
3+
REPO_DIR=$(git rev-parse --show-toplevel)
4+
5+
listTargetFiles() {
6+
pushd "$REPO_DIR/fuzz" > /dev/null || exit 1
7+
find fuzz_targets/ -type f -name "*.rs"
8+
popd > /dev/null || exit 1
9+
}
10+
11+
targetFileToName() {
12+
echo "$1" \
13+
| sed 's/^fuzz_targets\///' \
14+
| sed 's/\.rs$//' \
15+
| sed 's/\//_/g'
16+
}
17+
18+
targetFileToHFuzzInputArg() {
19+
baseName=$(basename "$1")
20+
dirName="${baseName%.*}"
21+
if [ -d "hfuzz_input/$dirName" ]; then
22+
echo "HFUZZ_INPUT_ARGS=\"-f hfuzz_input/$FILE/input\""
23+
fi
24+
}
25+
26+
listTargetNames() {
27+
for target in $(listTargetFiles); do
28+
targetFileToName "$target"
29+
done
30+
}
31+
32+
# Utility function to avoid CI failures on Windows
33+
checkWindowsFiles() {
34+
incorrectFilenames=$(find . -type f -name "*,*" -o -name "*:*" -o -name "*<*" -o -name "*>*" -o -name "*|*" -o -name "*\?*" -o -name "*\**" -o -name "*\"*" | wc -l)
35+
if [ "$incorrectFilenames" -gt 0 ]; then
36+
echo "Bailing early because there is a Windows-incompatible filename in the tree."
37+
exit 2
38+
fi
39+
}
40+
41+
# Checks whether a fuzz case output some report, and dumps it in hex
42+
checkReport() {
43+
reportFile="hfuzz_workspace/$1/HONGGFUZZ.REPORT.TXT"
44+
if [ -f "$reportFile" ]; then
45+
cat "$reportFile"
46+
for CASE in "hfuzz_workspace/$1/SIG"*; do
47+
xxd -p -c10000 < "$CASE"
48+
done
49+
exit 1
50+
fi
51+
}

0 commit comments

Comments
 (0)