fuzz: add a payer proof deserialization target
What changed, and why it matters
This commit only adds a new fuzz test target. Fuzz tests are automated tools that throw random or crafted input at a piece of code to look for crashes or bugs. The commit does not change any production code, protocol behavior, or user-facing functionality, so it does not introduce or fix a security issue by itself.
No security action required. This is a testing-only addition. If the fuzz target later finds crashes, those should be triaged and fixed separately.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change adds a fuzzing harness for PayerProof::try_from deserialization and round-trip serialization. It creates a new target file, registers it in the fuzz target generator script, adds the module to fuzz/src/lib.rs, and declares the C-compatible entry point in targets.h. The actual deserialization/serialization logic in the lightning crate is untouched.
Changed components
fuzz/fuzz-fake-hashes/src/bin/payer_proof_deser_target.rsfuzz/src/bin/gen_target.shfuzz/src/lib.rsfuzz/src/payer_proof_deser.rsfuzz/targets.hInspect captured patch +171 / −0
diff --git a/fuzz/fuzz-fake-hashes/src/bin/payer_proof_deser_target.rs b/fuzz/fuzz-fake-hashes/src/bin/payer_proof_deser_target.rs
new file mode 100644
index 0000000..c7f9d86
--- /dev/null
+++ b/fuzz/fuzz-fake-hashes/src/bin/payer_proof_deser_target.rs
@@ -0,0 +1,137 @@
+// 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 target does not support cfg(not(hashes_fuzz))");
+
+#[cfg(not(secp256k1_fuzz))]
+compile_error!("Fuzz targets need cfg=secp256k1_fuzz");
+
+extern crate lightning_fuzz;
+use lightning_fuzz::payer_proof_deser::*;
+use lightning_fuzz::utils::test_logger;
+
+#[cfg(feature = "afl")]
+#[macro_use] extern crate afl;
+#[cfg(feature = "afl")]
+fn main() {
+ fuzz!(|data| {
+ payer_proof_deser_test(&data, test_logger::DevNull {});
+ });
+}
+
+#[cfg(feature = "honggfuzz")]
+#[macro_use] extern crate honggfuzz;
+#[cfg(feature = "honggfuzz")]
+fn main() {
+ loop {
+ fuzz!(|data| {
+ payer_proof_deser_test(&data, test_logger::DevNull {});
+ });
+ }
+}
+
+#[cfg(feature = "libfuzzer_fuzz")]
+#[macro_use] extern crate libfuzzer_sys;
+#[cfg(feature = "libfuzzer_fuzz")]
+fuzz_target!(|data: &[u8]| {
+ payer_proof_deser_test(data, test_logger::DevNull {});
+});
+
+#[cfg(feature = "stdin_fuzz")]
+fn main() {
+ use std::io::Read;
+
+ // On macOS, panic=abort causes the process to send SIGABRT which can leave it
+ // stuck in an uninterruptible state due to the ReportCrash daemon. Using
+ // process::exit in a panic hook avoids this by terminating cleanly.
+ #[cfg(target_os = "macos")]
+ std::panic::set_hook(Box::new(|panic_info| {
+ use std::io::Write;
+ let _ = std::io::stdout().flush();
+ eprintln!("{}\n{}", panic_info, std::backtrace::Backtrace::force_capture());
+ let _ = std::io::stderr().flush();
+ std::process::exit(1);
+ }));
+
+ let mut data = Vec::with_capacity(8192);
+ std::io::stdin().read_to_end(&mut data).unwrap();
+ if std::env::var_os("LDK_FUZZ_SUPPRESS_LOGS").is_some() {
+ payer_proof_deser_test(&data, test_logger::DevNull {});
+ } else {
+ payer_proof_deser_test(&data, test_logger::Stdout {});
+ }
+}
+
+#[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];
+ payer_proof_deser_test(&data, test_logger::DevNull {});
+ }
+ let mut threads = Vec::new();
+ let threads_running = Arc::new(atomic::AtomicUsize::new(0));
+ if let Ok(tests) = fs::read_dir("../test_cases/payer_proof_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 || {
+ payer_proof_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/bin/gen_target.sh b/fuzz/src/bin/gen_target.sh
index 868a076..9626871 100755
--- a/fuzz/src/bin/gen_target.sh
+++ b/fuzz/src/bin/gen_target.sh
@@ -34,6 +34,7 @@ GEN_FAKE_HASHES_TEST onion_message
GEN_FAKE_HASHES_TEST peer_crypt
GEN_FAKE_HASHES_TEST process_network_graph
GEN_FAKE_HASHES_TEST process_onion_failure
+GEN_FAKE_HASHES_TEST payer_proof_deser
GEN_FAKE_HASHES_TEST refund_deser
GEN_FAKE_HASHES_TEST router
GEN_FAKE_HASHES_TEST zbase32
diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs
index 25c2fff..be5b34a 100644
--- a/fuzz/src/lib.rs
+++ b/fuzz/src/lib.rs
@@ -36,6 +36,7 @@ pub mod lsps_message;
pub mod offer_deser;
pub mod onion_hop_data;
pub mod onion_message;
+pub mod payer_proof_deser;
pub mod peer_crypt;
pub mod process_network_graph;
pub mod process_onion_failure;
diff --git a/fuzz/src/payer_proof_deser.rs b/fuzz/src/payer_proof_deser.rs
new file mode 100644
index 0000000..adccbe5
--- /dev/null
+++ b/fuzz/src/payer_proof_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::payer_proof::PayerProof;
+use lightning::util::ser::Writeable;
+
+#[inline]
+pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
+ if let Ok(payer_proof) = PayerProof::try_from(data.to_vec()) {
+ let mut bytes = Vec::with_capacity(data.len());
+ payer_proof.write(&mut bytes).unwrap();
+ assert_eq!(data, bytes);
+ }
+}
+
+pub fn payer_proof_deser_test<Out: test_logger::Output>(data: &[u8], out: Out) {
+ do_test(data, out);
+}
+
+#[no_mangle]
+pub extern "C" fn payer_proof_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 ef8e899..3a0699d 100644
--- a/fuzz/targets.h
+++ b/fuzz/targets.h
@@ -12,6 +12,7 @@ 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);
void process_onion_failure_run(const unsigned char* data, size_t data_len);
+void payer_proof_deser_run(const unsigned char* data, size_t data_len);
void refund_deser_run(const unsigned char* data, size_t data_len);
void router_run(const unsigned char* data, size_t data_len);
void zbase32_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.