资讯 Go 1.24 默认用 MPTCP 监听:Linux 内核 CVE-2026-98432 的排查与缓解

2026-11-03 21:04:12

Go 1.24 默认用 MPTCP 监听:Linux 内核 CVE-2026-98432 的排查与缓解笔记

漏洞概况

CVE-2026-98432 是 Linux 内核 IPv4 TCP options 路径上的 RCU use-after-free,CVSS 3.1 评分 9.8(Critical)。

  • 引入版本:Linux 4.4,commit 079096f103fa("tcp/dccp: install syn_recv requests into ehash table")。
  • 上游修复:2026-10-05 进入主线。
  • 触发前提:目标暴露 MPTCP listener 时,存在已公开的零点击、未认证远程 root 利用。

机制

tcp_v4_syn_recv_sock() 把 ireq->ireq_opt 的所有权转给子 socket 的 newinet->inet_opt,整个过程不拷贝。另一个 CPU 可以并发处理同一 request socket 的重传 SYN,经 tcp_check_req() 发出 SYNACK。tcp_v4_send_synack()、inet_csk_route_req() 只在 rcu_read_lock() 下读 ireq->ireq_opt,而 ip_build_and_send_pkt() / ip_options_build() 会两次读取 opt->optlen。

由于 request socket 自 4.4 起在 ehash 中无 listener 锁处理,子 socket 可能在 SYNACK 还在构造时就被释放。TCP 子 socket 没有 SOCK_RCU_FREE,inet_sock_destruct() 用普通 kfree() 释放 inet_opt,于是 ip_options_build() 读到已释放内存。

迁移路径同理:设置 net.ipv4.tcp_migrate_req=1 或加载 BPF_SK_REUSEPORT_SELECT_OR_MIGRATE 程序时,reqsk_timer_handler() 用 inet_reqsk_clone() 克隆 request socket,原 socket 与克隆共享 ireq_opt,reqsk_migrate_reset() 清掉旧指针;tcp_v4_reqsk_destructor() 同样用普通 kfree()。

修复由 Eric Dumazet 完成,把两处 kfree() 换成 kfree_rcu():

  • net/ipv4/af_inet.c 的 inet_sock_destruct
  • net/ipv4/tcp_ipv4.c 的 tcp_v4_reqsk_destructor

IPv6 不受第一条路径影响(tcp_v6_syn_recv_sock 会复制 options);tcp_v6_reqsk_destructor 存在同样的迁移问题(ipv6_opt,仅在 CALIPSO 设置时),由另一个补丁处理。

受影响与修复版本

受影响:4.4 起,维护分支上低于下列版本的内核。

分支修复版本
5.105.10.272
5.155.15.223
6.16.1.190
6.66.6.159
6.126.12.113
6.186.18.56
7.27.2.10
7.37.3(7.3-rc7 之前受影响)

发行版状态(截至 2026-10-30):Ubuntu 与 RHEL 尚未发布修复内核;Debian 12/13 已有 linux 6.1.190-1(DLA-4841-1)、6.12.113-1(DSA-6568-1 / DLA-4850-1)。

公开利用

2026-10-27,Anthropic Frontier Red Team(研究员 Xinyang Ge)披露零点击未认证远程 root 利用。前提是目标暴露 MPTCP listener。利用先泄内核堆指针破 KASLR(约 48 分钟),再溢出报文缓冲覆写回调指针执行命令(约 5 分钟);输掉竞态只会导致握手失败,可以无限重试。

Go 1.24+ 为什么相关

2026-10-29/30,Go Security Team 发布公告(golang/go#82717、oss-security 2026/10/30):Go 1.24 起(commit 3d4daa38a7,internal/godebugs/table.go)把 multipathtcp 默认值从 "0" 改为 "2"(listeners only)。

结果是 Go 1.24/1.25/1.26 编译的服务在 Linux 上通过 net.Listen("tcp") / http.ListenAndServe 创建的是 IPPROTO_MPTCP(262) 监听 socket,即使客户端从未协商 MPTCP。普通 TCP 客户端照样能连,所以很难察觉,需要 ss -Mltn 才能看到。

GODEBUG multipathtcp 取值:

  • 0:拨号与监听都关
  • 1:都开
  • 2:只开监听(Go 1.24+ 默认)
  • 3:只开拨号

程序内可用 net.Dialer.SetMultipathTCP / net.ListenConfig.SetMultipathTCP 逐 socket 覆盖 GODEBUG。

排查

# 内核版本与分支
uname -r

# 系统是否启用 MPTCP
sysctl net.mptcp.enabled

# 直接看 MPTCP 监听 socket(普通 ss -ltn 看不出来)
ss -Mltnp

# 二进制是用哪个 Go 版本构建的
go version -m /path/to/service

# 代码里是否显式操作过 MPTCP 开关
rg SetMultipathTCP

临时缓解

按优先级:

  1. 升级内核到上表对应版本并重启。
  2. 关闭 Go 程序的 MPTCP:
GODEBUG=multipathtcp=0 ./service

或在 go.mod 里固定:

godebug multipathtcp=0
  1. 系统级关闭,并重启 Go 服务使其生效:
sysctl -w net.mptcp.enabled=0
  1. 在 nftables 丢弃带 IPv4 options 的入站 SYN:
nft add rule inet filter input ip hdrlength \> 20 tcp flags syn drop

逐程序关闭是脆弱缓解,且这个 bug 不只与 MPTCP 相关,仍必须打内核补丁。

Go 侧显式关闭示例

net.ListenConfig.SetMultipathTCP(false)(Go 1.24+)逐 listener 覆盖:

package main

import (
	"context"
	"log"
	"net"
	"net/http"
)

func main() {
	lc := net.ListenConfig{}
	lc.SetMultipathTCP(false)

	ln, err := lc.Listen(context.Background(), "tcp", ":8080")
	if err != nil {
		log.Fatal(err)
	}

	log.Fatal(http.Serve(ln, mux))
}

gRPC 同理:把上面得到的 ln 交给 grpcServer.Serve(ln),而不是让 grpc 自己调 net.Listen。

只关 MPTCP 不算修复

这处 UAF 位于通用 IPv4 TCP 路径:tcp_v4_syn_recv_sock() 的指针转移、inet_sock_destruct() 的 kfree() 与 MPTCP 无关。关掉 MPTCP 只是移除了已公开利用所要求的监听 socket 前置条件,未打补丁的内核仍可能经重传 SYN、tcp_migrate_req / reuseport 迁移等其它路径命中同一段竞态。内核补丁是唯一根治手段。

复制全文 生成海报 Go Linux 安全

推荐文章

程序员茄子在线接单