//! Mutation fuzzing for the filter decoders (tests only). //! //! A decoder fed a random or mutated frame may fail, but must not panic — //! tests build with overflow checks and debug assertions, so an unchecked //! subtraction, multiplication or shift on a header field, or an //! out-of-range slice, fails the test — and must not return more than its //! output limit. #[cfg(not(feature = "std"))] extern crate alloc; #[cfg(not(feature = "std"))] use alloc::vec::Vec; use crate::error::FormatError; /// xorshift64*: deterministic, so a failure reproduces. pub(crate) struct Rng(u64); impl Rng { pub(crate) fn new(seed: u64) -> Rng { Rng(seed.max(1)) } pub(crate) fn next_u64(&mut self) -> u64 { let mut x = self.0; x ^= x >> 12; x ^= x << 25; x ^= x >> 27; self.0 = x; x.wrapping_mul(0x2545_F491_4F6C_DD1D) } /// Uniform in `0..n` (`n` > 0). pub(crate) fn below(&mut self, n: usize) -> usize { (self.next_u64() % n as u64) as usize } pub(crate) fn bytes(&mut self, n: usize) -> Vec { (0..n).map(|_| self.next_u64() as u8).collect() } /// A u32 that tends to hit edge cases in size and offset fields. fn interesting_u32(&mut self, len: usize) -> u32 { match self.below(10) { 0 => 0, 1 => 1, 2 => self.below(20) as u32, 3 => 15 + self.below(3) as u32, 4 => u32::MAX - self.below(16) as u32, 5 => 1 << self.below(32), 6 => (len as u32) .wrapping_add(self.below(9) as u32) .wrapping_sub(4), 7 => i32::MAX as u32, _ => self.next_u64() as u32, } } } /// One to four random edits of `seed`. pub(crate) fn mutate(rng: &mut Rng, seed: &[u8]) -> Vec { let mut v = seed.to_vec(); for _ in 0..1 + rng.below(4) { let len = v.len(); match rng.below(9) { 0 if len > 0 => { let i = rng.below(len); v[i] ^= 1 << rng.below(8); } 1 if len > 0 => { let i = rng.below(len); v[i] = rng.next_u64() as u8; } 2 if len > 0 => { let i = rng.below(len); v[i] = [0, 0xff, 0x7f, 0x80, 0x20, 0x1f][rng.below(6)]; } // A size or offset field: little- or big-endian, anywhere, but // most often in the first 32 bytes where headers live. 3 | 4 if len >= 4 => { let span = if rng.below(2) == 0 { len.min(32) } else { len }; let i = rng.below(span - 3); let x = rng.interesting_u32(len); let b = if rng.below(2) == 0 { x.to_le_bytes() } else { x.to_be_bytes() }; v[i..i + 4].copy_from_slice(&b); } 5 if len > 0 => v.truncate(rng.below(len)), 6 => { let n = 1 + rng.below(64); let extra = rng.bytes(n); v.extend_from_slice(&extra); } 7 if len > 1 => { let a = rng.below(len); let b = a + rng.below(len - a); let copy = v[a..b].to_vec(); let at = rng.below(len); v.splice(at..at, copy); } _ if len > 0 => { let i = rng.below(len); v[i] = v[i].wrapping_add(1 + rng.below(3) as u8); } _ => v.push(rng.next_u64() as u8), } } v } /// Feed `iters` inputs to `decode`: mostly mutations of `seeds`, some pure /// noise and some truncated seeds. Asserts only "no panic, output within /// `limit`". pub(crate) fn fuzz_decoder( seed: u64, seeds: &[Vec], iters: usize, limit: usize, mut decode: impl FnMut(&[u8]) -> Result, FormatError>, ) { assert!(!seeds.is_empty()); let mut rng = Rng::new(seed); for s in seeds { // The seeds themselves must be valid, or the fuzz explores nothing. decode(s).expect("seed frame must decode"); } for _ in 0..iters { let input = match rng.below(16) { 0 => { let n = rng.below(96); rng.bytes(n) } 1 => { let s = &seeds[rng.below(seeds.len())]; s[..rng.below(s.len() + 1)].to_vec() } _ => { let s = &seeds[rng.below(seeds.len())]; mutate(&mut rng, s) } }; if let Ok(out) = decode(&input) { assert!(out.len() <= limit, "decoded {} > limit {limit}", out.len()); } } }