Skip to content

Commit 9d05999

Browse files
committed
refactor: Port fuzz crate to cargo-fuzz
Update and run fuzz/generate.sh. Update lockfiles. Rewrite each fuzz target to work with cargo-fuzz. Use base 64 to handle crash seeds. cargo-fuzz has special entry points for its fuzz tests, which is why every fuzz target is prefixed with #![no_main]. However, this breaks our unit tests that live inside the same file, presumably because unit tests need a normal entry point. My solution is conditionally include the fuzzing code when the `fuzzing` flag is set. Meanwhile, clippy needs a main function in each target to work, so I conditionally add that too. Allow the `fuzzing` flag in Cargo.toml because it is nonstandandard.
1 parent 2aa015f commit 9d05999

File tree

8 files changed

+136
-179
lines changed

8 files changed

+136
-179
lines changed

.github/workflows/fuzz.yml

+8-8
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ decode_program,
2323
parse_human,
2424
]
2525
steps:
26-
- name: Install test dependencies
27-
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
28-
2926
- name: Checkout Crate
3027
uses: actions/checkout@v4
3128

@@ -38,15 +35,18 @@ parse_human,
3835
fuzz/target
3936
target
4037
key: cache-${{ matrix.target }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
38+
4139
- name: Install Toolchain
42-
uses: dtolnay/rust-toolchain@stable
40+
uses: dtolnay/rust-toolchain@master
4341
with:
44-
toolchain: '1.65.0'
42+
toolchain: nightly-2024-07-01
43+
components: "llvm-tools-preview"
44+
45+
- name: Install Dependencies
46+
run: cargo update && cargo update -p cc --precise 1.0.83 && cargo install cargo-fuzz
4547

4648
- name: Run Fuzz Target
47-
run: |
48-
echo "Using RUSTFLAGS $RUSTFLAGS"
49-
cd fuzz && cargo update && cargo update -p cc --precise 1.0.83 && ./fuzz.sh "${{ matrix.fuzz_target }}"
49+
run: ./fuzz/fuzz.sh "${{ matrix.fuzz_target }}"
5050

5151
- name: Prepare Artifact
5252
run: echo "${{ matrix.fuzz_target }}" >executed_${{ matrix.fuzz_target }}

Cargo-recent.lock

+31-38
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ dependencies = [
1111
"memchr",
1212
]
1313

14+
[[package]]
15+
name = "arbitrary"
16+
version = "1.4.1"
17+
source = "registry+https://github.com/rust-lang/crates.io-index"
18+
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
19+
1420
[[package]]
1521
name = "arrayvec"
1622
version = "0.7.6"
@@ -33,6 +39,12 @@ version = "0.21.7"
3339
source = "registry+https://github.com/rust-lang/crates.io-index"
3440
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
3541

42+
[[package]]
43+
name = "base64"
44+
version = "0.22.1"
45+
source = "registry+https://github.com/rust-lang/crates.io-index"
46+
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
47+
3648
[[package]]
3749
name = "bech32"
3850
version = "0.11.0"
@@ -117,6 +129,8 @@ version = "1.2.14"
117129
source = "registry+https://github.com/rust-lang/crates.io-index"
118130
checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9"
119131
dependencies = [
132+
"jobserver",
133+
"libc",
120134
"shlex",
121135
]
122136

@@ -173,14 +187,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
173187
checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
174188

175189
[[package]]
176-
name = "honggfuzz"
177-
version = "0.5.56"
190+
name = "jobserver"
191+
version = "0.1.32"
178192
source = "registry+https://github.com/rust-lang/crates.io-index"
179-
checksum = "7c76b6234c13c9ea73946d1379d33186151148e0da231506b964b44f3d023505"
193+
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
180194
dependencies = [
181-
"lazy_static",
182-
"memmap2",
183-
"rustc_version",
195+
"libc",
184196
]
185197

186198
[[package]]
@@ -193,18 +205,22 @@ dependencies = [
193205
"wasm-bindgen",
194206
]
195207

196-
[[package]]
197-
name = "lazy_static"
198-
version = "1.5.0"
199-
source = "registry+https://github.com/rust-lang/crates.io-index"
200-
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
201-
202208
[[package]]
203209
name = "libc"
204210
version = "0.2.169"
205211
source = "registry+https://github.com/rust-lang/crates.io-index"
206212
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
207213

214+
[[package]]
215+
name = "libfuzzer-sys"
216+
version = "0.4.9"
217+
source = "registry+https://github.com/rust-lang/crates.io-index"
218+
checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75"
219+
dependencies = [
220+
"arbitrary",
221+
"cc",
222+
]
223+
208224
[[package]]
209225
name = "log"
210226
version = "0.4.25"
@@ -217,15 +233,6 @@ version = "2.7.4"
217233
source = "registry+https://github.com/rust-lang/crates.io-index"
218234
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
219235

220-
[[package]]
221-
name = "memmap2"
222-
version = "0.9.5"
223-
source = "registry+https://github.com/rust-lang/crates.io-index"
224-
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
225-
dependencies = [
226-
"libc",
227-
]
228-
229236
[[package]]
230237
name = "miniscript"
231238
version = "12.3.0"
@@ -328,15 +335,6 @@ version = "0.8.5"
328335
source = "registry+https://github.com/rust-lang/crates.io-index"
329336
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
330337

331-
[[package]]
332-
name = "rustc_version"
333-
version = "0.4.1"
334-
source = "registry+https://github.com/rust-lang/crates.io-index"
335-
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
336-
dependencies = [
337-
"semver",
338-
]
339-
340338
[[package]]
341339
name = "santiago"
342340
version = "1.3.1"
@@ -390,12 +388,6 @@ dependencies = [
390388
"secp256k1-sys",
391389
]
392390

393-
[[package]]
394-
name = "semver"
395-
version = "1.0.25"
396-
source = "registry+https://github.com/rust-lang/crates.io-index"
397-
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
398-
399391
[[package]]
400392
name = "serde"
401393
version = "1.0.217"
@@ -426,15 +418,16 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
426418
name = "simpcli"
427419
version = "0.3.0"
428420
dependencies = [
429-
"base64",
421+
"base64 0.21.7",
430422
"simplicity-lang",
431423
]
432424

433425
[[package]]
434426
name = "simplicity-fuzz"
435427
version = "0.0.1"
436428
dependencies = [
437-
"honggfuzz",
429+
"base64 0.22.1",
430+
"libfuzzer-sys",
438431
"simplicity-lang",
439432
]
440433

fuzz/Cargo.toml

+19-1
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,39 @@ publish = false
1010
cargo-fuzz = true
1111

1212
[dependencies]
13-
honggfuzz = { version = "0.5.55", default-features = false }
13+
libfuzzer-sys = "0.4"
1414
simplicity-lang = { path = "..", features = ["test-utils"] }
1515

16+
[dev-dependencies]
17+
base64 = "0.22.1"
18+
19+
[lints.rust]
20+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }
21+
1622
[[bin]]
1723
name = "c_rust_merkle"
1824
path = "fuzz_targets/c_rust_merkle.rs"
25+
test = false
26+
doc = false
27+
bench = false
1928

