[BUG] MXF: update_tid_lut() writes past tracks[32] onto nb_tracks, turning a file-supplied track id into the write offset

オープン 初心者向け
#2,457 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 7 日以内に返信

@pranayr710 がすでに取り組んでいます。

2026年11月10日 から。

  • #2458 @pranayr710 による — オープン

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
65/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
c
領域
backend, security

調査の方向性

src/lib_ccx/ccx_demuxer_mxf.c の update_tid_lut() から始め、満杯の tracks 配列をインデックスで参照する前にどのように処理しているか確認してください。呼び出し元とトラック数を扱うループを読み、上限の動作を確認したうえで、32 を超えるトラックを宣言する MXF ファイルを作成して検証してください。余分なトラックが安全に無視され、トラック数と隣接するフィールドがそのまま保たれれば完了です。

索引モデルが issue の本文から書いたものです。

説明

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_id at byte 280, i.e. into nb_tracks;
  • statement 2 re-evaluates ctx->tracks[ctx->nb_tracks] using the value just
    written, so its destination is 24 + 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.)

主要言語
C
スター
913
フォーク
603
平均マージ
6日 8時間
マージ済み PR(30日)
33

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

CCExtractor/ccextractor のほかの issue

CCExtractor/ccextractor の issue をすべて見る

似ている issue

C の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。