use rusty_jpeg::decode::Decoder;
use std::io::Cursor;
fn xorshift(s: &mut u64) -> u64 {
*s ^= *s << 13;
*s ^= *s >> 7;
*s ^= *s << 17;
*s
}
fn mutate(src: &[u8], kind: u64, rng: &mut u64) -> Vec<u8> {
let mut v = src.to_vec();
if v.len() < 8 {
return v;
}
match kind % 6 {
0 => {
let i = (xorshift(rng) as usize) % v.len();
v[i] ^= 1 << (xorshift(rng) % 8);
}
1 => {
let n = (xorshift(rng) as usize) % v.len();
v.truncate(n.max(2));
}
2 => {
for i in 2..v.len().saturating_sub(4) {
if v[i] == 0xFF && !matches!(v[i + 1], 0x00 | 0xD8 | 0xD9) {
v[i + 2] = xorshift(rng) as u8;
v[i + 3] = xorshift(rng) as u8;
break;
}
}
}
3 => {
let i = (xorshift(rng) as usize) % (v.len() - 2) + 1;
v[i] = 0xFF;
v[i + 1] = (xorshift(rng) % 0xFD) as u8 + 1;
}
4 => {
let i = (xorshift(rng) as usize) % v.len();
let n = ((xorshift(rng) as usize) % 64).min(v.len() - i);
for b in &mut v[i..i + n] {
*b = xorshift(rng) as u8;
}
}
_ => {
let i = (xorshift(rng) as usize) % v.len();
let n = ((xorshift(rng) as usize) % 32).min(v.len() - i);
v[i..i + n].fill(0);
}
}
v
}
fn main() {
let files: Vec<String> = std::env::args().skip(1).collect();
if files.is_empty() {
eprintln!("usage: fuzzprobe <seed.jpg>...");
std::process::exit(2);
}
let iters: usize = std::env::var("FUZZ_ITERS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(20_000);
let mut ok = 0u64;
let mut err = 0u64;
let mut panics = Vec::new();
for path in &files {
let Ok(src) = std::fs::read(path) else {
eprintln!("skip {path}");
continue;
};
for i in 0..iters {
let mut rng = 0x9E3779B97F4A7C15u64 ^ (i as u64).wrapping_mul(0x0F1B_2C3D);
xorshift(&mut rng);
let data = mutate(&src, i as u64, &mut rng);
let planar = i % 2 == 0;
let res = std::panic::catch_unwind(|| {
let mut d = Decoder::new(Cursor::new(&data));
d.set_single_threaded(true);
if planar {
d.decode_planar().map(|p| p.components.len())
} else {
d.decode().map(|p| p.len())
}
});
match res {
Ok(Ok(_)) => ok += 1,
Ok(Err(_)) => err += 1,
Err(_) => {
panics.push((path.clone(), i, planar, data.len()));
if panics.len() > 20 {
break;
}
}
}
}
}
println!(
"{} seeds x {iters} mutations: {ok} decoded, {err} rejected, {} PANICS",
files.len(),
panics.len()
);
for (f, i, planar, n) in panics.iter().take(20) {
println!(
" PANIC seed={} case={i} path={} bytes={n}",
f,
if *planar { "planar" } else { "packed" }
);
}
if !panics.is_empty() {
std::process::exit(1);
}
}