|
1 | 1 | // SPDX-License-Identifier: CC0-1.0 |
2 | 2 |
|
3 | 3 | //! Fuzz test for the receive_garbage function. |
4 | | -//! |
5 | | -//! This focused test fuzzes only the garbage terminator detection logic, |
6 | | -//! which is more effective than trying to fuzz the entire handshake. |
7 | 4 |
|
8 | 5 | #![no_main] |
9 | | -use bip324::{GarbageResult, Handshake, Initialized, Network, ReceivedKey, Role}; |
| 6 | +use bip324::{Handshake, Initialized, Network, ReceivedKey, Role}; |
10 | 7 | use libfuzzer_sys::fuzz_target; |
| 8 | +use rand::SeedableRng; |
11 | 9 |
|
12 | 10 | fuzz_target!(|data: &[u8]| { |
| 11 | + // Cap input size to avoid wasting time on obviously invalid large inputs |
| 12 | + // The protocol limit is 4095 garbage bytes + 16 terminator bytes = 4111 total |
| 13 | + // Test up to ~5000 bytes to cover boundary cases |
| 14 | + if data.len() > 5000 { |
| 15 | + return; |
| 16 | + } |
| 17 | + |
| 18 | + // Use deterministic seeds for reproducible fuzzing |
| 19 | + let seed = [42u8; 32]; |
| 20 | + let mut rng = rand::rngs::StdRng::from_seed(seed); |
| 21 | + let secp = secp256k1::Secp256k1::signing_only(); |
| 22 | + |
13 | 23 | // Set up a valid handshake in the SentVersion state |
14 | | - let initiator = Handshake::<Initialized>::new(Network::Bitcoin, Role::Initiator).unwrap(); |
| 24 | + let initiator = |
| 25 | + Handshake::<Initialized>::new_with_rng(Network::Bitcoin, Role::Initiator, &mut rng, &secp) |
| 26 | + .unwrap(); |
15 | 27 | let mut initiator_key = vec![0u8; Handshake::<Initialized>::send_key_len(None)]; |
16 | 28 | let initiator = initiator.send_key(None, &mut initiator_key).unwrap(); |
17 | 29 |
|
18 | | - let responder = Handshake::<Initialized>::new(Network::Bitcoin, Role::Responder).unwrap(); |
| 30 | + let mut rng2 = rand::rngs::StdRng::from_seed([43u8; 32]); |
| 31 | + let responder = |
| 32 | + Handshake::<Initialized>::new_with_rng(Network::Bitcoin, Role::Responder, &mut rng2, &secp) |
| 33 | + .unwrap(); |
19 | 34 | let mut responder_key = vec![0u8; Handshake::<Initialized>::send_key_len(None)]; |
20 | 35 | let responder = responder.send_key(None, &mut responder_key).unwrap(); |
21 | 36 |
|
22 | 37 | // Exchange keys using real keys to get valid ECDH shared secrets |
23 | 38 | let initiator = initiator |
24 | 39 | .receive_key(responder_key[..64].try_into().unwrap()) |
25 | 40 | .unwrap(); |
26 | | - let _responder = responder |
| 41 | + let responder = responder |
27 | 42 | .receive_key(initiator_key[..64].try_into().unwrap()) |
28 | 43 | .unwrap(); |
29 | 44 |
|
| 45 | + // Get the real responder's garbage terminator from responder's send_version output |
| 46 | + let mut responder_version = vec![0u8; Handshake::<ReceivedKey>::send_version_len(None)]; |
| 47 | + let _responder = responder |
| 48 | + .send_version(&mut responder_version, None) |
| 49 | + .unwrap(); |
| 50 | + |
| 51 | + // The responder's garbage terminator is in the first 16 bytes of their version output |
| 52 | + let responder_terminator = &responder_version[..16]; |
| 53 | + |
30 | 54 | // Send version to reach SentVersion state |
31 | 55 | let mut initiator_version = vec![0u8; Handshake::<ReceivedKey>::send_version_len(None)]; |
32 | 56 | let initiator = initiator |
33 | 57 | .send_version(&mut initiator_version, None) |
34 | 58 | .unwrap(); |
35 | 59 |
|
36 | | - // Now fuzz the receive_garbage function with arbitrary data |
37 | | - match initiator.receive_garbage(data) { |
38 | | - Ok(GarbageResult::FoundGarbage { |
39 | | - handshake: _, |
40 | | - consumed_bytes, |
41 | | - }) => { |
42 | | - // Successfully found garbage terminator |
43 | | - // Verify consumed_bytes is reasonable |
44 | | - assert!(consumed_bytes <= data.len()); |
45 | | - assert!(consumed_bytes >= 16); // At least the terminator size |
46 | | - |
47 | | - // The garbage should be everything before the terminator |
48 | | - let garbage_len = consumed_bytes - 16; |
49 | | - assert!(garbage_len <= 4095); // Max garbage size |
50 | | - } |
51 | | - Ok(GarbageResult::NeedMoreData(_)) => { |
52 | | - // Need more data - valid outcome for short inputs |
53 | | - // This should happen when: |
54 | | - // 1. Buffer is too short to contain terminator |
55 | | - // 2. Buffer doesn't contain the terminator yet |
56 | | - } |
57 | | - Err(_) => { |
58 | | - // Error parsing garbage - valid outcome |
59 | | - // This should happen when: |
60 | | - // 1. No terminator found within max garbage size |
61 | | - } |
62 | | - } |
| 60 | + // Create realistic test case: fuzz_data + real_terminator |
| 61 | + let mut realistic_input = data.to_vec(); |
| 62 | + realistic_input.extend_from_slice(responder_terminator); |
| 63 | + |
| 64 | + // Test the receive_garbage function with realistic input |
| 65 | + let _ = initiator.receive_garbage(&realistic_input); |
63 | 66 | }); |
0 commit comments