From 1176c654bad7510ca2b67045f57c8ff06a154b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Sun, 18 Aug 2024 02:08:07 +0200 Subject: [PATCH] fuzz: fuzz everything at once --- fuzzing/Cargo.toml | 3 +- fuzzing/README.md | 2 +- fuzzing/fuzz/Cargo.toml | 6 +++ fuzzing/fuzz/fuzz_targets/all.rs | 9 ++++ fuzzing/fuzz/src/all.rs | 70 ++++++++++++++++++++++++++++++++ fuzzing/fuzz/src/html/mod.rs | 4 +- fuzzing/fuzz/src/lib.rs | 15 +++---- fuzzing/fuzz/src/parser.rs | 4 +- fuzzing/src/main.rs | 2 +- 9 files changed, 97 insertions(+), 18 deletions(-) create mode 100644 fuzzing/fuzz/fuzz_targets/all.rs create mode 100644 fuzzing/fuzz/src/all.rs diff --git a/fuzzing/Cargo.toml b/fuzzing/Cargo.toml index 2e5722aa..d9731dd9 100644 --- a/fuzzing/Cargo.toml +++ b/fuzzing/Cargo.toml @@ -6,8 +6,9 @@ license = "MIT OR Apache-2.0" publish = false [dependencies] -fuzz = { path = "fuzz"} +fuzz = { path = "fuzz" } +arbitrary = "1.3.2" pretty-error-debug = "0.3.0" thiserror = "1.0.63" diff --git a/fuzzing/README.md b/fuzzing/README.md index 52750b44..8a90adf9 100644 --- a/fuzzing/README.md +++ b/fuzzing/README.md @@ -13,7 +13,7 @@ Then execute in this folder: RUST_BACKTRACE=1 nice cargo +nightly fuzz run ``` -`fuzz_target` is one out of `html` or `parser`. +`fuzz_target` is one out of `all`, `html` or `parser`. The execution won't stop, but continue until you kill it with ctrl+c. Or until it finds a panic. diff --git a/fuzzing/fuzz/Cargo.toml b/fuzzing/fuzz/Cargo.toml index de12f4a0..2cd0dba5 100644 --- a/fuzzing/fuzz/Cargo.toml +++ b/fuzzing/fuzz/Cargo.toml @@ -15,6 +15,12 @@ arbitrary = { version = "1.3.2", features = ["derive"] } html-escape = "0.2.13" libfuzzer-sys = "0.4.7" +[[bin]] +name = "all" +path = "fuzz_targets/all.rs" +test = false +doc = false + [[bin]] name = "html" path = "fuzz_targets/html.rs" diff --git a/fuzzing/fuzz/fuzz_targets/all.rs b/fuzzing/fuzz/fuzz_targets/all.rs new file mode 100644 index 00000000..20fd1655 --- /dev/null +++ b/fuzzing/fuzz/fuzz_targets/all.rs @@ -0,0 +1,9 @@ +#![no_main] + +use fuzz::Scenario; + +libfuzzer_sys::fuzz_target!(|data: &[u8]| { + if let Ok(scenario) = fuzz::all::Scenario::new(data) { + let _ = scenario.run(); + } +}); diff --git a/fuzzing/fuzz/src/all.rs b/fuzzing/fuzz/src/all.rs new file mode 100644 index 00000000..d7826198 --- /dev/null +++ b/fuzzing/fuzz/src/all.rs @@ -0,0 +1,70 @@ +macro_rules! this_file { + ($(crate :: $mod:ident :: $ty:ident;)*) => { + use std::fmt; + + use arbitrary::{Arbitrary, Unstructured}; + + #[derive(Debug)] + pub enum Scenario<'a> { + $($ty(crate::$mod::Scenario<'a>),)* + } + + impl<'a> super::Scenario<'a> for Scenario<'a> { + type RunError = RunError; + + fn new(data: &'a [u8]) -> Result { + let mut data = Unstructured::new(data); + let target = Arbitrary::arbitrary(&mut data)?; + let data = Arbitrary::arbitrary_take_rest(data)?; + + match target { + $(Target::$ty => Ok(Self::$ty(crate::$mod::Scenario::new(data)?)),)* + } + } + + fn run(&self) -> Result<(), Self::RunError> { + match self { + $(Self::$ty(scenario) => scenario.run().map_err(RunError::$ty),)* + } + } + } + + #[derive(Arbitrary)] + enum Target { + $($ty,)* + } + + pub enum RunError { + $($ty( as crate::Scenario<'static>>::RunError),)* + } + + impl fmt::Display for RunError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + $(Self::$ty(err) => err.fmt(f),)* + } + } + } + + impl fmt::Debug for RunError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + $(Self::$ty(err) => err.fmt(f),)* + } + } + } + + impl std::error::Error for RunError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + $(RunError::$ty(err) => Some(err),)* + } + } + } + }; +} + +this_file! { + crate::html::Html; + crate::parser::Parser; +} diff --git a/fuzzing/fuzz/src/html/mod.rs b/fuzzing/fuzz/src/html/mod.rs index a9d87034..88f52307 100644 --- a/fuzzing/fuzz/src/html/mod.rs +++ b/fuzzing/fuzz/src/html/mod.rs @@ -11,11 +11,9 @@ pub enum Scenario<'a> { } impl<'a> super::Scenario<'a> for Scenario<'a> { - type NewError = arbitrary::Error; - type RunError = std::convert::Infallible; - fn new(data: &'a [u8]) -> Result { + fn new(data: &'a [u8]) -> Result { Self::arbitrary_take_rest(Unstructured::new(data)) } diff --git a/fuzzing/fuzz/src/lib.rs b/fuzzing/fuzz/src/lib.rs index 999d535c..efcd1038 100644 --- a/fuzzing/fuzz/src/lib.rs +++ b/fuzzing/fuzz/src/lib.rs @@ -1,3 +1,4 @@ +pub mod all; pub mod html; pub mod parser; @@ -5,18 +6,17 @@ use std::error::Error; use std::fmt; pub const TARGETS: &[(&str, TargetBuilder)] = &[ + ("all", |data| NamedTarget::new::(data)), ("html", |data| NamedTarget::new::(data)), ("parser", |data| NamedTarget::new::(data)), ]; -pub type TargetBuilder = - for<'a> fn(&'a [u8]) -> Result, Box>; +pub type TargetBuilder = for<'a> fn(&'a [u8]) -> Result, arbitrary::Error>; pub trait Scenario<'a>: fmt::Debug + Sized { - type NewError: Error + Send + 'static; type RunError: Error + Send + 'static; - fn new(data: &'a [u8]) -> Result; + fn new(data: &'a [u8]) -> Result; fn run(&self) -> Result<(), Self::RunError>; } @@ -53,11 +53,8 @@ impl fmt::Debug for NamedTarget<'_> { impl<'a> NamedTarget<'a> { #[inline] - fn new + 'a>(data: &'a [u8]) -> Result> { - match S::new(data) { - Ok(scenario) => Ok(Self(Box::new(scenario))), - Err(err) => Err(Box::new(err)), - } + fn new + 'a>(data: &'a [u8]) -> Result { + Ok(Self(Box::new(S::new(data)?))) } } diff --git a/fuzzing/fuzz/src/parser.rs b/fuzzing/fuzz/src/parser.rs index f2f647cd..122fd16d 100644 --- a/fuzzing/fuzz/src/parser.rs +++ b/fuzzing/fuzz/src/parser.rs @@ -8,11 +8,9 @@ pub struct Scenario<'a> { } impl<'a> super::Scenario<'a> for Scenario<'a> { - type NewError = arbitrary::Error; - type RunError = rinja_parser::ParseError; - fn new(data: &'a [u8]) -> Result { + fn new(data: &'a [u8]) -> Result { let mut data = Unstructured::new(data); let syntax = ArbitrarySyntax::arbitrary(&mut data)?; diff --git a/fuzzing/src/main.rs b/fuzzing/src/main.rs index 624eb7a4..1432862d 100644 --- a/fuzzing/src/main.rs +++ b/fuzzing/src/main.rs @@ -52,7 +52,7 @@ enum Error { #[error("could not read opened input file {}", .1.display())] Read(#[source] std::io::Error, PathBuf), #[error("could not build scenario")] - Build(#[source] Box), + Build(#[source] arbitrary::Error), #[error("could not run scenario")] Run(#[source] Box), }