Fuzz test for bech32 decoding
authorJeffrey Czyz <jkczyz@gmail.com>
Thu, 9 Feb 2023 17:09:23 +0000 (11:09 -0600)
committerJeffrey Czyz <jkczyz@gmail.com>
Fri, 24 Feb 2023 00:25:49 +0000 (18:25 -0600)
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.

fuzz/src/bech32_parse.rs [new file with mode: 0644]
fuzz/src/bin/bech32_parse_target.rs [new file with mode: 0644]
fuzz/src/bin/gen_target.sh
fuzz/src/lib.rs
fuzz/targets.h

diff --git a/fuzz/src/bech32_parse.rs b/fuzz/src/bech32_parse.rs
new file mode 100644 (file)
index 0000000..f3dd5ac
--- /dev/null
@@ -0,0 +1,57 @@
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+use crate::utils::test_logger;
+use core::convert::TryFrom;
+use lightning::offers::parse::{Bech32Encode, ParseError};
+
+#[inline]
+pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
+       if let Ok(bech32_encoded) = std::str::from_utf8(data) {
+               if let Ok(bytes) = Bytes::from_bech32_str(bech32_encoded) {
+                       let bech32_encoded = bytes.to_string();
+                       assert_eq!(bytes, Bytes::from_bech32_str(&bech32_encoded).unwrap());
+               }
+       }
+}
+
+#[derive(Debug, PartialEq)]
+struct Bytes(Vec<u8>);
+
+impl Bech32Encode for Bytes {
+       const BECH32_HRP: &'static str = "lno";
+}
+
+impl AsRef<[u8]> for Bytes {
+       fn as_ref(&self) -> &[u8] {
+               &self.0
+       }
+}
+
+impl TryFrom<Vec<u8>> for Bytes {
+       type Error = ParseError;
+       fn try_from(data: Vec<u8>) -> Result<Self, ParseError> {
+               Ok(Bytes(data))
+       }
+}
+
+impl core::fmt::Display for Bytes {
+       fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
+               self.fmt_bech32_str(f)
+       }
+}
+
+pub fn bech32_parse_test<Out: test_logger::Output>(data: &[u8], out: Out) {
+       do_test(data, out);
+}
+
+#[no_mangle]
+pub extern "C" fn bech32_parse_run(data: *const u8, datalen: usize) {
+       do_test(unsafe { std::slice::from_raw_parts(data, datalen) }, test_logger::DevNull {});
+}
diff --git a/fuzz/src/bin/bech32_parse_target.rs b/fuzz/src/bin/bech32_parse_target.rs
new file mode 100644 (file)
index 0000000..629112f
--- /dev/null
@@ -0,0 +1,113 @@
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+// This file is auto-generated by gen_target.sh based on target_template.txt
+// To modify it, modify target_template.txt and run gen_target.sh instead.
+
+#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
+extern crate lightning_fuzz;
+use lightning_fuzz::bech32_parse::*;
+
+#[cfg(feature = "afl")]
+#[macro_use] extern crate afl;
+#[cfg(feature = "afl")]
+fn main() {
+       fuzz!(|data| {
+               bech32_parse_run(data.as_ptr(), data.len());
+       });
+}
+
+#[cfg(feature = "honggfuzz")]
+#[macro_use] extern crate honggfuzz;
+#[cfg(feature = "honggfuzz")]
+fn main() {
+       loop {
+               fuzz!(|data| {
+                       bech32_parse_run(data.as_ptr(), data.len());
+               });
+       }
+}
+
+#[cfg(feature = "libfuzzer_fuzz")]
+#[macro_use] extern crate libfuzzer_sys;
+#[cfg(feature = "libfuzzer_fuzz")]
+fuzz_target!(|data: &[u8]| {
+       bech32_parse_run(data.as_ptr(), data.len());
+});
+
+#[cfg(feature = "stdin_fuzz")]
+fn main() {
+       use std::io::Read;
+
+       let mut data = Vec::with_capacity(8192);
+       std::io::stdin().read_to_end(&mut data).unwrap();
+       bech32_parse_run(data.as_ptr(), data.len());
+}
+
+#[test]
+fn run_test_cases() {
+       use std::fs;
+       use std::io::Read;
+       use lightning_fuzz::utils::test_logger::StringBuffer;
+
+       use std::sync::{atomic, Arc};
+       {
+               let data: Vec<u8> = vec![0];
+               bech32_parse_run(data.as_ptr(), data.len());
+       }
+       let mut threads = Vec::new();
+       let threads_running = Arc::new(atomic::AtomicUsize::new(0));
+       if let Ok(tests) = fs::read_dir("test_cases/bech32_parse") {
+               for test in tests {
+                       let mut data: Vec<u8> = Vec::new();
+                       let path = test.unwrap().path();
+                       fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap();
+                       threads_running.fetch_add(1, atomic::Ordering::AcqRel);
+
+                       let thread_count_ref = Arc::clone(&threads_running);
+                       let main_thread_ref = std::thread::current();
+                       threads.push((path.file_name().unwrap().to_str().unwrap().to_string(),
+                               std::thread::spawn(move || {
+                                       let string_logger = StringBuffer::new();
+
+                                       let panic_logger = string_logger.clone();
+                                       let res = if ::std::panic::catch_unwind(move || {
+                                               bech32_parse_test(&data, panic_logger);
+                                       }).is_err() {
+                                               Some(string_logger.into_string())
+                                       } else { None };
+                                       thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel);
+                                       main_thread_ref.unpark();
+                                       res
+                               })
+                       ));
+                       while threads_running.load(atomic::Ordering::Acquire) > 32 {
+                               std::thread::park();
+                       }
+               }
+       }
+       let mut failed_outputs = Vec::new();
+       for (test, thread) in threads.drain(..) {
+               if let Some(output) = thread.join().unwrap() {
+                       println!("\nOutput of {}:\n{}\n", test, output);
+                       failed_outputs.push(test);
+               }
+       }
+       if !failed_outputs.is_empty() {
+               println!("Test cases which failed: ");
+               for case in failed_outputs {
+                       println!("{}", case);
+               }
+               panic!();
+       }
+}
index fe10e66470004a538d893b28f4ddae0451a62e6a..d7928188d8c57d4cbc959943d40e89a3c1e2a6d6 100755 (executable)
@@ -6,6 +6,7 @@ GEN_TEST() {
        echo "void $1_run(const unsigned char* data, size_t data_len);" >> ../../targets.h
 }
 
+GEN_TEST bech32_parse
 GEN_TEST chanmon_deser
 GEN_TEST chanmon_consistency
 GEN_TEST full_stack
index 3964086376a827161296d1a38083192c5994e3de..92142e5642313d436d86eddcdfee4e6c9fd25295 100644 (file)
@@ -14,6 +14,7 @@ extern crate hex;
 
 pub mod utils;
 
+pub mod bech32_parse;
 pub mod chanmon_deser;
 pub mod chanmon_consistency;
 pub mod full_stack;
index 53831866e37f5e422a37bcca15c999f337196335..8f846c5e037e0e53edc9924f5643a7ebce94fb1f 100644 (file)
@@ -1,4 +1,5 @@
 #include <stdint.h>
+void bech32_parse_run(const unsigned char* data, size_t data_len);
 void chanmon_deser_run(const unsigned char* data, size_t data_len);
 void chanmon_consistency_run(const unsigned char* data, size_t data_len);
 void full_stack_run(const unsigned char* data, size_t data_len);