Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ dependencies = [
"mime",
"multi_try",
"multimap 0.10.1",
"nom",
"nom 7.1.3",
"nom_locate",
"parking_lot",
"percent-encoding",
Expand Down Expand Up @@ -1387,13 +1387,14 @@ dependencies = [

[[package]]
name = "bnf"
version = "0.5.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c09ea5795b3dd735ff47c4b8adf64c46e3ce056fa3c4880b865a352e4c40a2"
checksum = "35b77b055f8cb1d566fa4ef55bc699f60eefb17927dc25fa454a05b6fabf7aa4"
dependencies = [
"getrandom 0.2.16",
"nom",
"rand 0.8.5",
"getrandom 0.3.4",
"hashbrown 0.16.1",
"nom 8.0.0",
"rand 0.9.2",
"serde",
"serde_json",
]
Expand Down Expand Up @@ -4416,6 +4417,15 @@ dependencies = [
"minimal-lexical",
]

[[package]]
name = "nom"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
dependencies = [
"memchr",
]

[[package]]
name = "nom_locate"
version = "4.2.0"
Expand All @@ -4424,7 +4434,7 @@ checksum = "1e3c83c053b0713da60c5b8de47fe8e494fe3ece5267b2f23090a07a053ba8f3"
dependencies = [
"bytecount",
"memchr",
"nom",
"nom 7.1.3",
]

[[package]]
Expand Down Expand Up @@ -5637,7 +5647,7 @@ dependencies = [
"cookie-factory",
"crc16",
"log",
"nom",
"nom 7.1.3",
]

[[package]]
Expand Down Expand Up @@ -5941,7 +5951,6 @@ dependencies = [
"http 1.4.0",
"libfuzzer-sys",
"log",
"rand 0.8.5",
"reqwest",
"schemars",
"serde",
Expand Down Expand Up @@ -6335,9 +6344,9 @@ dependencies = [

[[package]]
name = "serde_json"
version = "1.0.148"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"indexmap 2.12.1",
"itoa",
Expand Down
4 changes: 1 addition & 3 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ libfuzzer-sys = "=0.4.10"
apollo-federation = { path = "../apollo-federation" }
apollo-parser.workspace = true
apollo-smith.workspace = true
bnf = "0.5.0"
bnf = "0.6"
env_logger = "0.11.0"
log = "0.4"
# Required until https://github.com/shnewto/bnf/pull/175, remove when bnf 0.6 is out
rand = "=0.8.5"
reqwest = { workspace = true, features = ["json", "blocking"] }
serde_json.workspace = true

Expand Down
56 changes: 30 additions & 26 deletions fuzz/fuzz_targets/connector_selection_parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,26 @@
use std::sync::LazyLock;

use apollo_federation::connectors::JSONSelection;
use bnf::CoverageGuided;
use bnf::Grammar;
use libfuzzer_sys::arbitrary;
use libfuzzer_sys::arbitrary::Arbitrary;
use libfuzzer_sys::fuzz_target;
use libfuzzer_sys::Corpus;
use rand::rngs::StdRng;

fuzz_target!(|input: GeneratedSelection| -> Corpus {
// Generating a selection might choose a path which recurses too deeply, so
// we just mark those traversals as being rejected since they would require
// seeding and iterating the Rng.
let Some(selection) = input.0 else {
/// Generations per fuzz input. CoverageGuided prefers grammar productions
/// not yet exercised; multiple generations let that coverage accumulate.
const GENERATIONS_PER_INPUT: usize = 8;

fuzz_target!(|input: GeneratedSelections| -> Corpus {
if input.0.is_empty() {
return Corpus::Reject;
};
}

let parsed = JSONSelection::parse(&selection).unwrap();
drop(parsed);
for selection in &input.0 {
let parsed = JSONSelection::parse(selection).unwrap();
drop(parsed);
}

Corpus::Keep
});
Expand Down Expand Up @@ -84,29 +87,30 @@ const BNF_GRAMMAR: &str = r##"
"##;
static GRAMMAR: LazyLock<Grammar> = LazyLock::new(|| BNF_GRAMMAR.parse().unwrap());

struct GeneratedSelection(Option<String>);
impl<'a> Arbitrary<'a> for GeneratedSelection {
/// One fuzz input: a seed produces multiple grammar-generated strings via
/// CoverageGuided, which prefers productions not yet used so we exercise
/// more of the grammar per input.
struct GeneratedSelections(Vec<String>);
impl<'a> Arbitrary<'a> for GeneratedSelections {
fn arbitrary(u: &mut libfuzzer_sys::arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let bytes = <[u8; 32] as Arbitrary>::arbitrary(u)?;
let mut rng: StdRng = rand::SeedableRng::from_seed(bytes);
let mut strategy = CoverageGuided::from_seed(bytes);

let selection = GRAMMAR.generate_seeded(&mut rng).ok();
Ok(GeneratedSelection(selection))
let selections: Vec<String> = (0..GENERATIONS_PER_INPUT)
.filter_map(|_| GRAMMAR.generate_seeded_with_strategy(&mut strategy).ok())
.collect();
Ok(GeneratedSelections(selections))
}
}

impl std::fmt::Debug for GeneratedSelection {
impl std::fmt::Debug for GeneratedSelections {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0
.as_deref()
.map(|selection| {
write!(f, "```original\n{}\n```", selection)?;
if let Ok(parsed) = JSONSelection::parse(selection) {
write!(f, "\n\n```pretty\n{}\n```", parsed)?;
}

Ok(())
})
.unwrap_or(Ok(()))
for selection in &self.0 {
write!(f, "```original\n{}\n```", selection)?;
if let Ok(parsed) = JSONSelection::parse(selection) {
write!(f, "\n\n```pretty\n{}\n```", parsed)?;
}
}
Ok(())
}
}