想给笔记本下载一个 NixOS 的安装镜像,结果以「下载总是失败」为起点,一路查到无线 AP 的硬件卸载驱动丢了 CPU 缓存同步,最后为了关掉那个开关,写了一条 CVE-2022-0185 加 pipe buffer 操纵的内核提权链,把自己的 TP-Link EAP770 路由器拿到了 root。这篇发表在博客 blog.dfb.sh 上的排查记录 10 月 13 日登上 Hacker News 首页,87 个赞、40 多条评论,评论区吵得最凶的反倒是作者披露的一个细节:由于 Anthropic 的安全策略,Opus 5.5 拒绝继续协助这个项目,漏洞链的后半段是换了模型才走完的。

四年没修的下载损坏
故事从 2023 年 8 月说起。作者买了一台 TP-Link EAP770(EU)v1.0,高通 IPQ9574 方案,运行 Linux 5.4.164,出厂固件 1.0.0 Build 20230605。从第一天起,超过几个 GiB 的大文件经 Wi-Fi 传输就会失败:SSH 报 MAC 错误,rsync 传输中断。作者最初怀疑 rsync 和 SSH 本身,WireShark 抓包却显示零坏校验和、零重传,单条 UDP 随机字节流也完整到达。他信任了 TCP 的校验和,写了个带重试的 rsync 包装脚本就翻篇了。
直到改用 Tailscale 之后,传输不再需要重试。WireGuard 会丢弃认证失败的包,隧道里的 TCP 连接把这种情况当成丢包正常重传。问题被掩盖了两年多。
2026 年切换到 NixOS 后问题重新浮现:从 Fastly 的 Nix 二进制缓存下载 128 MiB 文件,HTTP/2 失败而 HTTP/1.1 成功;git clone 失败;wget 下载 NixOS ISO 时报出关键的错误信息 decryption failed or bad record mac。TLS 层拒绝了数据,说明损坏发生在 TCP 认为数据已经正确送达之后。
这次作者没有再放过它。用 tcpdump 抓到一次失败的 HTTPS 下载,找到了确凿证据:一个 1440 字节的 TCP 段里有连续 64 字节的错误数据,但 TCP 校验和完全合法。设备的 TcpInCsumErrors 计数器纹丝不动——TCP 接受了这个段,把它交给了 TLS,然后 TLS 用 bad record mac 拒绝。
一个带着有效校验和的损坏段,意味着路径上有东西在数据损坏之后重算校验和。接下来就是定位这个重算者。
定位:排除法走到 AP 内部
作者搭了一个原始 TCP 测试:有线机器向 Wi-Fi 笔记本发送已知数据,逐字节比对。损坏只在多条 TCP 流并发、且方向为 LAN 到 Wi-Fi 时出现,8192 MiB 数据里找到 3 个坏块,每个都是 64 字节、且都以零开头。
排除法开始:换成另一块 Qualcomm 网卡,损坏依旧,笔记本无罪;有线客户端全干净,网线无罪;坏帧上的 CCMP MIC 完整,空中传输无罪。剩下的嫌疑人是 AP 内部以太网与 Wi-Fi 之间的桥。
64 字节正好是 IPQ9574 核心的缓存行大小,指向缓存或 DMA 层面的问题而不是比特翻转。而在那条桥接路径上,既加速流量又重算校验和的组件只有一个:高通的 SFE/PPE 硬件卸载。
读驱动源码给出了解决方案:往 /sys/sfe/l2_feature 和 /sys/sfe/ppe_rfs_feature 写 0,关闭卸载。问题在于这两个 sysfs 文件只有 root 可写,而 TP-Link 的 SSH shell 以 uid 1 运行、没有任何 capabilities;固件没有提供任何 GUI 开关。
作者尝试了所有正规路径,全部走死:v1 设备已被 TP-Link 标记 End-of-Life,官网只有 v2 固件,而 v2 是不同硬件,直接刷会被拒绝;新型号 v2 固件已加密;直刷芯片需要买编程夹具,还有变砖风险。官方支持的原话是:「EAP770 Version 1 is an End-of-Life product... no longer supported with firmware updates。」这台机器出厂的固件就是它一生唯一的固件。
提权:一次性的 CVE-2022-0185 链
正规路走死之后,剩下的入口只有内核。内核版本 5.4.164 受 CVE-2022-0185 影响:legacy_parse_param() 里的整数下溢,可以从无特权的 user namespace 触发,让一次写操作溢出 4 KiB 的 slab 对象。叠加公开已知的 pipe buffer 操纵技术,这个溢出可以升级为任意写。
约束条件很苛刻:漏洞利用必须保持 AP 在线运行,因为重启后损坏的卸载行为会回来;而 4 KiB slab 被溢出后处于异常状态,第二次尝试会直接 panic,每次开机只有一发机会。
作者没有 v1 设备的 GPL 源码,用了最接近的 EAP783 公开源码构建 QEMU 环境,其内核配置与目标设备的 /proc/config.gz 只差三行无关紧要的配置。在 QEMU 里跑通整条链之后移植到真机,遇到的第一道坎是内核加载地址:QEMU 构建加载在物理地址 0x40080000,真机加载在 0x42080000,全部计算偏移了 0x2000000;正确的地址藏在 EAP783 源码的构建配置变量里。而且设备的 .text、.rodata、.data 三个段相对 QEMU 构建的偏移量各不相同,一个统一偏移量解决不了。
完整利用链七步:
- unshare 进入 user namespace,让
fsopen可达,触发整数下溢; - 用几百个同尺寸分配整理(defragment)4 KiB slab,再精确布防(groom),让溢出源、受害 keyring 对象、SysV 消息和 pipe ring 相邻排列;
- 溢出改写受害 key 的元数据、放大其长度字段,读回 key 就能泄漏相邻对象,包括 pipe 的
buf[0].ops指针,内含anon_pipe_buf_ops的地址; - 把一条 pipe 的缓冲区指向另一条 pipe 的 ring page,用泄漏的 ops 指针在第二条 ring 里伪造条目。
pipe_buf_can_merge()只校验 ops 指针匹配,伪造条目发起的 merge write 因此能写到任意物理页,包括内核镜像所在的页; - 扫描内核
.data段定位modprobe_path。这一步的难点是读取内核镜像页的正常路径会调用put_page(),碰到保留页直接Bad page mappanic。作者利用pipe_read只调用->confirm和->release、不校验 ops 表真实性的事实,伪造了一个两个函数指针都指向generic_pipe_buf_confirm的pipe_buf_operations——这个函数返回零且不做任何事,扫描得以分批读完.data; - 覆写
modprobe_path,execve一个魔数无法识别的文件,内核调用request_module(),以全局 uid 0 运行替换后的路径; - 保留一个持有伪造 pipe 的子进程不放,进程退出会拆掉 pipe、碰到保留页引发 panic。
从读设备二进制(radare2)、核对防护配置到调通全部崩溃,整个exploit 开发花了约 17 小时。AP 保持在线,关掉卸载之后,数据损坏消失了。
根因:回收的缓冲区,脏掉的缓存
真正的 bug 在高通的 qca-nss-dp 驱动里,这是个内核树之外的 NSS 代码,OpenWrt 仓库有镜像。驱动在把包缓冲区交还接收队列复用时,没有对整个接收缓冲区做 CPU 缓存同步。
在这块 ARM 平台上,DMA 直接写 DRAM,不维护与 CPU 缓存的同步。驱动代码里的注释写明了复用逻辑:SFE 快速发送路径只清理当前 skb 线性部分的 2-3 个缓存行,然后设置 fast_recycled = 1 标志,把缓冲区放回 per-CPU 回收列表;接收路径 refill 看到这个标志,直接跳过缓存失效操作(invalidate),注释原文是「we can be assured that invalidate was already done at the time of previous transmit」。
问题在于发送时清理的范围是 2-3 个缓存行,而接收区域是 1856 字节、29 个缓存行,两者起点还不同。上次发送残留的脏缓存行落在清理范围之外,在网卡 DMA 写入新包之后被逐出写回,一次覆盖 64 字节收到的数据。触发方式很日常:正常的缓存逐出就能引发写回;arm64 上非对齐范围的 invalidate 操作用的 dc civac(clean and invalidate)指令也会碰掉边缘部分的缓存行。
最后的链条是:脏缓存行覆盖 DRAM 里的包数据,SFE/PPE 在损坏之后计算校验和,客户端收到一个对垃圾数据完全合法的 TCP 校验和,TCP 照单全收,TLS 和 SSH 的端到端 MAC 成为唯一的防线,报出 bad record mac。

