
CVE-2026-86476の概念実証:unixfdデシリアライズを介したGStreamer GstVideoMetaのヒープ領域における境界外読み取り/書き込みで、ASanによる再現とRCEチェーンを含む。
GStreamer の libgstvideo (gst-plugins-base) におけるヒープバッファオーバーフロー。GstVideoMeta は、動画フレームのピクセルがメモリ内でどのように配置されるかを記述する。この記述が信頼できない unixfd ピアからデシリアライズされる際、それが主張するジオメトリがバッキングバッファ内に収まるかどうかを検証するものは何もない。ピアは過大なプレーンストライドを設定でき、コンシューマは割り当てを超えて読み書きする。
| フィールド | 値 |
|---|---|
| CVE | CVE-2026-86476 |
| クラス | CWE-125 (境界外読み取り)、CWE-787 (境界外書き込み)、CWE-502 (信頼できないデータのデシリアライズ) を経由して到達 |
| コンポーネント | libgstvideo: gstvideometa.c、video-frame.c |
| 影響 | 境界外読み取り (確認済み)、コード実行に至る境界外書き込み (実証済み) |
| トリガー | 偽造されたプレーンストライドを持つ GstVideoMeta を unixfd ピアからデシリアライズ |
| 障害箇所 | video_orc_convert_I420_BGRA、videoconvert の ORC カーネル |
このバグは、複合的に作用する 3 つの欠落したチェックである。
フレームマップのメタ分岐におけるサイズチェックの欠如。 video-frame.c の gst_video_frame_map_id() には 2 つの分岐がある。メタなし分岐は if (frame->map[0].size < info->size) goto invalid_size; で終わる。GstVideoMeta が存在する場合に取られるメタ分岐は、info->width <= meta->width と info->height <= meta->height のみを強制する。これらは下限である。プレーンのジオメトリを実際のメモリのサイズと比較するものは何もない。
default_map() はプレーンごとに 1 バイトしかチェックしない。 gstvideometa.c では、プレーンのメモリは gst_buffer_find_memory(buffer, offset, 1, ...) で解決される。サイズ引数は 1 である。このコードはプレーンの開始オフセットに 1 バイトが存在することを確認し、そのままストライドをコピーする。プレーンの実際の範囲である stride * height は、割り当てと比較されることはない。チェックのすぐ上には FIXME use plane size というコメントがある。
デシリアライザは何も範囲チェックしない。 video_meta_deserialize() は、バイトリーダが入力を読み尽くしていないこと、n_planes <= GST_VIDEO_MAX_PLANES であること、version != 0 であることのみを検証する。オフセットとストライドはストリームからそのままコピーされる。各ストライドは符号付き gint32 として読み取られるため、負のストライドも受け入れられる。
受信側では、gst_unix_fd_src_create() が空のバッファに対してメタをデシリアライズし、その後でピアのメモリをアタッチする。メタが持つストライドと、バッファをバッキングするメモリのサイズが比較されることはない。
悪意のあるピアが、320x240 の I420 フレーム用の GstVideoMeta を、すべてのプレーンストライドを 1000000 に設定してシリアライズする。コンシューマは範囲チェックなしでそれをデシリアライズし、小さな実メモリをアタッチしてフレームをマップする。各プレーンの最初のバイトが存在するため、default_map() は満たされる。その後 videoconvert は各入力プレーンを stride * height バイト分読み取り、割り当てを超えて進む。
これが読み取りである。書き込みはその鏡像である。gst_video_frame_copy_plane() は stride * height バイトの memcpy を行うため、嘘のメタを持つ小さなバッファがコピーの宛先である場合、memcpy はそのバッキングを超えて書き込む。
unixfd トランスポートは非 FD バッファを memfd にコピーするため、標準のパイプラインでは読み取りが mmap されたマッピング境界に達する (クラッシュと情報漏洩)。同じ信頼できないバイトで同じデシリアライザをヒープバッキングの宛先に対して駆動すると、書き込みプリミティブが行使される。トランスポートは、オーバーフローがマッピング境界に当たるかヒープに当たるかを決める。バグが存在するかどうかは決めない。
ヒープ書き込みでは、概念実証は隣接する GstMiniObject のヘッダ内の free 関数ポインタを上書きし、コマンド文字列を第 1 引数レジスタに載る位置に配置し、unref をトリガーして偽造されたポインタを system() として呼び出させる。
unixfd 経由で、完全には信頼していないプロセスからビデオバッファを受け取るあらゆるパイプライン。サンドボックス化されたメディアデコーダ、ポータル形式の分割パイプライン、および特権境界や信頼境界を越えてフレームを受信するために unixfdsrc を使用するあらゆるもの。
確認済み:
libgstreamer-1.0.so.0.2804.0 (1.28.4-2) に対する書き込み側。unix ソケットを介した 2 つのプロセス、標準の要素のみを使用。
fakesrc ! capsfilter ! unixfdsink。実際のストライドが 320/160/160 である有効な 320x240 I420 バッファをプッシュするが、GstVideoMeta のプレーンストライドは 1000000 である。unixfdsrc ! videoconvert ! capsfilter ! fakesink。ここで capsfilter は I420 を BGRA に強制するため、videoconvert はメタのストライドに従ってフレームを読み取らなければならない。| 実行 | メタストライド | 結果 |
|---|---|---|
| 1 | 1000000 / 1000000 / 1000000 | video_orc_convert_I420_BGRA 内で ASan により範囲外読み取りを検出 |
| 2 | 320 / 160 / 160 | 正常に完了し、バッファは fakesink に到達 |
読み取り側の証明には、-Db_sanitize=address を付けて GStreamer をビルドすること。RCE の概念実証は標準ビルドで実行される。ASan ビルドは最初のオーバーフローする memcpy で中止されるためである。
default_map() 内で、1 バイトの gst_buffer_find_memory(..., 1, ...) を、stride * height のプレーン範囲に対するチェックに置き換える。gst_video_frame_map_id() のメタ分岐に、メタなし分岐と同じ invalid_size チェックを与える。video_meta_deserialize() 内で、オフセットとストライドをバッファに対して範囲チェックし、負のストライドを拒否する。これらのいずれか 1 つでも連鎖を断ち切る。信頼境界を越える構造体に対しては、3 つすべてが正しい修正である。