VYPR
Unrated severityNVD Advisory· Published May 23, 2026

net: skbuff: preserve shared-frag marker during coalescing

CVE-2026-43503

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

1

Patches

14
fc6eb39c55e9

net: skbuff: propagate shared-frag marker through frag-transfer helpers

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitHyunwoo KimMay 15, 2026Fixed in 6.12.91via kernel-cna
3 files changed · +13 2
  • net/core/gro.c+4 0 modified
    diff --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 modified
    diff --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 modified
    diff --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
    
    
    
9bc9d6d6967a

net: skbuff: propagate shared-frag marker through frag-transfer helpers

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitHyunwoo KimMay 15, 2026Fixed in 7.0.10via kernel-cna
3 files changed · +13 2
  • net/core/gro.c+4 0 modified
    diff --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 modified
    diff --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 modified
    diff --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
    
    
    
ff375cc75f91

net: skbuff: propagate shared-frag marker through frag-transfer helpers

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitHyunwoo KimMay 15, 2026Fixed in 6.18.33via kernel-cna
3 files changed · +13 2
  • net/core/gro.c+4 0 modified
    diff --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 modified
    diff --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 modified
    diff --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
    
    
    
179f1852bded

net: skbuff: propagate shared-frag marker through frag-transfer helpers

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitHyunwoo KimMay 15, 2026Fixed in 5.15.208via kernel-cna
1 file changed · +12 2
  • net/core/skbuff.c+12 2 modified
    diff --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
    
    
    
989214c66884

net: skbuff: propagate shared-frag marker through frag-transfer helpers

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitHyunwoo KimMay 15, 2026Fixed in 6.6.141via kernel-cna
4 files changed · +13 2
  • net/core/gro.c+2 0 modified
    diff --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 modified
    diff --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 modified
    diff --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 modified
    diff --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
    
    
    
12401fcfb01f

net: skbuff: propagate shared-frag marker through frag-transfer helpers

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitHyunwoo KimMay 15, 2026Fixed in 6.1.174via kernel-cna
3 files changed · +12 2
  • net/core/gro.c+2 0 modified
    diff --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 modified
    diff --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 modified
    diff --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
    
    
    
fbeab9555564

net: skbuff: propagate shared-frag marker through frag-transfer helpers

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitHyunwoo KimMay 15, 2026Fixed in 5.10.257via kernel-cna
1 file changed · +12 2
  • net/core/skbuff.c+12 2 modified
    diff --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
    
    
    
3884358a9286

net: skbuff: preserve shared-frag marker during coalescing

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitWilliam BowlingMay 13, 2026Fixed in 7.0.10via kernel-cna
1 file changed · +2 1
  • net/core/skbuff.c+2 1 modified
    diff --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
    
    
    
3bd9e113d500

net: skbuff: preserve shared-frag marker during coalescing

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitWilliam BowlingMay 13, 2026Fixed in 6.18.33via kernel-cna
1 file changed · +2 1
  • net/core/skbuff.c+2 1 modified
    diff --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
    
    
    
78bf6b6bb195

net: skbuff: preserve shared-frag marker during coalescing

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitWilliam BowlingMay 13, 2026Fixed in 6.6.141via kernel-cna
1 file changed · +2 1
  • net/core/skbuff.c+2 1 modified
    diff --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
    
    
    
760e1addc27b

net: skbuff: preserve shared-frag marker during coalescing

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitWilliam BowlingMay 13, 2026Fixed in 6.12.91via kernel-cna
1 file changed · +2 1
  • net/core/skbuff.c+2 1 modified
    diff --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
    
    
    
9d3e5fd19fe1

net: skbuff: preserve shared-frag marker during coalescing

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitWilliam BowlingMay 13, 2026Fixed in 6.1.174via kernel-cna
1 file changed · +2 1
  • net/core/skbuff.c+2 1 modified
    diff --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
    
    
    
2f2b16022a2e

net: skbuff: preserve shared-frag marker during coalescing

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitWilliam BowlingMay 13, 2026Fixed in 5.15.208via kernel-cna
1 file changed · +2 1
  • net/core/skbuff.c+2 1 modified
    diff --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
    
    
    
3599e6b3cc1a

net: skbuff: preserve shared-frag marker during coalescing

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitWilliam BowlingMay 13, 2026Fixed in 5.10.257via kernel-cna
1 file changed · +2 1
  • net/core/skbuff.c+2 1 modified
    diff --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

News mentions

0

No linked articles in our index yet.