From 83732df1adfc84c4899edb42d43ac487acf99d6c Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Fri, 3 Apr 2026 23:34:47 +0100 Subject: [PATCH 1/5] Add Miri witnesses for C API provenance bugs --- src/c_export.rs | 55 +++++++++++++++++++++++++++++++++++++++++++++++++ src/tdef.rs | 40 +++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/src/c_export.rs b/src/c_export.rs index 1bee699..3182178 100644 --- a/src/c_export.rs +++ b/src/c_export.rs @@ -299,3 +299,58 @@ unmangle!( }) } ); + +#[cfg(test)] +mod test { + use super::*; + use crate::tdef::Compressor; + + #[test] + fn miri_witness_stream_oxide_try_new_input_provenance() { + let data = *b"stream input"; + let mut stream = mz_stream { + next_in: data.as_ptr(), + avail_in: data.len() as c_uint, + data_type: StateTypeEnum::DeflateType, + ..Default::default() + }; + + // Under Miri this trips the raw-pointer-to-reference widening of the input buffer. + let stream_oxide = unsafe { StreamOxide::::try_new(&mut stream) }.unwrap(); + assert_eq!(stream_oxide.next_in.unwrap(), &data); + } + + #[test] + fn miri_witness_stream_oxide_try_new_output_provenance() { + let mut out = [0_u8; 16]; + let mut stream = mz_stream { + next_out: out.as_mut_ptr(), + avail_out: out.len() as c_uint, + data_type: StateTypeEnum::DeflateType, + ..Default::default() + }; + + // Under Miri this trips the raw-pointer-to-reference widening of the output buffer. + let mut stream_oxide = + unsafe { StreamOxide::::try_new(&mut stream) }.unwrap(); + assert_eq!(stream_oxide.next_out.as_mut().unwrap().len(), out.len()); + } + + #[test] + fn miri_witness_mz_adler32_input_provenance() { + let data = *b"adler witness"; + + // Under Miri this trips the raw-pointer-to-reference widening in `mz_adler32`. + let checksum = unsafe { mz_adler32(MZ_ADLER32_INIT as c_ulong, data.as_ptr(), data.len()) }; + assert_eq!(checksum as u32, mz_adler32_oxide(MZ_ADLER32_INIT, &data)); + } + + #[test] + fn miri_witness_mz_crc32_input_provenance() { + let data = *b"crc witness"; + + // Under Miri this trips the raw-pointer-to-reference widening in `mz_crc32`. + let checksum = unsafe { mz_crc32(MZ_CRC32_INIT, data.as_ptr(), data.len()) }; + assert_eq!(checksum as u32, mz_crc32_oxide(MZ_CRC32_INIT as u32, &data)); + } +} diff --git a/src/tdef.rs b/src/tdef.rs index 1898fbd..3517b69 100644 --- a/src/tdef.rs +++ b/src/tdef.rs @@ -494,4 +494,44 @@ mod test { assert!(dec.as_slice() == &data[..]); } } + + #[test] + fn miri_witness_tdefl_compress_mem_to_heap_input_provenance() { + let data = b"miri witness"; + let mut out_len = 0; + // Under Miri this trips the raw-pointer-to-reference widening in `tdefl_compress`. + let out_data = unsafe { + tdefl_compress_mem_to_heap(data.as_ptr().cast::(), data.len(), &mut out_len, 0) + }; + assert!(!out_data.is_null()); + unsafe { + crate::miniz_def_free_func(ptr::null_mut(), out_data); + } + } + + #[test] + fn miri_witness_tdefl_compress_output_provenance() { + let mut compressor = Compressor::default(); + let init = unsafe { tdefl_init(Some(&mut compressor), None, ptr::null_mut(), 0) }; + assert!(init == tdefl_status::TDEFL_STATUS_OKAY); + + let mut in_size = 0; + let mut out_size = 64; + let mut out = [0_u8; 64]; + // Under Miri this trips the raw-pointer-to-reference widening of the output buffer. + let status = unsafe { + tdefl_compress( + Some(&mut compressor), + ptr::null(), + Some(&mut in_size), + out.as_mut_ptr() as *mut c_void, + Some(&mut out_size), + tdefl_flush::TDEFL_FINISH, + ) + }; + + assert!(status == tdefl_status::TDEFL_STATUS_DONE); + assert_eq!(in_size, 0); + assert!(out_size <= out.len()); + } } From d5673f83be877786d38b85fac0da039114b48ca5 Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Fri, 3 Apr 2026 23:35:54 +0100 Subject: [PATCH 2/5] Fix tdef provenance handling --- src/tdef.rs | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/tdef.rs b/src/tdef.rs index 3517b69..9288678 100644 --- a/src/tdef.rs +++ b/src/tdef.rs @@ -170,19 +170,19 @@ unmangle!( return tdefl_status::TDEFL_STATUS_BAD_PARAM; } - let in_slice = (in_buf as *const u8) - .as_ref() - .map_or(&[][..], |in_buf| slice::from_raw_parts(in_buf, in_buf_size)); + let empty_in = []; + let in_slice = if in_buf_size == 0 { + &empty_in[..] + } else { + slice::from_raw_parts(in_buf as *const u8, in_buf_size) + }; let res = match compressor_wrap.callback { - None => match (out_buf as *mut u8).as_mut() { - Some(out_buf) => compress( - compressor, - in_slice, - slice::from_raw_parts_mut(out_buf, out_buf_size), - flush, - ), - None => { + None => { + let mut empty_out = []; + let out_slice = if out_buf_size == 0 { + &mut empty_out[..] + } else if out_buf.is_null() { if let Some(size) = in_size { *size = 0 } @@ -190,8 +190,12 @@ unmangle!( *size = 0 } return tdefl_status::TDEFL_STATUS_BAD_PARAM; - } - }, + } else { + slice::from_raw_parts_mut(out_buf as *mut u8, out_buf_size) + }; + + compress(compressor, in_slice, out_slice, flush) + } Some(ref func) => { if out_buf_size > 0 || !out_buf.is_null() { if let Some(size) = in_size { @@ -205,7 +209,7 @@ unmangle!( let res = compress_to_output(compressor, in_slice, flush, |out: &[u8]| { (func.put_buf_func)( - &(out[0]) as *const u8 as *const c_void, + out.as_ptr().cast::(), out.len() as i32, func.put_buf_user, ) != 0 From 5aa47ab1daa70cd51f4df8f3ffe2067a4c00194f Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Fri, 3 Apr 2026 23:36:44 +0100 Subject: [PATCH 3/5] Fix StreamOxide pointer slice conversion --- src/c_export.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/c_export.rs b/src/c_export.rs index 3182178..20c21d2 100644 --- a/src/c_export.rs +++ b/src/c_export.rs @@ -231,15 +231,23 @@ impl<'io, ST: StateType> StreamOxide<'io, ST> { return Err(MZError::Param); } - let in_slice = stream - .next_in - .as_ref() - .map(|ptr| slice::from_raw_parts(ptr, stream.avail_in as usize)); - - let out_slice = stream - .next_out - .as_mut() - .map(|ptr| slice::from_raw_parts_mut(ptr, stream.avail_out as usize)); + let in_slice = if stream.next_in.is_null() { + None + } else { + Some(slice::from_raw_parts( + stream.next_in, + stream.avail_in as usize, + )) + }; + + let out_slice = if stream.next_out.is_null() { + None + } else { + Some(slice::from_raw_parts_mut( + stream.next_out, + stream.avail_out as usize, + )) + }; Ok(StreamOxide { next_in: in_slice, From 438c494ea9404b5d95dc67c20aa56bcd01a896c4 Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Fri, 3 Apr 2026 23:37:27 +0100 Subject: [PATCH 4/5] Fix checksum helper pointer slice conversion --- src/c_export.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/c_export.rs b/src/c_export.rs index 20c21d2..44402de 100644 --- a/src/c_export.rs +++ b/src/c_export.rs @@ -290,10 +290,12 @@ unmangle!( /// /// Returns MZ_ADLER32_INIT if ptr is `ptr::null`. pub unsafe extern "C" fn mz_adler32(adler: c_ulong, ptr: *const u8, buf_len: usize) -> c_ulong { - ptr.as_ref().map_or(MZ_ADLER32_INIT as c_ulong, |r| { - let data = slice::from_raw_parts(r, buf_len); + if ptr.is_null() { + MZ_ADLER32_INIT as c_ulong + } else { + let data = slice::from_raw_parts(ptr, buf_len); mz_adler32_oxide(adler as u32, data) as c_ulong - }) + } } /// Calculate crc-32 of the provided buffer with the initial CRC32 checksum of `crc`. @@ -301,10 +303,12 @@ unmangle!( /// /// Returns MZ_CRC32_INIT if ptr is `ptr::null`. pub unsafe extern "C" fn mz_crc32(crc: c_ulong, ptr: *const u8, buf_len: size_t) -> c_ulong { - ptr.as_ref().map_or(MZ_CRC32_INIT, |r| { - let data = slice::from_raw_parts(r, buf_len); + if ptr.is_null() { + MZ_CRC32_INIT + } else { + let data = slice::from_raw_parts(ptr, buf_len); mz_crc32_oxide(crc as u32, data) as c_ulong - }) + } } ); From abcfea54537a808211b001e7762f27fa79974f1e Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Sat, 4 Apr 2026 10:33:29 +0100 Subject: [PATCH 5/5] Simplify zero-length tdef slices --- src/tdef.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tdef.rs b/src/tdef.rs index 9288678..6a9b418 100644 --- a/src/tdef.rs +++ b/src/tdef.rs @@ -170,18 +170,16 @@ unmangle!( return tdefl_status::TDEFL_STATUS_BAD_PARAM; } - let empty_in = []; let in_slice = if in_buf_size == 0 { - &empty_in[..] + &[] } else { slice::from_raw_parts(in_buf as *const u8, in_buf_size) }; let res = match compressor_wrap.callback { None => { - let mut empty_out = []; let out_slice = if out_buf_size == 0 { - &mut empty_out[..] + &mut [] } else if out_buf.is_null() { if let Some(size) = in_size { *size = 0