Copy Fail: ページキャッシュ汚染によるコンテナ分離の破壊

The security of modern containerized environments relies on the assumption that namespaces and cgroups provide a robust boundary between workloads. However, a recently disclosed vulnerability known as "Copy Fail" shatters this assumption by targeting a fundamental component of the Linux kernel: the page cache. Unlike traditional kernel exploits that rely on fragile race conditions or Use-After-Free (UAF) bugs to achieve code execution, Copy Fail provides a deterministic primitive for rewriting cached file contents, enabling attackers to move laterally between pods or escape to the host entirely.

Copy Failの仕組み

本質的に、Copy Failはローカル特権昇格脆弱性であり、IPSec ESP拡張シーケンス番号(authencesn)を処理するカーネルコードのメモリ破壊欠陥を悪用します。この機能は、Linuxカーネル暗号サブシステムのユーランドインターフェースである AF_ALG ソケットを通じて、特権のないユーザーに公開されています。

カーネルを、ページキャッシュへの可変参照を使い捨てのスクラッチメモリとして扱うように混乱させることで、攻撃者は splice(2) を使用して任意の読み取り可能なファイルを裏付けるページキャッシュに対し、制御された4バイト書き込みを実行できます。これにより、攻撃者は物理ディスク上のバイトを変更することなく、ファイルのキャッシュされたバージョンを改ざんできます。書き込みが標準の会計処理や overlayfs の「copy-up」メカニズムをバイパスするため、変更は共有された下位レイヤーのファイルに直接行われます。

なぜコンテナが脆弱なのか

コンテナの分離は、マウント、ネットワーク、PID、ユーザー、IPC の各名前空間によって実装されています。重要なのは、これらの名前空間のいずれもコンテナごとのページキャッシュを作成しないことです。カーネルのページキャッシュはシステム全体で共有されています。

Kubernetes環境では、コンテナイメージは読み取り専用の下位レイヤーで構成されています。スペースを節約するために、コンテナランタイム(containerd や CRI-O など)はコンテンツハッシュによりこれらのレイヤーを重複排除します。同一ノード上の異なるポッドが同じベースイメージ(例:debian:bookworm-slimpython:3.12-slim)を共有している場合、同一の基盤ホスト inode と address_space を共有します。

Copy Fail がページキャッシュ内のフォリオを改変すると、同じ address_space を指すすべてのコンテナ内のファイルディスクリプタが改変されたバイトを参照します。これにより、主に以下の2つの攻撃ベクトルが生じます:

シナリオ1: コンテナ間汚染

このシナリオでは、1つのポッドでコード実行権限を持つ(または単にポッド作成権限を持つ)攻撃者が、広く共有されているベースレイヤーを標的にできます。

  1. ターゲット選択: 攻撃者は、site-packages 内の Python モジュールやベースレイヤー内の共有ライブラリ glibc など、共通のファイルを特定します。
  2. 書き込み: Copy Fail を使用して、攻撃者は4バイト書き込みを連鎖させ、ページキャッシュ内の対象ファイルをパッチします。
  3. トリガー: 同一ノード上の別の無関係なポッドがそのモジュールをインポートしたりライブラリを実行したりすると、キャッシュから汚染されたバイトがロードされ、攻撃者のコードが実行されます。

これにより、攻撃者は同一ノード上で侵害されたまたは攻撃者が制御するポッドとベースイメージを共有しているだけで、堅牢化されたバックエンドポッドを侵害できます。さらに、攻撃者が pods/create 権限を持っていれば、被害者のノード上に意図的にポッドをスケジュールし、同じベースイメージを取得して汚染をトリガーできます。

シナリオ2: コンテナからホストのルートへの脱出

Copy Fail は、特権のないコンテナからホストへの完全な脱出にも利用できます。この手順は「Dirty Pipe」脱出パターンに類似しています。

  1. runc 実行の強制: 攻撃者はコンテナ内の /bin/sh/proc/self/exe を指すシバンに上書きします。管理者が kubectl exec を実行すると、runc が呼び出され、コンテナの PID 名前空間に固定されます。
  2. 特定と汚染: 攻撃者は runc プロセスを特定し、その /proc/<pid>/exe シンボリックリンクを開きます。runc はホストからバインドマウントされているため、ページキャッシュ内のホスト側 runc バイナリへのパスが得られます。
  3. 実行: 攻撃者は Copy Fail を使用して runc の ELF ヘッダーを悪意あるペイロードで上書きします。次にホスト上で runc が実行されると(プローブ、ポッド起動、または別の exec など)、悪意あるコードがホストの root 権限で実行されます。

検出と緩和策

Copy Fail の最も危険な側面の一つは、従来のセキュリティツールに対して不可視であることです。ディスク上のバイトは変更されないため、イメージレジストリスキャナ(Trivy、Clair)、エージェントレスのディスクスキャナ、ファイル整合性モニタ(AIDE、Tripwire)などはシステムがクリーンであると報告します。

有効な防御策

防御メカニズム 効果 備考
カーネルパッチ適用 唯一の恒久的な修正は、ホストカーネルをパッチ適用済みバージョンに更新することです。
Seccomp プロファイル socket(AF_ALG, ...) をブロックすることで、エクスプロイトの原始手段が除去されます。
gVisor / Kata Containers これらは別個のカーネルまたはマイクロVMを提供し、共有ページキャッシュを排除します。
マネージドマイクロVM (Fargate) ポッドごとのカーネルにより、ポッド間の汚染が防止されます。
ランタイム EDR 部分的 攻撃後の挙動やメモリ内ページの不一致を検知できます。

結論

Copy Fail は、Linuxページキャッシュの共有特性がコンテナセキュリティにおける重大なアーキテクチャ上の盲点であることを示しています。名前空間はリソースの論理的分離を提供しますが、カーネルの基礎となるメモリ管理は依然としてグローバルリソースです。厳格なマルチテナンシーを必要とする組織にとって、この脆弱性は、コンテナを主要なセキュリティ境界として使用すべきではなく、VMベースの分離(または gVisor のようなサンドボックスランタイム)が高リスクワークロードの保護に不可欠であるという主張を裏付けます。

Sources