Skip to content

Commit 4ecd532

Browse files
committed
added better validation to fuzz target
1 parent 928cad1 commit 4ecd532

1 file changed

Lines changed: 33 additions & 7 deletions

File tree

fuzz/fuzz_targets/validate_with_python.rs

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
//! - mutator selection (via bit flags)
2424
//! - arbitrary data seed for generation
2525
//!
26-
//! the generated pickles are validated using Python's `pickletools.dis()` to ensure
27-
//! they are structurally valid and can be parsed by the reference implementation.
28-
//! this catches bugs in:
26+
//! the generated pickles are validated using Python's `pickletools.genops()` (same
27+
//! validation logic as scripts/validate-pickles.py) to ensure they are structurally
28+
//! valid and can be parsed by the reference implementation. this catches bugs in:
2929
//! - opcode emission logic
3030
//! - stack simulation
3131
//! - protocol version handling
@@ -52,7 +52,7 @@
5252
//! Generated pickles must:
5353
//! 1. Be non-empty
5454
//! 2. End with STOP opcode (0x2e / '.')
55-
//! 3. Successfully parse with `pickletools.dis()`
55+
//! 3. Successfully parse with `pickletools.genops()` (via Python subprocess)
5656
#![no_main]
5757

5858
use libfuzzer_sys::fuzz_target;
@@ -61,9 +61,29 @@ use pickle_whip::mutators::{
6161
BitFlipMutator, BoundaryMutator, OffByOneMutator,
6262
StringLengthMutator, CharacterMutator, MemoIndexMutator,
6363
};
64+
use std::process::{Command, Stdio};
65+
use std::io::Write;
6466

65-
mod common;
66-
use crate::common::validate_with_loads;
67+
/// Validate pickle using Python's pickletools.genops() (same as validate-pickles.py)
68+
fn validate_with_python(pickle_bytes: &[u8]) -> bool {
69+
let mut child = match Command::new("python3")
70+
.arg("-c")
71+
.arg("import sys, pickletools; list(pickletools.genops(sys.stdin.buffer.read()))")
72+
.stdin(Stdio::piped())
73+
.stdout(Stdio::null())
74+
.stderr(Stdio::null())
75+
.spawn()
76+
{
77+
Ok(child) => child,
78+
Err(_) => return true, // Skip validation if Python unavailable
79+
};
80+
81+
if let Some(mut stdin) = child.stdin.take() {
82+
let _ = stdin.write_all(pickle_bytes);
83+
}
84+
85+
child.wait().map(|status| status.success()).unwrap_or(false)
86+
}
6787

6888
fuzz_target!(|data: &[u8]| {
6989
// need at least 7 bytes for configuration + some arbitrary data
@@ -75,7 +95,7 @@ fuzz_target!(|data: &[u8]| {
7595
let protocol = (data[0] % 6) as usize;
7696
let version = Version::try_from(protocol).unwrap();
7797

78-
let mut gen = Generator::new(version);
98+
let gen = Generator::new(version);
7999

80100
// bytes 1-4: opcode range (min/max)
81101
let min_opcodes = u16::from_le_bytes([data[1], data[2]]) as usize;
@@ -126,5 +146,11 @@ fuzz_target!(|data: &[u8]| {
126146
// basic structural validation
127147
assert!(!pickle.is_empty(), "generated pickle must not be empty");
128148
assert_eq!(pickle[pickle.len() - 1], b'.', "pickle must end with STOP opcode");
149+
150+
// validate with Python's pickletools
151+
assert!(
152+
validate_with_python(&pickle),
153+
"generated pickle failed Python validation"
154+
);
129155
}
130156
});

0 commit comments

Comments
 (0)