Skip to content

Commit c13e36f

Browse files
authored
feat(fuzz): add archive_fuzz target (#1150)
## Summary - Add fuzz target for tar/gzip/gunzip builtins in `builtins/archive.rs` - Tests decompression of arbitrary binary data, tar listing, and gzip roundtrip - Exercises zip bomb detection, malformed header handling, and truncated archives Closes #1101 ## Test plan - [x] `cargo check` in fuzz crate passes - [x] `cargo fmt --check` clean - [x] `cargo clippy` clean
1 parent 91ed1c8 commit c13e36f

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed

crates/bashkit/fuzz/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,10 @@ path = "fuzz_targets/csv_fuzz.rs"
122122
test = false
123123
doc = false
124124
bench = false
125+
126+
[[bin]]
127+
name = "archive_fuzz"
128+
path = "fuzz_targets/archive_fuzz.rs"
129+
test = false
130+
doc = false
131+
bench = false
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//! Fuzz target for the archive builtins (tar, gzip, gunzip)
2+
//!
3+
//! Tests tar/gzip decompression with binary format parsing to find:
4+
//! - Panics on malformed headers or truncated archives
5+
//! - Zip bomb detection bypass via decompression ratio checks
6+
//! - Memory exhaustion from pathological compressed data
7+
//! - Edge cases in gzip header parsing and tar entry extraction
8+
//!
9+
//! Run with: cargo +nightly fuzz run archive_fuzz -- -max_total_time=300
10+
11+
#![no_main]
12+
13+
use libfuzzer_sys::fuzz_target;
14+
15+
fuzz_target!(|data: &[u8]| {
16+
// Limit input size to prevent OOM (binary data, no UTF-8 requirement)
17+
if data.len() > 2048 {
18+
return;
19+
}
20+
21+
// Need at least some data to be meaningful
22+
if data.is_empty() {
23+
return;
24+
}
25+
26+
// Convert to a hex-escaped string for safe shell embedding
27+
let hex: String = data.iter().map(|b| format!("\\x{b:02x}")).collect();
28+
if hex.len() > 16384 {
29+
return;
30+
}
31+
32+
let rt = tokio::runtime::Builder::new_current_thread()
33+
.enable_all()
34+
.build()
35+
.unwrap();
36+
37+
rt.block_on(async {
38+
let mut bash = bashkit::Bash::builder()
39+
.limits(
40+
bashkit::ExecutionLimits::new()
41+
.max_commands(50)
42+
.max_subst_depth(3)
43+
.max_stdout_bytes(8192)
44+
.max_stderr_bytes(4096)
45+
.timeout(std::time::Duration::from_millis(200)),
46+
)
47+
.build();
48+
49+
// Test 1: attempt gunzip on arbitrary binary data
50+
let script = format!(
51+
"printf '{}' | gunzip 2>/dev/null; true",
52+
hex,
53+
);
54+
let _ = bash.exec(&script).await;
55+
56+
// Test 2: attempt tar listing on arbitrary binary data
57+
let script2 = format!(
58+
"printf '{}' | tar -tf - 2>/dev/null; true",
59+
hex,
60+
);
61+
let _ = bash.exec(&script2).await;
62+
63+
// Test 3: attempt gzip then gunzip roundtrip on valid UTF-8
64+
if let Ok(text) = std::str::from_utf8(data) {
65+
if text.len() <= 512 {
66+
let script3 = format!(
67+
"echo '{}' | gzip | gunzip 2>/dev/null; true",
68+
text.replace('\'', "'\\''"),
69+
);
70+
let _ = bash.exec(&script3).await;
71+
}
72+
}
73+
});
74+
});

0 commit comments

Comments
 (0)