[BUG] MXF: update_tid_lut() writes past tracks[32] onto nb_tracks, turning a file-supplied track id into the write offset
Maintainers usually reply within 7 days
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 65/100
Research direction
Start in src/lib_ccx/ccx_demuxer_mxf.c at update_tid_lut() and check how it handles a full tracks array before indexing it. Read the callers and track-count loops to confirm the limit behavior, then build and exercise an MXF file declaring more than 32 tracks; done when excess tracks are safely ignored and the track count and neighboring fields remain intact.
Written by the indexing model from the issue text.
Description
Summary
update_tid_lut() in src/lib_ccx/ccx_demuxer_mxf.c appends to a fixed
MXFTrack tracks[32] without checking nb_tracks first. nb_tracks is the
struct field directly behind that array, so the 33rd track a file declares
overwrites the bound itself with a 32-bit value taken from the file — and the
very next statement re-reads that bound to choose where to write.
The overlap is exact
typedef struct MXFContext
{
enum MXFCaptionType type; /* byte 0 */
int cap_track_id; /* byte 4 */
UID cap_essence_key; /* byte 8 */
MXFTrack tracks[32]; /* byte 24 .. 279 */
int nb_tracks; /* byte 280 */
int cap_count; /* byte 284 */
struct ccx_rational edit_rate; /* byte 288 */
} MXFContext; /* 296 bytes, heap allocated in ccx_mxf_init() */
sizeof(MXFTrack) is 8, so tracks[32] begins at 24 + 32*8 = 280 — the
same address as nb_tracks.
What happens on the 33rd track
ctx->tracks[ctx->nb_tracks].track_id = track_id;
memcpy(ctx->tracks[ctx->nb_tracks].track_number, track_number, 4);
ctx->edit_rate = edit_rate;
ctx->nb_tracks++;
- statement 1 writes
track_idat byte 280, i.e. intonb_tracks; - statement 2 re-evaluates
ctx->tracks[ctx->nb_tracks]using the value just
written, so its destination is24 + 8*track_id + 4.
track_id is read straight from the file in mxf_read_timeline_track_metadata():
case MXF_TAG_TRACK_ID:
track_id = buffered_get_be32(demux);
With track_id = 0x7FFFFFF0 the memcpy targets byte 17179869084 of a 296
byte allocation — about 16 GB past it. Smaller ids give smaller, more usable
offsets; the write is at 8 * track_id bytes from the block, with four bytes of
file-supplied content.
Everything afterwards is affected too: every for (i = 0; i < ctx->nb_tracks;
i++) over tracks[] (here and in update_cap_essence_key()) becomes an
unbounded read, and cap_count at byte 284 is overwritten by track_number.
Reachability
ccx_probe_mxf() scans the first bytes for the 14-byte header partition pack
key and nothing else, so any file carrying that key is handed to this demuxer.
Each Timeline Track set in the header metadata calls update_tid_lut() once —
33 of them is enough. The C path is live; the Rust side only re-exports
ccx_probe_mxf / ccx_mxf_init ("shall be removed after mxf" in
src/rust/build.rs).
Demonstrating the mechanism
To be clear about what this evidence is and is not: the run below is a
standalone program that reproduces MXFContext and update_tid_lut()
verbatim, with no other CCExtractor code. It demonstrates the pointer
arithmetic and the crash; it is not CCExtractor itself crashing. My sanitizer
build is unavailable at the moment, so I have not yet put the crafted .mxf
files through the real binary end to end — I will add that as a comment rather
than claim it now. The struct offsets below are computed from the real
declaration, and the reachability argument is from the source.
Reproducing the exact struct and function (no other CCExtractor code):
MASTER build
after 32 tracks: nb_tracks = 32
adding 33rd track, track_id = 0x7FFFFFF0 ...
Segmentation fault
FIXED build
after 32 tracks: nb_tracks = 32
adding 33rd track, track_id = 0x7FFFFFF0 ...
[guard] table full at 32, ignoring track 0x7FFFFFF0
survived: nb_tracks = 32, cap_count = 0x00000000
and the intermediate state, printed between the two statements:
nb_tracks before = 32
nb_tracks after stmt 1 = 0x7FFFFFF0 <-- clobbered by the file's track_id
statement 2 will memcpy to tracks[2147483632].track_number = byte 17179869084 of a 296 byte block
Suggested fix
Refuse to append once the table is full:
if (ctx->nb_tracks >= (int)(sizeof(ctx->tracks) / sizeof(ctx->tracks[0])))
{
mprint("MXF: more than %d tracks, ignoring the rest\n", ...);
return;
}
I have a patch and a generator for the test files, and will open a PR
referencing this issue.
(Analysis and patch prepared with AI assistance; the reproducers are my own.)
- Dominant language
- C
- Stars
- 913
- Forks
- 603
- Avg merge
- 6d 8h
- Merged PRs (30d)
- 33
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from CCExtractor/ccextractor
-
[Security] Heap buffer overflow (OOB read past GPAC sample buffer) in process_avc_sample() at mp4.c:99 when parsing a crafted MP4 filePossibly taken @yasar710 claimed this 6 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
CCExtractor/ccextractor#2449 ·
Maintainers usually reply within 7 days
-
[BUG] DVB subtitle decoder: heap-buffer-overflow write in dvbsub_parse_clut_segment via unbounded entry_idPossibly taken @Aditya-Saroha claimed this 9 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
CCExtractor/ccextractor#2440 ·
Maintainers usually reply within 7 days
-
[BUG] Filename sequence expansion (`+`) panics on numeric values exceeding `i32::MAX`May be free again A pull request for this issue was closed without being merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
CCExtractor/ccextractor#2402 ·
Maintainers usually reply within 7 days
-
[BUG] CEA-708 .srt is written as Latin-1 and ignores --utf8/--latin1/--unicodePossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 3/5 1-2 days Newbie friendliness 65/100
CCExtractor/ccextractor#2450 · 3 comments ·
Maintainers usually reply within 7 days
-
[Security] NULL pointer dereference (SEGV at address 0x0) in sei_message() when parsing a crafted MP4 filePossibly taken @yasar710 claimed this 7 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 50/100
CCExtractor/ccextractor#2444 ·
Maintainers usually reply within 7 days
All issues in CCExtractor/ccextractor
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
tursodatabase/libsql#2312 ·
Maintainers usually reply within 11 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
follow-up Prio 📌
Difficulty 1/5 Under an hour Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
area: test kind: bug preview-effort: small priority: low
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 1 day