midi-harbor/fuzz/fuzz_targets/rtpmidi_journal.rs
2026-09-28 13:59:10 -05:00

60 lines
2.4 KiB
Rust

//! Feeds hostile recovery journals to the decoder, and what it accepts to recovery.
//!
//! A journal arrives on the tail of any data packet, and recovery runs whenever a packet was lost,
//! so both are reachable by a peer that simply lies. Neither may allocate more than a small
//! multiple of the journal's size, and a decoded journal has to encode back to one that decodes
//! the same.
#![no_main]
use libfuzzer_sys::fuzz_target;
use midi_harbor_rtpmidi::journal::RecoveryJournal;
/// Channels a journal can describe, which the format caps with a four-bit count.
const MAX_CHANNELS: usize = 16;
/// Messages recovery may produce per journal octet. The densest chapter is a note-off bitmap,
/// which releases eight notes per octet.
const MESSAGES_PER_OCTET: usize = 8;
/// Heap bytes decoding and recovery may hold per journal octet.
const BYTES_PER_OCTET: u64 = 256;
/// Heap bytes decoding and recovery may hold whatever the input, for fixed-size bookkeeping.
const FIXED_BYTES: u64 = 8192;
fuzz_target!(
init: {
// The allocation checks below prove nothing unless the counter is really installed.
let probe = allocation_counter::measure(|| drop(std::hint::black_box(vec![0_u8; 64])));
assert!(probe.count_total > 0, "the allocation counter is not installed");
},
|data: &[u8]| {
let bound = u64::try_from(data.len())
.unwrap_or(u64::MAX)
.saturating_mul(BYTES_PER_OCTET)
.saturating_add(FIXED_BYTES);
let mut journal = None;
let allocations =
allocation_counter::measure(|| journal = RecoveryJournal::decode(data).ok());
assert!(allocations.bytes_max <= bound, "decode held {allocations:?}");
let Some(journal) = journal else {
return;
};
assert!(journal.channels.len() <= MAX_CHANNELS, "more channels than the format has");
let mut recovered = Vec::new();
let allocations = allocation_counter::measure(|| recovered = journal.recover());
assert!(allocations.bytes_max <= bound, "recovery held {allocations:?}");
assert!(
recovered.len() <= data.len().saturating_mul(MESSAGES_PER_OCTET),
"recovered {} messages from {} octets",
recovered.len(),
data.len(),
);
let again = RecoveryJournal::decode(&journal.encode());
assert_eq!(again.as_ref().map(RecoveryJournal::recover), Ok(recovered));
}
);