//! Reading from a source that short-reads and errors on a fuzzer-chosen //! schedule. //! //! The arbitrary-bytes targets read from a slice, which only ever succeeds or //! hits clean EOF. Real sources — sockets, pipes, failing disks — return //! `Interrupted`, short reads, and persistent errors, and those paths are //! where a reader is most likely to spin or lose its place. This target is the //! one that reaches them. #![no_main] use arbitrary::Arbitrary; use libfuzzer_sys::fuzz_target; use star_lite::Reader; use std::io::{self, Read}; #[derive(Arbitrary, Debug)] enum Step { Ok(u8), Interrupted, WouldBlock, Broken, } #[derive(Arbitrary, Debug)] struct Input { archive: Vec, schedule: Vec, } /// Serves `data` according to `schedule`, cycling once the schedule runs out. struct FlakySource { data: Vec, pos: usize, schedule: Vec, step: usize, } impl Read for FlakySource { fn read(&mut self, buf: &mut [u8]) -> io::Result { if buf.is_empty() { return Ok(0); } let step = if self.schedule.is_empty() { &Step::Ok(u8::MAX) } else { let s = &self.schedule[self.step % self.schedule.len()]; self.step += 1; s }; match step { Step::Interrupted => Err(io::Error::from(io::ErrorKind::Interrupted)), Step::WouldBlock => Err(io::Error::from(io::ErrorKind::WouldBlock)), Step::Broken => Err(io::Error::from(io::ErrorKind::BrokenPipe)), Step::Ok(n) => { let remaining = self.data.len() - self.pos; if remaining == 0 { return Ok(0); // clean EOF } // A short read of at least one byte, capped by the buffer. let n = (*n as usize).clamp(1, buf.len()).min(remaining); buf[..n].copy_from_slice(&self.data[self.pos..self.pos + n]); self.pos += n; Ok(n) } } } } fuzz_target!(|input: Input| { let bound = input.archive.len() + 2; let source = FlakySource { data: input.archive, pos: 0, schedule: input.schedule, step: 0, }; let Ok(mut reader) = Reader::new(source) else { return; }; let mut seen = 0usize; while let Some(item) = reader.next() { seen += 1; assert!( seen <= bound, "reader did not make progress: {seen} items from {bound} bytes" ); if item.is_err() { assert!( reader.next().is_none(), "an error must be terminal for the iterator" ); break; } } });