Skip to content

Commit 51c7e67

Browse files
committed
feat(fuzz): add yaml_fuzz target
Closes #1097 — Fuzz target for the hand-written YAML parser in the yaml builtin. Tests parsing with get queries, keys listing, and type detection on arbitrary YAML documents.
1 parent 51f63a9 commit 51c7e67

File tree

2 files changed

+90
-0
lines changed

2 files changed

+90
-0
lines changed

crates/bashkit/fuzz/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,10 @@ path = "fuzz_targets/sed_fuzz.rs"
9494
test = false
9595
doc = false
9696
bench = false
97+
98+
[[bin]]
99+
name = "yaml_fuzz"
100+
path = "fuzz_targets/yaml_fuzz.rs"
101+
test = false
102+
doc = false
103+
bench = false
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//! Fuzz target for the yaml builtin
2+
//!
3+
//! Tests the hand-written YAML parser to find:
4+
//! - Panics on malformed YAML documents
5+
//! - Stack overflow from deeply nested structures
6+
//! - Edge cases with anchors, special characters, multiline strings
7+
//! - Memory exhaustion from pathological input
8+
//!
9+
//! Run with: cargo +nightly fuzz run yaml_fuzz -- -max_total_time=300
10+
11+
#![no_main]
12+
13+
use libfuzzer_sys::fuzz_target;
14+
15+
fuzz_target!(|data: &[u8]| {
16+
// Only process valid UTF-8
17+
if let Ok(input) = std::str::from_utf8(data) {
18+
// Limit input size to prevent OOM
19+
if input.len() > 1024 {
20+
return;
21+
}
22+
23+
// Split input into YAML content and query path
24+
let (yaml_doc, query) = match input.find('\n') {
25+
Some(pos) => (&input[..pos], &input[pos + 1..]),
26+
None => (input, "." as &str),
27+
};
28+
29+
// Skip empty documents
30+
if yaml_doc.trim().is_empty() {
31+
return;
32+
}
33+
34+
// Reject deeply nested structures
35+
let depth = yaml_doc
36+
.bytes()
37+
.filter(|&b| b == b'{' || b == b'[')
38+
.count();
39+
if depth > 20 {
40+
return;
41+
}
42+
43+
let rt = tokio::runtime::Builder::new_current_thread()
44+
.enable_all()
45+
.build()
46+
.unwrap();
47+
48+
rt.block_on(async {
49+
let mut bash = bashkit::Bash::builder()
50+
.limits(
51+
bashkit::ExecutionLimits::new()
52+
.max_commands(50)
53+
.max_subst_depth(3)
54+
.max_stdout_bytes(4096)
55+
.max_stderr_bytes(4096)
56+
.timeout(std::time::Duration::from_millis(200)),
57+
)
58+
.build();
59+
60+
// Test 1: parse YAML and query with get
61+
let script = format!(
62+
"echo '{}' | yaml get '{}' 2>/dev/null; true",
63+
yaml_doc.replace('\'', "'\\''"),
64+
query.replace('\'', "'\\''"),
65+
);
66+
let _ = bash.exec(&script).await;
67+
68+
// Test 2: parse YAML and list keys
69+
let script2 = format!(
70+
"echo '{}' | yaml keys 2>/dev/null; true",
71+
yaml_doc.replace('\'', "'\\''"),
72+
);
73+
let _ = bash.exec(&script2).await;
74+
75+
// Test 3: parse YAML and get type
76+
let script3 = format!(
77+
"echo '{}' | yaml type 2>/dev/null; true",
78+
yaml_doc.replace('\'', "'\\''"),
79+
);
80+
let _ = bash.exec(&script3).await;
81+
});
82+
}
83+
});

0 commit comments

Comments
 (0)