net: skbuff: preserve shared-frag marker during coalescing
Description
In the Linux kernel, the following vulnerability has been resolved:
net: skbuff: preserve shared-frag marker during coalescing
skb_try_coalesce() can attach paged frags from @from to @to. If @from has SKBFL_SHARED_FRAG set, the resulting @to skb can contain the same externally-owned or page-cache-backed frags, but the shared-frag marker is currently lost.
That breaks the invariant relied on by later in-place writers. In particular, ESP input checks skb_has_shared_frag() before deciding whether an uncloned nonlinear skb can skip skb_cow_data(). If TCP receive coalescing has moved shared frags into an unmarked skb, ESP can see skb_has_shared_frag() as false and decrypt in place over page-cache backed frags.
Propagate SKBFL_SHARED_FRAG when skb_try_coalesce() transfers paged frags. The tailroom copy path does not need the marker because it copies bytes into @to's linear data rather than transferring frag descriptors.
Affected products
1Patches
14fc6eb39c55e9net: skbuff: propagate shared-frag marker through frag-transfer helpers
3 files changed · +13 −2
net/core/gro.c+4 −0 modifieddiff --git a/net/core/gro.c b/net/core/gro.c index ac498c9f82cf5..f5c80c2f69df7 100644 --- a/net/core/gro.c +++ b/net/core/gro.c @@ -214,10 +214,12 @@ done: p->data_len += len; p->truesize += delta_truesize; p->len += len; + skb_shinfo(p)->flags |= skbinfo->flags & SKBFL_SHARED_FRAG; if (lp != p) { lp->data_len += len; lp->truesize += delta_truesize; lp->len += len; + skb_shinfo(lp)->flags |= skbinfo->flags & SKBFL_SHARED_FRAG; } NAPI_GRO_CB(skb)->same_flow = 1; return 0; @@ -245,6 +247,8 @@ int skb_gro_receive_list(struct sk_buff *p, struct sk_buff *skb) p->truesize += skb->truesize; p->len += skb->len; + skb_shinfo(p)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; + NAPI_GRO_CB(skb)->same_flow = 1; return 0;
net/core/skbuff.c+8 −1 modifieddiff --git a/net/core/skbuff.c b/net/core/skbuff.c index 00d60588fb09e..aa9e914884736 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -2214,6 +2214,7 @@ struct sk_buff *__pskb_copy_fclone(struct sk_buff *skb, int headroom, skb_frag_ref(skb, i); } skb_shinfo(n)->nr_frags = i; + skb_shinfo(n)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; } if (skb_has_frag_list(skb)) { @@ -4289,6 +4290,8 @@ onlymerged: tgt->ip_summed = CHECKSUM_PARTIAL; skb->ip_summed = CHECKSUM_PARTIAL; + skb_shinfo(tgt)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; + skb_len_add(skb, -shiftlen); skb_len_add(tgt, shiftlen); @@ -4899,7 +4902,8 @@ normal: skb_copy_from_linear_data_offset(head_skb, offset, skb_put(nskb, hsize), hsize); - skb_shinfo(nskb)->flags |= skb_shinfo(head_skb)->flags & + skb_shinfo(nskb)->flags |= (skb_shinfo(head_skb)->flags | + skb_shinfo(frag_skb)->flags) & SKBFL_SHARED_FRAG; if (skb_zerocopy_clone(nskb, frag_skb, GFP_ATOMIC)) @@ -4916,6 +4920,9 @@ normal: nfrags = skb_shinfo(list_skb)->nr_frags; frag = skb_shinfo(list_skb)->frags; frag_skb = list_skb; + + skb_shinfo(nskb)->flags |= skb_shinfo(frag_skb)->flags & SKBFL_SHARED_FRAG; + if (!skb_headlen(list_skb)) { BUG_ON(!nfrags); } else {
net/ipv4/tcp_output.c+1 −1 modifieddiff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index 33c2fb60d0562..c76672f544be4 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -2391,6 +2391,7 @@ static int tcp_clone_payload(struct sock *sk, struct sk_buff *to, todo = min_t(int, skb_frag_size(fragfrom), probe_size - len); len += todo; + skb_shinfo(to)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; if (lastfrag && skb_frag_page(fragfrom) == skb_frag_page(lastfrag) && skb_frag_off(fragfrom) == skb_frag_off(lastfrag) + -- cgit 1.3-korg
9bc9d6d6967anet: skbuff: propagate shared-frag marker through frag-transfer helpers
3 files changed · +13 −2
net/core/gro.c+4 −0 modifieddiff --git a/net/core/gro.c b/net/core/gro.c index 31d21de5b15a7..9f8960789b2cf 100644 --- a/net/core/gro.c +++ b/net/core/gro.c @@ -213,10 +213,12 @@ done: p->data_len += len; p->truesize += delta_truesize; p->len += len; + skb_shinfo(p)->flags |= skbinfo->flags & SKBFL_SHARED_FRAG; if (lp != p) { lp->data_len += len; lp->truesize += delta_truesize; lp->len += len; + skb_shinfo(lp)->flags |= skbinfo->flags & SKBFL_SHARED_FRAG; } NAPI_GRO_CB(skb)->same_flow = 1; return 0; @@ -244,6 +246,8 @@ int skb_gro_receive_list(struct sk_buff *p, struct sk_buff *skb) p->truesize += skb->truesize; p->len += skb->len; + skb_shinfo(p)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; + NAPI_GRO_CB(skb)->same_flow = 1; return 0;
net/core/skbuff.c+8 −1 modifieddiff --git a/net/core/skbuff.c b/net/core/skbuff.c index bcf620fef77ea..28bd8304796d7 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -2258,6 +2258,7 @@ struct sk_buff *__pskb_copy_fclone(struct sk_buff *skb, int headroom, skb_frag_ref(skb, i); } skb_shinfo(n)->nr_frags = i; + skb_shinfo(n)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; } if (skb_has_frag_list(skb)) { @@ -4373,6 +4374,8 @@ onlymerged: tgt->ip_summed = CHECKSUM_PARTIAL; skb->ip_summed = CHECKSUM_PARTIAL; + skb_shinfo(tgt)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; + skb_len_add(skb, -shiftlen); skb_len_add(tgt, shiftlen); @@ -4983,7 +4986,8 @@ normal: skb_copy_from_linear_data_offset(head_skb, offset, skb_put(nskb, hsize), hsize); - skb_shinfo(nskb)->flags |= skb_shinfo(head_skb)->flags & + skb_shinfo(nskb)->flags |= (skb_shinfo(head_skb)->flags | + skb_shinfo(frag_skb)->flags) & SKBFL_SHARED_FRAG; if (skb_zerocopy_clone(nskb, frag_skb, GFP_ATOMIC)) @@ -5000,6 +5004,9 @@ normal: nfrags = skb_shinfo(list_skb)->nr_frags; frag = skb_shinfo(list_skb)->frags; frag_skb = list_skb; + + skb_shinfo(nskb)->flags |= skb_shinfo(frag_skb)->flags & SKBFL_SHARED_FRAG; + if (!skb_headlen(list_skb)) { BUG_ON(!nfrags); } else {
net/ipv4/tcp_output.c+1 −1 modifieddiff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index 51e7f40e7e313..a51186b42be93 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -2608,6 +2608,7 @@ static int tcp_clone_payload(struct sock *sk, struct sk_buff *to, todo = min_t(int, skb_frag_size(fragfrom), probe_size - len); len += todo; + skb_shinfo(to)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; if (lastfrag && skb_frag_page(fragfrom) == skb_frag_page(lastfrag) && skb_frag_off(fragfrom) == skb_frag_off(lastfrag) + -- cgit 1.3-korg
ff375cc75f91net: skbuff: propagate shared-frag marker through frag-transfer helpers
3 files changed · +13 −2
net/core/gro.c+4 −0 modifieddiff --git a/net/core/gro.c b/net/core/gro.c index ef61695fbdbb6..867611d171dbb 100644 --- a/net/core/gro.c +++ b/net/core/gro.c @@ -215,10 +215,12 @@ done: p->data_len += len; p->truesize += delta_truesize; p->len += len; + skb_shinfo(p)->flags |= skbinfo->flags & SKBFL_SHARED_FRAG; if (lp != p) { lp->data_len += len; lp->truesize += delta_truesize; lp->len += len; + skb_shinfo(lp)->flags |= skbinfo->flags & SKBFL_SHARED_FRAG; } NAPI_GRO_CB(skb)->same_flow = 1; return 0; @@ -246,6 +248,8 @@ int skb_gro_receive_list(struct sk_buff *p, struct sk_buff *skb) p->truesize += skb->truesize; p->len += skb->len; + skb_shinfo(p)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; + NAPI_GRO_CB(skb)->same_flow = 1; return 0;
net/core/skbuff.c+8 −1 modifieddiff --git a/net/core/skbuff.c b/net/core/skbuff.c index 3e8f0b8226caf..a8911f1b90c15 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -2188,6 +2188,7 @@ struct sk_buff *__pskb_copy_fclone(struct sk_buff *skb, int headroom, skb_frag_ref(skb, i); } skb_shinfo(n)->nr_frags = i; + skb_shinfo(n)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; } if (skb_has_frag_list(skb)) { @@ -4301,6 +4302,8 @@ onlymerged: tgt->ip_summed = CHECKSUM_PARTIAL; skb->ip_summed = CHECKSUM_PARTIAL; + skb_shinfo(tgt)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; + skb_len_add(skb, -shiftlen); skb_len_add(tgt, shiftlen); @@ -4911,7 +4914,8 @@ normal: skb_copy_from_linear_data_offset(head_skb, offset, skb_put(nskb, hsize), hsize); - skb_shinfo(nskb)->flags |= skb_shinfo(head_skb)->flags & + skb_shinfo(nskb)->flags |= (skb_shinfo(head_skb)->flags | + skb_shinfo(frag_skb)->flags) & SKBFL_SHARED_FRAG; if (skb_zerocopy_clone(nskb, frag_skb, GFP_ATOMIC)) @@ -4928,6 +4932,9 @@ normal: nfrags = skb_shinfo(list_skb)->nr_frags; frag = skb_shinfo(list_skb)->frags; frag_skb = list_skb; + + skb_shinfo(nskb)->flags |= skb_shinfo(frag_skb)->flags & SKBFL_SHARED_FRAG; + if (!skb_headlen(list_skb)) { BUG_ON(!nfrags); } else {
net/ipv4/tcp_output.c+1 −1 modifieddiff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index 5a443fb14a973..6937b2b03ef93 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -2543,6 +2543,7 @@ static int tcp_clone_payload(struct sock *sk, struct sk_buff *to, todo = min_t(int, skb_frag_size(fragfrom), probe_size - len); len += todo; + skb_shinfo(to)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; if (lastfrag && skb_frag_page(fragfrom) == skb_frag_page(lastfrag) && skb_frag_off(fragfrom) == skb_frag_off(lastfrag) + -- cgit 1.3-korg
179f1852bdednet: skbuff: propagate shared-frag marker through frag-transfer helpers
1 file changed · +12 −2
net/core/skbuff.c+12 −2 modifieddiff --git a/net/core/skbuff.c b/net/core/skbuff.c index aadb87aa5e7eb..a8d09eff26f11 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -1661,6 +1661,7 @@ struct sk_buff *__pskb_copy_fclone(struct sk_buff *skb, int headroom, skb_frag_ref(skb, i); } skb_shinfo(n)->nr_frags = i; + skb_shinfo(n)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; } if (skb_has_frag_list(skb)) { @@ -3650,6 +3651,8 @@ onlymerged: tgt->ip_summed = CHECKSUM_PARTIAL; skb->ip_summed = CHECKSUM_PARTIAL; + skb_shinfo(tgt)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; + /* Yak, is it really working this way? Some helper please? */ skb->len -= shiftlen; skb->data_len -= shiftlen; @@ -4017,6 +4020,8 @@ int skb_gro_receive_list(struct sk_buff *p, struct sk_buff *skb) p->truesize += skb->truesize; p->len += skb->len; + skb_shinfo(p)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; + NAPI_GRO_CB(skb)->same_flow = 1; return 0; @@ -4251,7 +4256,8 @@ normal: skb_copy_from_linear_data_offset(head_skb, offset, skb_put(nskb, hsize), hsize); - skb_shinfo(nskb)->flags |= skb_shinfo(head_skb)->flags & + skb_shinfo(nskb)->flags |= (skb_shinfo(head_skb)->flags | + skb_shinfo(frag_skb)->flags) & SKBFL_SHARED_FRAG; if (skb_zerocopy_clone(nskb, frag_skb, GFP_ATOMIC)) @@ -4268,6 +4274,9 @@ normal: nfrags = skb_shinfo(list_skb)->nr_frags; frag = skb_shinfo(list_skb)->frags; frag_skb = list_skb; + + skb_shinfo(nskb)->flags |= skb_shinfo(frag_skb)->flags & SKBFL_SHARED_FRAG; + if (!skb_headlen(list_skb)) { BUG_ON(!nfrags); } else { @@ -4490,10 +4499,12 @@ done: p->data_len += len; p->truesize += delta_truesize; p->len += len; + skb_shinfo(p)->flags |= skbinfo->flags & SKBFL_SHARED_FRAG; if (lp != p) { lp->data_len += len; lp->truesize += delta_truesize; lp->len += len; + skb_shinfo(lp)->flags |= skbinfo->flags & SKBFL_SHARED_FRAG; } NAPI_GRO_CB(skb)->same_flow = 1; return 0; -- cgit 1.3-korg
989214c66884net: skbuff: propagate shared-frag marker through frag-transfer helpers
4 files changed · +13 −2
net/core/gro.c+2 −0 modifieddiff --git a/net/core/gro.c b/net/core/gro.c index 92cb86d4ce50a..0a9d4a3bb104d 100644 --- a/net/core/gro.c +++ b/net/core/gro.c @@ -216,10 +216,12 @@ done: p->data_len += len; p->truesize += delta_truesize; p->len += len; + skb_shinfo(p)->flags |= skbinfo->flags & SKBFL_SHARED_FRAG; if (lp != p) { lp->data_len += len; lp->truesize += delta_truesize; lp->len += len; + skb_shinfo(lp)->flags |= skbinfo->flags & SKBFL_SHARED_FRAG; } NAPI_GRO_CB(skb)->same_flow = 1; return 0;
net/core/skbuff.c+8 −1 modifieddiff --git a/net/core/skbuff.c b/net/core/skbuff.c index 88e0bf8004bf2..8b05866e93b19 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -2050,6 +2050,7 @@ struct sk_buff *__pskb_copy_fclone(struct sk_buff *skb, int headroom, skb_frag_ref(skb, i); } skb_shinfo(n)->nr_frags = i; + skb_shinfo(n)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; } if (skb_has_frag_list(skb)) { @@ -4086,6 +4087,8 @@ onlymerged: tgt->ip_summed = CHECKSUM_PARTIAL; skb->ip_summed = CHECKSUM_PARTIAL; + skb_shinfo(tgt)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; + skb_len_add(skb, -shiftlen); skb_len_add(tgt, shiftlen); @@ -4658,7 +4661,8 @@ normal: skb_copy_from_linear_data_offset(head_skb, offset, skb_put(nskb, hsize), hsize); - skb_shinfo(nskb)->flags |= skb_shinfo(head_skb)->flags & + skb_shinfo(nskb)->flags |= (skb_shinfo(head_skb)->flags | + skb_shinfo(frag_skb)->flags) & SKBFL_SHARED_FRAG; if (skb_zerocopy_clone(nskb, frag_skb, GFP_ATOMIC)) @@ -4675,6 +4679,9 @@ normal: nfrags = skb_shinfo(list_skb)->nr_frags; frag = skb_shinfo(list_skb)->frags; frag_skb = list_skb; + + skb_shinfo(nskb)->flags |= skb_shinfo(frag_skb)->flags & SKBFL_SHARED_FRAG; + if (!skb_headlen(list_skb)) { BUG_ON(!nfrags); } else {
net/ipv4/tcp_output.c+1 −0 modifieddiff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index ea982c3670d33..429b94a86b5bb 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -2366,6 +2366,7 @@ static int tcp_clone_payload(struct sock *sk, struct sk_buff *to, todo = min_t(int, skb_frag_size(fragfrom), probe_size - len); len += todo; + skb_shinfo(to)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; if (lastfrag && skb_frag_page(fragfrom) == skb_frag_page(lastfrag) && skb_frag_off(fragfrom) == skb_frag_off(lastfrag) +
net/ipv4/udp_offload.c+2 −1 modifieddiff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c index cd860d8d497b4..84ae2759ff195 100644 --- a/net/ipv4/udp_offload.c +++ b/net/ipv4/udp_offload.c @@ -547,6 +547,8 @@ static int skb_gro_receive_list(struct sk_buff *p, struct sk_buff *skb) p->truesize += skb->truesize; p->len += skb->len; + skb_shinfo(p)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; + NAPI_GRO_CB(skb)->same_flow = 1; return 0; -- cgit 1.3-korg
12401fcfb01fnet: skbuff: propagate shared-frag marker through frag-transfer helpers
3 files changed · +12 −2
net/core/gro.c+2 −0 modifieddiff --git a/net/core/gro.c b/net/core/gro.c index 52b91cfb3bf19..ea6571c01faa9 100644 --- a/net/core/gro.c +++ b/net/core/gro.c @@ -281,10 +281,12 @@ done: p->data_len += len; p->truesize += delta_truesize; p->len += len; + skb_shinfo(p)->flags |= skbinfo->flags & SKBFL_SHARED_FRAG; if (lp != p) { lp->data_len += len; lp->truesize += delta_truesize; lp->len += len; + skb_shinfo(lp)->flags |= skbinfo->flags & SKBFL_SHARED_FRAG; } NAPI_GRO_CB(skb)->same_flow = 1; return 0;
net/core/skbuff.c+8 −1 modifieddiff --git a/net/core/skbuff.c b/net/core/skbuff.c index fd743051c8987..8bc4b26de5e53 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -1798,6 +1798,7 @@ struct sk_buff *__pskb_copy_fclone(struct sk_buff *skb, int headroom, skb_frag_ref(skb, i); } skb_shinfo(n)->nr_frags = i; + skb_shinfo(n)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; } if (skb_has_frag_list(skb)) { @@ -3789,6 +3790,8 @@ onlymerged: tgt->ip_summed = CHECKSUM_PARTIAL; skb->ip_summed = CHECKSUM_PARTIAL; + skb_shinfo(tgt)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; + skb_len_add(skb, -shiftlen); skb_len_add(tgt, shiftlen); @@ -4362,7 +4365,8 @@ normal: skb_copy_from_linear_data_offset(head_skb, offset, skb_put(nskb, hsize), hsize); - skb_shinfo(nskb)->flags |= skb_shinfo(head_skb)->flags & + skb_shinfo(nskb)->flags |= (skb_shinfo(head_skb)->flags | + skb_shinfo(frag_skb)->flags) & SKBFL_SHARED_FRAG; if (skb_zerocopy_clone(nskb, frag_skb, GFP_ATOMIC)) @@ -4379,6 +4383,9 @@ normal: nfrags = skb_shinfo(list_skb)->nr_frags; frag = skb_shinfo(list_skb)->frags; frag_skb = list_skb; + + skb_shinfo(nskb)->flags |= skb_shinfo(frag_skb)->flags & SKBFL_SHARED_FRAG; + if (!skb_headlen(list_skb)) { BUG_ON(!nfrags); } else {
net/ipv4/udp_offload.c+2 −1 modifieddiff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c index 58cabb2bb32a9..35c014e10f24b 100644 --- a/net/ipv4/udp_offload.c +++ b/net/ipv4/udp_offload.c @@ -546,6 +546,8 @@ static int skb_gro_receive_list(struct sk_buff *p, struct sk_buff *skb) p->truesize += skb->truesize; p->len += skb->len; + skb_shinfo(p)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG; + NAPI_GRO_CB(skb)->same_flow = 1; return 0; -- cgit 1.3-korg
fbeab9555564net: skbuff: propagate shared-frag marker through frag-transfer helpers
1 file changed · +12 −2
net/core/skbuff.c+12 −2 modifieddiff --git a/net/core/skbuff.c b/net/core/skbuff.c index c195107434b85..f7100f5af37ca 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -1596,6 +1596,7 @@ struct sk_buff *__pskb_copy_fclone(struct sk_buff *skb, int headroom, skb_frag_ref(skb, i); } skb_shinfo(n)->nr_frags = i; + skb_shinfo(n)->tx_flags |= skb_shinfo(skb)->tx_flags & SKBTX_SHARED_FRAG; } if (skb_has_frag_list(skb)) { @@ -3502,6 +3503,8 @@ onlymerged: tgt->ip_summed = CHECKSUM_PARTIAL; skb->ip_summed = CHECKSUM_PARTIAL; + skb_shinfo(tgt)->tx_flags |= skb_shinfo(skb)->tx_flags & SKBTX_SHARED_FRAG; + /* Yak, is it really working this way? Some helper please? */ skb->len -= shiftlen; skb->data_len -= shiftlen; @@ -3843,6 +3846,8 @@ int skb_gro_receive_list(struct sk_buff *p, struct sk_buff *skb) p->truesize += skb->truesize; p->len += skb->len; + skb_shinfo(p)->tx_flags |= skb_shinfo(skb)->tx_flags & SKBTX_SHARED_FRAG; + NAPI_GRO_CB(skb)->same_flow = 1; return 0; @@ -4076,7 +4081,8 @@ normal: skb_copy_from_linear_data_offset(head_skb, offset, skb_put(nskb, hsize), hsize); - skb_shinfo(nskb)->tx_flags |= skb_shinfo(head_skb)->tx_flags & + skb_shinfo(nskb)->tx_flags |= (skb_shinfo(head_skb)->tx_flags | + skb_shinfo(frag_skb)->tx_flags) & SKBTX_SHARED_FRAG; if (skb_zerocopy_clone(nskb, frag_skb, GFP_ATOMIC)) @@ -4093,6 +4099,9 @@ normal: nfrags = skb_shinfo(list_skb)->nr_frags; frag = skb_shinfo(list_skb)->frags; frag_skb = list_skb; + + skb_shinfo(nskb)->tx_flags |= skb_shinfo(frag_skb)->tx_flags & SKBTX_SHARED_FRAG; + if (!skb_headlen(list_skb)) { BUG_ON(!nfrags); } else { @@ -4309,10 +4318,12 @@ done: p->data_len += len; p->truesize += delta_truesize; p->len += len; + skb_shinfo(p)->tx_flags |= skbinfo->tx_flags & SKBTX_SHARED_FRAG; if (lp != p) { lp->data_len += len; lp->truesize += delta_truesize; lp->len += len; + skb_shinfo(lp)->tx_flags |= skbinfo->tx_flags & SKBTX_SHARED_FRAG; } NAPI_GRO_CB(skb)->same_flow = 1; return 0; -- cgit 1.3-korg
3884358a9286net: skbuff: preserve shared-frag marker during coalescing
1 file changed · +2 −1
net/core/skbuff.c+2 −1 modifieddiff --git a/net/core/skbuff.c b/net/core/skbuff.c index 59fb4b2bb8217..bcf620fef77ea 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -6224,6 +6224,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; -- cgit 1.3-korg
3bd9e113d500net: skbuff: preserve shared-frag marker during coalescing
1 file changed · +2 −1
net/core/skbuff.c+2 −1 modifieddiff --git a/net/core/skbuff.c b/net/core/skbuff.c index a4695882d1c4c..3e8f0b8226caf 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -6149,6 +6149,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; -- cgit 1.3-korg
78bf6b6bb195net: skbuff: preserve shared-frag marker during coalescing
1 file changed · +2 −1
net/core/skbuff.c+2 −1 modifieddiff --git a/net/core/skbuff.c b/net/core/skbuff.c index c81ef99d39b04..88e0bf8004bf2 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -5824,6 +5824,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; -- cgit 1.3-korg
760e1addc27bnet: skbuff: preserve shared-frag marker during coalescing
1 file changed · +2 −1
net/core/skbuff.c+2 −1 modifieddiff --git a/net/core/skbuff.c b/net/core/skbuff.c index a753d01b587b9..00d60588fb09e 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -6066,6 +6066,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; -- cgit 1.3-korg
9d3e5fd19fe1net: skbuff: preserve shared-frag marker during coalescing
1 file changed · +2 −1
net/core/skbuff.c+2 −1 modifieddiff --git a/net/core/skbuff.c b/net/core/skbuff.c index ef24911af05a8..fd743051c8987 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -5512,6 +5512,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; -- cgit 1.3-korg
2f2b16022a2enet: skbuff: preserve shared-frag marker during coalescing
1 file changed · +2 −1
net/core/skbuff.c+2 −1 modifieddiff --git a/net/core/skbuff.c b/net/core/skbuff.c index d4c821d97b545..aadb87aa5e7eb 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -5512,6 +5512,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; -- cgit 1.3-korg
3599e6b3cc1anet: skbuff: preserve shared-frag marker during coalescing
1 file changed · +2 −1
net/core/skbuff.c+2 −1 modifieddiff --git a/net/core/skbuff.c b/net/core/skbuff.c index 297a2efd6322d..c195107434b85 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -5315,6 +5315,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->tx_flags |= from_shinfo->tx_flags & SKBTX_SHARED_FRAG; if (!skb_cloned(from)) from_shinfo->nr_frags = 0; -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"Frag-transfer helpers (skb_try_coalesce, __pskb_copy_fclone, skb_shift, GRO merge, tcp_clone_payload) fail to propagate the SKBFL_SHARED_FRAG flag from source to destination skb, breaking the invariant that skb_has_shared_frag() accurately reflects shared-page references."
Attack vector
An attacker can trigger the bug by causing TCP receive coalescing (via `skb_try_coalesce()`) or any other frag-transfer helper to move page-cache-backed or externally-owned frags from one skb to another without the `SKBFL_SHARED_FRAG` marker being set on the destination. This can be achieved with a single nftables 'dup to <local>' rule, or any `nf_dup_ipv4()` / `xt_TEE` caller, which lands a `pskb_copy()`'d skb in ESP input (`esp4.c`/`esp6.c`). Because `skb_has_shared_frag()` returns false on the unmarked skb, ESP decrypts in place over shared pages, letting an unprivileged user write into the page cache of a root-owned read-only file via authencesn-ESN stray writes [patch_id=1742198][patch_id=1805398].
Affected code
The vulnerability spans multiple frag-transfer paths in `net/core/skbuff.c`: `skb_try_coalesce()`, `__pskb_copy_fclone()`, `skb_shift()`, and the GRO merge helpers in `net/core/gro.c` (and `net/ipv4/udp_offload.c` on some branches). The `tcp_clone_payload()` function in `net/ipv4/tcp_output.c` is also affected. All of these functions move paged-frag descriptors between skbs without propagating the `SKBFL_SHARED_FRAG` (or `SKBTX_SHARED_FRAG` on older kernels) flag from the source to the destination skb.
What the fix does
The patches add a single line after every frag-transfer point that ORs the source's `SKBFL_SHARED_FRAG` (or `SKBTX_SHARED_FRAG`) bit into the destination's `skb_shinfo()->flags` (or `tx_flags`). In `skb_try_coalesce()` the condition `if (from_shinfo->nr_frags)` guards the propagation because the tailroom-copy path copies bytes into linear data rather than transferring frag descriptors [patch_id=1742198]. In `__pskb_copy_fclone()`, `skb_shift()`, the GRO merge helpers, and `tcp_clone_payload()`, the flag is unconditionally ORed after frags are moved [patch_id=1805398]. This restores the invariant that `skb_has_shared_frag()` accurately reflects whether the skb holds references to shared pages, preventing in-place writers like ESP from incorrectly skipping `skb_cow_data()`.
Preconditions
- inputThe attacker must be able to trigger TCP receive coalescing or other frag-transfer operations (e.g., via nftables 'dup to ', nf_dup_ipv4(), or xt_TEE) that move shared page-cache-backed frags between skbs.
- configThe system must be using IPsec ESP with authencesn-ESN (or any in-place decrypt path that checks skb_has_shared_frag()).
- networkThe attacker must be able to send network traffic that results in an skb with SKBFL_SHARED_FRAG set being coalesced or copied without the marker being propagated.
Generated on May 23, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- git.kernel.org/stable/c/2f2b16022a2e10ca7bccfb98db5ed2ec0f72641cmitre
- git.kernel.org/stable/c/3599e6b3cc1ada96883d496a50a210d3afbb6987mitre
- git.kernel.org/stable/c/3884358a9286b17f389a72b1426fc4547c23c111mitre
- git.kernel.org/stable/c/3bd9e113d50034db99d7ef69fd8e5242d15e414amitre
- git.kernel.org/stable/c/760e1addc27ba1a7beb4a0a7e8b3e9ec49e7a34emitre
- git.kernel.org/stable/c/78bf6b6bb19541d19fbda6242e7cfe2c682763c0mitre
- git.kernel.org/stable/c/9d3e5fd19fe1063bf607219e8562fbd567b8e8d5mitre
News mentions
0No linked articles in our index yet.