Skip to main content

max / shop

3.8 KB · 102 lines History Blame Raw
1 //! Replay every committed fuzz input through the oracle, on stable.
2 //!
3 //! The soak tier runs on astra, on nightly, when the box is idle. That is the
4 //! wrong place for the only copy of a property to live: a crash found there is
5 //! fixed here, and nothing on a developer's machine would notice it coming
6 //! back.
7 //!
8 //! So both corpora replay as an ordinary `cargo test`:
9 //!
10 //! - `fuzz/seeds/apc/` is human intent and real client traffic, the shapes
11 //! worth reaching first.
12 //! - `fuzz/regressions/` is inputs that once found a bug. Anything landing
13 //! there is a permanent test by virtue of the directory, with no test
14 //! function to write and no chance of forgetting one.
15 //!
16 //! The assertion is `kittygfx::oracle::check_bodies`, the same function the
17 //! libFuzzer target calls, so the two cannot check different things.
18
19 use std::path::{Path, PathBuf};
20
21 fn dir(rel: &str) -> PathBuf {
22 Path::new(env!("CARGO_MANIFEST_DIR")).join(rel)
23 }
24
25 fn replay(dir: &Path) -> usize {
26 let Ok(entries) = std::fs::read_dir(dir) else {
27 return 0;
28 };
29
30 let mut count = 0;
31 for entry in entries {
32 let path = entry.expect("readable dir entry").path();
33 if !path.is_file() {
34 continue;
35 }
36 if path.extension().is_some_and(|e| e == "md") {
37 continue;
38 }
39 let bytes = std::fs::read(&path).expect("readable input");
40 let outcome = std::panic::catch_unwind(|| kittygfx::oracle::check_bodies(&bytes));
41 assert!(outcome.is_ok(), "oracle failed on {}", path.display());
42 count += 1;
43 }
44 count
45 }
46
47 #[test]
48 fn every_seed_holds_the_oracle() {
49 let n = replay(&dir("fuzz/seeds/apc"));
50 // A seed directory that silently emptied would make this test pass while
51 // checking nothing, which is the failure mode a corpus-replay test has.
52 assert!(
53 n >= 30,
54 "expected the committed seed corpus, found {n} inputs"
55 );
56 }
57
58 #[test]
59 fn every_regression_holds_the_oracle() {
60 // No lower bound. This is empty until the first crash, and that is the
61 // good state.
62 replay(&dir("fuzz/regressions"));
63 }
64
65 /// The oracle must actually run, not merely not panic.
66 #[test]
67 fn the_oracle_feeds_the_bodies_it_is_given() {
68 let input = b"\x1b_Ga=q,f=24,t=d,i=3;AAAA\x1b\\\x1b_Ga=d,d=A\x1b\\";
69 assert_eq!(kittygfx::oracle::check_bodies(input), 2);
70 }
71
72 /// The two ways a partial transmission can grow without bound, replayed at
73 /// sizes the committed corpus cannot carry.
74 ///
75 /// `fuzz/regressions/` holds a small witness for each. Neither growth
76 /// amplifies: the id-space one is cheap per entry and the single-id one runs
77 /// under 1:1, so what fails on a parser that evicts nothing is feeding it more
78 /// than the caps, and that is a hundred megabytes, not a file to commit.
79 #[test]
80 fn unbounded_transmission_growth_stays_capped() {
81 // 200,000 unfinished chunks under distinct ids. Without eviction each one
82 // leaves an entry behind for the life of the terminal.
83 let mut many = Vec::new();
84 for i in 0..200_000u32 {
85 many.extend_from_slice(format!("\x1b_Ga=T,f=32,i={i},m=1;QUJD\x1b\\").as_bytes());
86 }
87 kittygfx::oracle::check_bodies(&many);
88
89 // A single id fed past the budget, never finished. Without the cap,
90 // retained memory is bounded only by how many bytes the writer sends.
91 let chunk = "A".repeat(4096);
92 // Two and a half times the budget. Enough that an uncapped parser lands
93 // over the absolute ceiling rather than merely near it; a capped one stops
94 // decoding at the budget, so the extra costs it nothing.
95 let bodies = (kittygfx::MAX_IN_FLIGHT_BYTES / 3072) * 5 / 2;
96 let mut one = Vec::new();
97 for _ in 0..bodies {
98 one.extend_from_slice(format!("\x1b_Ga=T,f=32,i=1,m=1;{chunk}\x1b\\").as_bytes());
99 }
100 kittygfx::oracle::check_bodies(&one);
101 }
102