//! Replay every committed sanitizer input through the oracle, on stable. //! //! The soak tier runs on astra, on nightly, when the box is idle. That is the //! wrong place for the only copy of a property to live: a crash found there is //! fixed here, and nothing on a developer's machine would notice it coming //! back. //! //! So both corpora replay as an ordinary `cargo test`: //! //! - `fuzz/seeds/{html,css}/` is human intent, the shapes worth reaching first. //! - `fuzz/regressions/` is inputs that once found a bug. Anything landing //! there is a permanent test by virtue of the directory, with no test //! function to write and no chance of forgetting one. //! //! The assertions are `custom_pages::oracle::check_html` and `check_css`, the //! same functions the libFuzzer targets will call, so the two cannot check //! different things. (The fuzz crate itself belongs to the soak-target tasks //! this extraction unblocks; the oracle and these corpora do not wait on it.) use std::path::Path; const OWNER_SCOPE: &str = "11111111-1111-1111-1111-111111111111"; fn policy() -> custom_pages::UrlPolicy { custom_pages::UrlPolicy::new( "https://u.makenot.work/alice/proj", [ "makenot.work".to_string(), "u.makenot.work".to_string(), "cdn.makenot.work".to_string(), ], ) .expect("the fixture policy is well-formed") } /// Replay one directory, returning how many inputs it held. /// /// A missing directory is fine and returns zero: `fuzz/regressions/` does not /// exist until the first crash does. /// /// **Each input is replayed on its own thread, with a large stack**, for the /// reason the css fuzz target spawns one too: an input can flatten to a /// stylesheet far larger and more deeply nested than itself (214 bytes to /// 219 KB, measured), and the oracle's reparse of that recurses per nesting /// level. A debug build's frames are several times a release build's, so this /// suite aborted on a default 2 MiB test-thread stack while the same inputs /// passed under `--release`. It aborted on both fw13 and astra, and was found /// by a `cargo mutants` BASELINE rather than by a test run, because a stack /// overflow kills the process before libtest can print a result line -- so /// `cargo test | grep "test result"` showed the lib suite passing and said /// nothing at all about this one. /// /// A spawned thread is also the only option: a stack overflow aborts the /// process and `catch_unwind` cannot see it. fn replay(dir: &Path, check: impl Fn(&str) + Copy + Send + 'static) -> usize { let Ok(entries) = std::fs::read_dir(dir) else { return 0; }; let mut count = 0; for entry in entries { let path = entry.expect("readable dir entry").path(); if !path.is_file() { continue; } // READMEs are documentation, not inputs. if path.extension().is_some_and(|e| e == "md") { continue; } let bytes = std::fs::read(&path).expect("readable input"); // The targets take `&str`, so a non-UTF-8 artifact is not one of ours. let Ok(text) = std::str::from_utf8(&bytes) else { continue; }; let owned = text.to_string(); let handle = std::thread::Builder::new() .stack_size(256 * 1024 * 1024) .spawn(move || check(&owned)) .expect("spawning the replay thread"); // Naming the file matters: a bare oracle panic in a loop over twenty // inputs says nothing about which one broke. assert!(handle.join().is_ok(), "oracle failed on {}", path.display()); count += 1; } count } /// A regressions directory holds inputs for both targets, so each is replayed /// through both oracles. An HTML crash file is valid CSS input and vice versa; /// running both costs nothing and removes the question of which subdirectory a /// new artifact belongs in. fn replay_both(dir: &Path) -> usize { let html = replay(dir, |s: &str| { custom_pages::oracle::check_html(s, &policy()); }); let css = replay(dir, |s: &str| { custom_pages::oracle::check_css(s, OWNER_SCOPE, &policy()); }); assert_eq!(html, css, "the same directory yielded two different counts"); html } #[test] fn html_seeds_satisfy_the_oracle() { let n = replay( Path::new(env!("CARGO_MANIFEST_DIR")) .join("fuzz/seeds/html") .as_path(), |s: &str| custom_pages::oracle::check_html(s, &policy()), ); assert!(n > 0, "no HTML seeds found; the corpus is the point"); } #[test] fn css_seeds_satisfy_the_oracle() { let n = replay( Path::new(env!("CARGO_MANIFEST_DIR")) .join("fuzz/seeds/css") .as_path(), |s: &str| custom_pages::oracle::check_css(s, OWNER_SCOPE, &policy()), ); assert!(n > 0, "no CSS seeds found; the corpus is the point"); } #[test] fn every_past_crash_stays_fixed() { // No assertion on the count. An empty regressions directory is the good // state, and a test that demanded a crash to have happened would be a test // that fails on a healthy crate. let n = replay_both( Path::new(env!("CARGO_MANIFEST_DIR")) .join("fuzz/regressions") .as_path(), ); println!("replayed {n} regression inputs"); }