diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index 450aa0e0e..b1ba72ffc 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -152,6 +152,12 @@ version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + [[package]] name = "arraydeque" version = "0.5.1" @@ -1920,6 +1926,16 @@ version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + [[package]] name = "libredox" version = "0.1.3" @@ -2343,6 +2359,19 @@ dependencies = [ "url", ] +[[package]] +name = "payjoin-fuzz" +version = "0.0.0" +dependencies = [ + "bitcoin 0.32.7", + "bitcoin-ohttp", + "bitcoin_uri", + "libfuzzer-sys", + "payjoin", + "payjoin-test-utils", + "url", +] + [[package]] name = "payjoin-test-utils" version = "0.0.1" diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 450aa0e0e..b1ba72ffc 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -152,6 +152,12 @@ version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + [[package]] name = "arraydeque" version = "0.5.1" @@ -1920,6 +1926,16 @@ version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + [[package]] name = "libredox" version = "0.1.3" @@ -2343,6 +2359,19 @@ dependencies = [ "url", ] +[[package]] +name = "payjoin-fuzz" +version = "0.0.0" +dependencies = [ + "bitcoin 0.32.7", + "bitcoin-ohttp", + "bitcoin_uri", + "libfuzzer-sys", + "payjoin", + "payjoin-test-utils", + "url", +] + [[package]] name = "payjoin-test-utils" version = "0.0.1" diff --git a/Cargo.toml b/Cargo.toml index be8e8de7f..e525c8d31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["payjoin", "payjoin-cli", "payjoin-directory", "payjoin-test-utils", "payjoin-ffi"] +members = ["payjoin", "payjoin-cli", "payjoin-directory", "payjoin-test-utils", "payjoin-ffi", "fuzz"] resolver = "2" [patch.crates-io] diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 000000000..1a45eee77 --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 000000000..b1c81c580 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "payjoin-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[features] +default = ["fuzzing"] +fuzzing = [] + +[dependencies] +bitcoin = { version = "0.32.7", features = ["base64"] } +bitcoin_uri = { version = "0.1.0" } +payjoin = { version = "1.0.0-rc.0", default-features = false, features = ["_core", "v1", "v2"] } +payjoin-test-utils = { path = "../payjoin-test-utils" } +libfuzzer-sys = { version = "0.4.0" } +ohttp = { package = "bitcoin-ohttp", version = "0.6.0" } +url = { version = "2.5.4", default-features=false, features = ["serde"] } + +[[bin]] +name = "pjuri_roundtrip" +path = "fuzz_targets/uri/pjuri_roundtrip.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "psbt_roundtrip" +path = "fuzz_targets/psbt/psbt_roundtrip.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/psbt/psbt_roundtrip.rs b/fuzz/fuzz_targets/psbt/psbt_roundtrip.rs new file mode 100644 index 000000000..0694765ae --- /dev/null +++ b/fuzz/fuzz_targets/psbt/psbt_roundtrip.rs @@ -0,0 +1,7 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|_data: &[u8]| { + // fuzzed code goes here +}); diff --git a/fuzz/fuzz_targets/uri/pjuri_roundtrip.rs b/fuzz/fuzz_targets/uri/pjuri_roundtrip.rs new file mode 100644 index 000000000..c5a206840 --- /dev/null +++ b/fuzz/fuzz_targets/uri/pjuri_roundtrip.rs @@ -0,0 +1,73 @@ +#![no_main] + +use std::any::{Any, TypeId}; + +use bitcoin::Amount; +use bitcoin_uri::Param; +#[cfg(feature = "fuzzing")] +use libfuzzer_sys::fuzz_target; +use payjoin::{Uri, UriExt}; + +fn do_test(data: &[u8]) { + if let Ok(uri_str) = std::str::from_utf8(data) { + let pj_uri = match uri_str.parse::>() { + Ok(pj_uri) => pj_uri.assume_checked(), + Err(_) => return, + }; + let address = pj_uri.address.is_spend_standard(); + if !address { + return; + } + let amount = pj_uri.amount; + + if let Some(label) = pj_uri.clone().label { + if TypeId::of::() != label.type_id() { + return; + } + }; + if let Some(message) = pj_uri.clone().message { + if TypeId::of::() != message.type_id() { + return; + } + }; + let extras = pj_uri.clone().check_pj_supported().unwrap().extras; + assert_eq!(pj_uri.to_string(), uri_str); + assert!(amount.is_none_or(|btc| btc < Amount::MAX_MONEY)); + assert!( + TypeId::of::() == extras.output_substitution().type_id() + ); + assert!(TypeId::of::() == extras.endpoint().type_id()) + } +} + +#[cfg(feature = "fuzzing")] +fuzz_target!(|data| { + do_test(data); +}); + +#[cfg(test)] +mod tests { + fn extend_vec_from_hex(hex: &str, out: &mut Vec) { + let mut b = 0; + for (idx, c) in hex.as_bytes().iter().enumerate() { + b <<= 4; + match *c { + b'A'..=b'F' => b |= c - b'A' + 10, + b'a'..=b'f' => b |= c - b'a' + 10, + b'0'..=b'9' => b |= c - b'0', + _ => panic!("Bad hex"), + } + if (idx & 1) == 1 { + out.push(b); + b = 0; + } + } + } + + #[test] + fn duplicate_crash() { + let mut a = Vec::new(); + extend_vec_from_hex("00000000", &mut a); + super::do_test(&a); + } +}