2029
[[bin]]
2130
name = "decode_natural"
2231
path = "fuzz_targets/decode_natural.rs"
32+
test = false
33+
doc = false
34+
bench = false
2335

2436
[[bin]]
2537
name = "decode_program"
2638
path = "fuzz_targets/decode_program.rs"
39+
test = false
40+
doc = false
41+
bench = false
2742

2843
[[bin]]
2944
name = "parse_human"
3045
path = "fuzz_targets/parse_human.rs"
46+
test = false
47+
doc = false
48+
bench = false

fuzz/fuzz_targets/c_rust_merkle.rs

+17-32
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
// SPDX-License-Identifier: CC0-1.0
22

3-
use honggfuzz::fuzz;
4-
5-
use simplicity::ffi::tests::{ffi::SimplicityErr, run_program, TestUpTo};
6-
use simplicity::hashes::sha256::Midstate;
7-
use simplicity::jet::Elements;
8-
use simplicity::{BitIter, RedeemNode};
3+
#![cfg_attr(fuzzing, no_main)]
94

5+
#[cfg(any(fuzzing, test))]
106
fn do_test(data: &[u8]) {
7+
use simplicity::ffi::tests::{ffi::SimplicityErr, run_program, TestUpTo};
8+
use simplicity::hashes::sha256::Midstate;
9+
use simplicity::jet::Elements;
10+
use simplicity::{BitIter, RedeemNode};
11+
1112
// To decode the program length, we first try decoding the program using
1213
// `decode_expression` which will not error on a length check. Alternately
1314
// we could decode using RedeemNode::decode and then extract the length
@@ -54,37 +55,21 @@ fn do_test(data: &[u8]) {
5455
}
5556
}
5657

