完全に非特権のコンテナが、共有コンテナイメージレイヤーを通じてCVE-2026-31431 Linuxカーネルのページキャッシュ破損バグを悪用し、Kubernetes上でノードレベルのコード実行を達成できることを実証するProof-of-Conceptです。
免責事項: このリポジトリは教育および防御目的でのみ公開されています。所有しているシステム、または明示的なテスト許可を得たシステムでのみ使用してください。
CVE-2026-31431("Copy Fail")は、ページキャッシュのコピーオンライト(CoW)パスにおけるLinuxカーネルの脆弱性です。AF_ALGスプライスレースにより、非特権プロセスが読み取り専用ファイルのページキャッシュページを破損させることができます。この破損はカーネルのページキャッシュに永続し、その後そのファイルを読み取るまたは実行するすべてのプロセス(他のコンテナやホスト上のプロセスを含む)から参照可能になります。
元の脆弱性の詳細については、copy.failを参照してください。
攻撃チェーンには3つの段階があります:ページキャッシュ破損、クロスコンテナ伝播、特権実行です。
カーネルのAF_ALG(暗号)サブシステムは、ユーザー空間の暗号操作のためのソケットベースのインターフェースを公開しています。このエクスプロイトは、ファイルからAF_ALGソケットへのsplice()をカーネルが処理する際のレースコンディションを悪用します:
/usr/sbin/ipset)を読み取り専用で開きます。authesn(hmac(sha256),cbc(aes))にバインドされたAF_ALG AEADソケットを作成します。MSG_MOREフラグを付けてAF_ALGソケット経由で小さなペイロードチャンクを送信し、カーネルにさらなるデータが来ることを伝えます。splice()します。エクスプロイトは、ターゲットバイナリのキャッシュされたページ全体がカスタムペイロードで上書きされるまで、これを4バイトウィンドウごとに繰り返します。
ファイルへの書き込み権限は不要です。ディスク上のファイルは変更されません — メモリ内のページキャッシュのみが破損します。
コンテナランタイム(containerd、CRI-O)はoverlayファイルシステムを使用します。2つのコンテナが同じイメージレイヤーを共有する場合、カーネルは同じページキャッシュページからファイル読み取りを提供します。
このPoCイメージはFROM registry.k8s.io/kube-proxy:v1.35.2でビルドされています。すべてのKubernetesノード上のkube-proxy DaemonSetはまったく同じベースレイヤーを使用します。その結果、両方のコンテナ内の/usr/sbin/ipsetは同一のページキャッシュページセットにマッピングされます。
非特権のPoCコンテナがipsetのページキャッシュを破損すると、その破損はクロスコンテナ通信を一切行わずに、同じノード上の特権kube-proxyコンテナに即座に認識されます。
kube-proxyはhostNetwork: trueを持つ特権DaemonSetとして実行されます。iptables/ipsetルールを管理するために/usr/sbin/ipsetを定期的に呼び出します。次にipsetを実行するとき、カーネルは破損したページキャッシュページをロードし、kube-proxyの完全な特権で攻撃者のペイロードを実行します:
このPoCのペイロード(payload/payload.c)は、ホストのルートファイルシステムをマウントし、ノードレベルのコード実行の証明として/root/resにマーカーファイルを書き込むだけです。
┌──────────────────────────┐ ┌──────────────────────────┐
│ PoCコンテナ │ │ kube-proxyコンテナ │
│ (非特権) │ │ (特権) │
│ │ │ │
│ 1. /usr/sbin/ipsetを開く │ │ │
│ (読み取り専用) │ │ │
│ │ │ │
│ 2. AF_ALGスプライスレース│ │ │
│ ページキャッシュを破損 │ │ │
│ │ │ │ │
└──────────┼───────────────┘ └──────────────────────────┘
│ │
▼ │
┌─────────────────────┐ │
│ カーネルページキャッシュ│ │
│ /usr/sbin/ipset │◄────────────────────┘
│ (破損) │ 3. kube-proxyがipsetを実行
│ 攻撃者のペイロードを │ → 破損ページをロード
│ 含む │ → ペイロードがホスト上で
└─────────────────────┘ rootとして実行
.
├── cmd/copyfail/main.go # エントリポイント; コンパイル済みペイロードを埋め込み
├── internal/
│ ├── exploit/
│ │ ├── exploit.go # コアエクスプロイト: AF_ALGスプライスレースループ
│ │ └── patch.go # ペイロードを4バイトパッチウィンドウに分割
│ └── alg/
│ └── alg.go # AF_ALG AEADソケット抽象化
├── payload/
│ ├── payload.c # 検証ペイロード (ホストfsのマウント、マーカー書き込み)
│ └── nolibc/ # 静的・依存関係なしペイロード用のカーネルの小型libc
├── deploy/
│ └── poc.yaml # Kubernetes Deploymentマニフェスト
├── Dockerfile # イメージレイヤー共有のためkube-proxyからビルド
├── Makefile # ビルドオーケストレーション
└── docs/ # ACK (Alibaba Cloud) からの検証エビデンス
x86_64-linux-gnu-gcc)imagePullPolicy: IfNotPresent(デフォルト)で実行しているKubernetesクラスター# ペイロード + Goバイナリをビルド
make build
# Dockerイメージをビルド
make docker-build
# GHCRにビルドしてプッシュ
make docker-push IMAGE=ghcr.io/<you>/copy-fail-poc TAG=latest
arm64ターゲットの場合:
make build CC=aarch64-linux-gnu-gcc GOARCH=arm64
kubectl apply -f deploy/poc.yaml
Deploymentは単一の非特権ポッドを作成します。そのポッドは:
/bin/copyfail -target /usr/sbin/ipsetを実行してページキャッシュを破損します。kube-proxyが次にipsetを実行した後(通常はリコンシリエーションループにより数秒以内、または次の再起動時)、ノードを確認します:
# ノードにSSH接続するか、特権デバッグポッドを使用
cat /root/res
# 期待される出力: [*] success
ホストファイルシステム上に/root/resが存在することは、攻撃者が提供したコードがノードレベルの特権で実行されたことを証明します — kube-proxyの特権コンテナコンテキスト内から書き込まれます。
kubectl delete -f deploy/poc.yaml
# 影響を受けたノードで、マーカーを削除しkube-proxyを再起動:
rm -f /root/res
systemctl restart kubelet # またはkube-proxyポッドを削除して再プルを強制
kube-proxyは理想的なターゲットです。なぜなら:
privileged: true、hostNetwork: true。imagePullPolicy: IfNotPresentを使用 — 攻撃者のイメージがプルされ同じベースレイヤーを共有すると、overlayのlower-dirページが共有されます。予測可能なバイナリを含むイメージを持つ特権DaemonSetは、同じ方法でターゲットにできる可能性があります。
デフォルトのペイロード(payload/payload.c)は、マーカーファイルを書き込む検証専用プログラムです。カスタムペイロードをビルドするには:
payload/payload.cを編集します。このプログラムは静的で依存関係のないバイナリのため、nolibc(カーネルの最小Cライブラリ)に対してビルドされます。make payloadを実行してクロスコンパイルします。//go:embedを介してGoバイナリに埋め込まれます。tools/include/nolibc/)このリポジトリのGoエクスプロイトコードは、研究目的で現状のまま提供されます。
ペイロード(payload/payload.c)はcopy-fail-cから派生しており、LGPL-2.1-or-later OR MITのデュアルライセンスです。LICENSE-LGPLおよびLICENSE-MITを参照してください。