From ead505aa3d964a17bb9b264a9dc270de85d1374b Mon Sep 17 00:00:00 2001 From: GuTS805 Date: Wed, 5 Aug 2026 04:49:50 +0530 Subject: [PATCH 1/2] Fix silent success return for unhandled DVB string-coded subtitle objects dvbsub_parse_object_segment() previously returned 0 (success) when encountering a character-coded (coding_method == 1) DVB subtitle object, silently dropping the caption content. Now returns -1 so the caller (dvbsub_decode) correctly treats this as a failure. Fixes #2303 --- src/lib_ccx/dvb_subtitle_decoder.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib_ccx/dvb_subtitle_decoder.c b/src/lib_ccx/dvb_subtitle_decoder.c index afcf79ba8..8e16c2834 100644 --- a/src/lib_ccx/dvb_subtitle_decoder.c +++ b/src/lib_ccx/dvb_subtitle_decoder.c @@ -1121,7 +1121,8 @@ static int dvbsub_parse_object_segment(void *dvb_ctx, const uint8_t *buf, } else if (coding_method == 1) { - mprint("FIXME support for string coding standard\n"); + mprint("dvbsub_parse_object_segment(): character-coded (string) subtitle objects are not supported. Giving up on this object.\n"); + return -1; } else { From cacec9401f70e4b78209f7614a87e6be755e3f15 Mon Sep 17 00:00:00 2001 From: GuTS805 Date: Tue, 11 Aug 2026 08:36:31 +0530 Subject: [PATCH 2/2] Fix format string vulnerability in FFmpeg MP4 demuxer diagnostics processmp4_rust() and dumpchapters_rust() passed diagnostic strings built from untrusted input (e.g. the input filename) directly as the format-string argument to mprint(), which forwards to vfprintf(). Any '%' conversion specifier in that data would be interpreted against nonexistent varargs, causing a crash (reproduced: a filename like '%s%s%s%s%s%s%s%s.mp4' segfaults inside vfprintf). Added mprint_str(), which always calls mprint() with a literal "%s" format string and the message as its argument, and routed all 16 mprint() call sites in this file through it so untrusted data can never be interpreted as a format string. Fixes #2312 --- src/rust/src/demuxer/mp4.rs | 43 ++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/src/rust/src/demuxer/mp4.rs b/src/rust/src/demuxer/mp4.rs index 55167faad..3d17ff69f 100644 --- a/src/rust/src/demuxer/mp4.rs +++ b/src/rust/src/demuxer/mp4.rs @@ -82,6 +82,15 @@ extern "C" { fn encode_sub(enc_ctx: *mut encoder_ctx, sub: *mut cc_subtitle) -> c_int; } +/// Prints `message` through `mprint`, always as the argument to a literal +/// `"%s"` format string. `message` may contain data derived from the input +/// filename or other untrusted input; passing it as `mprint`'s `fmt` +/// parameter directly would let any `%` conversion specifier it contains be +/// interpreted by the underlying `vfprintf` with no corresponding varargs. +unsafe fn mprint_str(message: *const c_char) { + mprint(b"%s\0".as_ptr() as *const c_char, message); +} + /// Track types we can extract captions from #[derive(Debug, Clone, Copy, PartialEq)] enum TrackType { @@ -143,20 +152,20 @@ pub unsafe fn processmp4_rust(ctx: *mut lib_ccx_ctx, path: &CStr, sub: *mut cc_s let path_display = String::from_utf8_lossy(path_str); let open_msg = format!("Opening '{}' with FFmpeg: \0", path_display); - mprint(open_msg.as_ptr() as *const c_char); + mprint_str(open_msg.as_ptr() as *const c_char); // Open the file with FFmpeg let fmt_ctx = match AVFormatContextInput::open(path, None, &mut None) { Ok(ctx) => ctx, Err(e) => { let err_msg = format!("Failed to open input file with FFmpeg: {}\n\0", e); - mprint(err_msg.as_ptr() as *const c_char); + mprint_str(err_msg.as_ptr() as *const c_char); return -2; } }; let ok_msg = b"ok\n\0"; - mprint(ok_msg.as_ptr() as *const c_char); + mprint_str(ok_msg.as_ptr() as *const c_char); // Set up encoder/decoder let dec_ctx = update_decoder_list(ctx); @@ -225,7 +234,7 @@ pub unsafe fn processmp4_rust(ctx: *mut lib_ccx_ctx, path: &CStr, sub: *mut cc_s "Track {}, type={} timescale={}\n\0", i, type_name, timescale ); - mprint(msg.as_ptr() as *const c_char); + mprint_str(msg.as_ptr() as *const c_char); tracks.push(TrackInfo { stream_index: i, @@ -261,11 +270,11 @@ pub unsafe fn processmp4_rust(ctx: *mut lib_ccx_ctx, path: &CStr, sub: *mut cc_s hevc_count, cc_count ); - mprint(summary.as_ptr() as *const c_char); + mprint_str(summary.as_ptr() as *const c_char); if tracks.is_empty() { let msg = b"No processable tracks found\n\0"; - mprint(msg.as_ptr() as *const c_char); + mprint_str(msg.as_ptr() as *const c_char); return 0; } @@ -490,25 +499,25 @@ pub unsafe fn processmp4_rust(ctx: *mut lib_ccx_ctx, path: &CStr, sub: *mut cc_s ccx_mp4_report_progress(ctx, 100, 100); let close_msg = b"\nClosing media: ok\n\0"; - mprint(close_msg.as_ptr() as *const c_char); + mprint_str(close_msg.as_ptr() as *const c_char); if avc_count > 0 { let msg = format!("Found {} AVC track(s). \0", avc_count); - mprint(msg.as_ptr() as *const c_char); + mprint_str(msg.as_ptr() as *const c_char); } else { let msg = b"Found no AVC track(s). \0"; - mprint(msg.as_ptr() as *const c_char); + mprint_str(msg.as_ptr() as *const c_char); } if hevc_count > 0 { let msg = format!("Found {} HEVC track(s). \0", hevc_count); - mprint(msg.as_ptr() as *const c_char); + mprint_str(msg.as_ptr() as *const c_char); } if cc_count > 0 { let msg = format!("Found {} CC track(s).\n\0", cc_count); - mprint(msg.as_ptr() as *const c_char); + mprint_str(msg.as_ptr() as *const c_char); } else { let msg = b"Found no dedicated CC track(s).\n\0"; - mprint(msg.as_ptr() as *const c_char); + mprint_str(msg.as_ptr() as *const c_char); } (*ctx).freport.mp4_cc_track_cnt = cc_count as u32; @@ -639,24 +648,24 @@ pub unsafe fn dumpchapters_rust(_ctx: *mut lib_ccx_ctx, path: &CStr) -> c_int { let path_display = String::from_utf8_lossy(path_str); let open_msg = format!("Opening '{}': \0", path_display); - mprint(open_msg.as_ptr() as *const c_char); + mprint_str(open_msg.as_ptr() as *const c_char); let fmt_ctx = match AVFormatContextInput::open(path, None, &mut None) { Ok(ctx) => ctx, Err(_) => { let msg = b"failed to open\n\0"; - mprint(msg.as_ptr() as *const c_char); + mprint_str(msg.as_ptr() as *const c_char); return 5; } }; let ok_msg = b"ok\n\0"; - mprint(ok_msg.as_ptr() as *const c_char); + mprint_str(ok_msg.as_ptr() as *const c_char); let nb_chapters = (*fmt_ctx.as_ptr()).nb_chapters as usize; if nb_chapters == 0 { let msg = b"No chapters information found!\n\0"; - mprint(msg.as_ptr() as *const c_char); + mprint_str(msg.as_ptr() as *const c_char); return 0; } @@ -667,7 +676,7 @@ pub unsafe fn dumpchapters_rust(_ctx: *mut lib_ccx_ctx, path: &CStr) -> c_int { }; let writing_msg = format!("Writing chapters into {}\n\0", out_name); - mprint(writing_msg.as_ptr() as *const c_char); + mprint_str(writing_msg.as_ptr() as *const c_char); use std::io::Write; for i in 0..nb_chapters {