57-
fn main() {
58-
loop {
59-
fuzz!(|data| {
60-
do_test(data);
61-
});
62-
}
63-
}
58+
#[cfg(fuzzing)]
59+
libfuzzer_sys::fuzz_target!(|data| do_test(data));
60+
61+
#[cfg(not(fuzzing))]
62+
fn main() {}
6463

6564
#[cfg(test)]
6665
mod tests {
67-
fn extend_vec_from_hex(hex: &str, out: &mut Vec<u8>) {
68-
let mut b = 0;
69-
for (idx, c) in hex.as_bytes().iter().enumerate() {
70-
b <<= 4;
71-
match *c {
72-
b'A'..=b'F' => b |= c - b'A' + 10,
73-
b'a'..=b'f' => b |= c - b'a' + 10,
74-
b'0'..=b'9' => b |= c - b'0',
75-
_ => panic!("Bad hex"),
76-
}
77-
if (idx & 1) == 1 {
78-
out.push(b);
79-
b = 0;
80-
}
81-
}
82-
}
66+
use base64::Engine;
8367

8468
#[test]
8569
fn duplicate_crash() {
86-
let mut a = Vec::new();
87-
extend_vec_from_hex("ffffff0000010080800000000000380000001adfc7040000000000000000000007fffffffffffffe1000000000000000000001555600000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff0300000000000000000000000000000000000000000000000000008000000000000000ffffffffffffff7f00000000000000ffffff151515111515155555555555d6eeffffff00", &mut a);
88-
super::do_test(&a);
70+
let data = base64::prelude::BASE64_STANDARD
71+
.decode("Cg==")
72+
.expect("base64 should be valid");
73+
super::do_test(&data);
8974
}
9075
}

fuzz/fuzz_targets/decode_natural.rs

+15-30
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
// SPDX-License-Identifier: CC0-1.0
22

3-
use honggfuzz::fuzz;
4-
5-
use simplicity::encode_natural;
6-
use simplicity::{BitIter, BitWriter};
3+
#![cfg_attr(fuzzing, no_main)]
74

5+
#[cfg(any(fuzzing, test))]
86
fn do_test(data: &[u8]) {
7+
use simplicity::encode_natural;
8+
use simplicity::{BitIter, BitWriter};
9+
910
let mut iter = BitIter::new(data.iter().cloned());
1011

1112
if let Ok(natural) = iter.read_natural(None) {
@@ -28,37 +29,21 @@ fn do_test(data: &[u8]) {
2829
}
2930
}
3031

31-
fn main() {
32-
loop {
33-
fuzz!(|data| {
34-
do_test(data);
35-
});
36-
}
37-
}
32+
#[cfg(fuzzing)]
33+
libfuzzer_sys::fuzz_target!(|data| do_test(data));
34+
35+
#[cfg(not(fuzzing))]
36+
fn main() {}
3837

3938
#[cfg(test)]
4039
mod tests {
41-
fn extend_vec_from_hex(hex: &str, out: &mut Vec<u8>) {
42-
let mut b = 0;
43-
for (idx, c) in hex.as_bytes().iter().enumerate() {
44-
b <<= 4;
45-
match *c {
46-
b'A'..=b'F' => b |= c - b'A' + 10,
47-
b'a'..=b'f' => b |= c - b'a' + 10,
48-
b'0'..=b'9' => b |= c - b'0',
49-
_ => panic!("Bad hex"),
50-
}
51-
if (idx & 1) == 1 {
52-
out.push(b);
53-
b = 0;
54-
}
55-
}
56-
}
40+
use base64::Engine;
5741

5842
#[test]
5943
fn duplicate_crash() {
60-
let mut a = Vec::new();
61-
extend_vec_from_hex("00000", &mut a);
62-
super::do_test(&a);
44+
let data = base64::prelude::BASE64_STANDARD
45+
.decode("Cg==")
46+
.expect("base64 should be valid");
47+
super::do_test(&data);
6348
}
6449
}

0 commit comments

Comments
 (0)