From 4f067fbc35d27a53f755a4d1dc241bda72a88778 Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Sat, 14 Mar 2026 10:24:36 +1300 Subject: [PATCH 1/6] 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 dbdfafaf..2b200945 100644 --- a/mp4parse_capi/src/lib.rs +++ b/mp4parse_capi/src/lib.rs @@ -831,8 +831,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 d76fc04689e1e3a5431a44109eaaedf2a0908903 Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Sat, 14 Mar 2026 10:24:53 +1300 Subject: [PATCH 2/6] 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 2b200945..4534d4f8 100644 --- a/mp4parse_capi/src/lib.rs +++ b/mp4parse_capi/src/lib.rs @@ -1386,7 +1386,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; } @@ -1565,7 +1565,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 1ebf37598b9fef7c37d7ef17e140cebe6f4c07db Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Sat, 14 Mar 2026 11:22:26 +1300 Subject: [PATCH 3/6] 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 4534d4f8..0c7a73b4 100644 --- a/mp4parse_capi/src/lib.rs +++ b/mp4parse_capi/src/lib.rs @@ -341,7 +341,7 @@ pub enum Mp4parseAvifLoopMode { } #[repr(C)] -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Mp4parseAvifInfo { pub premultiplied_alpha: bool, pub major_brand: [u8; 4], @@ -383,7 +383,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 @@ -1170,6 +1170,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 @@ -1352,6 +1355,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 cbb480dd8df73cada25e45e299ffc36505a8ebf9 Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Sat, 14 Mar 2026 11:22:41 +1300 Subject: [PATCH 4/6] 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 0c7a73b4..21c9408b 100644 --- a/mp4parse_capi/src/lib.rs +++ b/mp4parse_capi/src/lib.rs @@ -680,7 +680,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 918d50472acae68d90f9a442a4c937e0f9e998c4 Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Sat, 14 Mar 2026 11:22:54 +1300 Subject: [PATCH 5/6] ffi: Assert that the read callback does not return more bytes than requested. --- mp4parse_capi/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mp4parse_capi/src/lib.rs b/mp4parse_capi/src/lib.rs index 21c9408b..26ad3327 100644 --- a/mp4parse_capi/src/lib.rs +++ b/mp4parse_capi/src/lib.rs @@ -499,6 +499,10 @@ 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 97f1cc16e3e56f2fbb94e8c4a1c3652e6a17c409 Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Sat, 14 Mar 2026 11:23:12 +1300 Subject: [PATCH 6/6] 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 26ad3327..f564b02a 100644 --- a/mp4parse_capi/src/lib.rs +++ b/mp4parse_capi/src/lib.rs @@ -301,6 +301,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 @@ -431,6 +436,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,