
CVE-2026-31431 の再現分析、C で改編した exp。
.
├── container
│ ├── container.md # コンテナエスケープの考え方
│ ├── expenv # コンテナ環境
│ │ ├── bin
│ │ │ ├── gscontainer
│ │ │ └── share
│ │ ├── Dockerfile
│ │ ├── run.sh
│ │ └── src
│ │ ├── gscontainer.c
│ │ └── share.c
│ ├── images
│ │ ├── container.png
│ │ ├── getshell.png
│ │ ├── host.png
│ │ ├── payload.png
│ │ └── share.png
│ ├── recode.bin # シングルステージの shellcode
│ └── stager
│ ├── shell.elf # exp 内に埋め込まれたステージ型 shellcode
│ ├── stager1.bin
│ ├── stager2.bin
│ ├── stager3.bin
│ └── stager4.bin
├── doc
│ ├── cve-2026-31431.md # xint.io による分析まとめ
│ └── shellcode.md # shellcode とは何かの解説
├── exp.py # curl http://copy.fail/exp -o exp.py
├── README.md
├── success
│ ├── code.py # exp.py が抽出した shellcode
│ └── exploit.c # C言語で簡潔化した exp。ネイティブの msf 権限昇格用 shellcode を含むため、自身でAV回避が必要
└── test
├── exptest.c # コメント除去版
└── test.c # 正しくコメント付きで、理解しやすい
個々には無害な3つのコミットが、組み合わさることで脆弱性が生じた:
| 時期 | コミット | 影響 |
|---|---|---|
| 2011 | a5079d084f8b | authencesn が追加され、ESN 並べ替えの一時領域として dst scatterlist が使用された。この時点では無害——旧 AEAD インターフェースが AAD を分離したままであり、唯一の呼び出し元はカーネル xfrm だったため。 |
| 2015 | 104880a6b470 | authencesn が新 AEAD インターフェースに変換され、「出力境界を越える」assoclen+cryptlen 位置への seqno_lo 書き込み動作が導入された。依然として悪用不可——AF_ALG が out-of-place モードを使用しており、req->src と req->dst が分離され、page cache ページは読み取り専用の src にのみ存在していたため。 |
| 2017 | 72548b093ee3 | AF_ALG in-place 最適化。AAD+暗号文を TX SGL から RX buffer にコピーするが、tag ページは sg_chain でチェーンされる。req->src = req->dst が設定される。これにより、splice が導入した page cache ページが書き込み可能な dst scatterlist にも含まれるようになった。脆弱性が成立。 |
splice(ファイル → pipe → AF_ALG)
↓
af_alg_sendmsg: ファイルの page cache ページ → TX SGL (ゼロコピー)
↓
_aead_recvmsg: in-place 最適化
├─ memcpy_sglist: AAD+暗号文を RX SGL にコピー
├─ sg_chain: tag ページ(依然としてファイルの page cache を指す)を RX SGL にチェーン
└─ req->src = req->dst (= ファイルの page cache ページを含む scatterlist)
↓
crypto_authenc_esn_decrypt: ESN 並べ替え
└─ scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1)
↓ dst scatterlist 内を tag の位置まで走査
↓ kmap_local_page() でファイルの page cache ページをマップ
↓ memcpy: seqno_lo をファイルの page cache ページに書き込み
↓
HMAC 検証失敗 → -EBADMSG を返す
↓
ファイルの page cache ページは変更されたが、カーネルはダーティページとしてマークせず、ディスク上のファイルは不変
| ディストリビューション | カーネルバージョン | 影響あり |
|---|---|---|
| Ubuntu 24.04 LTS | 6.17.0-1007-aws | はい |
| Amazon Linux 2023 | 6.18.8-9.213.amzn2023 | はい |
| RHEL 10.1 | 6.12.0-124.45.1.el10_1 | はい |
| SUSE 16 | 6.12.0-160000.9-default | はい |
論文によると、2017 年以降「ほぼすべての Linux ディストリビューション」が影響を受ける。page cache はコンテナ間で共有されるため、この脆弱性はローカル権限昇格 (LPE) であると同時に、コンテナエスケープのベクトルでもある。
カーネル mainline の修正コミット a664bf3d603d は、AF_ALG AEAD 操作を out-of-place(src と dst を分離)に戻す:
Before: aead_request_set_crypt(..., rsgl_src,
areq->first_rsgl.sgl.sgt.sgl, ...)
^ src == dst ^
After: aead_request_set_crypt(..., tsgl_src,
areq->first_rsgl.sgl.sgt.sgl, ...)
^ src ≠ dst ^ (TX SGL for src, RX SGL for dst)
コミットメッセージ:"There is no benefit in operating in-place in algif_aead since the source and destination come from different mappings."
緊急時の緩和策:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead
または、seccomp ポリシーにより AF_ALG ソケットの作成 (socket(AF_ALG, ...)) を禁止する。
| 特徴 | ソースコード検証 | 説明 |
|---|---|---|
| authencesn が末尾に seqno_lo を書き込む | authencesn.c:134 | assoclen+cryptlen の位置に書き込まれるのは seqno_lo(下位 32 ビット)。ユーザーの説明は正しい。 |
| ESN 並べ替えの規約 | ブログ (xint.io) で確認 | AAD bytes 0-3 = seqno_hi、bytes 4-7 = seqno_lo。tmp[1] (=bytes 4-7=seqno_lo) が末尾に書き込まれる。 |
| 復号パスは上書きされたバイトを復元しない | authencesn.c:270-273 vs 215-217 | decrypt_tail は ESN を offset 0-7 に復元するが、assoclen+cryptlen の位置の元のバイトは決して復元しない。暗号化パス (genicv_tail) は ICV 書き込み前に復元する。 |
| AF_ALG インプレース暗号化/復号 | algif_aead.c:189-252 | rsgl_src と areq->first_rsgl.sgl.sgt.sgl は同一の RX SGL を指す。 |
| AF_ALG tag チェーン | algif_aead.c:238-244 | 復号時、tag はコピーではなく sg_chain で参照される——splice のシナリオでは tag ページは依然としてファイルの page cache を指す。脆弱性の根本原因。 |
| splice ゼロコピー転送 | fs/splice.c:876, af_alg.c:1049, lib/scatterlist.c:1167 | pipe ページを直接参照するため、ゼロコピー。 |
| CVE-2026-31431 | 3つのコミットチェーン | authencesn(2011/2015) + in-place(2017) + splice ゼロコピー = 制御可能な page cache 書き込みプリミティブ。 |