- Chrome の安定版アップデートに含まれていた唯一のセキュリティ修正 CVE-2023-4863 は、WebP 画像ライブラリにおけるヒープバッファオーバーフローであり、Google はすでに実環境でエクスプロイトが存在すると明らかにしている
- Apple SEAR による 2023 年 9 月 6 日の報告、Apple の CVE-2023-41064、Citizen Lab の BLASTPASS の状況証拠を合わせると、同じバグである可能性が高い
- 脆弱性は、WebP の可逆圧縮 VP8L における Huffman テーブル 構築過程で、事前計算されたバッファサイズを超えて書き込み可能だった問題と分析されている
- 一般的なファジングで見逃されやすかった理由は、複数の最大サイズの Huffman テーブルと特定の無効テーブルを順番に作る必要がある 厄介なトリガー条件 にある
- upstream の libwebp パッチは十分に見えるが、libwebp はブラウザ・OS・アプリで広く使われているため、パッチの波及 とサンドボックス化が重要である
Chrome のパッチが BLASTPASS につながる理由
- Google は 2023 年 9 月初旬の Chrome 安定版アップデートで、Apple Security Engineering and Architecture(SEAR) が報告した CVE-2023-4863 を修正した
- 脆弱性は WebP 画像ライブラリのヒープバッファオーバーフローである
- Google は「CVE-2023-4863 のエクスプロイトが実環境に存在する」と認識していると述べた
- 同時期に Citizen Lab は、ワシントン DC を拠点とする市民社会団体に所属する個人の iPhone で不審な挙動を検出した
- BLASTPASS は、iMessage のゼロクリックゼロデイエクスプロイトとして NSO Group の Pegasus スパイウェアを配布した事例と結び付けられている
- Citizen Lab が技術分析の結果を Apple に渡した後、Apple は 9 月 7 日のセキュリティ告知で 2 件の CVE を公開した
- Apple の 1 件目の CVE である CVE-2023-41061 は、PassKit 添付ファイルに画像エクスプロイトを組み合わせて iMessage の BlastDoor サンドボックスを回避した状況と符合している
- 悪意ある画像は、サンドボックスのない別プロセスで処理されたとみられる
- 2 件目の CVE である CVE-2023-41064 は、Apple ImageIO のバッファオーバーフロー脆弱性である
- ImageIO は、複数の画像形式を判別して適切なデコーダへ接続する Apple の画像解析フレームワークである
- 技術的詳細がないため、CVE-2023-41064 がどの画像形式に影響するかは確認されていない
- Apple が最近 ImageIO に WebP サポートを追加していたこと、Apple のセキュリティチームが 9 月 6 日に Chrome へ WebP 脆弱性を報告したこと、Google が 5 日で緊急パッチを出し実際の悪用として扱ったことから、BLASTPASS と CVE-2023-4863 が同じバグである可能性 は高い
libwebp パッチが示した脆弱箇所
- Chrome セキュリティ告知のバグ ID と libwebp のオープンソースコミットを突き合わせると、Fix OOB write in BuildHuffmanTable パッチが CVE-2023-4863 に相当する
- パッチは Apple の報告翌日である 2023 年 9 月 7 日に作成された
- 脆弱性は、WebP の可逆圧縮サポートである VP8L にある
- WebP の可逆圧縮は、ピクセルを 100% 正確に保存・復元するために Huffman coding を使用する
- 最新実装では、概念的なツリー構造の代わりにテーブルを使ってデコードを最適化している
- 脆弱なバージョンは、固定テーブルから得た 事前計算バッファサイズ でメモリを確保し、その領域に Huffman テーブルを直接構築していた
- 新バージョンでは、最初のパスで出力テーブルに必要な総サイズだけを計算し、実際の書き込みは行わない
- 総サイズが事前計算バッファを超える場合は、より大きな領域を確保するよう変更された
- 主要な処理の流れは、
src/dec/vp8l_dec.c の ReadHuffmanCodes で確保される huffman_tables と、ReadHuffmanCode 内の VP8LBuildHuffmanTable / BuildHuffmanTable 呼び出しにある
- Huffman テーブルは、異なるアルファベットサイズを持つ 5 つのセグメントに分かれている
- オーバーフローを発生させるには、この 5 つのテーブルをすべて精密に構成する必要がある
トリガーファイルを作る過程
- WebP の可逆圧縮は、入力ピクセルの頻度分析をもとに、頻出する値には短いビット列を、まれな値には長いビット列を割り当てる
- デコーダがビット列長を常に区別できるよう、コードは構成される
- 圧縮画像には、コードと値の対応を再現するための統計情報とコード割り当て情報が含まれていなければならない
- WebP は、Huffman テーブル自体もファイルサイズ削減のために Huffman coding で圧縮する
- この構造のため、脆弱性の分析とトリガー生成の過程は複雑になる
@mistymntncop は、任意の Huffman coding データ、すなわち code lengths を持つ正しい形式の WebP を作るハーネスコードを提供した
- このハーネスにより、対象の
BuildHuffmanTable 呼び出しへ任意の code_lengths 配列を渡すことができた
- 手作業の実験では、
BuildHuffmanTable 内のヒストグラム、num_open、num_nodes、ReplicateValue の開始位置を追跡する key 値の相互作用が非常に複雑だった
count[0] から count[8] までの root table は total_size に直接大きな影響を与えないが、その後の内部状態を変えうる
count[9] から count[15] までの second level tables は最終的な total_size に直接影響する
- Mark Adler の enough.c は、アルファベットサイズ、root table サイズ、最大コード長に対して、取り得る最大 Huffman ツリー lookup table のヒストグラムを出力する
- libwebp の
kTableSize は、このツールで計算された値だとコメントに記されている
- このツールにより事前計算バッファサイズを再現でき、
@mistymntncop のツールにより “enough” が生成した code lengths が huffman_tables の確保領域を 100% 埋めることも確認された
実際にオーバーフローが発生する条件
enough ツールは、有効かつ完全なコード に対する最大値を計算する
- 小さなテーブルの 1 つである、シンボルサイズ 40、root table 8 ビット、最大コード長 15 の最大サイズは 410 である
BuildHuffmanTable が有効とみなすコードの中で、410 を超えるサイズを作る事例は見つかっていない
- オーバーフローは、有効な Huffman ツリーだけで作るのではなく、最後の段階で無効な Huffman ツリーを入れたときに発生する
- まず 4 つの有効な Huffman ツリーで最大サイズの出力テーブルを作る
- 最後のテーブルに無効な Huffman ツリーを入れると、最終整合性チェックの前に
ReplicateValue が範囲外へ書き込める
- 再現は、脆弱な libwebp コミット
7ba44f80f3b94fc0138db159afea770ef06532a0 をチェックアウトし、AddressSanitizer を有効化したうえで、@mistymntncop の PoC コードで bad.webp を生成し、dwebp でデコードする方法で行える
- AddressSanitizer は
BuildHuffmanTable で heap-buffer-overflow を報告する
- 報告された書き込みは、11816 バイト領域の直後のアドレスで発生する
huffman_tables をオーバーフローさせる入力は複数存在する
- 発見された code lengths の中には、
huffman_tables の確保領域末尾から 400 バイト先まで書き込む例がある
- 書き込む値の制御が部分的でも、エクスプロイト可能に見える
- 無効入力の Huffman ツリーは部分的に不均衡であり、不均衡な分岐の一部区間には子を持たない内部ノードが多数含まれる
- この構造が、有効なツリーでは到達できない
key インデックスを生み出す
パッチがオーバーフローを防ぐ仕組み
- 当初は、パッチは必要サイズに合わせてバッファを動的に拡張し、ヒープオーバーフローを防ぐ方式に見えた
- 実際には、パッチ済みバージョンの最初のパスが
BuildHuffmanTable を実行し、必要な総サイズを計算するがテーブルには書き込まない
- オーバーフローを引き起こしていた無効入力では、この最初のパスで
BuildHuffmanTable が失敗し 0 を返す
- 最初のパスでは書き込みを行わないため、無効ツリーが部分処理されても範囲外書き込みは発生しない
- 有効かつ完全なコードの中で同じオーバーフローを起こす事例は見つかっていないため、このパッチは十分に見える
ファジングで見逃されやすかった理由
- libwebp は Google OSS-Fuzz で長年ファジングされており、WebP 可逆圧縮サポートも広範にファジングされていた
- 核心的な難しさは、フォーマットもトリガー条件も複雑な点にある
- 数十億通りの可能性の中から、アルファベットサイズ 280 と 256 について最大サイズの Huffman テーブル 4 個を先に構成しなければならない
- その後、アルファベットサイズ 40 について非常に特定の形をした無効な Huffman テーブルを作る必要がある
- どの段階でも 1 ビットでも間違えば、画像デコーダはエラーを出して安全に終了する
- Google は WebP 0day 修正後、WebP Huffman ルーチン専用の新しいファザーを公開した
- そのファザーを実行しても CVE-2023-4863 は発見されなかった
- 標準的なビット反転変異、コードカバレッジのフィードバックループ、AFL++ の CmpLog のような入力状態ベースの手法でも、この極端な状態まで中間段階を通過するのは難しい
- Quarkslab の TritonDSE のような動的シンボリック実行技法には可能性があるかもしれないが、確認はされていない
- FreeType の Load_SBit_Png 脆弱性とは性質が異なる
- Load_SBit_Png は、ファジングハーネスが API の使われ方を十分に反映していなかったため発見されなかった
- CVE-2023-4863 は、ハーネス不足というより脆弱性自体の制約ゆえにファジングが難しい事例に近い
影響範囲と対応状況
- Citizen Lab は実環境で使われた高度なエクスプロイトを捕捉しており、Apple と Chrome は数日以内に数十億ユーザーへアップデートを配布したとみられる
- Android は当時、なお影響を受ける可能性が残っていた
- Android の BitmapFactory は Apple ImageIO と同様に画像デコードを扱い、libwebp をサポートしている
- 当時の Android セキュリティ告知には CVE-2023-4863 の修正は含まれていなかったが、修正自体は AOSP にマージされていた
- Android が影響を受けるなら、Signal や WhatsApp のようなアプリに対するリモートエクスプロイトにつながる可能性がある
- 10 月のセキュリティ告知で修正されると見込まれていた
- upstream の libwebp パッチは正しく適用されたように見え、必要な場所へ広がりつつある
- libwebp は多くの場所で使われているため、パッチが十分に普及するまでには時間がかかる可能性がある
- 画像デコードのようなゼロクリックのリモートエクスプロイト攻撃面に近い複雑なパーサコードでは、ファジングだけで安全性を保証するのは難しく、先行的なソースコードレビュー と適切な サンドボックス化 への投資が必要である
技術情報公開の非対称性
- ベンダーが技術的詳細を十分に公開しないと、防御側は脆弱性の影響を検証しにくくなる
- 攻撃側は N-day 脆弱性を追跡してエクスプロイトしようとする動機が強く、詳細が公開されなくても大きくは遅らされない
- 防御側は、このレベルの技術分析を行うためのリソースが不足していることが多い
- 基本的な攻撃動作の情報まで隠してしまうと、攻撃側が防御側よりも脆弱性とエクスプロイトについて多くの洞察を持つという非対称性が生じる
1件のコメント
Hacker News の意見
このバグは、2015年の Timsort バグに最もよく似ているように見える [1]
Timsort は CPython から生まれた巧妙なハイブリッドソートアルゴリズムで、OpenJDK を含む複数の実装が、ほぼソース単位の翻訳として取り込んでいた。ソート済みの run スタックを維持し、構造上あり得る最大スタックサイズには十分小さい有限の上限があるという証明があったが、元の CPython 実装はその証明と正確には一致しておらず、まれにスタックオーバーフローが起こり得た。そのため CPython では深刻なセキュリティバグだったが、Java はその場合に例外を投げるため、OpenJDK では同じ形のセキュリティ問題ではなかった
同様に今回の WebP バグも、最大テーブルサイズは形式的に証明されていたものの、実際のソースコードに入っていた値と一致していなかったことから生じた。この種のバグは検証もレビューも難しい。証明があり、ソースもその証明に合っているように見えるので、大丈夫だと思いやすい。人間によるレビューより、手の届く形式検証、そしてメモリ安全な言語の利用が強く必要だというシグナルに見える。型システムも弱い形の形式検証と見なせる
[1] http://envisage-project.eu/wp-content/uploads/2015/02/sortin...
https://github.com/google/wuffs
同じチェックを明示的に書いてこの要件を満たすこともできるが、高性能ソフトウェアなのでチェックは不要だと信じていたなら、WUFFS のツールがコードを受け付けない箇所で、自分が間違っていたと気づく可能性が高い。完全な形式検証よりは弱いが、プログラムの安全性という目的には大きな改善であり、人間が「LGTM」と言うよりはるかに良い
この記事には、「今何にパッチを当てるべきか」以外にも興味深い点がいくつかあった。脆弱性の場所と修正内容が分かっていても、エクスプロイト PoC を再現するにはかなりの作業が必要な場合があり、画像デコーダ内のロスレス伸長器はファジングに対してかなり強いことがある。セキュリティ関係者には当たり前かもしれないが、非専門家としては読んでいて面白かった
はっきりした答えを見つけられない質問がいくつかある。1) Brave のような他の Chrome ベースのブラウザも影響を受けるのか。2) デスクトップ版 Chrome も影響を受けるのか、それともモバイルだけの問題なのか。3) なぜ WebP を聞いたことがないのだろう。自分が世間から隔絶されていたのか、それともモバイル優先の技術なのか気になる
デスクトップ版 Chrome も Linux と Windows の両方で影響を受けていた。Chrome は独自に libwebp をバンドルしているため、Linux ディストリビューションがまだパッチを当てていなくても、Chrome が最新なら、少なくともブラウザ攻撃の面では問題ない
主要なブラウザや OS は、驚くほど馴染みのない画像フォーマットを多くサポートしている。たとえば MacOS では KTX2(Khronos Texture Container)を、Android では DNG(Adobe Digital Negative)をロードできる。攻撃者が探索するに値する、興味深く露出度の高い攻撃対象領域が多い
バグはコーデックライブラリにあり、WebP は実装が事実上一元化されたエコシステムなので、皆が同じライブラリを使っており、全員がパッチを当てる必要がある
Google は10年前に WebP を推進したが、長い間 Chrome 専用だったため大きな勢いは得られなかった。きちんと標準化されてもいないが、オープンソースではある。低品質画像は JPEG よりよく圧縮するが、高品質画像では色がにじんでぼやける傾向がある。皮肉なことに、WebP が広くサポートされ始めた時点では、すでに AVIF と JPEG XL によって技術的には時代遅れになりつつあった
Android 端末がサポート終了状態なら、今は実際に出回っているゼロクリック・エクスプロイトにさらされているということなのか? それとも SMS アプリ、Chrome、WhatsApp、Signal などをアップデートするだけで、主要な入力経路は十分に塞げるのか気になる
サポート終了端末でも Chrome をアップデートすれば Chrome の問題は直るが、Signal や WhatsApp のようなアプリを直すには Android OS のアップグレードが必要になる。Chrome は独自の libwebp をバンドルしているが、メッセージングアプリや Gmail のように露出度の高いアプリは、画像表示に OS 提供のインターフェースを使っている。セキュリティサポート中の Android 端末には、10月初めからアップデートが出ることを期待している
「実際に huffman_tables をオーバーフローさせる入力が多い」というのは、より一般化された問題のように見える。ソフトウェア A がルックアップテーブル B を作り、そのテーブルが入力データストリームの処理に使われる構造だ
これからは、セキュリティや正確性を少しでも気にする開発者なら、次のどちらかを保証しなければならない。A) どんな入力データもルックアップテーブルを誤用させたり失敗させたりできないようにソフトウェアが書かれていること、または B) 制御された環境、たとえば通信する両者がともに信頼されているポイントツーポイント通信でのみ使われ、ストリームが絶対にルックアップテーブルを誤用したり異常を起こしたりしない保証があること
B は小さなグループやオフィス以外ではほぼ不可能で、インターネット規模では本当に不可能なので、結局 A だけが残る。今後のソフトウェア工学における一般化されたベストプラクティスは、何らかの理由でルックアップテーブルを使うなら、そのテーブルがあらゆるデータ型で正しく動作することを開発者が保証するか、不正なデータがテーブルに到達する前に検出して適切に処理しなければならない、というものになる
真剣なセキュリティ研究者で時間があるなら、過去にルックアップテーブルと何らかの形で関連したすべてのセキュリティ脆弱性の一覧を集め、1つずつ読んで共通点を比較してみると思う。おそらくパターンがあるはずだ。そのうえで、データストリームにルックアップテーブルを使うすべてのソフトウェアを洗い、脆弱性を監査するだろうが、これは1人が一生でできることではなくチームスポーツだ
NSO がこのバグを見つけるうえで、ソースコード公開はどの程度役に立ったのだろうか? コードがバイナリブロブだけだった場合でも、現代のデコンパイラだけでコードを理解し、このような難解なバグを見つけるのに十分だったのか気になる
画像フォーマットの「新しい」部分ではないという点に驚いた。ハフマン圧縮は70年以上前からあり、正準ハフマンもそれより数十年新しいだけで、JPEG でさえハフマンを使っている。何十年も前からある技術で、実装方法を扱った文章も数えきれないほどあるのだから、今では複数の実装でバグが潰されていてよさそうな技術に見える
以前 JPEG 仕様を読んでデコーダも書いたことがあるので、WebP 仕様を見てみた: https://developers.google.com/speed/webp/docs/webp_lossless_...
重要な内容は第6節にある。まず目につくのは、コードがどのように構成されるのかが JPEG 仕様と違って明確ではない点だ。JPEG には処理について読みやすいフローチャートが多い。WebP は LZH/deflate(zlib) に似ているように見え、「仕様」というより説明付きのソースコード片の寄せ集めに近い
GIF、JPEG、PNG に続いて WebP デコーダも書いてみようかと思うが、上の「仕様」だけを見ると、ほとんど書くなと言われているように感じる
ハフマン木の実装には互いに異なるトレードオフが多いため、古い技術だからといってバグがすべて潰されているとは考えにくい。Charles Bloom は、1997年のハフマン最適化に関する決定的な論文 [1] ですら2010年当時まであまり知られておらず、多くの最適化が再発見されては忘れられてきたと言っていた。だから非効率な実装が多い可能性は高い
[1] https://cbloomrants.blogspot.com/2010/08/08-12-10-lost-huffm...
今では画像を見ることさえ、セキュリティの心配なしにはできないようだ
安全でない言語である C を使うことで生じる典型的な成長痛のように聞こえる。ブラウザの速度のために魅力があるのは理解できるが、業界が C を使い始めて以来繰り返してきた同じ過ちを助長するやり方から抜け出してほしい
速いという理由でリベットではなくセルフタッピングねじで橋を造っているようなものだ。橋がたわみ始めたら、ねじをさらに打ち込めばいい、という具合だ
「良いニュースは、Apple と Chrome がこの問題の緊急性に見合う形で見事に対応したことだ」なんて、納得しがたい。この問題を Chrome 専用に分類したのは Google だ。この7日間だけ見ても、主要な Linux ディストリビューションはすべてアップデートを出さなければならず、Red Hat は9.6点を付けた。10億回以上 pull された Python Docker イメージ、Puppeteer、WordPress、Node.js なども影響を受けたのに、CRBug はいまだに非公開だ
BleepingComputer のようなサイトは調査せず、見えるままに報じたし、この問題を第三者の出来事のように扱った多くのセキュリティ企業も同じだ。相手がデューデリジェンスをしていないと分かると、信頼を築くのは本当に難しい
Adam Caudill は、1Password が初期にパッチを当てた事例とともに「Whose CVE is it anyway?」[0]という良い記事を書いており、ここで言っている問題をうまく指摘している。Citizen Lab は2つの問題が関連しているかどうかについて回答を拒んだが、天才でなくても見える部分はある
[0]: https://adamcaudill.com/2023/09/14/whose-cve-is-it-anyway/
筆者も、他の多くのシステムもパッチを当てる必要があると言っている。ただ、10億回 pull された Python Docker イメージのうち、信頼できない WebP 画像をレンダリングするものがどれほどあるだろうか? Node なども同様だ。もちろん迅速にパッチを当てるべきだが、iOS/Android/Chrome と同じレベルではない
世界中の数百万人がこのライブラリの影響を受け、それぞれが使うデバイスやアプリごとに影響は何倍にも膨らむ
C は好きだが、世界中の数百万人が使うライブラリは C を使うべきではないと思う。こうした中核ライブラリではリスクの割合が高すぎる。C の専門家でも、いつかはミスをする
これが修正のようだ https://github.com/webmproject/libwebp/commit/dce8397fec159c...
「malloc fail」とは、もどかしい。Slack、Discord、Teams、あらゆる現代的 OS まで、すべて影響を受ける
C/C++ と動的言語は、未定義動作や微妙なバグの表面を大きくし、最も賢い開発者にとっても lint しにくく負担が大きい。基盤ライブラリは seL4 と似た方法で形式検証されるべきだ
もう一つの問題は、FOSS 全体に広がる非専門性、そして厳密性・品質・正確性・セキュリティの軽視だ。今のように砂の上に帝国を築くやり方は愚かだ