
pedit COW
CVE-2026-46331(昵称 “pedit COW”)是 Linux 流量控制子系统中一个本地内核权限提升漏洞。无特权用户(在无特权网络命名空间中)可以配置 act_pedit(包编辑器)过滤器,触发对页缓存的 部分写时复制(COW)写入。实际上,内核将攻击者控制的数据写入文件的内存映像而无需将页面标记为私有,从而损坏该文件的缓存副本。至关重要的是,该漏洞仅需 CAP_NET_ADMIN(可在用户命名空间中获取),且不会修改磁盘上的文件。实践中,一个名为 packet_edit_meme 的有效概念验证(PoC)已于 2026 年 6 月 17 日发布,演示了如何覆盖 setuid 二进制文件(例如 /bin/su)的页缓存映像以获取 root shell。该漏洞源于 tcf_pedit_act() 中错误的 COW 范围计算,并已通过将可写区域检查移至每个键的循环内得到上游修复(2026 年 6 月 4 日)。
act_pedit 的 Linux 内核(约 v5.18 至 7.1-rc6)。未打补丁的稳定版本(包括许多发行版内核)存在漏洞。tc pedit 过滤器,并在内存中用 shellcode 覆盖 setuid 二进制文件的 ELF 入口点。skb_ensure_writable() 移入键循环内)。作为权宜之计,阻止或卸载 act_pedit 模块,或禁用无特权用户命名空间(例如 sysctl user.max_user_namespaces=0)。缓解后,丢弃缓存(echo 3 > /proc/sys/vm/drop_caches)以驱逐任何被投毒的页面。本报告提供 CVE-2026-46331 的详细技术分析:其原因、利用、检测和修复策略,并附有供应商公告、CVE 及公开漏洞的参考信息。
定义: CVE-2026-46331 是 Linux 内核 流量控制(net/sched)子系统中的一个越界写入漏洞,具体位于 act_pedit(包编辑器)动作中。函数 tcf_pedit_act() 在遍历类型化键之前,使用静态提示 tcfp_off_max_hint 为包编辑操作计算一个“写时复制”范围。然而,某些键(例如 TCP/UDP 头部编辑)仅在运行时确定其最终字节偏移量。该代码从未针对这些动态偏移量重新检查可写性。结果,写入可能发生在预 COW 区域之外:包写入的一部分从未被设置为私有,导致 部分 COW。如果包缓冲区恰好引用了文件页面,则此错误写入会传播到文件的共享页缓存内存中,从而损坏缓存的文件映像。
背景: Linux 包编辑器(pedit) 动作允许管理员在数据包通过配置的 tc 过滤器时重写包头部中的任意字节。它通过指定一个偏移量(可能锚定到某个头部)和一个 32 位值/掩码来工作。内部,pedit 操作套接字缓冲区(sk_buff),并且必须在修改之前使目标包内存可写(通过 skb_ensure_writable() 以 COW 方式)。理想情况下,内核应在写入前克隆(私有复制)任何共享页面,以避免篡改其他地方使用的内存。
根本原因: 在 tcf_pedit_act() 中,代码错误地只提前计算一次可写范围,使用 tcfp_off_max_hint(最大静态偏移量)。此提示不包含类型化键在包处理过程中添加的任何 运行时头部偏移量。像 TCP 或 UDP 这样的键可以根据运行时 IP 头部的位置计算偏移量(例如,如果前面的键移动了网络头部)。因此,在逐键循环期间,某个键的实际偏移量可能超过预分配为可写的范围。代码随后通过 skb_store_bits() 写入包内存,但由于预 COW 区域之外的页面并未被设置为私有,该写入会破坏仍与页缓存共享的页面。简而言之,“过早计算可写包范围” 导致了越界、跨页写入。负偏移量(例如在入口处编辑以太网头部)也被错误处理,甚至 offset_valid() 也缺少对 INT_MIN 的防护,进一步加剧了缺陷。
为何发生: 此漏洞本质上是写时复制范围计算中的逻辑错误。内核假设 静态 最大偏移量(在加载时已知)足以应对所有编辑。它未能在实际应用具有动态偏移量的键时更新 COW 范围。经过一系列排队编辑后,最终写入可能位于预检查区域之外。由于包缓冲区可能引用内存映射的文件页面(例如,通过零拷贝机制),这种“部分 COW”写入可能到达磁盘上文件的页缓存。实践中,包编辑器动作可能接收来自 sendfile 或 splice 的页面;因此,单个包过滤器操作可以间接地将攻击者选择的数据写入文件的内存映像,而无需修改磁盘。
组件与数据流: 漏洞代码位于 Linux net/sched 子系统(act_pedit.c)中。当数据包匹配已配置的 pedit 规则时,会调用 tcf_pedit_act()。内部它仅调用一次 skb_ensure_writable(skb, X),其中 X = tcfp_off_max_hint。这使得包的前 X 字节变为私有(COW)。然后,在每个 键(编辑操作)的循环中,它通过将 运行时头部偏移量 添加到键的指定偏移量来计算键的实际写入偏移量,并将一个 32 位值写入包中。伪代码如下:```c
u32 off_max = action->tcfp_off_max_hint;
skb_ensure_writable(skb, off_max);
for (i = 0; i < num_keys; i++) {
u32 hdr_off = compute_header_offset(skb, key[i].hdr_type);
u32 write_off = hdr_off + key[i].offset;
skb_store_bits(skb, write_off, &key[i].value, 4);
}
因为 `hdr_off` 只在处理每个键时计算,初始的 `skb_ensure_writable()` 调用没有考虑它。如果 `hdr_off + key[i].offset` 超过 `off_max`,代码会回退到在分片而非主线性区域上调用 `skb_store_bits()`,这意味着它写入了一个未设为私有的页面。这就是失败点。
**攻击面:** 唯一需要的接口是带有 `pedit` 动作的 **tc filter**,这通常需要 **CAP_NET_ADMIN** 能力。然而,普通用户可以在私有网络命名空间(用户命名空间克隆)内获得 CAP_NET_ADMIN,而无需真实权限。因此,无特权用户可以进入用户+网络命名空间,并在 loopback 上创建 `tc pedit` 规则。当数据包被处理时发生写入(攻击者通常在 loopback 上生成流量以触发它)。信任边界(用户与内核)被跨越,因为内核信任自己的 COW 设置,但用户提供的偏移量打破了这一假设。
**内部机制:** 在内核侧,该漏洞表现为**越界写入**(CWE-787)。它破坏映射到用户空间的内核内存(文件页缓存)。具体来说,它可以覆盖恰好在套接字缓冲区中映射的任何文件页的内容。在概念验证中,`/bin/su` 通过被发送到套接字缓冲区而 mmapped,因此漏洞利用翻转其在内存中的入口点字节。这不会修改磁盘上的文件,但该二进制文件的任何后续执行都会从缓存中读取中毒的映像。博客分析指出:
> “因为 skb 可以引用通过 sendfile 引入的零拷贝页,该越界写入可能落在支持真实文件的共享页缓存内存中。内核认为它已经使数据包内存可安全修改;但实际上,随后的写入超出了它实际私有化的区域。”
**信任边界:** 内核错误地认为 `skb_ensure_writable()`(快速路径 COW)会保证所有后续写入的安全性。它没有为每个键重新检查。用户仅控制包过滤器配置和包内容;内核(通过网络命名空间)授予了这一点。一旦该信任被破坏,写入就会逃逸到本应受到保护的文件后备内存中。
## 根因分析
根因是 **pedit 动作中错误的 COW 范围计算**。在代码层面,一次 `skb_ensure_writable()` 调用基于 `tcfp_off_max_hint` 的长度被调用,然后在循环内部实际偏移量可能超过这个值。一个小补丁(2026 年 5 月)通过将 `skb_ensure_writable()` *移动到*循环内部(在知道真实偏移量之后)并添加对负偏移量的检查和特殊处理来修复。换句话说:
- **有缺陷的代码:** ```c
skb_ensure_writable(skb, action->tcfp_off_max_hint);
for each key:
// compute offset (hdr_off + key_offset)
skb_store_bits(skb, write_off, ...);
另外,该修复确保对于负偏移(以太网头部编辑)使用 skb_cow() 处理头部空间,并防范 INT_MIN 情况。提交信息(stack.watch 摘要)指出:“通过将 skb_ensure_writable() 移到实际写入偏移已知的每个键循环内部,并添加偏移算术的溢出检查来修复。”
因此,其存在原因:在代码审查或设计期间,针对每个键的重新计算被忽略了。静态提示优化绕过了每个键需要重新评估的需求。这看似是一个诚实的错误而非恶意疏忽,但其影响严重,因为它违反了 COW 假设。正如 TuxCare 所述,此漏洞是在“常规数据损坏”修复的伪装下合并的,没有附带安全背景。
该漏洞由内核提交 8b796475fd78(2022年5月)引入,直到2026年初才被发现。据消息来源,修复(提交 899ee91156e5,2026年5月31日)作为普通数据损坏补丁提交到 netdev 邮件列表。内核维护者于2026年6月4日合并了该修复(net-7.1-rc7)。仅在2026年6月16日,CVE-2026-46331 才正式分配(大约在补丁出现两周后)。2026年6月17日出现了完全武器化的公开漏洞(packet_edit_meme PoC)。
实际操作顺序为:
多方通过公开补丁注意到了该漏洞。例如,网络安全研究员 Massimiliano Oldani 在随后不久发布了一份详细的技术分析和漏洞利用,指出*“CVE 分配后24小时内,一个名为 packet_edit_meme 的公开工作概念验证漏洞出现在 GitHub 上”*。CloudLinux、TuxCare 和 SentinelOne 在 PoC 公开及 CVE 分配后发布了分析。Debian 安全跟踪器和 PT DBugs 也总结了该问题和可用的公告(见参考文献)。
实际攻击所需的前提条件极少:
攻击者能力: 目标机器上的本地非特权用户。该用户必须能够创建带有网络命名空间的新用户命名空间(通过 unshare(CLONE_NEWUSER|CLONE_NEWNET)),这会在该命名空间内授予 CAP_NET_ADMIN 而无需实际 root 权限。非特权用户命名空间在许多内核(如 RHEL、Debian)上默认启用,在 Ubuntu 上可通过 aa-exec 变通方法重新启用。
目标条件: 目标必须运行易受攻击的 Linux 内核(约 5.18–7.1-rc6),且 act_pedit 模块可用。如果 act_pedit 是内置或已加载,则立即可利用。如果它是模块,则在配置 tc pedit 规则时自动加载。目标不应已应用上游补丁。值得注意的是,攻击者无需对任何文件具有写权限;漏洞通过数据包过滤器写入文件。
攻击链:
unshare --map-root-user --net --pid bash 的命令,创建新的用户+网络命名空间。这在该命名空间中授予 CAP_NET_ADMIN(用户映射为内部的 root)。ifconfig lo up),并可选择启动监听器(例如 nc -l 127.0.0.1 9999)。这为 TC 操作提供了数据包流。tc 命令或 netlink,攻击者在 lo 上创建 qdisc 和过滤器,匹配所有数据包(例如 match u32 0 0),并附加一个包含特制键的 操作。每个键具有动态头部类型(例如用于 L4 偏移的 IP 头部)和一个偏移,使得实际写入位置(头部起始 + 偏移)刚好超出 覆盖的范围。影响: 如果成功,攻击者在本地获得完全 root 权限。该漏洞可以通过单条命令完成,且具有确定性。此外,如果以不同方式使用,对任意文件映射页的损坏可能导致拒绝服务(系统崩溃)。公开的 PoC 专门覆盖了 /bin/su 的入口点以插入 shellcode,但攻击者可以针对任何可映射的文件。该利用链不需要特定时机或竞态条件,并已在多个发行版(RHEL、Ubuntu、Debian 等)上得到演示。
公开的漏洞利用 packet_edit_meme 可在 GitHub(sgkdev/packet_edit_meme)上获取,并针对 /bin/su。我们描述其基本逻辑,不包含破坏性载荷:```c
/* Pseudocode outline of the exploit (simplified) /
int main() {
/ 1. Identify a setuid binary (su) and its ELF entry offset */
int fd = open("/bin/su", O_RDONLY);
long entry = elf_entry_offset(fd);
if (entry < 0) abort();
printf("Target %s (UID=%d), entry offset 0x%lx\n", "/bin/su", getuid(), entry);
/* 2. Unshare user+net namespace to get CAP_NET_ADMIN locally */
if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) abort();
/* Map UID/GID to root (handled via /proc/self/uid_map, /gid_map) */
// (omit details: write "0 <uid> 1" to /proc/self/uid_map and gid_map, and deny setgroups)
/* 3. Setup environment: bring up loopback and listener */
if (system("ip link set lo up") < 0) abort();
if (system("nc -l 127.0.0.1 9999 &") < 0) abort();
/* 4. Configure a tc pedit action via netlink (simplified) */
// Assume 'pedit_write' sends a packet-edit command to the kernel.
// The key offsets below are chosen such that they exceed the initial COW range.
char shellcode[/*size=48*/] = {
// (assembly for setgid(0); setuid(0); execve("/bin/sh").., padded to 36 or 48 bytes)
};
size_t total = sizeof(shellcode), sent = 0;
while (sent < total) {
int chunk = min(PEDIT_MAX_WRITE, total - sent);
/* Issue TC pedit action to write next chunk */
if (pedit_write(fd, entry + sent, &shellcode[sent], chunk) != 0) {
fprintf(stderr, "pedit_write failed\n");
exit(1);
}
sent += chunk;
}
/* 5. Trigger execution of su (in original namespace) */
execl("/bin/su", "su", NULL); // This will run the poisoned binary as root
return 0;
}
此伪代码说明了流程:打开 `/bin/su`,解绑命名空间以获得 CAP_NET_ADMIN,配置回环和 TC pedit 规则,然后调用函数 `pedit_write(fd, offset, data, len)`(在实际 PoC 中,这底层使用了 netlink 调用)来覆盖目标的页缓存。最后,执行该二进制文件,产生一个 root shell。
实际的 PoC 更为复杂(处理 UID/GID 映射、网络监听和系统调用级的 shellcode 字节),但核心概念如上所述。我们强调**不要运行此利用代码**,除非在安全的测试环境中,并且不要针对任何真实系统。以上内容仅供演示。
**注意:** 如果不存在公开的安全可用 PoC,我们会明确说明。在此情况下,PoC 是公开的,我们对其进行了概念性描述。为简洁和安全起见,我们省略了原始的 shellcode 字节和实际的 netlink 细节。
## 利用流程
1. **入口点:** 攻击者必须首先获得 CAP_NET_ADMIN。通常这意味着从一个非特权进程创建用户+网络命名空间(`unshare`),这会在命名空间本地授予 CAP_NET_ADMIN。
2. **初始访问:** 在此命名空间内,攻击者可以使用常规工具(`ip`、`tc`)配置流量控制。内核的 `act_pedit` 代码路径现在对数据包可达。
3. **触发:** 攻击者在回环接口上设置一个 `tc filter ... action pedit`。此过滤器匹配数据包(例如 0-match)并指定一个或多个**类型化键**,包含头部类型(IP、TCP)和偏移量。选择的偏移量使得*在内核循环内计算头部基址之后,最终的写入偏移量超出初始 COW 范围*。
4. **利用:** 当匹配过滤器的数据包被处理时,内核调用 `tcf_pedit_act()`。它执行了一个*不充分的* `skb_ensure_writable()`,然后遍历键。对于至少一个键,写入会落在*未*克隆到私有副本的页面上。这导致了一个**越界写入**到共享页缓存。如果套接字缓冲区已准备好引用某个文件的页面(通过 sendfile/splice),那么该写入会损坏那些文件页。
5. **利用后:** 攻击者的 shellcode 已被写入目标二进制文件的页缓存中(例如 `/bin/su`)。攻击者(在原始命名空间中)随后执行 `/bin/su`。内核读取内存中的镜像(包含注入的有效载荷)并运行 shellcode,从而赋予攻击者一个 root shell。至此,完全的系统沦陷发生。
6. **影响:** 攻击者获得 root 权限。机密数据可能被覆盖但不会直接泄露。完整性被彻底破坏(攻击者可以更改任何文件的内存镜像)。可用性也可能受到影响(写入关键页面错误可能导致进程或系统崩溃)。CVSS 指标:总体中等(CVSS 3.1=6.0),但实际影响是严重的本地提权。
此流程的图示如下:```mermaid
flowchart LR
A[Attacker (unprivileged user)] --> B[Unshare into user+net namespace<br>(gains CAP_NET_ADMIN)]
B --> C[Configure TC pedit filter on lo]
C --> D{Packet processing by kernel}
D --> E[act_pedit computes wrong COW range]
E --> F[skb_store_bits writes beyond COW'd region]
F --> G[Page cache of target file is corrupted]
G --> H[Attacker executes poisoned setuid binary]
H --> I[Root shell obtained]
act_pedit 模块意外地出现在 lsmod 中(例如,在 Web 服务器上执行 lsmod | grep act_pedit 的结果非空)。tc 使用情况: 来自非特权进程的异常 tc 命令或 netlink 消息。审计日志可能显示 CAP_NET_ADMIN 被授予非 root 进程。netstat -tulnp 显示 nc 或自定义监听器在 127.0.0.1 上可能是一个迹象。tcf_pedit_act、skb_ensure_writable 的 oops 或警告,或在环回接口出现大量流量时的软锁,或 tc 处理中的错误(这些情况不常见,通常表明存在损坏)。auditd)显示程序通过用户命名空间获取 CAP_NET_ADMIN,或写入 /proc/[pid]/uid_map。例如,一个行为指标 (IOA) 是 页面缓存中的文件损坏:一个排查清单可能包括验证内存中的文件内容与磁盘是否一致,特别是在发生大量 tc 活动后 for setuid 二进制文件。另一个是 新命名空间的创建:监控对 unshare(CLONE_NEWUSER|CLONE_NEWNET) 的调用可能会触发告警。简而言之,防御者应关注以下任一情况:act_pedit 的使用、用户命名空间的使用、以及 RAM 中可执行文件的突然修改。
为了检测利用尝试:
act_pedit 过滤器的 tc 配置或 netlink 消息发出告警。例如,Sigma 规则可以查找包含 TCA_ACT_KIND: pedit 或类似内容的事件。监控审计日志中非 root 进程的 capset CAP_NET_ADMIN 事件,或写入 /proc/*/uid_map 的操作。/bin/su(或其他敏感二进制文件)的进程,并观察它们是否在命名空间/unshare 系统调用后立即执行。对任何既有打开 setuid 二进制文件又创建用户命名空间的进程发出告警。skb_ensure_writable() 不能被欺骗(尽管目前没有已知的内置检查)。总之,防御者应记录和审计用户命名空间的使用、tc 命令和模块加载。一个关键方法:拒绝或记录任何不受信任用户调用的 tc pedit。在被入侵的主机上,检查是否已应用 /etc/modprobe.d/disable-act_pedit.conf(应预先放置)。
应用补丁: 主要修复方法是内核更新。所有主要发行版已于 2026 年 6 月发布更新。升级到修补后的内核(Linux 7.1.0 或更高版本,或发行版反向移植版本)是最终解决方案。
配置更改: 如果无法立即打补丁,请实施缓解措施:
act_pedit: 如果您的负载不需要 tc pedit,请将该模块列入黑名单。例如: ```
echo 'install act_pedit /bin/true' | sudo tee /etc/modprobe.d/disable-act_pedit.conf
lsmod | grep -w act_pedit && sudo rmmod act_pedit
这确保该操作无法被加载。 (这是由CloudLinux和TuxCare推荐的。) 请勿在合法使用tc pedit的主机上应用。
在 Ubuntu 22.04+ 上: ``` sudo sysctl -w kernel.unprivileged_userns_clone=0 echo 'kernel.unprivileged_userns_clone = 0' | sudo tee /etc/sysctl.d/99-pedit-cow.conf
这可以阻止非特权用户创建必要的用户命名空间以获得 CAP_NET_ADMIN。注意:禁用命名空间可能会破坏无根容器和一些沙盒应用程序。
- **清除页面缓存(遏制):** 如果你怀疑漏洞已运行,二进制文件的内存副本可能已被污染。请立即清除缓存以驱逐它们: ```
sudo sh -c "echo 3 > /proc/sys/vm/drop_caches"
这强制页面从磁盘重新加载。警告: 如果攻击者已经拥有 root 权限,清空缓存不会移除他们安装的任何持久化后门。此类主机应被视为已失陷。
最小权限原则: 审计并限制谁可以使用 tc。此漏洞仅需 CAP_NET_ADMIN;确保只有可信任的管理员拥有此能力。使用 RBAC 或容器化来限制能力授予。
网络控制: 虽然此漏洞无法直接通过网络利用,但应监控回环接口的使用。对 127.0.0.1 设置防火墙并不现实,但应确保只有本地回环流量用于 TC 操作。
厂商公告: 请参考您操作系统的官方公告。Red Hat 发布了针对 RHEL 8/9/10 的 RHSA-2026:27354(及相关公告),Debian 发布了 DSA-6355-1,Ubuntu 的 CVE 页面列出了已修复的内核等。(参见参考文献。)
长期修复措施是确保所有受影响的系统已更新至最新内核。应安装包含修复的内核包并重启系统。对于无法重启的容器或系统,可考虑实时补丁解决方案(例如 KernelCare),它们已准备就绪。
此外,系统设计应假设用户空间可访问的内核接口会随时间变化。限制 CAP_NET_ADMIN 和过滤 tc 的用法是本漏洞之外的良好实践。
如果发生了入侵,请重建系统。此漏洞仅毒化页缓存,但拥有 root 权限的攻击者可能已执行其他恶意操作;需要进行取证验证。不要依赖漏洞利用后的文件完整性扫描,因为如前所述,PoC 不会改变磁盘上的文件。重启并打补丁是安全的补救路径。
基于这些因素,典型的 CVSS v3.1 向量为 AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H,得出基础分数 6.0(中等)。但请注意,CVSS 并未体现此漏洞授予向 root 的权限提升,这在实际中至关重要。(某些来源为类似漏洞计算了 CVSSv4;例如 PT DBugs 列出了 8.5 的 CVSSv4 分数。)
该漏洞的严重性通常被厂商评为重要/严重。Red Hat 针对此 CVE 的公告将其标记为重要,AWS 将其标记为中等(CVSS 6.0)。无论如何,由于获得了 root 权限,在多用户或共享系统中的实际风险最高。
此漏洞属于页缓存投毒漏洞家族。其他值得注意的 CVE 包括:
splice() 可以在 COW 边界之外写入页缓存。它也允许在内存中覆盖文件(不更改磁盘)。/proc/self/mem 写时复制中的一个较旧缺陷,允许本地写入只读映射。这些漏洞都涉及内核快路径写入它认为独占的内存,但实际上并非如此。CVE-2026-46331 独特之处在于它发生在 net/sched pedit 动作中,并利用用户命名空间绕过权限限制。与脏管道或脏牛不同,它不需要任何辅助的特权进程(例如行为异常的系统)——单个无特权用户即可触发。
所有参考文献均来自可信来源(厂商公告、已发布的分析、CVE/NVD 条目)。
act_pedit 中,过早计算可写性导致了页缓存损坏。peditskb_ensure_writable()echo '' > /dev/udp/127.0.0.1/53)触发过滤器。内核调用 tcf_pedit_act(),分配一个 COW 范围,然后迭代这些键。至少有一个键的写入落在预 COW 区域之外,导致写入进入共享的页缓存。sendfile 或类似方式将 /bin/su mmap 到套接字中,从而使数据包缓冲区引用该文件的页面。越界写入随后损坏了内存中的 /bin/su 副本(特别是 ELF 入口点)。/bin/su)。由于内核无意中注入了执行 setgid(0); setuid(0); execve("/bin/sh") 的 shellcode,运行 su 会生成一个 root shell。磁盘上的文件从未被更改,因此磁盘文件完整性工具不会显示变化。