Skip to content

Commit 4c1e945

Browse files
committed
Add fuzz infra and fuzz a bit in CI
1 parent 13473b3 commit 4c1e945

File tree

10 files changed

+403
-0
lines changed

10 files changed

+403
-0
lines changed

.github/workflows/build.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,26 @@ jobs:
7676
rustup component add rustfmt
7777
- name: Run rustfmt checks
7878
run: ci/rustfmt.sh
79+
80+
fuzz:
81+
runs-on: ubuntu-latest
82+
env:
83+
TOOLCHAIN: 1.63
84+
steps:
85+
- name: Checkout source code
86+
uses: actions/checkout@v4
87+
- name: Install Rust ${{ env.TOOLCHAIN }} toolchain
88+
run: |
89+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ env.TOOLCHAIN }}
90+
rustup override set ${{ env.TOOLCHAIN }}
91+
- name: Install dependencies for honggfuzz
92+
run: |
93+
sudo apt-get update
94+
sudo apt-get -y install build-essential binutils-dev libunwind-dev
95+
- name: Sanity check fuzz targets on Rust ${{ env.TOOLCHAIN }}
96+
run: |
97+
cd fuzz
98+
RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" cargo test --verbose --color always
99+
cargo clean
100+
- name: Run fuzzers
101+
run: cd fuzz && ./ci-fuzz.sh && cd ..

fuzz/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
hfuzz_target
2+
target
3+
hfuzz_workspace

fuzz/Cargo.toml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
[package]
2+
name = "fuzz"
3+
version = "0.0.1"
4+
authors = ["Automatically generated"]
5+
publish = false
6+
edition = "2021"
7+
8+
[package.metadata]
9+
cargo-fuzz = true
10+
11+
[features]
12+
afl_fuzz = ["afl"]
13+
honggfuzz_fuzz = ["honggfuzz"]
14+
libfuzzer_fuzz = ["libfuzzer-sys"]
15+
stdin_fuzz = []
16+
17+
[dependencies]
18+
bitcoin = { version = "0.32", default-features = false, features = ["std"] }
19+
bitcoin-payment-instructions = { path = "../", default-features = false }
20+
21+
afl = { version = "0.12", optional = true }
22+
honggfuzz = { version = "0.5", optional = true, default-features = false }
23+
libfuzzer-sys = { version = "0.4", optional = true }
24+
25+
[build-dependencies]
26+
cc = "1.0"
27+
28+
[profile.release]
29+
lto = true
30+
codegen-units = 1
31+
debug-assertions = true
32+
overflow-checks = true
33+
34+
# When testing a large fuzz corpus, -O1 offers a nice speedup
35+
[profile.dev]
36+
opt-level = 1
37+
38+
[lib]
39+
name = "fuzz"
40+
path = "src/lib.rs"
41+
crate-type = ["rlib", "dylib", "staticlib"]

