
Rust製のLinuxカーネルLPE CVE-2026-31431(AF_ALGページキャッシュ書き込み)向けエクスプロイトPoC。さらに、再起動不要でLSMまたはkprobeを介してAF_ALGソケット作成をブロックするeBPFランタイム防御も含む。
CVE-2026-31431(Copy Fail)は、Linuxカーネルのalgif_aeadモジュール(AF_ALGサブシステム)におけるローカル権限昇格の脆弱性です。2017年8月に導入されたロジックの欠陥により、権限のないローカルユーザーがAF_ALGとsplice()を介して、読み取り可能な任意のファイルのページキャッシュに4バイトの制御データを書き込むことができ、その後、破損したsetuidバイナリを実行してroot権限を取得できます。このエクスプロイトは決定的です — レースコンディションなし、カーネルオフセット不要、システムクラッシュなし。カーネル4.14から7.0-rcまでを搭載する主要なLinuxディストリビューションすべてに影響します。
CVSS: 7.8 | 修正バージョン: カーネル7.0、6.19.12、6.18.22 | メインライン修正: コミットa664bf3d603d
ソース: copy.fail、The Hacker News、CloudLinuxアドバイザリ
crates/
├── exp/ # エクスプロイトPoC(Rustによる再実装)
├── copy_fail_guard/ # ユーザースペースeBPFローダー — LSMモード
├── copy_fail_guard-ebpf/ # eBPF LSMプログラム(ワークスペース外)
├── copy_fail_guard_kprobe/ # ユーザースペースeBPFローダー — kprobeモード
└── copy_fail_guard_kprobe-ebpf/ # eBPF kprobeプログラム(ワークスペース外)
scripts/
└── build_guard.sh # ワンクリックビルド → dist/に出力
根本原因は、3つの独立したカーネル機能が安全でない形で連鎖的に相互作用することです:
AF_ALGソケット — カーネルの暗号APIを権限のないユーザースペースに公開splice() — ファイルデータをコピーではなくページキャッシュ参照として暗号scatterlistにゼロコピー転送authencesn AEADテンプレート — 呼び出し元の出力バッファをスクラッチ領域として使用し、dst[assoclen + cryptlen]に4バイトを書き込む2017年、algif_aead.c(72548b093ee3)のインプレース最適化によりreq->src == req->dstとなり、ページキャッシュページが書き込み可能な宛先scatterlistに連結されました。authencesnがスクラッチバイトを書き込む際、出力バッファを越えて連結されたページキャッシュページにまで到達します。攻撃者は以下を制御できます:
破損したページはダーティとしてマークされません — ディスク上のファイルは変更されませんが、execve()はページキャッシュから読み取ります。setuidバイナリを破損 → root権限取得。
公開されている732バイトのPython PoCのRustによる再実装です。/usr/bin/suをターゲットにし、そのページキャッシュページをAF_ALG AEADソケットにスプライスし、圧縮されたシェルペイロードで上書きします。
Rust 1.85+(edition 2024)が必要です。
cargo build --release -p copy_fail
このバイナリはLinux専用です。他のプラットフォームではUnsupportedエラーで終了します。
警告: これは実際のカーネル欠陥を悪用します。自分が所有・管理するシステムでのみ実行してください。理想的には使い捨てのVMが推奨されます。
脆弱なカーネル(カーネル< 7.0 / < 6.19.12 / < 6.18.22の一般的なディストリビューション)を実行するVMを起動します。
ビルドしたバイナリをVMにコピーし、非rootユーザーとして実行します:
./target/release/copy_fail
脆弱な場合: rootシェル(#)が表示されます。whoamiを実行してrootを確認します。
脆弱でない場合(パッチ適用済みカーネル): AF_ALG操作が失敗するか、suバイナリが正常に動作します。エラーまたは通常のsuパスワードプロンプトが表示されます。
カーネルのアップグレードや再起動なしでCVE-2026-31431をブロックするランタイムカーネル防御です。eBPFを使用してAF_ALGソケットの作成をインターセプトし、エクスプロイトの最初のステップを遮断します。
2つのモードが提供されています。ワンクリックローダーが利用可能な最適なモードを自動選択します:
┌──────────────────────────────────────────────────┐
│ run_guard.sh │
│ • BPF LSMサポートを検出 │
│ • LSM利用可能 → copy_fail_guard (EPERM) │
│ • LSM利用不可 → copy_fail_guard_kprobe (KILL) │
└──────────────┬───────────────────────────────────┘
│
┌──────────────▼───────────────────────────────────┐
│ eBPFプログラム │
│ if socket family == 38 (AF_ALG) │
│ → ブロック (EPERM または SIGKILL) │
│ else │
│ → 許可 │
└──────────────────────────────────────────────────┘
AF_ALGのブロックは、一般的なシステムにほぼゼロの影響しか与えません:
AF_ALGを介さず、カーネル内暗号APIを直接使用しますAF_ALGを使用するように明示的に設定されたアプリケーション(例: afalgエンジンが有効なOpenSSL、一部の組み込み暗号オフロードパス)socket(AF_ALG, ...)を呼び出さないものにはゼロオーバーヘッドワンクリックビルド(推奨):
./scripts/build_guard.sh
これにより、不足しているツールチェーン(nightly、bpf-linker)が自動的にインストールされ、すべてのeBPFプログラムとユーザースペースローダーがコンパイルされ、すべてがdist/に出力されます:
dist/
├── copy_fail_guard.bpf.o # eBPF LSMプログラム
├── copy_fail_guard_kprobe.bpf.o # eBPF kprobeプログラム
├── copy_fail_guard # ユーザースペースローダー(LSM)
├── copy_fail_guard_kprobe # ユーザースペースローダー(kprobe)
└── run_guard.sh # 自動選択: sudo ./run_guard.sh
dist/ディレクトリを任意のターゲットマシンにコピーし、sudo ./run_guard.shを実行して保護を有効化します。
手動ビルド(ステップバイステップ):
ステップ1: eBPFプログラムをコンパイル(Linux上で実行する必要があります):
# LSMバリアント
cd crates/copy_fail_guard-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
# kprobeバリアント
cd crates/copy_fail_guard_kprobe-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
ステップ2: ユーザースペースローダーをビルド:
cargo build --release -p copy_fail_guard -p copy_fail_guard_kprobe
推奨 — 最適なモードを自動選択:
sudo ./dist/run_guard.sh
手動 — 特定のモードを実行:
# LSMモード(lsm=bpfが必要)
sudo GUARD_BPF_OBJ=path/to/copy_fail_guard.bpf.o RUST_LOG=info ./copy_fail_guard
# kprobeモード(どこでも動作)
sudo GUARD_BPF_OBJ=path/to/copy_fail_guard_kprobe.bpf.o RUST_LOG=info ./copy_fail_guard_kprobe
Ctrl-Cを押すとeBPFプログラムがデタッチされ、通常の動作に戻ります。
一方のターミナルでガードを実行中に:
# 別のターミナルでエクスプロイトを試行:
./target/release/copy_fail
# LSMモード: "error: Operation not permitted"
# kprobeモード: "已杀死" / "Killed"
# またはPythonで直接テスト:
python3 -c "import socket; socket.socket(38, 5, 0)"
# LSMモード: PermissionError: [Errno 1] Operation not permitted
# kprobeモード: Killed
AF_ALGソケット作成を無条件にブロックします。PID、cgroup、コマンド名による特定プロセスの除外メカニズムはありません。これは将来のバージョンで計画されています(eBPF HashMapマップ経由)。ほとんどのシステムではAF_ALGを使用するものはほとんどないため、問題ありません。bpf-linkerを使用して個別にコンパイルする必要があります(bpfel-unknown-noneをターゲットとするため、通常のワークスペースメンバーにはできません)。ほとんどのディストリビューションではBPF LSMはデフォルトで有効になっていません。よりクリーンなLSMモード(SIGKILLではなくEPERM)が必要な場合:
# 現在のLSMを確認:
cat /sys/kernel/security/lsm
# "bpf"がない場合は追加:
sudo sed -i 's/^GRUB_CMDLINE_LINUX="\(.*\)"/GRUB_CMDLINE_LINUX="\1 lsm=lockdown,capability,yama,apparmor,bpf"/' /etc/default/grub
sudo update-grub && sudo reboot
kprobeモードはこの手順なしで動作します。
カーネルを≥ 7.0 / ≥ 6.19.12 / ≥ 6.18.22にパッチ適用後:
# カーネルバージョンを確認
uname -r
# エクスプロイトを再実行 — rootシェルが生成されなくなります
./target/release/copy_fail
または、脆弱なモジュールが無効化されていることを確認:
modinfo algif_aead | grep filename
# 組み込みの場合はinitcallをブラックリスト化(再起動が必要)
sudo grubby --update-kernel=ALL --args="initcall_blacklist=algif_aead_init"
sudo reboot
| LSMモード | kprobeモード |
|---|
| ブロック方法 | -EPERMを返す(ソケット作成拒否) | SIGKILL(プロセス強制終了) |
| カーネル要件 | ≥ 5.7、lsm=bpfブートパラメータ付き | ≥ 5.3、特別なパラメータ不要 |
| 有効化に再起動が必要 | 場合による(lsm=bpfが未設定の場合) | 不要 |
| フックポイント | socket_create LSMフック | __sys_socket kprobe |