Skip to content

Commit 527d6fd

Browse files
authored
feat(fuzz): add envsubst_fuzz target (#1144)
## Summary - Add fuzz target for the `envsubst` builtin covering arbitrary text with variable references - Tests special characters in variable names, nested references, unclosed braces, `-v` flag, and SHELL-FORMAT restriction ## Test plan - [x] `cargo check` passes in fuzz directory - [ ] CI green Closes #1105
1 parent 1c50187 commit 527d6fd

File tree

2 files changed

+91
-0
lines changed

2 files changed

+91
-0
lines changed

crates/bashkit/fuzz/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,10 @@ path = "fuzz_targets/base64_fuzz.rs"
8080
test = false
8181
doc = false
8282
bench = false
83+
84+
[[bin]]
85+
name = "envsubst_fuzz"
86+
path = "fuzz_targets/envsubst_fuzz.rs"
87+
test = false
88+
doc = false
89+
bench = false
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//! Fuzz target for the envsubst builtin
2+
//!
3+
//! Tests environment variable substitution to find:
4+
//! - Panics on malformed variable references ($, ${, ${VAR, etc.)
5+
//! - Edge cases with special characters in variable names
6+
//! - Nested or recursive variable references
7+
//! - Unclosed braces and partial substitution syntax
8+
//!
9+
//! Run with: cargo +nightly fuzz run envsubst_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+
// Reject deeply nested braces
24+
let depth: i32 = input
25+
.bytes()
26+
.map(|b| match b {
27+
b'{' => 1,
28+
b'}' => -1,
29+
_ => 0,
30+
})
31+
.scan(0i32, |acc, d| {
32+
*acc += d;
33+
Some(*acc)
34+
})
35+
.max()
36+
.unwrap_or(0);
37+
if depth > 15 {
38+
return;
39+
}
40+
41+
let rt = tokio::runtime::Builder::new_current_thread()
42+
.enable_all()
43+
.build()
44+
.unwrap();
45+
46+
rt.block_on(async {
47+
let mut bash = bashkit::Bash::builder()
48+
.limits(
49+
bashkit::ExecutionLimits::new()
50+
.max_commands(50)
51+
.max_subst_depth(3)
52+
.max_stdout_bytes(4096)
53+
.max_stderr_bytes(4096)
54+
.timeout(std::time::Duration::from_millis(200)),
55+
)
56+
.env("HOME", "/home/user")
57+
.env("PATH", "/usr/bin")
58+
.env("LANG", "en_US.UTF-8")
59+
.env("TESTVAR", "hello world")
60+
.build();
61+
62+
// Test 1: envsubst on fuzzed text with variable references
63+
let script = format!(
64+
"echo '{}' | envsubst 2>/dev/null; true",
65+
input.replace('\'', "'\\''"),
66+
);
67+
let _ = bash.exec(&script).await;
68+
69+
// Test 2: envsubst with -v flag to list variables
70+
let script2 = format!(
71+
"echo '{}' | envsubst -v 2>/dev/null; true",
72+
input.replace('\'', "'\\''"),
73+
);
74+
let _ = bash.exec(&script2).await;
75+
76+
// Test 3: envsubst with SHELL-FORMAT restriction
77+
let script3 = format!(
78+
"echo '{}' | envsubst '$HOME $PATH' 2>/dev/null; true",
79+
input.replace('\'', "'\\''"),
80+
);
81+
let _ = bash.exec(&script3).await;
82+
});
83+
}
84+
});

0 commit comments

Comments
 (0)