fuzz/ci-fuzz.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/bash
2+
set -e
3+
set -x
4+
5+
pushd src/bin
6+
rm *_target.rs
7+
./gen_target.sh
8+
[ "$(git diff)" != "" ] && exit 1
9+
popd
10+
11+
cargo install --color always --force honggfuzz --no-default-features
12+
sed -i 's/lto = true//' Cargo.toml
13+
14+
export RUSTFLAGS="--cfg=secp256k1_fuzz --cfg=hashes_fuzz"
15+
export HFUZZ_BUILD_ARGS="--features honggfuzz_fuzz"
16+
17+
cargo --color always hfuzz build
18+
for TARGET in src/bin/*.rs; do
19+
FILENAME=$(basename $TARGET)
20+
FILE="${FILENAME%.*}"
21+
HFUZZ_RUN_ARGS="--exit_upon_crash -v -n2 -N10000000"
22+
export HFUZZ_RUN_ARGS
23+
cargo --color always hfuzz run $FILE
24+
if [ -f hfuzz_workspace/$FILE/HONGGFUZZ.REPORT.TXT ]; then
25+
cat hfuzz_workspace/$FILE/HONGGFUZZ.REPORT.TXT
26+
for CASE in hfuzz_workspace/$FILE/SIG*; do
27+
cat $CASE | xxd -p
28+
done
29+
exit 1
30+
fi
31+
done

fuzz/src/bin/gen_target.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/sh
2+
3+
echo "#include <stdint.h>" > ../../targets.h
4+
GEN_TEST() {
5+
cat target_template.txt | sed s/TARGET_NAME/$1/ | sed s/TARGET_MOD/$2$1/ > $1_target.rs
6+
echo "void $1_run(const unsigned char* data, size_t data_len);" >> ../../targets.h
7+
}
8+
9+
GEN_TEST parse

fuzz/src/bin/parse_target.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
// This file is auto-generated by gen_target.sh based on target_template.txt
11+
// To modify it, modify target_template.txt and run gen_target.sh instead.
12+
13+
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
14+
#![cfg_attr(rustfmt, rustfmt_skip)]
15+
16+
#[cfg(not(fuzzing))]
17+
compile_error!("Fuzz targets need cfg=fuzzing");
18+
19+
#[cfg(not(hashes_fuzz))]
20+
compile_error!("Fuzz targets need cfg=hashes_fuzz");
21+
22+
#[cfg(not(secp256k1_fuzz))]
23+
compile_error!("Fuzz targets need cfg=secp256k1_fuzz");
24+
25+
extern crate lightning_fuzz;
26+
use lightning_fuzz::parse::*;
27+
28+
#[cfg(feature = "afl")]
29+
#[macro_use] extern crate afl;
30+
#[cfg(feature = "afl")]
31+
fn main() {
32+
fuzz!(|data| {
33+
parse_run(data.as_ptr(), data.len());
34+
});
35+
}
36+
37+
#[cfg(feature = "honggfuzz")]
38+
#[macro_use] extern crate honggfuzz;
39+
#[cfg(feature = "honggfuzz")]
40+
fn main() {
41+
loop {
42+
fuzz!(|data| {
43+
parse_run(data.as_ptr(), data.len());
44+
});
45+
}
46+
}
47+
48+
#[cfg(feature = "libfuzzer_fuzz")]
49+
#[macro_use] extern crate libfuzzer_sys;
50+
#[cfg(feature = "libfuzzer_fuzz")]
51+
fuzz_target!(|data: &[u8]| {
52+
parse_run(data.as_ptr(), data.len());
53+
});
54+
55+
#[cfg(feature = "stdin_fuzz")]
56+
fn main() {
57+
use std::io::Read;
58+
59+
let mut data = Vec::with_capacity(8192);
60+
std::io::stdin().read_to_end(&mut data).unwrap();
61+
parse_run(data.as_ptr(), data.len());
62+
}
63+
64+
#[test]
65+
fn run_test_cases() {
66+
use std::fs;
67+
use std::io::Read;
68+
use lightning_fuzz::utils::test_logger::StringBuffer;
69+
70+
use std::sync::{atomic, Arc};
71+
{
72+
let data: Vec<u8> = vec![0];
73+
parse_run(data.as_ptr(), data.len());
74+
}
75+
let mut threads = Vec::new();
76+
let threads_running = Arc::new(atomic::AtomicUsize::new(0));
77+
if let Ok(tests) = fs::read_dir("test_cases/parse") {
78+
for test in tests {
79+
let mut data: Vec<u8> = Vec::new();
80+
let path = test.unwrap().path();
81+
fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap();
82+
threads_running.fetch_add(1, atomic::Ordering::AcqRel);
83+
84+
let thread_count_ref = Arc::clone(&threads_running);
85+
let main_thread_ref = std::thread::current();
86+
threads.push((path.file_name().unwrap().to_str().unwrap().to_string(),
87+
std::thread::spawn(move || {
88+
let string_logger = StringBuffer::new();
89+
90+
let panic_logger = string_logger.clone();
91+
let res = if ::std::panic::catch_unwind(move || {
92+
parse_test(&data, panic_logger);
93+
}).is_err() {
94+
Some(string_logger.into_string())
95+
} else { None };
96+
thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel);
97+
main_thread_ref.unpark();
98+
res
99+
})
100+
));
101+
while threads_running.load(atomic::Ordering::Acquire) > 32 {
102+
std::thread::park();
103+
}
104+
}
105+
}
106+
let mut failed_outputs = Vec::new();
107+
for (test, thread) in threads.drain(..) {
108+
if let Some(output) = thread.join().unwrap() {
109+
println!("\nOutput of {}:\n{}\n", test, output);
110+
failed_outputs.push(test);
111+
}
112+
}
113+
if !failed_outputs.is_empty() {
114+
println!("Test cases which failed: ");
115+
for case in failed_outputs {
116+
println!("{}", case);
117+
}
118+
panic!();
119+
}
120+
}

fuzz/src/bin/target_template.txt

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
// This file is auto-generated by gen_target.sh based on target_template.txt
11+
// To modify it, modify target_template.txt and run gen_target.sh instead.
12+
13+
#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
14+
#![cfg_attr(rustfmt, rustfmt_skip)]
15+
16+
#[cfg(not(fuzzing))]
17+
compile_error!("Fuzz targets need cfg=fuzzing");
18+
19+
#[cfg(not(hashes_fuzz))]
20+
compile_error!("Fuzz targets need cfg=hashes_fuzz");
21+
22+
#[cfg(not(secp256k1_fuzz))]
23+
compile_error!("Fuzz targets need cfg=secp256k1_fuzz");
24+
25+
extern crate lightning_fuzz;
26+
use lightning_fuzz::TARGET_MOD::*;
27+
28+
#[cfg(feature = "afl")]
29+
#[macro_use] extern crate afl;
30+
#[cfg(feature = "afl")]
31+
fn main() {
32+
fuzz!(|data| {
33+
TARGET_NAME_run(data.as_ptr(), data.len());
34+
});
35+
}
36+
37+
#[cfg(feature = "honggfuzz")]
38+
#[macro_use] extern crate honggfuzz;
39+
#[cfg(feature = "honggfuzz")]
40+
fn main() {
41+
loop {
42+
fuzz!(|data| {
43+
TARGET_NAME_run(data.as_ptr(), data.len());
44+
});
45+
}
46+
}
47+
48+
#[cfg(feature = "libfuzzer_fuzz")]
49+
#[macro_use] extern crate libfuzzer_sys;
50+
#[cfg(feature = "libfuzzer_fuzz")]
51+
fuzz_target!(|data: &[u8]| {
52+
TARGET_NAME_run(data.as_ptr(), data.len());
53+
});
54+
55+
#[cfg(feature = "stdin_fuzz")]
56+
fn main() {
57+
use std::io::Read;
58+
59+
let mut data = Vec::with_capacity(8192);
60+
std::io::stdin().read_to_end(&mut data).unwrap();
61+
TARGET_NAME_run(data.as_ptr(), data.len());
62+
}
63+
64+
#[test]
65+
fn run_test_cases() {
66+
use std::fs;
67+
use std::io::Read;
68+
use lightning_fuzz::utils::test_logger::StringBuffer;
69+
70+
use std::sync::{atomic, Arc};
71+
{
72+
let data: Vec<u8> = vec![0];
73+
TARGET_NAME_run(data.as_ptr(), data.len());
74+
}
75+
let mut threads = Vec::new();
76+
let threads_running = Arc::new(atomic::AtomicUsize::new(0));
77+
if let Ok(tests) = fs::read_dir("test_cases/TARGET_NAME") {
78+
for test in tests {
79+
let mut data: Vec<u8> = Vec::new();
80+
let path = test.unwrap().path();
81+
fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap();
82+
threads_running.fetch_add(1, atomic::Ordering::AcqRel);
83+
84+
let thread_count_ref = Arc::clone(&threads_running);
85+
let main_thread_ref = std::thread::current();
86+
threads.push((path.file_name().unwrap().to_str().unwrap().to_string(),
87+
std::thread::spawn(move || {
88+
let string_logger = StringBuffer::new();
89+
90+
let panic_logger = string_logger.clone();
91+
let res = if ::std::panic::catch_unwind(move || {
92+
TARGET_NAME_test(&data, panic_logger);
93+
}).is_err() {
94+
Some(string_logger.into_string())
95+
} else { None };
96+
thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel);
97+
main_thread_ref.unpark();
98+
res
99+
})
100+
));
101+
while threads_running.load(atomic::Ordering::Acquire) > 32 {
102+
std::thread::park();
103+
}
104+
}
105+
}
106+
let mut failed_outputs = Vec::new();
107+
for (test, thread) in threads.drain(..) {
108+
if let Some(output) = thread.join().unwrap() {
109+
println!("\nOutput of {}:\n{}\n", test, output);
110+
failed_outputs.push(test);
111+
}
112+
}
113+
if !failed_outputs.is_empty() {
114+
println!("Test cases which failed: ");
115+
for case in failed_outputs {
116+
println!("{}", case);
117+
}
118+
panic!();
119+
}
120+
}

fuzz/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
extern crate bitcoin;
11+
extern crate bitcoin_payment_instructions;
12+
13+
pub mod parse;

0 commit comments

Comments
 (0)