
Linux LPE - 確実な Jail/コンテナエスケープ
CentOS / RHEL 10 向けの、信頼性のある非特権コンテナ / 脱獄(jail escape)の概念実証です。
これは、__ip6_append_data() の(現在は修正済みの)IPv6 フラグメンテーションのバグ(上流では 38becddc でクローズ、CVE なし)を利用します。これは、パケット自身のヘッダオブジェクトの末尾にある skb_shared_info へのスラブ内線形オーバーフローです。この README は悪用チェーンのみを説明します。トリガについては扱いません。
6.12.0-242.el10)。これは概念実証であり、意図的にターンキー型の兵器ではありません。信頼性のための仕組みは意図的に省かれています。CPU ごとの PCP グルーミングも、クロスキャッシュ SLUB アーマリングも、skb ヘッダを静かな共有キャッシュに配置する処理もありません。同梱されているものは、チェーンを証明するのに十分な頻度で発火しますが、それ以上ではありません。対象は CentOS / RHEL 10 のみです。
CONFIG_INIT_ON_ALLOC_DEFAULT_ON がオフであること(RHEL / CentOS のデフォルト)。この PoC は未初期化のスラブバイトに stale ポインタを仕込みます。init_on_alloc=1 ではそのスロットがゼロ化され、この特定の手法ではカーネルをクラッシュさせるだけです(NULL デリファレンス)。これはバグではなく手法の限界です。修正と CVE が公開され次第、別の手法でそれらのカーネルもカバーする完全なエクスプロイトが続きます。/sys/kernel/btf/vmlinux が存在し、ワールドリーダブルであること(RHEL / CentOS では標準)。| ディストリビューション | カーネル | 結果 |
|---|---|---|
| CentOS Stream 10 | 6.12.0-242.el10 | root、コンテナエスケープ |
| RHEL 10 | 6.12.0-228.el10 | httpd_t コンテキストエスケープ |
スラブ内オーバーフローから自己 UAF へ。 このバグにより、skb_shared_info 内の単一の nr_frags バイトを制御できます。解放パス(skb_release_data())は frags[0 .. nr_frags) を走査して各エントリに put_page() を呼びますが、frags[] は決して初期化されません。パイプバッファページ用の struct page * を、制御したキャッシュ再利用を通じてそのスラブスロットにあらかじめ仕込み、その後 nr_frags を 1 に設定することで、解放処理は自分がまだ所有しているページの参照を一つ減らします。線形オーバーフローはページの use-after-free になります。オーバーフローは frags[0] に触れないため、スプレーに対してタイミングを合わせる必要がありません。これが、この手法を寛容にしている主な理由です。
ページ UAF から Dirty-Pagetable へ。 解放されたパイプページは、新しい匿名マッピングにフォルトさせることで、最終レベルページテーブルとして再利用されます。1 つの物理ページが、生きたリーフページテーブルであると同時に、パイプが引き続き読み書きするページになります。パイプに 8 バイト書き込むと偽造 PTE がインストールされ、パイプを読み出すとテーブルが読み戻されます。これは、テーブルあたり約 460 個の PTE ウィンドウに相当する、有限の任意物理読み書きです。
KASLR を無効化。 物理読み取りを使って、低メモリの固定 SMP トランポリンページテーブルをスキャンします。これは KASLR が決して再配置しないものです。そのカーネル半分のエントリは level4_kernel_pgt、つまりカーネルの物理ベースを指しています。そこから、自己参照エントリ 511 で識別できる init_top_pgt は普遍的な仮想から物理への変換器となり、カーネルの仮想ベースを回復し、自分のアドレス空間を物理メモリに結び付けます。
有限から無限の読み書きへ。 リーフテーブル内に、テーブル自身の物理アドレスを指す PTE を 1 つ偽造します。対応する仮想ウィンドウはページテーブル自体をエイリアスするため、PTE は単なるメモリになります。つまり、ループ内にパイプを使わない、無制限でランダムアクセス可能なカーネル読み書きです。Ring 3 TLB の一貫性は、過大サイズの mprotect() でフル mm フラッシュを強制して処理します。
オフセット解決と資格情報の取得。 構造体メンバのオフセットは実行時に /sys/kernel/btf/vmlinux から読み取られ、ハードコードされません。vmemmap の struct page ウォークと mm_struct.owner を通じて自分の task_struct を特定し、資格情報 ID をゼロ化し、cred と real_cred のすべてのケーパビリティセットを埋めます。シンボルアドレスは、任意読み取りを通じてカーネル自身の kallsyms テーブルを解析する内部リゾルバから取得します。それが失敗した場合のみ、cred.user_ns を init_user_ns に向け(初期名前空間で CAP_SYSLOG が有効になる)、実際のアドレスを /proc/kallsyms から読み取るフォールバックを使用します。
enforcing を切り替えずに SELinux を無効化。 avc_denied() のプロローグを xor eax, eax ; ret スタブで上書きします(IBT 下では endbr64 をスキップ)。これにより、すべての拒否がプロセス全体で許可済みとして返る一方、getenforce は依然として Enforcing を報告します。これは脱出の前提条件でもあります。コアダンプハンドラのクロスドメイン再実行は、そうでなければ拒否されるためです。
core_pattern を通じた脱出。 グローバルな core_pattern を | で始まるハンドラで上書きし、/proc/<PID>/root(クラッシュしたタスクのコンテナ chroot)を通じて自分のバイナリを指すようにしてから、子プロセスをクラッシュさせます。カーネルはハンドラを usermodehelper として実行します。これは初期名前空間の root であり、コンテナ内にバインドされた Unix ソケット経由で対話的な root シェルを中継します。ハンドラは init 名前空間内で生成されるため、その場での名前空間の外科的変更や、PID 名前空間に関する危険性は一切不要です。