Skip to content

Commit abef37a

Browse files
serde_2026: dedicated panic-finder fuzz target
The existing serde-2026 fuzzer is a roundtrip checker — it Corpus::Reject's any blob that fails to fully decode, which means libfuzzer never accumulates seeds for the "almost-decodes-then-aborts" failure mode. The historical de.rs OOM (tiny blob declaring instruction_count = 2^54, abort in Vec::with_capacity before any instruction is read) lives in exactly that bucket and was not surfaced by the existing fuzzer; it took Cursor's static analysis to find. Add serde-2026-panic, which feeds arbitrary bytes to all three public entry points — deserialize_2026_body, node_from_bytes_serde_2026, serialized_length_serde_2026 — under both strict={false,true}, and asserts only that nothing panics, aborts, or overflows the stack. Result::Err is fine; SIGABRT, unwinding panic, or SO is a bug. CI's existing fuzz job auto-discovers targets via `cargo +nightly fuzz list` and gives each 30s, so no workflow change is needed. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 6ac4265 commit abef37a

2 files changed

Lines changed: 47 additions & 0 deletions

File tree

fuzz/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,9 @@ name = "serde-2026-varint"
160160
path = "fuzz_targets/serde_2026_varint.rs"
161161
test = false
162162
doc = false
163+
164+
[[bin]]
165+
name = "serde-2026-panic"
166+
path = "fuzz_targets/serde_2026_panic.rs"
167+
test = false
168+
doc = false
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#![no_main]
2+
3+
//! Panic-finder for the serde_2026 entry points.
4+
//!
5+
//! Companion to `serde_2026.rs` (which proves *correctness* via roundtrip on
6+
//! valid inputs) and `serde_2026_varint.rs` (which targets the varint codec).
7+
//! This target proves *robustness*: feed every entry point arbitrary bytes
8+
//! under both `strict={false,true}` and assert only that none of them panic,
9+
//! abort, or stack-overflow. `Result::Err` is fine; `Ok` is fine. A SIGABRT
10+
//! from `handle_alloc_error`, an unwinding panic, or a stack overflow is a
11+
//! bug.
12+
//!
13+
//! This deliberately drops the `Corpus::Reject` filter that
14+
//! `serde_2026.rs` uses, so libfuzzer keeps mutating around blobs that fail
15+
//! validation — the historical OOM at `de.rs` (a tiny blob declaring
16+
//! `instruction_count = 2^54`) lives in exactly that "fails validation but
17+
//! aborts before erroring" bucket.
18+
19+
use clvmr::Allocator;
20+
use clvmr::serde_2026::{
21+
deserialize_2026_body, node_from_bytes_serde_2026, serialized_length_serde_2026,
22+
};
23+
use libfuzzer_sys::fuzz_target;
24+
25+
const FUZZ_MAX_ATOM_LEN: usize = 1 << 20;
26+
27+
fuzz_target!(|data: &[u8]| {
28+
for strict in [false, true] {
29+
// Body-only deserializer — slice length is the natural bound.
30+
let mut a = Allocator::new();
31+
let _ = deserialize_2026_body(&mut a, data, FUZZ_MAX_ATOM_LEN, strict);
32+
33+
// Prefix-aware deserializer — same body parser, plus magic-prefix strip.
34+
let mut a = Allocator::new();
35+
let _ = node_from_bytes_serde_2026(&mut a, data, FUZZ_MAX_ATOM_LEN, strict);
36+
37+
// Length probe — walks the wire format without building a tree, so
38+
// it has its own opportunity to allocate or recurse pathologically.
39+
let _ = serialized_length_serde_2026(data, FUZZ_MAX_ATOM_LEN, strict);
40+
}
41+
});

0 commit comments

Comments
 (0)