Add fuzzing for `StaticInvoice`
What changed, and why it matters
This commit only adds a new fuzzing test target for StaticInvoice deserialization. It does not change any production code, fix any bug, or alter behavior. It is a testing/infrastructure addition with no direct security relevance.
No action needed; this is a test-only change. Continue normal review of any future fuzzing findings.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces fuzzing harness files for StaticInvoice: a generated target binary, a fuzz module that tries to deserialize arbitrary bytes into a StaticInvoice and re-serializes it to verify round-trip equality, and registration in the fuzz target list. No library code is modified.
Changed components
fuzz/src/bin/gen_target.shfuzz/src/bin/static_invoice_deser_target.rsfuzz/src/lib.rsfuzz/src/static_invoice_deser.rsfuzz/targets.hInspect captured patch +154 / −0
diff --git a/fuzz/src/bin/gen_target.sh b/fuzz/src/bin/gen_target.sh
index 18f715e..0f7f9c9 100755
--- a/fuzz/src/bin/gen_target.sh
+++ b/fuzz/src/bin/gen_target.sh
@@ -14,6 +14,7 @@ GEN_TEST invoice_deser
GEN_TEST invoice_request_deser
GEN_TEST offer_deser
GEN_TEST bolt11_deser
+GEN_TEST static_invoice_deser
GEN_TEST onion_message
GEN_TEST peer_crypt
GEN_TEST process_network_graph
diff --git a/fuzz/src/bin/static_invoice_deser_target.rs b/fuzz/src/bin/static_invoice_deser_target.rs
new file mode 100644
index 0000000..573f0aa
--- /dev/null
+++ b/fuzz/src/bin/static_invoice_deser_target.rs
@@ -0,0 +1,120 @@
+// 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_attr(rustfmt, rustfmt_skip)]
+
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
+#[cfg(not(hashes_fuzz))]
+compile_error!("Fuzz targets need cfg=hashes_fuzz");
+
+#[cfg(not(secp256k1_fuzz))]
+compile_error!("Fuzz targets need cfg=secp256k1_fuzz");
+
+extern crate lightning_fuzz;
+use lightning_fuzz::static_invoice_deser::*;
+
+#[cfg(feature = "afl")]
+#[macro_use] extern crate afl;
+#[cfg(feature = "afl")]
+fn main() {
+ fuzz!(|data| {
+ static_invoice_deser_run(data.as_ptr(), data.len());
+ });
+}
+
+#[cfg(feature = "honggfuzz")]
+#[macro_use] extern crate honggfuzz;
+#[cfg(feature = "honggfuzz")]
+fn main() {
+ loop {
+ fuzz!(|data| {
+ static_invoice_deser_run(data.as_ptr(), data.len());
+ });
+ }
+}
+
+#[cfg(feature = "libfuzzer_fuzz")]
+#[macro_use] extern crate libfuzzer_sys;
+#[cfg(feature = "libfuzzer_fuzz")]
+fuzz_target!(|data: &[u8]| {
+ static_invoice_deser_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();
+ static_invoice_deser_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];
+ static_invoice_deser_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/static_invoice_deser") {
+ 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 || {
+ static_invoice_deser_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!();
+ }
+}
diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs
index bbd05c5..58956fa 100644
--- a/fuzz/src/lib.rs
+++ b/fuzz/src/lib.rs
@@ -42,6 +42,7 @@ pub mod process_network_graph;
pub mod process_onion_failure;
pub mod refund_deser;
pub mod router;
+pub mod static_invoice_deser;
pub mod zbase32;
pub mod msg_targets;
diff --git a/fuzz/src/static_invoice_deser.rs b/fuzz/src/static_invoice_deser.rs
new file mode 100644
index 0000000..4256ad2
--- /dev/null
+++ b/fuzz/src/static_invoice_deser.rs
@@ -0,0 +1,31 @@
+// 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::static_invoice::StaticInvoice;
+use lightning::util::ser::Writeable;
+
+#[inline]
+pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
+ if let Ok(static_invoice) = StaticInvoice::try_from(data.to_vec()) {
+ let mut bytes = Vec::with_capacity(data.len());
+ static_invoice.write(&mut bytes).unwrap();
+ assert_eq!(data, bytes);
+ }
+}
+
+pub fn static_invoice_deser_test<Out: test_logger::Output>(data: &[u8], out: Out) {
+ do_test(data, out);
+}
+
+#[no_mangle]
+pub extern "C" fn static_invoice_deser_run(data: *const u8, datalen: usize) {
+ do_test(unsafe { std::slice::from_raw_parts(data, datalen) }, test_logger::DevNull {});
+}
diff --git a/fuzz/targets.h b/fuzz/targets.h
index 899f260..99a88f5 100644
--- a/fuzz/targets.h
+++ b/fuzz/targets.h
@@ -7,6 +7,7 @@ void invoice_deser_run(const unsigned char* data, size_t data_len);
void invoice_request_deser_run(const unsigned char* data, size_t data_len);
void offer_deser_run(const unsigned char* data, size_t data_len);
void bolt11_deser_run(const unsigned char* data, size_t data_len);
+void static_invoice_deser_run(const unsigned char* data, size_t data_len);
void onion_message_run(const unsigned char* data, size_t data_len);
void peer_crypt_run(const unsigned char* data, size_t data_len);
void process_network_graph_run(const unsigned char* data, size_t data_len);
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.