From: Matt Corallo Date: Tue, 30 Apr 2024 14:31:57 +0000 (+0000) Subject: Substantially increase coverage in BOLT11 deserialization fuzzer X-Git-Tag: v0.0.124-beta~129^2~1 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=65ba75d500d7835b851e73cf572570e2b6056b39;p=rust-lightning Substantially increase coverage in BOLT11 deserialization fuzzer --- diff --git a/fuzz/src/bolt11_deser.rs b/fuzz/src/bolt11_deser.rs index 105235a7b..d2c0ab590 100644 --- a/fuzz/src/bolt11_deser.rs +++ b/fuzz/src/bolt11_deser.rs @@ -7,26 +7,62 @@ // You may not use this file except in accordance with one or both of these // licenses. -use bitcoin::bech32::{u5, FromBase32, ToBase32}; use crate::utils::test_logger; -use lightning_invoice::RawDataPart; +use bitcoin::bech32::{u5, FromBase32, ToBase32}; +use bitcoin::secp256k1::{Secp256k1, SecretKey}; +use lightning_invoice::{ + Bolt11Invoice, RawBolt11Invoice, RawDataPart, RawHrp, RawTaggedField, TaggedField, +}; +use std::str::FromStr; #[inline] pub fn do_test(data: &[u8], _out: Out) { - let bech32 = data.iter().map(|x| u5::try_from_u8(x % 32).unwrap()).collect::>(); - let invoice = match RawDataPart::from_base32(&bech32) { - Ok(invoice) => invoice, - Err(_) => return, - }; - - // Our encoding is not worse than the input - assert!(invoice.to_base32().len() <= bech32.len()); - - // Our serialization is loss-less - assert_eq!( - RawDataPart::from_base32(&invoice.to_base32()).expect("faild parsing out own encoding"), - invoice - ); + // Read a fake HRP length byte + let hrp_len = std::cmp::min(*data.get(0).unwrap_or(&0) as usize, data.len()); + if let Ok(s) = std::str::from_utf8(&data[..hrp_len]) { + let hrp = match RawHrp::from_str(s) { + Ok(hrp) => hrp, + Err(_) => return, + }; + let bech32 = + data.iter().skip(hrp_len).map(|x| u5::try_from_u8(x % 32).unwrap()).collect::>(); + let invoice_data = match RawDataPart::from_base32(&bech32) { + Ok(invoice) => invoice, + Err(_) => return, + }; + + // Our data encoding is not worse than the input + assert!(invoice_data.to_base32().len() <= bech32.len()); + + // Our data serialization is loss-less + assert_eq!( + RawDataPart::from_base32(&invoice_data.to_base32()) + .expect("faild parsing out own encoding"), + invoice_data + ); + + if invoice_data.tagged_fields.iter().any(|field| { + matches!(field, RawTaggedField::KnownSemantics(TaggedField::PayeePubKey(_))) + }) { + // We could forge a signature using the fact that signing is insecure in fuzz mode, but + // easier to just skip and rely on the fact that no-PayeePubKey invoices do pubkey + // recovery + return; + } + + let raw_invoice = RawBolt11Invoice { hrp, data: invoice_data }; + let signed_raw_invoice = match raw_invoice.sign(|hash| { + let private_key = SecretKey::from_slice(&[42; 32]).unwrap(); + Ok::<_, ()>(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)) + }) { + Ok(inv) => inv, + Err(_) => return, + }; + + if let Ok(invoice) = Bolt11Invoice::from_signed(signed_raw_invoice) { + invoice.amount_milli_satoshis(); + } + } } pub fn bolt11_deser_test(data: &[u8], out: Out) {