Skip to content

Commit

Permalink
Merge pull request #33 from memorysafety/gzip-inflate
Browse files Browse the repository at this point in the history
Gzip support for inflate
  • Loading branch information
folkertdev authored Feb 21, 2024
2 parents 0f6a4bc + e8d815d commit 336b6d4
Show file tree
Hide file tree
Showing 11 changed files with 850 additions and 119 deletions.
2 changes: 1 addition & 1 deletion fuzz/fuzz_targets/end_to_end.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::ffi::{c_char, c_int, c_uint};
fuzz_target!(|input: (String, DeflateConfig)| {
let (data, config) = input;

if !(0..=15).contains(&config.window_bits) {
if !(0..32).contains(&config.window_bits) {
// lower is raw, higher is gzip
return;
}
Expand Down
17 changes: 14 additions & 3 deletions libz-rs-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,20 @@ pub unsafe extern "C" fn inflateSetDictionary(
zlib_rs::inflate::set_dictionary(stream, dict) as _
}

// pub unsafe extern "C" fn inflateGetHeader(strm: z_streamp, head: gz_headerp) -> c_int {
// todo!("part of gzip support")
// }
// part of gzip
pub unsafe extern "C" fn inflateGetHeader(strm: z_streamp, head: gz_headerp) -> c_int {
if let Some(stream) = InflateStream::from_stream_mut(strm) {
let header = if head.is_null() {
None
} else {
Some(unsafe { &mut *(head) })
};

zlib_rs::inflate::get_header(stream, header) as i32
} else {
ReturnCode::StreamError as _
}
}

// undocumented but exposed function
pub unsafe extern "C" fn inflateUndermine(strm: *mut z_stream, subvert: i32) -> c_int {
Expand Down
231 changes: 223 additions & 8 deletions libz-rs-sys/src/tests/inflate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ use std::mem::ManuallyDrop;

use crate as libz_rs_sys;

use std::ffi::{c_char, c_int, c_void};
use std::ffi::{c_char, c_int, c_void, CStr};

use libz_rs_sys::*;
use zlib_rs::deflate::compress_slice;
use zlib_rs::inflate::{uncompress_slice, INFLATE_STATE_SIZE};
use zlib_rs::{Flush, MAX_WBITS};

const VERSION: *const c_char = "2.3.0\0".as_ptr() as *const c_char;
Expand Down Expand Up @@ -181,8 +183,31 @@ fn inf(input: &[u8], _what: &str, step: usize, win: i32, len: usize, err: c_int)

let mut out = vec![0u8; len];

let extra: [u8; 1024] = [0; 1024];
let name: [u8; 64] = [0; 64];
let comment: [u8; 64] = [0; 64];

// Set header
// See: https://www.zlib.net/manual.html
let mut header = gz_header {
text: 0,
time: 0,
xflags: 0,
os: 0,
extra: extra.as_ptr() as *mut u8,
extra_len: 0,
extra_max: 1024,
name: name.as_ptr() as *mut u8,
name_max: 64, // How / where should this be set?
comment: comment.as_ptr() as *mut u8,
comm_max: 64,
hcrc: 0,
done: 0,
};

if win == 47 {
todo!("gzip")
let err = unsafe { inflateGetHeader(&mut stream, &mut header) };
assert_eq!(ReturnCode::from(err), ReturnCode::Ok);
}

let mut have = input.len();
Expand All @@ -199,7 +224,9 @@ fn inf(input: &[u8], _what: &str, step: usize, win: i32, len: usize, err: c_int)
let ret = unsafe { inflate(&mut stream, Flush::NoFlush as _) };

if let Some(err) = err {
assert_eq!(ret, err)
if err != 9 {
assert_eq!(ret, err)
}
}

if !matches!(ret, Z_OK | Z_BUF_ERROR | Z_NEED_DICT) {
Expand Down Expand Up @@ -320,7 +347,7 @@ fn cover_wrap() {

// ------------------------------

let size = 2 * zlib_rs::inflate::INFLATE_STATE_SIZE + 256;
let size = 2 * INFLATE_STATE_SIZE + 256;
mem_limit(&mut strm, size);

ret = unsafe { inflatePrime(&mut strm, 16, 0) };
Expand Down Expand Up @@ -402,7 +429,6 @@ fn check_adler32() {
)
}
#[test]
#[ignore = "gzip"]
fn bad_header_crc() {
inf(
&[
Expand All @@ -417,7 +443,6 @@ fn bad_header_crc() {
}

#[test]
#[ignore = "gzip"]
fn check_gzip_length() {
inf(
&[
Expand All @@ -433,7 +458,6 @@ fn check_gzip_length() {
}

#[test]
#[ignore = "gzip"]
fn bad_zlib_header_check() {
inf(
&[0x78, 0x90],
Expand Down Expand Up @@ -814,7 +838,6 @@ fn copy_direct_from_output() {
}

#[test]
#[ignore = "gzip"]
fn cover_cve_2022_37434() {
inf(
&[
Expand Down Expand Up @@ -1070,3 +1093,195 @@ fn inflate_adler() {
let length = Ord::min(stream.total_out as usize, ORIGINAL.len());
assert_eq!(&uncompressed[..length], &ORIGINAL.as_bytes()[..length])
}

#[test]
fn inflate_get_header_non_gzip_stream() {
let mut stream = mem_setup();

let win = 15; // i.e. zlib compression (and not gzip)
let init_err = unsafe { inflateInit2_(&mut stream, win, VERSION, STREAM_SIZE) };
if init_err != Z_OK {
mem_done(&mut stream);
return;
}

let mut header = gz_header::default();

assert_eq!(
unsafe { inflateGetHeader(&mut stream, &mut header) },
ReturnCode::StreamError as i32
);
}

#[test]
fn inflate_window_bits_0_is_15() {
let input = b"Hello World!\n";

let mut compressed = [0; 64];
let (compressed, err) = compress_slice(&mut compressed, input, DeflateConfig::new(6));
assert_eq!(err, ReturnCode::Ok);

let config = InflateConfig { window_bits: 15 };
let mut output_15 = [0; 64];
let (output_15, err) = uncompress_slice(&mut output_15, compressed, config);
assert_eq!(err, ReturnCode::Ok);

let config = InflateConfig { window_bits: 0 };
let mut output_0 = [0; 64];
let (output_0, err) = uncompress_slice(&mut output_0, compressed, config);
assert_eq!(err, ReturnCode::Ok);

// for window size 0, the default of 15 is picked
// NOTE: the window size does not actually influence
// the output for an input this small.
assert_eq!(output_15, output_0);

assert_eq!(output_15, input);
}

#[test]
fn gzip_chunked() {
let input = b"Hello World\n";

let extra =
"Scheduling and executing async tasks is a job handled by an async runtime, such as\0";
let name =
"tokio, async-std, and smol. You’ve probably used them at some point, either directly or\0";
let comment =
"indirectly. They, along with many frameworks that require async, do their best to hide\0";

let config = DeflateConfig {
window_bits: 31,
..Default::default()
};

let mut stream = MaybeUninit::<libz_rs_sys::z_stream>::zeroed();

const VERSION: *const c_char = "2.1.4\0".as_ptr() as *const c_char;
const STREAM_SIZE: c_int = std::mem::size_of::<libz_rs_sys::z_stream>() as c_int;

let err = unsafe {
libz_rs_sys::deflateInit2_(
stream.as_mut_ptr(),
config.level,
config.method as i32,
config.window_bits,
config.mem_level,
config.strategy as i32,
VERSION,
STREAM_SIZE,
)
};
assert_eq!(err, 0);

let stream = unsafe { stream.assume_init_mut() };

let mut header = libz_rs_sys::gz_header {
text: 0,
time: 0,
xflags: 0,
os: 0,
extra: extra.as_ptr() as *mut _,
extra_len: extra.len() as _,
extra_max: 0,
name: name.as_ptr() as *mut _,
name_max: 0,
comment: comment.as_ptr() as *mut _,
comm_max: 0,
hcrc: 1,
done: 0,
};

let err = unsafe { libz_rs_sys::deflateSetHeader(stream, &mut header) };
assert_eq!(err, 0);

stream.next_in = input.as_ptr() as *mut _;
stream.avail_in = input.len() as _;

let mut output_rs = [0u8; 512];
stream.next_out = output_rs.as_mut_ptr();
stream.avail_out = output_rs.len() as _;

let err = unsafe { libz_rs_sys::deflate(stream, Flush::Finish as _) };
assert_eq!(err, ReturnCode::StreamEnd as i32);

let output_rs = &mut output_rs[..stream.total_out as usize];

let err = unsafe { libz_rs_sys::deflateEnd(stream) };
assert_eq!(err, 0);

{
let mut stream = MaybeUninit::<libz_rs_sys::z_stream>::zeroed();

const VERSION: *const c_char = "2.1.4\0".as_ptr() as *const c_char;
const STREAM_SIZE: c_int = std::mem::size_of::<libz_rs_sys::z_stream>() as c_int;

let err = unsafe {
libz_rs_sys::inflateInit2_(
stream.as_mut_ptr(),
config.window_bits,
VERSION,
STREAM_SIZE,
)
};
assert_eq!(err, 0);

let stream = unsafe { stream.assume_init_mut() };

stream.next_in = output_rs.as_mut_ptr() as _;
stream.avail_in = output_rs.len() as _;

let mut output = [0u8; 64];
stream.next_out = output.as_mut_ptr();
stream.avail_out = output.len() as _;

let mut extra_buf = [0u8; 64];
let mut name_buf = [0u8; 64];
let mut comment_buf = [0u8; 256];

let mut header = libz_rs_sys::gz_header {
text: 0,
time: 0,
xflags: 0,
os: 0,
extra: extra_buf.as_mut_ptr(),
extra_len: 0,
extra_max: extra_buf.len() as _,
name: name_buf.as_mut_ptr(),
name_max: name_buf.len() as _,
comment: comment_buf.as_mut_ptr(),
comm_max: comment_buf.len() as _,
hcrc: 0,
done: 0,
};

let err = unsafe { libz_rs_sys::inflateGetHeader(stream, &mut header) };
assert_eq!(err, 0);

let err = unsafe { libz_rs_sys::inflate(stream, Flush::NoFlush as _) };
assert_eq!(err, ReturnCode::StreamEnd as i32);

let err = unsafe { libz_rs_sys::inflateEnd(stream) };
assert_eq!(err, ReturnCode::Ok as i32);

assert!(!header.extra.is_null());
assert_eq!(
std::str::from_utf8(&extra_buf).unwrap(),
&extra[..extra_buf.len()]
);

assert!(!header.name.is_null());
assert_eq!(
std::str::from_utf8(&name_buf).unwrap(),
&name[..name_buf.len()]
);

assert!(!header.comment.is_null());
assert_eq!(
unsafe { CStr::from_ptr(comment_buf.as_ptr().cast()) }
.to_str()
.unwrap(),
comment.trim_end_matches('\0')
);
}
}
2 changes: 1 addition & 1 deletion load-dynamic-libz-ng/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ pub unsafe fn compress(
f(dest, destLen, source, sourceLen)
}

unsafe fn deflate(strm: *mut libz_ng_sys::z_stream, flush: i32) -> std::ffi::c_int {
pub unsafe fn deflate(strm: *mut libz_ng_sys::z_stream, flush: i32) -> std::ffi::c_int {
const LIBZ_NG_SO: &str = "/home/folkertdev/rust/zlib-ng/libz-ng.so";

let lib = libloading::Library::new(LIBZ_NG_SO).unwrap();
Expand Down
20 changes: 20 additions & 0 deletions zlib-rs/src/c_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,26 @@ pub struct gz_header {
pub done: i32,
}

impl Default for gz_header {
fn default() -> Self {
Self {
text: 0,
time: 0,
xflags: 0,
os: 0,
extra: std::ptr::null_mut(),
extra_len: 0,
extra_max: 0,
name: std::ptr::null_mut(),
name_max: 0,
comment: std::ptr::null_mut(),
comm_max: 0,
hcrc: 0,
done: 0,
}
}
}

impl gz_header {
// based on the spec https://www.ietf.org/rfc/rfc1952.txt
//
Expand Down
Loading

0 comments on commit 336b6d4

Please sign in to comment.