LinuxカーネルTCPのUAF脆弱性CVE-2026-98432、MPTCP待ち受けでroot実行
目次
TL;DR
影響 Linuxカーネル4.4以降のIPv4 TCP処理。カーネルCNAの評価はCVSS 9.8(Critical)、Red Hatの評価は8.1(Important)
対応 修正済みカーネル(5.10.272、5.15.223、6.1.190、6.6.159、6.12.113、6.18.56、7.2.10、7.3以降)か、ディストリビューションの修正パッケージへの更新と再起動
暫定 公開された攻撃例はMPTCPの待ち受けが前提。Go 1.24以降のプログラムは既定でMPTCPで待ち受けるため GODEBUG=multipathtcp=0 で外せるが、不具合自体はMPTCP専用ではない点に留意
2026年10月15日、LinuxカーネルのIPv4 TCP処理にある解放後使用(use-after-free、UAF)の脆弱性CVE-2026-98432が公開された。
解放後使用は、すでに解放したメモリ領域をプログラムが有効なものとして読み書きしてしまう不具合だ。解放された領域にはあとから別のデータが入るため、攻撃者がその中身を操作できると、機密情報の漏えいやメモリ書き換えにつながる。
公開時点ではカーネルの修正コミットの説明が中心だった。
10月27日、AnthropicのFrontier Red Teamが研究記事を公開し、この不具合を使って複数経路を束ねるTCP拡張(MPTCP)の待ち受けを持つLinuxサーバーで、認証なしでroot権限のコマンドを実行する攻撃実証を公表した。
10月29日にはGoのセキュリティチームも、Go製プログラムがこの攻撃の前提に当てはまりやすいとして注意喚起を出している。
SYNのIPv4オプションを子ソケットと再送処理が奪い合う
TCPでは、クライアントがSYN、サーバーがSYN-ACK、クライアントがACKを送る3ウェイハンドシェイクで接続を確立する。
Linuxカーネルは最初のSYNを受け取った時点で、確立途中の接続を表す軽量な一時オブジェクトである「リクエストソケット」を作る。
SYNのIPヘッダーにRecord Route(通信経路の記録)やTimestampといったIPv4オプションが付いていると、カーネルはその内容をリクエストソケットの ireq_opt に保存し、SYN-ACKを組み立てるときに使う。
ACKが届いて接続が確立すると、カーネルは接続確立処理 tcp_v4_syn_recv_sock() で、実際にアプリケーションと通信するための通常ソケット(子ソケット)を作る。
このとき ireq_opt のメモリは複製されず、ポインタごと子ソケットの inet_opt に引き渡される。
そのため、同じオプションのメモリ領域を、リクエストソケット側と子ソケット側の両方から参照される形になる。
別のCPUが同じリクエストソケット宛てに再送されたSYNパケットを処理すると、tcp_check_req() からSYN-ACKの再送処理が始まる。
SYN-ACKを組み立てる tcp_v4_send_synack() などは、オプションをRCU(Read-Copy Update)の保護区間の中で参照する。RCUは読み手側がロックを取らずに参照できる代わりに、解放側が「今読んでいる人が全員終わるまで」待ってからメモリを解放する仕組みだ。
その先で呼ばれる ip_build_and_send_pkt() と ip_options_build() は、オプション長 optlen を合わせて2回読む。
ip_build_and_send_pkt() はこの長さでヘッダー領域を確保し、ip_options_build() はこの長さ分のオプションをパケットにコピーする。
RCUで保護された読み手が存在する間は、書き手はメモリを即座に解放せず、読み手が抜けるまで待つ猶予期間(グレースピリオド)を置く仕様になっている。
しかし子ソケットを破棄するデストラクタ inet_sock_destruct() は、inet_opt を通常の kfree() で即座に解放していた。
接続がすぐに閉じられて子ソケットが破棄されると、別CPUがSYN-ACKを組み立てている最中にオプションメモリが解放され、ip_options_build() が解放済みの領域を参照してしまう。1回目と2回目の読み込みの間に解放領域が別用途で再利用されると、サイズの食い違いによるメモリ漏えいや境界外書き込みにつながる。
flowchart TD
A["攻撃者がIPv4オプション付きSYNを送る"] --> B["カーネルがリクエストソケットに<br/>ireq_optとして保存"]
B --> C["ACKで接続確立<br/>子ソケットのinet_optへポインタを引き渡し"]
B --> D["別CPUが再送SYNを処理<br/>SYN-ACKを組み立て中"]
C --> E["接続を閉じて子ソケット破棄<br/>kfreeで即座に解放"]
E --> F["組み立て中のSYN-ACKが<br/>解放済みのオプションを読む"]
D --> F
修正コミットの説明によると、この競合は2015年10月のコミット079096f103fa「tcp/dccp: install syn_recv requests into ehash table」で混入した。
このコミット以降、リクエストソケットは待ち受けソケットのロックを取らずに処理されるようになり、SYN-ACKの組み立て中に子ソケットが解放されるのを防ぐ排他制御がなくなった。
CVEレコードでは、このコミットが入ったLinux 4.4以降が影響範囲になっている。
コミットログでは、リスナー間で接続要求を引き継ぐリクエストソケットの移し替え(net.ipv4.tcp_migrate_req=1、または eBPFでソケットを選択・移行する BPF_SK_REUSEPORT_SELECT_OR_MIGRATE を使う構成)でも同様の解放後使用が起きると指摘されている。
移し替え処理では古いリクエストソケットと複製先が同じ ireq_opt を共有し、複製先を破棄する tcp_v4_reqsk_destructor() も通常の kfree() を使っていた。
IPv6はSYN再送処理との競合の影響を受けない。
tcp_v6_syn_recv_sock() はオプションを複製してから子ソケットに渡しているためだ。
移し替え処理での競合はIPv6の ipv6_opt にもあるが、このオプションはIPv6セキュリティラベル機構(CALIPSO)でしか設定されず、別のパッチで対処すると書かれている。
RCU対応の解放関数に変える修正
修正はEric Dumazet氏のコミットe3f33b0a1d89によるもので、変更は net/ipv4/af_inet.c と net/ipv4/tcp_ipv4.c の2行だ。
inet_sock_destruct() と tcp_v4_reqsk_destructor() の解放処理を kfree() から kfree_rcu() に変え、RCUの読み手が抜けるのを待ってからメモリを解放する。
- kfree(rcu_dereference_protected(inet->inet_opt, 1));
+ kfree_rcu(rcu_dereference_protected(inet->inet_opt, 1), rcu);
ソケットオプションを設定・変更する他の処理(do_ip_setsockopt() や cipso_v4_sock_setattr() など)は、もともと kfree_rcu() を使っていた。
ソケット破棄時の2か所だけが、RCUの読み手を待たずに即時解放していた。
CVSS 9.8の評価基準とRed Hatとの差
CVEレコードには、カーネルCNA(kernel.orgのCVE採番機関)が付けた共通脆弱性評価システム(CVSS 3.1)のスコア9.8(Critical)と、各評価項目の根拠が載っている。
メトリクスベクターは AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H で、主な根拠は次の表のとおり。
| 項目 | CVEレコードに書かれた根拠 |
|---|---|
| 攻撃元(AV:N) | 認証されていない相手が、Record Route(経路記録)かTimestampのオプションを付けたSYNを送れば届く |
| 攻撃の難しさ(AC:L) | オプション付きの接続要求と再送SYN、接続の確立と切断を攻撃者が何度でも繰り返せる |
| 必要な権限(PR:N) | アプリケーションの認証より前、TCPのハンドシェイク中に到達する |
| 機密性(C:H) | 解放後に再利用された領域の古い optlen の分だけデータがSYN-ACKにコピーされ、隣接する動的メモリ(カーネルヒープ)の内容が相手に返る |
| 完全性(I:H) | 再利用された領域の長さやオフセットに従って ip_options_build() が書き込むため、範囲外への書き込みが起きる |
| 可用性(A:H) | 長さによってはSYN-ACKのヘッダー領域を超えて skb_under_panic() でカーネルが停止する |
一方、Red HatはCVSS 8.1(Important)と評価しており、攻撃の難しさを「High(AC:H)」としている。
Red Hatの評価は10月30日時点で暫定(draft)で、AC:Hとした根拠は記載されていない。
NVDの状態は10月30日時点で受領済み(Received)で、分析結果はまだ公開されていない。
MPTCP待ち受け環境での攻撃実証
10月27日のAnthropicの研究記事「Discovering and exploiting vulnerabilities in critical, real-world software with Claude」では、5つの攻撃例の最後にこのCVEを取り上げている。
未公開の社内研究用モデルを使い、最初の指示を与えたあとはClaudeが攻撃コードを生成したと説明している。
対象は複数経路を束ねるTCP拡張(MPTCP)で待ち受けているLinuxサーバーだ。
攻撃者はパケットを送るだけで、認証情報も利用者の操作もなしにroot権限で任意のコマンドを実行できたという。
攻撃では、起動ごとにカーネルの配置アドレスを変えて攻撃コードの飛び先を隠すKASLR(カーネルアドレス空間配置のランダム化)を回避したうえで、制御フローを奪取している。
| 段階 | 内容 |
|---|---|
| 1. KASLRの回避 | 競合を起こしてカーネルに攻撃者が操作した長さを参照させ、余分なバイトを応答に載せて返させる。応答の中にカーネル内部のポインタが1つでも含まれていれば、カーネル全体の配置アドレスを逆算できる |
| 2. 制御フローの奪取 | 同じ不具合でバッファをあふれさせ、コールバック関数のポインタを書き換える。通常のプロトコル機能を使ってあらかじめカーネルメモリに置いておいた攻撃データ(ペイロード)へ実行を移し、既存のカーネル関数を順に呼び出して、最後にroot権限でシェルコマンドを実行する |
Anthropicの記事によると、競合で意図したタイミングにならなくてもハンドシェイクが失敗するだけでシステムはクラッシュしないため、成功するまで試行を繰り返せるという。実証ではカーネルポインタを得るまでに48分、そこからシェルコマンドの実行までにさらに5分かかったとしている。
ただし、前述のCVSS評価にあるように、長さの値によってはカーネルが停止する可能性があり、必ず安全に失敗するとは限らない。なお、Anthropicの記事ではMPTCPの待ち受けがなぜ攻撃の前提になるのかまでは説明されていない。
Go 1.24での既定有効化と注意喚起
Goは1.24から、Linuxで待ち受けるソケットに既定でMPTCPを使う。
Goのドキュメントでは、multipathtcp 設定の既定値が1.24から 2(待ち受け側だけMPTCPを有効)になったと書かれている。
そのため、Go 1.24以降でビルドした net.Listen のサーバーは、特に指定しなくてもMPTCPで待ち受ける状態になる。
10月29日、GitHubのGoリポジトリに既定のMPTCP無効化を提案するIssue #82717(proposal: net: Disable MPTCP by default)が作成された。
同日、Goのセキュリティチームがgolang-announceメーリングリストで注意喚起を出し、oss-securityにも転送された。
注意喚起では、最善の対策を修正済みカーネルへの更新としている。
そのうえでMPTCPを止める方法として、実行時に環境変数 GODEBUG=multipathtcp=0 を設定するか、ビルド時にメインモジュールの go.mod に godebug multipathtcp=0 を追加する方法を案内している。
ただし、実証された攻撃はMPTCPの待ち受けを前提にしているが、元の不具合はMPTCP専用ではない。
そのため、MPTCPの待ち受けがない環境でも似た攻撃が成り立つ可能性があると注意喚起は書いている。
# 実行時に止める
GODEBUG=multipathtcp=0 ./server
// ビルド時に止める(メインモジュールのgo.modに追記)
godebug multipathtcp=0
ソケット単位では net.ListenConfig.SetMultipathTCP と net.Dialer.SetMultipathTCP で指定でき、こちらは GODEBUG の設定より優先される。
セキュリティチームは、プログラムごとにMPTCPを止める方法は不完全な緩和策だとも書いている。
1つでも待ち受けが残ると攻撃条件が残り、MPTCPを経由しない別の攻撃経路が存在する可能性もあるためだ。
Go本体の既定値変更はまだ早いとして、Issueで議論を続けるとしている。
IssueでもGoチームのメンバーが、根本原因はIPv4 TCPの処理にあると書いている。
MPTCPの待ち受けがないシステムでroot権限の実行につながる方法があるかどうかは、現時点では未解明とされている。
カーネル側でMPTCPソケットの作成自体を止める設定もある。
カーネルのドキュメントでは、net.mptcp.enabled が1のときにMPTCPソケットを作成でき、既定値は1になっている。
ただしこれもGoの設定と同じく、示された攻撃の成立条件を回避するだけで、IPv4 TCP側の不具合は残る。
修正版カーネルとディストリビューションの状況
CVEレコードには次の修正版が記載されている。
メインライン(開発版)では7.3で修正され、Debianのトラッカーでは修正コミットが7.3-rc7で入ったと記録されている。
| 系列 | 修正版 |
|---|---|
| 5.10 | 5.10.272 |
| 5.15 | 5.15.223 |
| 6.1 | 6.1.190 |
| 6.6 | 6.6.159 |
| 6.12 | 6.12.113 |
| 6.18 | 6.18.56 |
| 7.2 | 7.2.10 |
| メインライン | 7.3 |
各ディストリビューションの10月30日時点の対応状況は次のとおり。
| ディストリビューション | 状況 |
|---|---|
| Debian 12(bookworm) | linux 6.1.190-1(DLA-4841-1)と、linux-6.12 6.12.113-1~deb12u1(DLA-4850-1)で修正 |
| Debian 13(trixie) | 6.12.113-1(DSA-6568-1)で修正 |
| Debian forky(次期開発版)・sid(不安定版) | 修正済み |
| Ubuntu | セキュリティトラッカーで未確認 |
| RHEL 8・9・10、OpenShift 4 | 影響あり。修正パッケージは未掲載 |
| RHEL 6・7 | Red HatのCVEデータでは影響なし(Not affected) |
修正を入れたカーネルパッケージは、インストールしただけでは反映されず、再起動して新しいカーネルで立ち上げる必要がある(再起動せずにカーネルを更新するライブパッチは別の仕組みとなる)。
動作中のカーネルは uname -r で、MPTCPソケットの作成可否は sysctl net.mptcp.enabled で確認できる。
uname -r
sysctl net.mptcp.enabled
ディストリビューションのカーネルは古い版へ修正を移植(バックポート)していることが多いため、uname -r のバージョンを上の表と単純に比べず、各セキュリティ情報に載っているパッケージのバージョンと突き合わせて確認する。