From 8bf41c68615065ca532a259fd3a507cc6f651a88 Mon Sep 17 00:00:00 2001 From: Martin Saposnic Date: Thu, 12 Jun 2025 11:41:19 -0300 Subject: [PATCH 1/2] fuzz: Add LSPS message decoder fuzzing --- fuzz/Cargo.toml | 2 + fuzz/src/bin/gen_target.sh | 1 + fuzz/src/bin/lsps_message_target.rs | 120 ++++++++++++++++++++++++++++ fuzz/src/lib.rs | 1 + fuzz/src/lsps_message.rs | 105 ++++++++++++++++++++++++ fuzz/targets.h | 1 + 6 files changed, 230 insertions(+) create mode 100644 fuzz/src/bin/lsps_message_target.rs create mode 100644 fuzz/src/lsps_message.rs diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 37c0cb20244..089e5625fb1 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -20,6 +20,8 @@ stdin_fuzz = [] [dependencies] lightning = { path = "../lightning", features = ["regex", "_test_utils"] } lightning-invoice = { path = "../lightning-invoice" } +lightning-liquidity = { path = "../lightning-liquidity" } +lightning-persister = { path = "../lightning-persister" } lightning-rapid-gossip-sync = { path = "../lightning-rapid-gossip-sync" } bech32 = "0.11.0" bitcoin = { version = "0.32.2", features = ["secp-lowmemory"] } diff --git a/fuzz/src/bin/gen_target.sh b/fuzz/src/bin/gen_target.sh index 5672363ddd3..1f59ad993eb 100755 --- a/fuzz/src/bin/gen_target.sh +++ b/fuzz/src/bin/gen_target.sh @@ -26,6 +26,7 @@ GEN_TEST onion_hop_data GEN_TEST base32 GEN_TEST fromstr_to_netaddress GEN_TEST feature_flags +GEN_TEST lsps_message GEN_TEST msg_accept_channel msg_targets:: GEN_TEST msg_announcement_signatures msg_targets:: diff --git a/fuzz/src/bin/lsps_message_target.rs b/fuzz/src/bin/lsps_message_target.rs new file mode 100644 index 00000000000..7ba7469ebc0 --- /dev/null +++ b/fuzz/src/bin/lsps_message_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 or the MIT license +// , 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::lsps_message::*; + +#[cfg(feature = "afl")] +#[macro_use] extern crate afl; +#[cfg(feature = "afl")] +fn main() { + fuzz!(|data| { + lsps_message_run(data.as_ptr(), data.len()); + }); +} + +#[cfg(feature = "honggfuzz")] +#[macro_use] extern crate honggfuzz; +#[cfg(feature = "honggfuzz")] +fn main() { + loop { + fuzz!(|data| { + lsps_message_run(data.as_ptr(), data.len()); + }); + } +} + +#[cfg(feature = "libfuzzer_fuzz")] +#[macro_use] extern crate libfuzzer_sys; +#[cfg(feature = "libfuzzer_fuzz")] +fuzz_target!(|data: &[u8]| { + lsps_message_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(); + lsps_message_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 = vec![0]; + lsps_message_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/lsps_message") { + for test in tests { + let mut data: Vec = 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 || { + lsps_message_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 4529aff67d3..bbd05c5db5f 100644 --- a/fuzz/src/lib.rs +++ b/fuzz/src/lib.rs @@ -33,6 +33,7 @@ pub mod full_stack; pub mod indexedmap; pub mod invoice_deser; pub mod invoice_request_deser; +pub mod lsps_message; pub mod offer_deser; pub mod onion_hop_data; pub mod onion_message; diff --git a/fuzz/src/lsps_message.rs b/fuzz/src/lsps_message.rs new file mode 100644 index 00000000000..4e019a3165f --- /dev/null +++ b/fuzz/src/lsps_message.rs @@ -0,0 +1,105 @@ +use crate::utils::test_logger; + +use bitcoin::blockdata::constants::genesis_block; +use bitcoin::hashes::{sha256, Hash}; +use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; +use bitcoin::Network; + +use lightning::chain::Filter; +use lightning::chain::{chainmonitor, BestBlock}; +use lightning::ln::channelmanager::{ChainParameters, ChannelManager}; +use lightning::ln::peer_handler::CustomMessageHandler; +use lightning::ln::wire::CustomMessageReader; +use lightning::onion_message::messenger::DefaultMessageRouter; +use lightning::routing::gossip::NetworkGraph; +use lightning::routing::router::DefaultRouter; +use lightning::sign::KeysManager; +use lightning::sign::NodeSigner; +use lightning::util::config::UserConfig; +use lightning::util::test_utils::{ + TestBroadcaster, TestChainSource, TestFeeEstimator, TestLogger, TestScorer, +}; +use lightning_persister::fs_store::FilesystemStore; + +use lightning_liquidity::lsps0::ser::LSPS_MESSAGE_TYPE_ID; +use lightning_liquidity::LiquidityManager; + +use core::time::Duration; + +type LockingWrapper = std::sync::Mutex; + +use std::sync::Arc; + +pub fn do_test(data: &[u8]) { + let network = Network::Bitcoin; + let tx_broadcaster = Arc::new(TestBroadcaster::new(network)); + let fee_estimator = Arc::new(TestFeeEstimator::new(253)); + let logger = Arc::new(TestLogger::with_id("node".into())); + let genesis_block = genesis_block(network); + let network_graph = Arc::new(NetworkGraph::new(network, logger.clone())); + let scorer = Arc::new(LockingWrapper::new(TestScorer::new())); + let now = Duration::from_secs(genesis_block.header.time as u64); + let seed = sha256::Hash::hash(b"lsps-message-seed").to_byte_array(); + let keys_manager = Arc::new(KeysManager::new(&seed, now.as_secs(), now.subsec_nanos())); + let router = Arc::new(DefaultRouter::new( + network_graph.clone(), + logger.clone(), + Arc::clone(&keys_manager), + scorer.clone(), + Default::default(), + )); + let msg_router = + Arc::new(DefaultMessageRouter::new(network_graph.clone(), Arc::clone(&keys_manager))); + let chain_source = Arc::new(TestChainSource::new(Network::Bitcoin)); + let kv_store = Arc::new(FilesystemStore::new("persister".into())); + let chain_monitor = Arc::new(chainmonitor::ChainMonitor::new( + Some(chain_source.clone()), + tx_broadcaster.clone(), + logger.clone(), + fee_estimator.clone(), + kv_store.clone(), + keys_manager.clone(), + keys_manager.get_peer_storage_key(), + )); + let best_block = BestBlock::from_network(network); + let params = ChainParameters { network, best_block }; + let manager = Arc::new(ChannelManager::new( + fee_estimator.clone(), + chain_monitor.clone(), + tx_broadcaster.clone(), + router.clone(), + msg_router.clone(), + logger.clone(), + keys_manager.clone(), + keys_manager.clone(), + keys_manager.clone(), + UserConfig::default(), + params, + genesis_block.header.time, + )); + + let liquidity_manager = Arc::new(LiquidityManager::new( + Arc::clone(&keys_manager), + Arc::clone(&manager), + None::>, + None, + None, + None, + )); + let mut reader = data; + if let Ok(Some(msg)) = liquidity_manager.read(LSPS_MESSAGE_TYPE_ID, &mut reader) { + let secp = Secp256k1::signing_only(); + let sender_node_id = + PublicKey::from_secret_key(&secp, &SecretKey::from_slice(&[1; 32]).unwrap()); + let _ = liquidity_manager.handle_custom_message(msg, sender_node_id); + } +} + +pub fn lsps_message_test(data: &[u8], _out: Out) { + do_test(data); +} + +#[no_mangle] +pub extern "C" fn lsps_message_run(data: *const u8, datalen: usize) { + do_test(unsafe { core::slice::from_raw_parts(data, datalen) }); +} diff --git a/fuzz/targets.h b/fuzz/targets.h index 3a3928b4f4d..2989c219131 100644 --- a/fuzz/targets.h +++ b/fuzz/targets.h @@ -19,6 +19,7 @@ void onion_hop_data_run(const unsigned char* data, size_t data_len); void base32_run(const unsigned char* data, size_t data_len); void fromstr_to_netaddress_run(const unsigned char* data, size_t data_len); void feature_flags_run(const unsigned char* data, size_t data_len); +void lsps_message_run(const unsigned char* data, size_t data_len); void msg_accept_channel_run(const unsigned char* data, size_t data_len); void msg_announcement_signatures_run(const unsigned char* data, size_t data_len); void msg_channel_reestablish_run(const unsigned char* data, size_t data_len); From d8176d1c23c0499a21bd06a964eb7b470360b6fd Mon Sep 17 00:00:00 2001 From: Martin Saposnic Date: Fri, 13 Jun 2025 12:59:24 -0300 Subject: [PATCH 2/2] fixup: use TestStore instead of FilesystemStore. Also do Arc::clone instead of .clone() --- fuzz/Cargo.toml | 1 - fuzz/src/lsps_message.rs | 45 ++++++++++++++++++++-------------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 089e5625fb1..6dbbfe13612 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,6 @@ stdin_fuzz = [] lightning = { path = "../lightning", features = ["regex", "_test_utils"] } lightning-invoice = { path = "../lightning-invoice" } lightning-liquidity = { path = "../lightning-liquidity" } -lightning-persister = { path = "../lightning-persister" } lightning-rapid-gossip-sync = { path = "../lightning-rapid-gossip-sync" } bech32 = "0.11.0" bitcoin = { version = "0.32.2", features = ["secp-lowmemory"] } diff --git a/fuzz/src/lsps_message.rs b/fuzz/src/lsps_message.rs index 4e019a3165f..01856e2eae8 100644 --- a/fuzz/src/lsps_message.rs +++ b/fuzz/src/lsps_message.rs @@ -17,9 +17,8 @@ use lightning::sign::KeysManager; use lightning::sign::NodeSigner; use lightning::util::config::UserConfig; use lightning::util::test_utils::{ - TestBroadcaster, TestChainSource, TestFeeEstimator, TestLogger, TestScorer, + TestBroadcaster, TestChainSource, TestFeeEstimator, TestLogger, TestScorer, TestStore, }; -use lightning_persister::fs_store::FilesystemStore; use lightning_liquidity::lsps0::ser::LSPS_MESSAGE_TYPE_ID; use lightning_liquidity::LiquidityManager; @@ -36,43 +35,43 @@ pub fn do_test(data: &[u8]) { let fee_estimator = Arc::new(TestFeeEstimator::new(253)); let logger = Arc::new(TestLogger::with_id("node".into())); let genesis_block = genesis_block(network); - let network_graph = Arc::new(NetworkGraph::new(network, logger.clone())); + let network_graph = Arc::new(NetworkGraph::new(network, Arc::clone(&logger))); let scorer = Arc::new(LockingWrapper::new(TestScorer::new())); let now = Duration::from_secs(genesis_block.header.time as u64); let seed = sha256::Hash::hash(b"lsps-message-seed").to_byte_array(); let keys_manager = Arc::new(KeysManager::new(&seed, now.as_secs(), now.subsec_nanos())); let router = Arc::new(DefaultRouter::new( - network_graph.clone(), - logger.clone(), + Arc::clone(&network_graph), + Arc::clone(&logger), Arc::clone(&keys_manager), - scorer.clone(), + Arc::clone(&scorer), Default::default(), )); let msg_router = - Arc::new(DefaultMessageRouter::new(network_graph.clone(), Arc::clone(&keys_manager))); + Arc::new(DefaultMessageRouter::new(Arc::clone(&network_graph), Arc::clone(&keys_manager))); let chain_source = Arc::new(TestChainSource::new(Network::Bitcoin)); - let kv_store = Arc::new(FilesystemStore::new("persister".into())); + let kv_store = Arc::new(TestStore::new(false)); let chain_monitor = Arc::new(chainmonitor::ChainMonitor::new( - Some(chain_source.clone()), - tx_broadcaster.clone(), - logger.clone(), - fee_estimator.clone(), - kv_store.clone(), - keys_manager.clone(), + Some(Arc::clone(&chain_source)), + Arc::clone(&tx_broadcaster), + Arc::clone(&logger), + Arc::clone(&fee_estimator), + Arc::clone(&kv_store), + Arc::clone(&keys_manager), keys_manager.get_peer_storage_key(), )); let best_block = BestBlock::from_network(network); let params = ChainParameters { network, best_block }; let manager = Arc::new(ChannelManager::new( - fee_estimator.clone(), - chain_monitor.clone(), - tx_broadcaster.clone(), - router.clone(), - msg_router.clone(), - logger.clone(), - keys_manager.clone(), - keys_manager.clone(), - keys_manager.clone(), + Arc::clone(&fee_estimator), + Arc::clone(&chain_monitor), + Arc::clone(&tx_broadcaster), + Arc::clone(&router), + Arc::clone(&msg_router), + Arc::clone(&logger), + Arc::clone(&keys_manager), + Arc::clone(&keys_manager), + Arc::clone(&keys_manager), UserConfig::default(), params, genesis_block.header.time,