- CVE-2023-6241 は Arm Mali GPU のメモリ管理ユニットにおける論理バグで、悪意ある Android アプリがカーネル MTE が有効な Pixel 8 でも任意のカーネルコード実行と root 権限の取得に到達できる
- 影響対象は Command Stream Frontend(CSF) を使う最新の Arm Mali GPU デバイスで、Google Pixel 7・Pixel 8 が含まれる
- 脆弱性は、JIT メモリ拡張中にロックが外れる短い区間を利用し、GPU マッピングと backing page 配列の間に不整合を作る方式で動作する
- エクスプロイトは、解放済み backing page に残った GPU マッピングを利用し、そのページを GPU コンテキストの PGD として再利用させたうえで、カーネルメモリとカーネルコードをマッピングする
- MTE はポインタとメモリタグの不一致を検出するが、この攻撃フローは GPU が物理アドレスへ直接アクセスし、MTE の保護範囲外で進む
脆弱性の範囲とパッチ状況
- CVE-2023-6241 は Arm Mali GPU の脆弱性で、悪意ある Android アプリがデバイス上で任意のカーネルコード実行と root 権限を得られる
- 2023年11月15日に Arm へ報告され、2023年12月14日に公開された Arm Mali driver r47p0 で修正された
- Android の修正は 2024年3月のセキュリティアップデート に含まれる
- 影響対象は CSF(Command Stream Frontend) 機能を使用する最新の Arm Mali GPU デバイスで、Google Pixel 7 と Pixel 8 が例として挙げられている
- Pixel 8 でカーネル MTE を有効にした状態でも、エクスプロイトの動作が確認された
Arm64 MTE の防御モデル
- MTE(Memory Tagging Extension) は最新の Arm プロセッサのハードウェア機能で、メモリ破損を検出するためにポインタとメモリブロックのタグを比較する
- Arm64 ポインタは 64 ビットだが、実際のアプリケーションアドレス空間は通常 52 ビット以下のため、上位ビットの一部をタグ保存に使える
- 線形オーバーフローでは隣接するメモリブロックのタグがポインタタグと異なる可能性があり、use-after-free では解放・再割り当て過程でのタグ変更により不一致が発生し得る
- kCFI のような後段の緩和策とは異なり、MTE はメモリ破損が最初に発生した時点で検出しようとする初期段階の緩和策である
- タグビット数には限りがあるため衝突は避けられず、4 ビットタグだけを使っても任意の成功率は 1/16 に下がる
- Spectre のようなサイドチャネル攻撃でポインタとメモリブロックの値を漏えいさせれば、正しいタグを合わせて MTE を回避できるが、こうした漏えいは主にローカル攻撃者にとって可能なものだ
- 現在、Google Pixel 8 だけが開発者向けオプションで MTE の有効化を許可しており、MTE はデフォルトでは無効になっている
- カーネルで MTE を有効にするには追加手順が必要である
Mali JIT メモリで発生するレース
- Mali GPU ドライバを使うユーザーアプリは、ドライバファイルを開き、
ioctl 呼び出しで kbase_context カーネルオブジェクトを作成・初期化する
kbase_context は GPU デバイスとユーザー空間アプリケーションの間で共有される複数種類のメモリを管理する
- Mali GPU のメモリ領域は
kbase_va_region で表現され、nr_pages は仮想サイズ、gpu_alloc->nents は実際の backing page 数を表す
- JIT メモリは、カーネルドライバが寿命を管理する native memory であり、アプリは GPU コマンドで JIT メモリを割り当てたり解放したりする
- CSF GPU では、ソフトウェアコマンドとハードウェアコマンドがそれぞれ別のキューに置かれる
KBASE_IOCTL_KCPU_QUEUE_CREATE で kbase_kcpu_command_queue を作成できる
KBASE_IOCTL_KCPU_QUEUE_ENQUEUE でコマンドをキューに入れる
BASE_KCPU_COMMAND_TYPE_JIT_ALLOC と BASE_KCPU_COMMAND_TYPE_JIT_FREE が JIT の割り当て・解放に使われる
kbase_jit_allocate は解放済み JIT メモリプールから再利用可能な領域を探し、物理サイズが足りなければ kbase_jit_grow で backing page を増やす
kbase_jit_grow は kbase_mem_pool_grow 呼び出し中に kctx->reg_lock と kctx->mem_partials_lock を一時的に解放することがある
kctx->reg_lock はメモリ領域への同時アクセスを保護するため、ロックが外れる区間がレースウィンドウになる
CVE-2023-6241 のトリガーフロー
- GPU が物理ページで backing されていないメモリ領域アドレスにアクセスすると、GPU メモリアクセスフォルトが発生する
kbase_mmu_page_fault_worker は領域が拡張可能かを確認したうえで、必要な backing page を即座に割り当ててマッピングできる
- JIT 領域は作成時に
KBASE_REG_PF_GROW と KBASE_REG_GPU_WR を含む GROWABLE_FLAGS_REQUIRED 条件を満たす
- JIT 領域の解放時に付く
KBASE_REG_DONT_NEED フラグは、kbase_jit_grow 冒頭の kbase_mem_evictable_unmake で削除される
- その結果、
kbase_mem_pool_grow 実行中のレースウィンドウで同じ JIT 領域に GPU ページフォルトを起こすと、フォルトハンドラがその領域を拡張できる
- フォルトハンドラが
reg->gpu_alloc->nents を変えると、kbase_jit_grow が前もって保存した old_size と delta の値が実際の状態とずれる
- その後、
kbase_alloc_phy_pages_helper_locked と kbase_mem_grow_gpu_mapping が stale な値で backing page の割り当てと GPU マッピングを行い、GPU マッピングと pages 配列の間に不整合が生じる
- このレースは
kbase_mem_pool_grow が大きなメモリ割り当てを含むため、容易に勝つことができる
GHSL-2023-005 パッチ以降に変わった攻撃方式
- 以前の脆弱性 GHSL-2023-005 では、別スレッドが
KBASE_IOCTL_MEM_COMMIT で JIT 領域を縮小し、old_size と delta を無効化できた
- GHSL-2023-005 のパッチ以降は、
KBASE_IOCTL_MEM_COMMIT ioctl で JIT メモリサイズを変更できなくなった
- CVE-2023-6241 では、レースウィンドウで領域を縮小できず、拡張することだけが可能である
- 単に拡張する場合は、最後の一部の backing page が GPU にマッピングされないだけで、最初から連続マッピングされた形は維持されるため、直ちに問題にはならない
- エクスプロイトは追加の GPU フォルトで unmapped gap の後ろに新しいマッピングを作り、その後の JIT 解放により、その gap 内部へ shrink 地点を合わせて悪用可能な状態を作る
GPU マッピング解除の脆弱な前提
kbase_mmu_teardown_pgd_pages は GPU ページテーブルをたどりながらエントリを invalid としてマークし、GPU アドレスマッピングを削除する
- この関数は、高レベル PTE が invalid であれば、そのエントリがカバーする大きなアドレス範囲全体がすでに unmapped であると見なしてスキップする
- レベル 2 PTE 1 つは 512 ページ範囲をカバーする
- 正常な
kbase_va_region では、マッピングされた仮想アドレスが常に領域の先頭から連続しており、中間に gap がないため、このスキップ動作は安全である
- CVE-2023-6241 のエクスプロイトは、マッピングの間に unmapped gap を作ったうえで、shrink 開始地点を gap の中に置く
kbase_mmu_teardown_pgd_pages は invalid なレベル 2 PTE に遭遇して 512 ページをスキップするが、その後方の一部アドレスは実際にはマッピングされている可能性がある
- 誤ってスキップされた GPU アドレスは、backing page が解放された後もその物理ページへのアクセスを維持する
カーネルコード実行につながる過程
- JIT 領域を解放すると backing page は返却されるが、誤って残った GPU マッピングは解放済みページへ引き続きアクセスできる
- 解放済み backing page は、その後ほかのカーネルページとして再利用され得る
- 使われた手法の 1 つは、解放済み backing page を GPU
kbase_context の PGD(page table global directory) として再利用させる方法である
- Mali ドライバの backing page 割り当ては階層的に行われる
- まず現在の
kbase_context の kbase_mem_pool からページを取得する
- 足りなければ
pool->next_pool を使う
- それでも不足する場合は、カーネルの buddy allocator を通じてページを直接割り当てる
pool->next_pool は Mali ドライバが管理し、すべての kbase_context が共有するメモリプールで、GPU コンテキストの PGD 割り当てにも使われる
- 解放済みページが PGD として再利用されると、残っている GPU アドレスを通じて、その PGD を GPU から再び書き込める
- PGD を書き換えると、任意のカーネルメモリとカーネルコードを GPU にマッピングできる
- この状態でカーネルコードを書き換えて任意のカーネルコード実行が可能になり、カーネルデータを読み書きしてプロセスの credential 変更や SELinux の無効化も可能になる
- Pixel 8 向けエクスプロイトと設定メモは GitHub Security Lab リポジトリ で公開されている
MTE 回避が可能な理由
- このエクスプロイトフローには、MTE 専用の回避ステップは特に必要ない
- MTE は、ポインタが指すメモリブロックのタグが一致するかを検査し、不正な逆参照を検出する機能である
- CVE-2023-6241 のトリガー時点では
pages 配列と GPU マッピングの間に不整合が生じるが、それぞれを個別に見ると invalid entry があるわけではない
kbase_mmu_teardown_pgd_pages が GPU マッピング削除をスキップすると、解放済みメモリページの物理アドレスが GPU ページテーブルに残る
- GPU がこの解放済みページにアクセスするときは物理アドレスへ直接アクセスするため、ポインタ逆参照検査を経由しない
- GPU メモリアクセスに MTE が及ぼす影響も明確ではない
- 結果として、このバグはコプロセッサである GPU が物理メモリへ直接アクセスする経路を利用し、MTE の保護を回避する
MTE 後にも残る攻撃対象領域
- CVE-2023-6241 は、カーネル MTE が有効な Pixel 8 でも単一のバグで任意のカーネルコード実行に到達できることを示している
- MTE はメモリ破損の緩和における重要な進展であり、多くのメモリ破損脆弱性を悪用不能にし得るが、万能の防御策ではない
- この事例では、GPU が物理メモリへ直接アクセスする方式で MTE を回避している
- CPU 側のハードウェア・ソフトウェア緩和策が増えるほど、コプロセッサとそのカーネルドライバは引き続き強力な攻撃対象領域になり得る
1件のコメント
Hacker News のコメント
ここでの要点は、GPUが以前から Android の悩みの種だったという点です。
GPU は AP に対して非常に強いアクセス権限を持っているため、前段に設けた緩和策を事実上回避できます。ドライバのマッピングコードのバグは強力な攻撃プリミティブにつながり、実際の野生のエクスプロイトでも繰り返し悪用されてきました。結局、構造を再設計するまでは大きく変わるのは難しそうです。
この脆弱性で興味深いのは、Arm Mali GPU のメモリ管理ユニットにある論理バグで、Memory Tagging Extensionを回避できるという点です。
ところが記事の残りの部分は、実際の原因は競合状態であり、解放後使用はその結果だと説明しているように見えます。
3月のアップデート前の GrapheneOS インストールにも影響があったのでしょうか?
ときにはリリース前の AOSP セキュリティパッチレベルを採用したり、まだ公開されていない AOSP やカーネルソースのセキュリティ修正もバックポートします。
追記: 無視してください。最近の GrapheneOS ブログにあった「MTE がすべてのシステムアプリにも適用される問題を見つけた」という記事と混同していました。GrapheneOS は 2024030600 リリースで「完全な 2024-03-05 セキュリティパッチレベル」を取り込んでいるので、このパッチも含まれているようです。
確率的な Arm MTE のメモリ安全性は、決定論的な CHERI ハードウェアへ向かう足がかりである。https://saaramar.github.io/memory_safety_blogpost_2022/ および https://news.ycombinator.com/item?id=39668053
正しい緩和策は、一次的な攻撃プリミティブ、つまりバグの根本原因を狙うべきである。ハードウェアによる解決策には CHERI(Morello, CheriIoT)、MTE があり、ソフトウェアによる緩和策には kalloc_type+dataPAC、AUTOSLAB、Firebloom、GuardedMemcpy、CastGuard、攻撃面の縮小がある。安全なプログラミング言語としては Rust と Swift がある。MTE と CHERI はうまくかみ合い、この領域のバグを根本原因から取り除く助けになる。MSR、MSRC、Azure Silicon は、最小の RISC-V コア仕様である RISC-V32E まで CHERI を縮小する方向を推進した
Microsoft Research は IoT デバイス向けの CHERI ハードウェア/ソフトウェアスタックをオープンソースで公開した。https://msrc.microsoft.com/blog/2023/02/first-steps-in-cheri...
CHERI ベースのマイクロコントローラは、命令セットアーキテクチャ(ISA)、アプリケーションバイナリインターフェース(ABI)、隔離モデル、ソフトウェアスタックの中核部を共同設計することで、非常に強いセキュリティ保証を得ることを目標としている。このマイクロコントローラは、CHERI-ISA 機能による空間安全性の決定論的緩和、ロードバリア・ゼロ化・回収・1ビット情報フロー制御によるヒープおよびコンパートメント間スタックの時間安全性の決定論的緩和、追加の CHERI-ISA 機能と小さなモニターによる細粒度のコンパートメント化を実現する
David Chisnall、ケンブリッジ大学、https://lobste.rs/s/gnjx2n/c_can_be_memory_safe#c_9ohzku via https://eclypsium.com/blog/a-faster-path-to-memory-safety-ch...
「複数の信頼できるコンピューティング基盤に含まれるオープンソースの C/C++ コードは約130億行あり、プロプライエタリコードを含めればさらに大きくなる。今すぐ全員が C/C++ の作成をやめ、すべてのソフトウェアエンジニアがレガシーコードを安全な言語で書き直すことに集中したとしても、すべてを置き換えるには5〜10年はかかるだろうし、長年検証されてきたコードを、安全な言語で許されるイディオムに合う別のアルゴリズムやデータ構造を必要とする新しいコードへ置き換える過程で、多くの論理バグも生まれる可能性が高い」
「書き直しをせず、C/C++ コードの作成をやめるだけなら、一般的なコード置換速度では、信頼できるコンピューティング基盤が完全に安全になるまで約50年かかる。全員が C/C++ をやめることで合意しないなら、少なくとも100年だ」
「一方で主要 CPU メーカーが5年以内に CHERI CPU を出荷するなら、プログラマーが行動を変えなくても、今日から15年以内に大半のマシン、とくに高価値なマシンはメモリ安全性を備えることになる」
1つ目は Sonata システムである: https://github.com/lowRISC/sonata-system。FPGA と複数の周辺機器およびヘッダーを備えた専用 PCB で構成される。PCB 設計は完了しており、Mouser 経由で入手できる予定で、ボードレイアウトまでオープンソースなので、望めば自分で組み立てることもできる。現在は FPGA 向け RTL に取り組んでいる。完成すれば、ドキュメントとツールまで備えた CHERIoT ベースのマイクロコントローラ型システムが得られる
さらに Sonata と OpenTitan Earl Grey の Root of Trust を組み合わせた Symphony システムも作っている: https://github.com/lowRISC/symphony-system
それに最後に見たとき、CHERI は健全ではなかった。その上でもメモリバグを書けたのだが、今は直ったのだろうか?
自転車置き場論争だけでもそれくらいかかりそうだ
このエクスプロイトは Rust を含め、ほとんどの言語で書けそうだ
ハードウェアが そこまで ひどいって? なんてことだ……
このやり方が可能なのは、GPU が CPU のように比較的直接的なハードウェアアクセスを許さないからだ
すばらしい研究と記事だが、GitHub のブログに載っているのは少し意外でもあり、うれしくもある。
こうした研究を GitHub が行う「ビジネス上の理由」を知っている人はいるだろうか? 必ずしもビジネス上の理由が必要だという意味ではないが、ここで見かけて少し驚いた。
その研究機能が GitHub Security Lab へとつながった。Semmle は CodeQL を作り、現在は GitHub が提供している(https://docs.github.com/en/code-security/code-scanning/intro...)。GitHub と Microsoft は CodeQL を「深いセキュリティ洞察」と結び付けたいと考えている(https://www.microsoft.com/en-us/security/blog/2023/11/02/ann...)
そのため、こうした斬新なセキュリティ研究に継続して資金を出しており、業界のセキュリティ実務者はそれに感謝している。
GitHub アプリもあり、そのアプリのセキュリティは GitHub の範囲外ではない。攻撃者がスマートフォンに潜むアプリをインストールできれば、ユーザーのふりをしてさまざまなことができる。GitHub 上で影響力のある人なら被害はかなり大きくなり得るので、こうした脆弱性を見つけることは GitHub にとっても利益になる。
そのため、MTE を利用したサンドボックス化のために、Arm ハードウェアのセキュリティ機能を確認・検証することに関心があるのかもしれない。
まず第一に考えれば、GitHub という製品に Android セキュリティ専門家が必ずしも必要なわけではない。しかし長期的には潜在的な利益がある。
[0]: https://en.wikipedia.org/wiki/Basic_research
GPU がほとんどない、あるいはまったくない CPU とスマートフォンを作り、ビジネスフォンと呼んだ例がまだないのは驚きだ。
セキュリティ、コスト、消費電力の面で明確な利点がありそうに見える。