Skip to content

Commit 70b3f2e

Browse files
committed
Fuzz test for bech32 decoding
Fuzz testing bech32 decoding along with deserializing the underlying message can result in overly exhaustive searches. Instead, the message deserializations are now fuzzed separately. Add fuzzing for bech32 decoding.
1 parent 50b6ea9 commit 70b3f2e

File tree

5 files changed

+173
-0
lines changed

5 files changed

+173
-0
lines changed

fuzz/src/bech32_parse.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
use crate::utils::test_logger;
11+
use core::convert::TryFrom;
12+
use lightning::offers::parse::{Bech32Encode, ParseError};
13+
14+
#[inline]
15+
pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
16+
if let Ok(bech32_encoded) = std::str::from_utf8(data) {
17+
if let Ok(bytes) = Bytes::from_bech32_str(bech32_encoded) {
18+
let bech32_encoded = bytes.to_string();
19+
assert_eq!(bytes, Bytes::from_bech32_str(&bech32_encoded).unwrap());
20+
}
21+
}
22+
}
23+
24+
#[derive(Debug, PartialEq)]
25+
struct Bytes(Vec<u8>);
26+
27+
impl Bech32Encode for Bytes {
28+
const BECH32_HRP: &'static str = "lno";
29+
}
30+
31+
impl AsRef<[u8]> for Bytes {
32+
fn as_ref(&self) -> &[u8] {
33+
&self.0
34+
}
35+
}
36+
37+
impl TryFrom<Vec<u8>> for Bytes {
38+
type Error = ParseError;
39+
fn try_from(data: Vec<u8>) -> Result<Self, ParseError> {
40+
Ok(Bytes(data))
41+
}
42+
}
43+
44+
impl core::fmt::Display for Bytes {
45+
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
46+
self.fmt_bech32_str(f)
47+
}
48+
}
49+
50+
pub fn bech32_parse_test<Out: test_logger::Output>(data: &[u8], out: Out) {
51+
do_test(data, out);
52+
}
53+
54+
#[no_mangle]
55+
pub extern "C" fn bech32_parse_run(data: *const u8, datalen: usize) {
56+
do_test(unsafe { std::slice::from_raw_parts(data, datalen) }, test_logger::DevNull {});
57+
}

fuzz/src/bin/bech32_parse_target.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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+
15+
#[cfg(not(fuzzing))]
16+
compile_error!("Fuzz targets need cfg=fuzzing");
17+
18+
extern crate lightning_fuzz;
19+
use lightning_fuzz::bech32_parse::*;
20+
21+
#[cfg(feature = "afl")]
22+
#[macro_use] extern crate afl;
23+
#[cfg(feature = "afl")]
24+
fn main() {
25+
fuzz!(|data| {
26+
bech32_parse_run(data.as_ptr(), data.len());
27+
});
28+
}
29+
30+
#[cfg(feature = "honggfuzz")]
31+
#[macro_use] extern crate honggfuzz;
32+
#[cfg(feature = "honggfuzz")]
33+
fn main() {
34+
loop {
35+
fuzz!(|data| {
36+
bech32_parse_run(data.as_ptr(), data.len());
37+
});
38+
}
39+
}
40+
41+
#[cfg(feature = "libfuzzer_fuzz")]
42+
#[macro_use] extern crate libfuzzer_sys;
43+
#[cfg(feature = "libfuzzer_fuzz")]
44+
fuzz_target!(|data: &[u8]| {
45+
bech32_parse_run(data.as_ptr(), data.len());
46+
});
47+
48+
#[cfg(feature = "stdin_fuzz")]
49+
fn main() {
50+
use std::io::Read;
51+
52+
let mut data = Vec::with_capacity(8192);
53+
std::io::stdin().read_to_end(&mut data).unwrap();
54+
bech32_parse_run(data.as_ptr(), data.len());
55+
}
56+
57+
#[test]
58+
fn run_test_cases() {
59+
use std::fs;
60+
use std::io::Read;
61+
use lightning_fuzz::utils::test_logger::StringBuffer;
62+
63+
use std::sync::{atomic, Arc};
64+
{
65+
let data: Vec<u8> = vec![0];
66+
bech32_parse_run(data.as_ptr(), data.len());
67+
}
68+
let mut threads = Vec::new();
69+
let threads_running = Arc::new(atomic::AtomicUsize::new(0));
70+
if let Ok(tests) = fs::read_dir("test_cases/bech32_parse") {
71+
for test in tests {
72+
let mut data: Vec<u8> = Vec::new();
73+
let path = test.unwrap().path();
74+
fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap();
75+
threads_running.fetch_add(1, atomic::Ordering::AcqRel);
76+
77+
let thread_count_ref = Arc::clone(&threads_running);
78+
let main_thread_ref = std::thread::current();
79+
threads.push((path.file_name().unwrap().to_str().unwrap().to_string(),
80+
std::thread::spawn(move || {
81+
let string_logger = StringBuffer::new();
82+
83+
let panic_logger = string_logger.clone();
84+
let res = if ::std::panic::catch_unwind(move || {
85+
bech32_parse_test(&data, panic_logger);
86+
}).is_err() {
87+
Some(string_logger.into_string())
88+
} else { None };
89+
thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel);
90+
main_thread_ref.unpark();
91+
res
92+
})
93+
));
94+
while threads_running.load(atomic::Ordering::Acquire) > 32 {
95+
std::thread::park();
96+
}
97+
}
98+
}
99+
let mut failed_outputs = Vec::new();
100+
for (test, thread) in threads.drain(..) {
101+
if let Some(output) = thread.join().unwrap() {
102+
println!("\nOutput of {}:\n{}\n", test, output);
103+
failed_outputs.push(test);
104+
}
105+
}
106+
if !failed_outputs.is_empty() {
107+
println!("Test cases which failed: ");
108+
for case in failed_outputs {
109+
println!("{}", case);
110+
}
111+
panic!();
112+
}
113+
}

fuzz/src/bin/gen_target.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ GEN_TEST() {
66
echo "void $1_run(const unsigned char* data, size_t data_len);" >> ../../targets.h
77
}
88

9+
GEN_TEST bech32_parse
910
GEN_TEST chanmon_deser
1011
GEN_TEST chanmon_consistency
1112
GEN_TEST full_stack

fuzz/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ extern crate hex;
1414

1515
pub mod utils;
1616

17+
pub mod bech32_parse;
1718
pub mod chanmon_deser;
1819
pub mod chanmon_consistency;
1920
pub mod full_stack;

fuzz/targets.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <stdint.h>
2+
void bech32_parse_run(const unsigned char* data, size_t data_len);
23
void chanmon_deser_run(const unsigned char* data, size_t data_len);
34
void chanmon_consistency_run(const unsigned char* data, size_t data_len);
45
void full_stack_run(const unsigned char* data, size_t data_len);

0 commit comments

Comments
 (0)