针对 CVE-2026-52910 的竞态复现器与压力测试工具包。该漏洞是 Linux 内核在处理 经典 BPF(cBPF)reuseport 选择器程序时出现的释放后使用(UAF),上游已通过提交 "bpf: Free reuseport cBPF prog after RCU grace period" 修复。
| CVE | CVE-2026-52910 |
| 类型 | 释放后使用 / 越界读取(CWE-125),CVSS 3.1 7.8 HIGH AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| 引入版本 | v4.5(随 reuseport cBPF 支持引入) |
| 修复版本 | 5.10.259、5.15.210、6.1.176、6.6.143、6.12.94、6.18.36、7.0.13(stable);主线 v7.1 |
| 上游报错 | BUG: KASAN: vmalloc-out-of-bounds in reuseport_select_sock(net/core/sock_reuseport.c:596) |
| 报告者 | Eulgyu Kim |
[!WARNING] 这是一个内核压力测试工具。 在存在漏洞的内核上,它会故意 扩大释放后使用竞态窗口;一旦命中可能导致机器崩溃或损坏。 请仅在您拥有或获得明确授权测试的机器上运行 (测试虚拟机、一次性 CI 机器),切勿在生产系统上运行。
SO_REUSEPORT 允许多个套接字绑定同一个 UDP 端口;对于每个
传入数据包,内核在 reuseport_select_sock()(net/core/sock_reuseport.c)中
从组内选择一个套接字。一个组可以安装一个
选择器程序——通过 setsockopt(SO_ATTACH_REUSEPORT_CBPF) 附加的经典 BPF 程序——
它按数据包决定组内哪个套接字接收该数据包。该程序在 RX 软中断(网络
接收处理)中、RCU 读侧临界区内执行。
漏洞在于:当程序通过 setsockopt()
(reuseport_attach_prog() / reuseport_detach_prog())被替换或分离时,旧的 cBPF 程序
会被 sk_reuseport_prog_free() 立即释放,而没有等待
正在进行的 RCU 读者。仍在遍历已释放程序指令的 CPU
会读取已释放的 vmalloc 内存:
sequenceDiagram
autonumber
participant C as CPU0 — churner thread
participant K as setsockopt() path
participant R as CPU1 — RX softirq
R->>R: rcu_read_lock()
R->>R: prog = rcu_dereference(reuse->prog)
C->>K: setsockopt(SO_ATTACH_REUSEPORT_CBPF, progB)
K->>K: swap progA → progB
K->>K: sk_reuseport_prog_free(progA)
Note right of K: unfixed kernels: bpf_prog_free()<br/>runs NOW — no RCU grace period
R->>R: execute progA->insns (run_bpf_filter)
Note right of R: progA was already freed<br/>KASAN: vmalloc-out-of-bounds
Note over K: fix: call_rcu(sk_reuseport_prog_free_rcu) —<br/>free deferred by one RCU grace periodeBPF 选择器路径(SO_ATTACH_REUSEPORT_EBPF)不受影响:它
已经通过延迟的 bpf_prog_put() 阶段释放程序。该修复
对 cBPF 路径做了同样处理——在释放旧程序前等待一个 RCU 宽限期。
上游 KASAN 报告(在 7.0 调试内核上):
BUG: KASAN: vmalloc-out-of-bounds in reuseport_select_sock+0xedc/0x1220
Read of size 4 at addr ffffc9000051e004 by task slowme/10208
net/core/sock_reuseport.c:596
在 Linux 测试机上:
$ make
$ sudo ./run_hammer.sh 600 # 10-minute run
...
== result: RC=0 (0 clean / 1 setup / 2 splat / 3 leak / 4 integrity) ==
要求:
gcc 和 bash。dmesg、/proc/vmallocinfo、sysctl);
hammer 本身以非特权身份运行(上游复现以 UID 1000 运行)。预期结果:
RC=2;偶尔
hammer 的完整性检查会先触发 → RC=4。RC=0,bpf_prog vmalloc 计数稳定。竞态窗口非常小(释放与正在进行的 RX 执行之间),因此单次 干净运行不能作为结论。真正的测试应运行数小时,例如:
$ sudo ./run_hammer.sh 86400 512 8 16 4 127.0.0.1 0
reuseport_race_hammer$ ./reuseport_race_hammer [dur_sec] [insns] [nports] [nsocks] [nsenders] [ip] [ebpf]
每个组运行一个 churner 线程,在紧密循环中通过 setsockopt() 切换和分离
cBPF 选择器,同时 sender 线程向该组洪泛
64 字节 UDP 数据报,接收方统计每个套接字的投递情况。
运行阶段(T = dur_sec):
time ──────────────────────────────────────────────────────────────►
[0 ──────────── T-15s) [T-15s ── T-10s) [T-10s ─────────── T]
CHURN + FLOOD SETTLE MEASURE
churner swaps/detaches churn frozen, deterministic program
the selector prog at final program (selects the LAST
max rate under full attached socket): EVERY packet
UDP flood — THE (selects LAST must land on the LAST
race window open socket) socket; snapshot A →
run → snapshot B
完整性检查: 在测量阶段,最终程序会确定性地 选择组内最后一个套接字。如果该窗口内组接收到的数据包 没有全部落在该套接字上,说明选择出错(即使没有 KASAN, 也可能是 UAF 的影响)→ 退出码 2。
hammer 退出码:0 通过 · 1 设置/运行时错误 · 2 完整性警告。
run_hammer.sh — 单次运行包装脚本运行 hammer 并添加使单次运行有意义的检查:
/proc/vmallocinfo 中的 bpf_prog 分配计数;net.core.optmem_max,使数 KB 的 cBPF 程序能干净地附加;DRAIN(默认 30 秒),让 RCU/workqueue 延迟释放完成;BUG:、Oops:、WARNING:、RIP:、
leaked、stuck);bpf_prog vmalloc 计数(泄漏检查),并
可选扫描 kmemleak(如果 /sys/kernel/debug/kmemleak 存在)。退出码:0 干净 · 1 设置错误(包括 hammer 设置失败)·
2 观察到内核报错 · 3 可能存在 bpf_prog 泄漏 · 4 hammer 完整性
警告。
livepatch_cycle.sh — livepatch 生命周期测试该修复从 call_rcu() 回调中释放旧的 cBPF 程序。如果您将
该修复作为 livepatch(内核热补丁——将代码补丁到
运行中的内核)发布,回调函数本身位于补丁模块中:在回调仍
待处理时回退/卸载,会在回调脚下释放模块代码。此脚本在
hammer 保持竞态窗口活跃的同时执行应用/回退循环,并监视
/sys/kernel/livepatch/*/transition 和 dmesg。
$ MODE=rcu ./livepatch_cycle.sh 20 120 # 20 cycles × 120s hammer each
| 模式 | 行为 |
|---|---|
cycle(默认) | 应用 → 回退,两者都在持续 hammer 负载下进行 |
rcu | 在最大切换压力下立即回退——上述危险场景 |
safe |
退出码:0 干净 · 1 命令失败 · 2 报错或转换卡住 ·
4 hammer 完整性警告。
CI 使用两组编译标志构建 hammer,并对脚本运行 shellcheck。 内核运行时测试有意不在共享 CI runner 上运行:复现器需要 控制 runner 的内核版本(而且在存在漏洞的内核上可能导致 runner oops)。请在真实测试机上运行这些测试。
GPL-2.0-only — 见 LICENSE。
| 文件 | 用途 |
|---|
reuseport_race_hammer.c | 复现器:多线程 hammer,在满 UDP 负载下不断切换 cBPF 选择器,然后验证投递完整性。 |
run_hammer.sh | 单次运行包装脚本:提高 net.core.optmem_max,运行 hammer,然后检查 dmesg 中的报错、/proc/vmallocinfo 中泄漏的 bpf_prog 分配,并可选检查 kmemleak。 |
livepatch_cycle.sh | 在 hammer 运行期间应用/回退携带修复的 livepatch——用于搜寻基于 call_rcu() 的修复在 livepatch 生命周期中的隐患。 |
Makefile | 构建 hammer。 |
.github/workflows/ci.yml | CI:构建 + shellcheck(不进行运行时内核测试;见 CI)。 |
| 参数 | 默认值 | 含义 |
|---|
dur_sec | 300(最小 45) | 总运行秒数 |
insns | 256 | 被切换的 cBPF 程序中的填充指令数;程序越大,被命中的已释放区域越大。如果附加失败并返回 ENOMEM,请提高 net.core.optmem_max(包装脚本会为您处理)。 |
nports | 4(最大 64) | reuseport 组数(每组一个 UDP 端口,从 21000 开始) |
nsocks | 8(最大 512) | 每组套接字数 |
nsenders | 4(最大 32) | 每组 UDP 发送线程数 |
ip | 127.0.0.1 | 目标地址;使用物理网卡 IP 可将 RX 软中断分散到多个 CPU(RSS) |
ebpf | 0 | 1 = 同时切换 SO_ATTACH_REUSEPORT_EBPF 的附加/分离(需要 CAP_BPF/CAP_NET_ADMIN);该路径不存在漏洞,此选项用于对比/覆盖 |
| 环境变量 | 默认值 | 含义 |
|---|
HAMMER | ./reuseport_race_hammer | hammer 二进制文件 |
DRAIN | 30 | 运行后检查前等待的秒数 |
OPTMEM_MAX | 131072 | net.core.optmem_max 的值;0 = 不修改 |
停止 hammer → 休眠 GRACE(默认 30 秒,一个宽限期)→ 回退 |
| 环境变量 | 默认值 | 含义 |
|---|
APPLY_CMD / REVERT_CMD | kpatch load $PATCH / kpatch unload $PATCH | livepatch 命令 |
PATCH | ./livepatch-reuseport.ko | 补丁模块 |
HAMMER | ./reuseport_race_hammer | hammer 二进制文件 |
HAMMER_ARGS | 256 4 8 4 127.0.0.1 0 | hammer 参数 |
TRANSITION_TIMEOUT | 60 | 等待 livepatch 转换的最长秒数 |
GRACE | 30 | MODE=safe 的宽限期休眠秒数 |
FORCE | 0 | 1 = 即使未检测到 livepatch 转换也运行 |