From 5442436d6ebcb687d318f8ec162baf8c8fb0a9fc Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Sat, 14 Mar 2026 09:39:51 +1300 Subject: [PATCH 1/7] ffi: Perform cache insert before initializing `indices`. Tiny defensive change: avoids exposing invalid heap pointers to FFI if the cache insert fails. The caller would've still received an error and should not expect the pointers to be valid, but it's better to avoid exposing them in this error case. --- mp4parse_capi/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mp4parse_capi/src/lib.rs b/mp4parse_capi/src/lib.rs index 636de840..f378625c 100644 --- a/mp4parse_capi/src/lib.rs +++ b/mp4parse_capi/src/lib.rs @@ -1480,8 +1480,10 @@ fn get_indice_table( }; if let Some(v) = create_sample_table(track, offset_time) { - indices.set_indices(&v); sample_table_cache.insert(track_id, v)?; + if let Some(cached) = sample_table_cache.get(&track_id) { + indices.set_indices(cached); + } return Ok(()); } From 77798f9b00847f769027aed951de1180b8dd2560 Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Sat, 14 Mar 2026 10:24:36 +1300 Subject: [PATCH 2/7] ffi: Reject out-of-range channel count and sample rate at the C boundary. QuickTime v2 audio sample entries can carry a u32 channel count and f64 sample rate that don't fit the u16/u32 C-API fields. Replace the silent truncating casts with checked conversions that return Invalid. --- mp4parse_capi/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mp4parse_capi/src/lib.rs b/mp4parse_capi/src/lib.rs index f378625c..9ce41b1d 100644 --- a/mp4parse_capi/src/lib.rs +++ b/mp4parse_capi/src/lib.rs @@ -811,8 +811,12 @@ fn get_track_audio_info( } } }; - sample_info.channels = audio.channelcount as u16; + sample_info.channels = + u16::try_from(audio.channelcount).map_err(|_| Mp4parseStatus::Invalid)?; sample_info.bit_depth = audio.samplesize; + if audio.samplerate < 0.0 || audio.samplerate > u32::MAX as f64 { + return Err(Mp4parseStatus::Invalid); + } sample_info.sample_rate = audio.samplerate as u32; // sample_info.profile is handled below on a per case basis From 0f3d08644a7031e425bc78e661980f15bfd4132c Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Sat, 14 Mar 2026 10:24:53 +1300 Subject: [PATCH 3/7] ffi: Add null checks for out-pointers in get_indice_table and is_fragmented. Both functions dereferenced their out-pointer without first checking for null, unlike every other C API entry point. Add the missing guards so callers that pass null get BadArg instead of a null-deref. --- mp4parse_capi/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mp4parse_capi/src/lib.rs b/mp4parse_capi/src/lib.rs index 9ce41b1d..52f9da4f 100644 --- a/mp4parse_capi/src/lib.rs +++ b/mp4parse_capi/src/lib.rs @@ -1366,7 +1366,7 @@ pub unsafe extern "C" fn mp4parse_get_indice_table( track_id: u32, indices: *mut Mp4parseByteData, ) -> Mp4parseStatus { - if parser.is_null() { + if parser.is_null() || indices.is_null() { return Mp4parseStatus::BadArg; } @@ -1547,7 +1547,7 @@ pub unsafe extern "C" fn mp4parse_is_fragmented( track_id: u32, fragmented: *mut u8, ) -> Mp4parseStatus { - if parser.is_null() { + if parser.is_null() || fragmented.is_null() { return Mp4parseStatus::BadArg; } From 88caba9813651187bb2d90b7b634a2b585eeab8d Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Sat, 14 Mar 2026 11:22:26 +1300 Subject: [PATCH 4/7] ffi: Initialize AVIF output structs to defaults before populating. Matches the pattern used by all other FFI getters, ensuring C callers always see valid (zeroed) fields even on error paths. --- mp4parse/src/lib.rs | 2 ++ mp4parse_capi/src/lib.rs | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/mp4parse/src/lib.rs b/mp4parse/src/lib.rs index 58bdcc36..c3b028f8 100644 --- a/mp4parse/src/lib.rs +++ b/mp4parse/src/lib.rs @@ -3815,8 +3815,10 @@ fn read_colr( /// Rotation in the positive (that is, anticlockwise) direction /// Visualized in terms of starting with (⥠) UPWARDS HARPOON WITH BARB LEFT FROM BAR /// similar to a DIGIT ONE (1) +#[derive(Default)] pub enum ImageRotation { /// ⥠ UPWARDS HARPOON WITH BARB LEFT FROM BAR + #[default] D0, /// ⥞ LEFTWARDS HARPOON WITH BARB DOWN FROM BAR D90, diff --git a/mp4parse_capi/src/lib.rs b/mp4parse_capi/src/lib.rs index 52f9da4f..c27325d4 100644 --- a/mp4parse_capi/src/lib.rs +++ b/mp4parse_capi/src/lib.rs @@ -339,7 +339,7 @@ pub enum Mp4parseAvifLoopMode { } #[repr(C)] -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Mp4parseAvifInfo { pub premultiplied_alpha: bool, pub major_brand: [u8; 4], @@ -381,7 +381,7 @@ pub struct Mp4parseAvifInfo { } #[repr(C)] -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Mp4parseAvifImage { pub primary_image: Mp4parseByteData, /// If no alpha item exists, members' `.length` will be 0 and `.data` will be null @@ -1150,6 +1150,9 @@ pub unsafe extern "C" fn mp4parse_avif_get_info( return Mp4parseStatus::BadArg; } + // Initialize fields to default values to ensure all fields are always valid. + *avif_info = Default::default(); + if let Ok(info) = mp4parse_avif_get_info_safe((*parser).context()) { *avif_info = info; Mp4parseStatus::Ok @@ -1332,6 +1335,9 @@ pub unsafe extern "C" fn mp4parse_avif_get_image( return Mp4parseStatus::BadArg; } + // Initialize fields to default values to ensure all fields are always valid. + *avif_image = Default::default(); + if let Ok(image) = mp4parse_avif_get_image_safe(&*parser) { *avif_image = image; Mp4parseStatus::Ok From e7eb51ba8fd43df10a0f76a2794aba92d5a5d7c1 Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Sat, 14 Mar 2026 11:22:41 +1300 Subject: [PATCH 5/7] ffi: Reject timescales that don't fit in u32 instead of truncating. --- mp4parse_capi/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mp4parse_capi/src/lib.rs b/mp4parse_capi/src/lib.rs index c27325d4..cb876e47 100644 --- a/mp4parse_capi/src/lib.rs +++ b/mp4parse_capi/src/lib.rs @@ -660,7 +660,10 @@ pub unsafe extern "C" fn mp4parse_get_track_info( let track = &context.tracks[track_index]; if let (Some(timescale), Some(context_timescale)) = (track.timescale, context.timescale) { - info.time_scale = timescale.0 as u32; + info.time_scale = match timescale.0.try_into() { + Ok(v) => v, + Err(_) => return Mp4parseStatus::Invalid, + }; let media_time: CheckedInteger = track .media_time .map_or(0.into(), |media_time| media_time.0.into()); From 0f0f1e38253f9cb01080e97d4fe90dae4c293236 Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Sat, 14 Mar 2026 11:22:54 +1300 Subject: [PATCH 6/7] ffi: Assert that the read callback does not return more bytes than requested. --- mp4parse_capi/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mp4parse_capi/src/lib.rs b/mp4parse_capi/src/lib.rs index cb876e47..e1b7ec8c 100644 --- a/mp4parse_capi/src/lib.rs +++ b/mp4parse_capi/src/lib.rs @@ -479,6 +479,7 @@ impl Read for Mp4parseIo { } let rv = self.read.unwrap()(buf.as_mut_ptr(), buf.len(), self.userdata); if rv >= 0 { + assert!(rv as usize <= buf.len(), "read callback returned more bytes than buffer size"); Ok(rv as usize) } else { Err(std::io::Error::other("I/O error in Mp4parseIo Read impl")) From c3cf685485b6d1152636bd94751377b283730f19 Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Sat, 14 Mar 2026 11:23:12 +1300 Subject: [PATCH 7/7] ffi: Document that thread safety is the caller's responsibility. --- mp4parse_capi/src/lib.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mp4parse_capi/src/lib.rs b/mp4parse_capi/src/lib.rs index e1b7ec8c..8dd4c6ca 100644 --- a/mp4parse_capi/src/lib.rs +++ b/mp4parse_capi/src/lib.rs @@ -299,6 +299,11 @@ pub struct Mp4parseFragmentInfo { /// Parser state for MP4 files, exposed to C callers via raw pointer. /// +/// # Thread safety +/// +/// A parser instance must not be accessed from multiple threads +/// concurrently. The caller is responsible for serializing all access. +/// /// # Pointer stability /// /// Several C API functions return raw pointers into data cached on this @@ -429,6 +434,12 @@ impl ContextParser for Mp4parseParser { } } +/// Parser state for AVIF files, exposed to C callers via raw pointer. +/// +/// # Thread safety +/// +/// A parser instance must not be accessed from multiple threads +/// concurrently. The caller is responsible for serializing all access. #[derive(Default)] pub struct Mp4parseAvifParser { context: AvifContext,