Skip to content

Commit 3945f49

Browse files
committed
add gz header fuzz test
1 parent 7701dd3 commit 3945f49

File tree

2 files changed

+164
-2
lines changed

2 files changed

+164
-2
lines changed

test-libz-rs-sys/src/deflate.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2540,3 +2540,166 @@ fn test_deflate_get_dict() {
25402540
dictionary
25412541
});
25422542
}
2543+
2544+
#[derive(Debug, Clone)]
2545+
struct GzHeaderData {
2546+
text: i32,
2547+
time: core::ffi::c_ulong,
2548+
os: i32,
2549+
extra: Vec<u8>,
2550+
name: CString,
2551+
comment: CString,
2552+
hcrc: i32,
2553+
}
2554+
2555+
impl GzHeaderData {
2556+
fn as_gz_header(&mut self) -> libz_rs_sys::gz_header {
2557+
libz_rs_sys::gz_header {
2558+
text: self.text,
2559+
time: self.time,
2560+
xflags: 0,
2561+
os: self.os,
2562+
extra: self.extra.as_mut_ptr(),
2563+
extra_len: self.extra.len().try_into().unwrap(),
2564+
extra_max: 0, // doesn't matter for writing.
2565+
name: self.name.as_ptr() as *mut u8, // hack: UB if written to, but we shouldn't write during deflate.
2566+
name_max: 0, // doesn't matter for writing.
2567+
comment: self.comment.as_ptr() as *mut u8, // hack: UB if written to, but we shouldn't write during deflate.
2568+
comm_max: 0, // doesn't matter for writing.
2569+
hcrc: self.hcrc,
2570+
done: 0, // doesn't matter for writing.
2571+
}
2572+
}
2573+
}
2574+
2575+
#[derive(Debug)]
2576+
struct Input {
2577+
source: String,
2578+
config: DeflateConfig,
2579+
flush: DeflateFlush,
2580+
header: GzHeaderData,
2581+
}
2582+
2583+
#[test]
2584+
fn test_gzip_deflate_config() {
2585+
let input = Input {
2586+
source: "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0~\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_\0\0\0\0%\0\0\0\0\0\0\0\0\0\u{13}\0\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}i\\\\\\\\\\\\\\\\\\\\\\\\\\b\\\\\\\\\\\\\\\\\\\\\\\\\0\0\0\0\0\0\\\\\0\0\0\0\0\0\0\0\0\0\0\0\0\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{1a}\u{1b}\u{1b}&\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\0\u{3}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\\\\\\\0\0\0\0\0\0\0_\0\0\0\0%\0\0\0\0\0\0\0\0\0\u{13}\0\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}i\\\\\\\\\\\\\\\\\\\\\\\\\\b\\\\\\\\\\\\\\\\\\\\\\\\\0\0\0\0\0\0\\\\\0\0\0\0\0\0\0\0\0\0\0\0\0\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{1a}\u{1b}\u{1b}&\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\0\u{3}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\\\\\\\\\\\\L\\\\L\\\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\0\u{3}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\\\\\\\\\\\\L\\\\L\\*".to_owned(),
2587+
config: DeflateConfig {
2588+
level: -1,
2589+
method: Method::Deflated,
2590+
window_bits: 31,
2591+
mem_level: 1,
2592+
strategy: Strategy::Fixed,
2593+
},
2594+
flush: DeflateFlush::Block,
2595+
header: GzHeaderData {
2596+
text: -120653576,
2597+
time: 17940362863843014904u64 as _, // may truncate, that's fine.
2598+
os: -1,
2599+
extra: vec![
2600+
0,
2601+
],
2602+
name: CString::default(),
2603+
comment: CString::from(c"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"),
2604+
hcrc: 1548508252,
2605+
},
2606+
};
2607+
2608+
assert_eq_rs_ng!({
2609+
let Input {
2610+
ref source,
2611+
config,
2612+
flush,
2613+
ref header,
2614+
} = input;
2615+
2616+
let mut header = header.clone();
2617+
2618+
// Initialize stream.
2619+
let mut stream = core::mem::MaybeUninit::zeroed();
2620+
let err = unsafe {
2621+
deflateInit2_(
2622+
stream.as_mut_ptr(),
2623+
config.level,
2624+
config.method as i32,
2625+
config.window_bits,
2626+
config.mem_level,
2627+
config.strategy as i32,
2628+
zlibVersion(),
2629+
size_of::<z_stream>() as c_int,
2630+
)
2631+
};
2632+
2633+
assert_eq!(err, ReturnCode::Ok as i32);
2634+
2635+
let streamp = unsafe { stream.assume_init_mut() };
2636+
2637+
// Create header.
2638+
let mut header = unsafe {
2639+
#[allow(clippy::useless_transmute)]
2640+
core::mem::transmute::<libz_rs_sys::gz_header, gz_header>(header.as_gz_header())
2641+
};
2642+
let err = unsafe { deflateSetHeader(streamp, &mut header as gz_headerp) };
2643+
if err != ReturnCode::Ok as i32 {
2644+
// Deallocate, so that we don't trigger ASAN leak detector.
2645+
let err = unsafe { deflateEnd(streamp) };
2646+
assert_eq!(err, ReturnCode::Ok as i32);
2647+
return;
2648+
}
2649+
2650+
let bound = unsafe { deflateBound(streamp, source.len() as _) };
2651+
let buf_size = match flush {
2652+
DeflateFlush::NoFlush | DeflateFlush::Finish => bound,
2653+
// Other flush options might require more than `bound` bytes, so add a safety margin. We
2654+
// _could_ catch Z_BUF_ERROR to allocate more memory, but that's not interesting right now.
2655+
// (In fact, it's more interesting if NoFlush/Finish triggers UB write if we're
2656+
// miscalculating)
2657+
DeflateFlush::PartialFlush
2658+
| DeflateFlush::SyncFlush
2659+
| DeflateFlush::FullFlush
2660+
| DeflateFlush::Block => bound * 2,
2661+
};
2662+
2663+
// We deliberately use uninitialized memory as the input buffer here, to simulate how these
2664+
// functions will likely be used from C, and to catch us ever reading uninitialized memory.
2665+
let mut dest: Vec<u8> = Vec::with_capacity(buf_size as usize);
2666+
2667+
let max = c_uint::MAX as usize;
2668+
2669+
let mut left = dest.capacity();
2670+
let mut source_len = source.len();
2671+
2672+
streamp.next_in = source.as_ptr().cast_mut().cast();
2673+
streamp.next_out = dest.as_mut_ptr().cast();
2674+
2675+
loop {
2676+
if streamp.avail_out == 0 {
2677+
streamp.avail_out = Ord::min(left, max) as _;
2678+
left -= streamp.avail_out as usize;
2679+
}
2680+
2681+
if streamp.avail_in == 0 {
2682+
streamp.avail_in = Ord::min(source_len, max) as _;
2683+
source_len -= streamp.avail_in as usize;
2684+
}
2685+
2686+
let flush = if source_len > 0 {
2687+
flush
2688+
} else {
2689+
DeflateFlush::Finish
2690+
};
2691+
2692+
let err = unsafe { deflate(streamp, flush as i32) };
2693+
if ReturnCode::from(err) != ReturnCode::Ok {
2694+
break;
2695+
}
2696+
}
2697+
2698+
unsafe { dest.set_len(streamp.total_out as usize) }
2699+
2700+
let err = unsafe { deflateEnd(streamp) };
2701+
assert_eq!(ReturnCode::from(err), ReturnCode::Ok);
2702+
2703+
Some(dest)
2704+
});
2705+
}

test-libz-rs-sys/src/gz.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
use zlib_rs::c_api::*;
2-
31
use z_rs::{
42
gzFile_s, gzbuffer, gzclearerr, gzclose, gzclose_r, gzclose_w, gzdirect, gzdopen, gzerror,
53
gzflush, gzfread, gzfwrite, gzgetc, gzgetc_, gzgets, gzoffset, gzopen, gzopen64, gzputc,
64
gzputs, gzread, gzrewind, gzseek, gzsetparams, gztell, gzungetc, gzwrite,
75
};
6+
use zlib_rs::c_api::*;
87

98
use libc::size_t;
109
use std::ffi::{c_char, c_int, c_uint, c_void, CStr, CString};

0 commit comments

Comments
 (0)