对比数据说明了代价和收益:卸载开启时 8192 MiB 传输出现 3 个坏块、速度 103.4 MiB/s;关闭后整夜运行零坏块、95.4 MiB/s。用约 8% 的吞吐换回了数据完整性。由于 CONFIG_SKB_RECYCLER 是编译期选项没有运行时开关,而这两条 sysfs 写入重启即失效,作者在 NAS 上部署了一个 watcher:轮询 AP 发现卸载恢复就重新跑一遍 exploit,失败就通过管理 API 重启 AP 下次开机再试,并设了重启次数硬上限。
还有一条范围限定要交代:只有下载(LAN 到 Wi-Fi)走 AP 的有线接收路径,上传不经过这条路径,有线到有线的流量走交换芯片,所以损坏只在下载方向出现。
真正让评论区炸锅的:模型拒答
文章里一段不起眼的更新成了 HN 讨论的中心。作者写道,从「诊断损坏」转入「实施修复」阶段后,Opus 5.5 开始大约每隔一条消息就切断会话,提到「local privilege escalation」就足以触发;账号开着 Anthropic 的 Cyber Verification Program(网络攻击 Clarification 计划,供安全从业者申请放宽安全限制)也没有用。而此前的 Opus 4.8 全程协助完成了漏洞开发,没有中断。GPT-6 Astra 读同一条文章也不需要任何验证。
评论区里安全从业者的抱怨高度一致。有人在 Cyber Verification Program 最高档位里,仍被拒绝分析 1994 年的 MS-DOS 恶意软件样本,理由是 Anthropic 对「逆向工程任何恶意软件」的全面禁令;有人让 Claude 复现一个漏洞报告被拒,威胁换中国模型,Claude 回复说它「不介意语气,但仍然拒绝服务」;还有人只是让模型实现一个测试床工具里的 TCP 服务器就被 cyber 过滤器拦下。多位用户点名 GLM、DeepSeek、OpenAI 在同样的防御性安全任务上不设这类限制。
这段讨论与博客 AI 内容矩阵此前追踪的现象是同一件事的两面:能力评测之外,模型供应商的安全策略正在成为安全从业者的实际选型变量。作者用 17 小时和 Opus 4.8 写完 exploit 的经历说明模型能力足够,而 5.5 的拒答说明同一家公司的下一代模型在这条路上反而更难用。
留给普通读者的三件事
第一,消费级网络设备的固件支持可能比你以为的短得多。这台 2023 年 8 月发售的 Wi-Fi 7 接入点,出厂固件即最终固件,而且它发售时 Wi-Fi 7 标准还没定稿。评论区的共识与作者的自我检讨一致:标准未定稿、设备未过认证就首发入手,是在给早期调试买单。
第二,bad record mac 这类报错值得重视而不是绕过。两年里作者用重试脚本、换 HTTP/1.1、上 Tailscale 三次绕过了同一个问题,每一次绕过都在推迟定位。TLS 和 SSH 的 MAC 校验是这个故事里真正工作正常的组件,它们把一次硬件数据损坏拦成了显式错误。
第三,x86 平台硬件维护 DMA 与 CPU 缓存的硬件一致性,同样的驱动缺陷在 x86 上不会造成损坏,这也是这类 bug 长期藏在 ARM 网络设备里的原因。作者已经在内核源码里发现了其他几个驱动存在类似的缓存维护缺失,还在寻找能上手的测试硬件。
一条 GNU GPL 合规线索放在结尾:Linux 内核模块必须随设备发布源码,TP-Link 没有为 v1 发布过内核源码,作者只能拿同型号系列的源码做近似构建。如果 v1 源码齐备,这条 exploit 链的开发时间大概率还能缩短。OpenWrt 主线没有这个 bug,对这台设备来说,刷 OpenWrt 依然是最干净的长期解法——前提是你愿意为它的硬件写移植。
来源: