Skip to content

Format string vulnerability (crash, CWE-134) in FFmpeg MP4 demuxer — filename passed directly as mprint() format string #2312

Description

@GuTS805

Component

Rust — src/rust/src/demuxer/mp4.rs (sink: mprint() in src/lib_ccx/utility.c)

Problem

processmp4_rust() builds a diagnostic string with the user-supplied input filename interpolated directly into it, then passes that string as the format-string argument to mprint():

https://github.com/CCExtractor/ccextractor/blob/master/src/rust/src/demuxer/mp4.rs#L145-L146

let open_msg = format!("Opening '{}' with FFmpeg: \0", path_display);
mprint(open_msg.as_ptr() as *const c_char);

mprint() forwards its first argument straight to vfprintf() (src/lib_ccx/utility.c:194) with no variadic arguments supplied. Since path_display is the raw input filename, any % conversion specifier in the filename (%s, %x, %n, ...) is interpreted by vfprintf against nonexistent stack arguments — undefined behavior ranging from a crash to a stack-memory read, and potentially worse depending on libc hardening.

The same unguarded format!()mprint() pattern is repeated 3 more times in the same file:

  • mp4.rs:152-153 (FFmpeg-open error message)
  • mp4.rs:641-642 and mp4.rs:668-670 (chapters-dump path)

Reproduction (confirmed against current master, d449d48b)

Built with the (opt-in) FFmpeg MP4 demuxer enabled and ran:

$ ./ccextractor '%s%s%s%s%s%s%s%s.mp4'
...
File seems to be a MP4[KAnalyzing data with FFmpeg (MP4 demuxer)
Segmentation fault (core dumped)

GDB backtrace confirms the crash is inside vfprintf processing the filename as a format string:

#0  __strlen_avx2 ()
#1  __printf_buffer (... format="Opening '%s%s%s%s%s%s%s%s.mp4' with FFmpeg: " ...)
#2  __vfprintf_internal (... format="Opening '%s%s%s%s%s%s%s%s.mp4' with FFmpeg: " ...)
#4  vfprintf (__fmt=... "Opening '%s%s%s%s%s%s%s%s.mp4' with FFmpeg: " ...)
#5  mprint (fmt=...) at src/lib_ccx/utility.c:194
#6  ccx_rust::demuxer::mp4::processmp4_rust ()
#7  ccxr_processmp4 ()
#8  start_ccx () at src/ccextractor.c:238

The test file was a real, valid FFmpeg-generated H.264 MP4, just renamed to contain %s sequences — a filename shape a user or an automated pipeline (uploads, downloads, batch renames) could plausibly produce, and trivially craftable by anyone who controls a filename before it reaches CCExtractor.

Scope

Only reachable in builds with the enable_mp4_ffmpeg Rust feature enabled (src/rust/Cargo.toml:52, not part of default features) / CMake WITH_FFMPEG=ON — i.e. this FFmpeg-based MP4 demuxer path, not the default GPAC-based one. Default distributed builds are not affected unless built with this flag.

Not a duplicate

Closed issue #2055 was a segfault in the old GPAC-based processmp4() (fixed by PR #2057, a NULL-deref/heap-overflow fix in parse_PAT/parse_PMT — different function, different file). This report is about the new FFmpeg-based processmp4_rust, merged only in the last few days (PR #2191). No open issue currently touches this file or this bug class.

Why it matters

This is CWE-134 (uncontrolled format string) with a real, reproducible SIGSEGV on attacker/user-influenceable input (a filename). Any service or pipeline that preserves user-supplied filenames before invoking CCExtractor with the FFmpeg MP4 path enabled is crashable this way, and format-string bugs of this class are well-established to sometimes escalate beyond a crash (e.g. stack memory disclosure via %x).

Proposed fix

Replace all 4 call sites with the safe pattern already used correctly elsewhere in the codebase — pass the message as a literal format string with the data as an argument, e.g.:

mprint(c"%s".as_ptr(), open_msg.as_ptr());

or equivalent (any variant that guarantees untrusted data is never used as the fmt parameter itself).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions