Netdev List
 help / color / mirror / Atom feed
* [PATCH net v4 1/2] net: core: propagate unreadable flag in skb_zerocopy
@ 2026-08-14 19:13 Mina Almasry
  2026-08-14 19:13 ` [PATCH net v4 2/2] net: tcp: block mixing readable and unreadable frags Mina Almasry
  2026-08-18 10:25 ` [PATCH net v4 1/2] net: core: propagate unreadable flag in skb_zerocopy Paolo Abeni
  0 siblings, 2 replies; 6+ messages in thread
From: Mina Almasry @ 2026-08-14 19:13 UTC (permalink / raw)
  To: Jakub Kicinski, Kaiyuan Zhang, Willem de Bruijn, Eric Dumazet,
	Mina Almasry, Paolo Abeni, Stanislav Fomichev, netdev,
	linux-kernel, dev
  Cc: David S. Miller, Simon Horman, Neal Cardwell, Kuniyuki Iwashima,
	Aaron Conole, Eelco Chaudron, Ilya Maximets, Jason Xing,
	Pavel Begunkov, Bobby Eshleman, Florian Westphal

skb_zerocopy() fails to propagate the unreadable flag when copying
unreadable fragments, causing target skbs to appear as readable memory.

This patch fixes the flag propagation. Additionally, it returns -EFAULT
if readable fragments are mixed with unreadable fragments during
extraction, and returns -EFAULT in openvswitch queue_userspace_packet().

Fixes: 65249feb6b3d ("net: add support for skbs with unreadable frags")
Cc: Pavel Begunkov <asml.silence@gmail.com>
Cc: Stanislav Fomichev <sdf@fomichev.me>
Cc: Bobby Eshleman <bobbyeshleman@gmail.com>
Cc: Florian Westphal <fw@strlen.de>
Cc: Aaron Conole <aconole@redhat.com>
Cc: Eelco Chaudron <echaudro@redhat.com>
Cc: Ilya Maximets <i.maximets@ovn.org>
Cc: Willem de Bruijn <willemb@google.com>
Signed-off-by: Mina Almasry <almasrymina@google.com>
Reviewed-by: Pavel Begunkov <asml.silence@gmail.com>
Reviewed-by: Ilya Maximets <i.maximets@ovn.org>
---
v4:
- Changed commit references from payload types to readable/unreadable
  fragments.
---
 net/core/skbuff.c          | 13 ++++++++++++-
 net/openvswitch/datapath.c |  3 +++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index ba3dbac80fb49..d21af68156950 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -3870,7 +3870,8 @@ EXPORT_SYMBOL_GPL(skb_zerocopy_headlen);
  *	Return value:
  *	0: everything is OK
  *	-ENOMEM: couldn't orphan frags of @from due to lack of memory
- *	-EFAULT: skb_copy_bits() found some problem with skb geometry
+ *	-EFAULT: skb_copy_bits() found some problem with skb geometry, or readable head
+ *      payload would be mixed with unreadable frags.
  */
 int
 skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen)
@@ -3905,10 +3906,17 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen)
 		}
 	}
 
+	if (!skb_frags_readable(from) && j > 0 && len) {
+		put_page(virt_to_head_page(from->head));
+		return -EFAULT;
+	}
+
 	skb_len_add(to, len + plen);
 
 	if (unlikely(skb_orphan_frags(from, GFP_ATOMIC))) {
 		skb_tx_error(from);
+		if (j > 0)
+			put_page(virt_to_head_page(from->head));
 		return -ENOMEM;
 	}
 	skb_zerocopy_clone(to, from, GFP_ATOMIC);
@@ -3928,6 +3936,9 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen)
 	}
 	skb_shinfo(to)->nr_frags = j;
 
+	if (i > 0 && from->unreadable)
+		to->unreadable = 1;
+
 	return 0;
 }
 EXPORT_SYMBOL_GPL(skb_zerocopy);
diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c
index ae69b2cabab9e..482893a5f67dc 100644
--- a/net/openvswitch/datapath.c
+++ b/net/openvswitch/datapath.c
@@ -467,6 +467,9 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
 	if (!dp_ifindex)
 		return -ENODEV;
 
+	if (!skb_frags_readable(skb))
+		return -EFAULT;
+
 	if (skb_vlan_tag_present(skb)) {
 		nskb = skb_clone(skb, GFP_ATOMIC);
 		if (!nskb)

base-commit: 24ef02f934eeb48830cff6b739abc3c62b1d107b
-- 
2.55.0.691.gc56d675ccc-goog


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH net v4 2/2] net: tcp: block mixing readable and unreadable frags
  2026-08-14 19:13 [PATCH net v4 1/2] net: core: propagate unreadable flag in skb_zerocopy Mina Almasry
@ 2026-08-14 19:13 ` Mina Almasry
  2026-08-18 10:26   ` Paolo Abeni
  2026-08-18 10:25 ` [PATCH net v4 1/2] net: core: propagate unreadable flag in skb_zerocopy Paolo Abeni
  1 sibling, 1 reply; 6+ messages in thread
From: Mina Almasry @ 2026-08-14 19:13 UTC (permalink / raw)
  To: Jakub Kicinski, Kaiyuan Zhang, Willem de Bruijn, Eric Dumazet,
	Mina Almasry, Paolo Abeni, Stanislav Fomichev, netdev,
	linux-kernel, dev
  Cc: David S. Miller, Simon Horman, Neal Cardwell, Kuniyuki Iwashima,
	Aaron Conole, Eelco Chaudron, Ilya Maximets, Jason Xing,
	Pavel Begunkov, Bobby Eshleman, Florian Westphal

Protect tcp_sendmsg_locked() from mistakenly mixing readable and
unreadable page fragments in the same SKB.

Check that the devmem binding matches the existing SKB's readability.
If a mismatch is detected, avoid collapsing and create a new segment.

Fixes: bd61848900bff ("net: devmem: Implement TX path")
Suggested-by: Eric Dumazet <edumazet@google.com>
Cc: Pavel Begunkov <asml.silence@gmail.com>
Cc: Stanislav Fomichev <sdf@fomichev.me>
Cc: Bobby Eshleman <bobbyeshleman@gmail.com>
Signed-off-by: Mina Almasry <almasrymina@google.com>
---
v4:
- Adopt Eric's suggestion to check 'binding' against SKB readability
  alongside tcp_skb_can_collapse_to().
- Drop pre-existing Reviewed-by tags due to significant implementation changes.
v3: https://lore.kernel.org/r/20260811195405.3979177-2-almasrymina@google.com
v2: https://lore.kernel.org/r/20260810180956.2348280-2-almasrymina@google.com
v1: https://lore.kernel.org/r/20260801125308.1342897-3-almasrymina@google.com
---
 net/ipv4/tcp.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index 455441f1b6949..b4237d0e994d6 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -1240,7 +1240,8 @@ int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
 
 		trace_tcp_sendmsg_locked(sk, msg, skb, size_goal);
 
-		if (copy <= 0 || !tcp_skb_can_collapse_to(skb)) {
+		if (copy <= 0 || !tcp_skb_can_collapse_to(skb) ||
+		    unlikely(skb_frags_readable(skb) != !binding)) {
 			bool first_skb;
 
 new_segment:
-- 
2.55.0.691.gc56d675ccc-goog


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH net v4 1/2] net: core: propagate unreadable flag in skb_zerocopy
  2026-08-14 19:13 [PATCH net v4 1/2] net: core: propagate unreadable flag in skb_zerocopy Mina Almasry
  2026-08-14 19:13 ` [PATCH net v4 2/2] net: tcp: block mixing readable and unreadable frags Mina Almasry
@ 2026-08-18 10:25 ` Paolo Abeni
  2026-08-18 18:36   ` Mina Almasry
  1 sibling, 1 reply; 6+ messages in thread
From: Paolo Abeni @ 2026-08-18 10:25 UTC (permalink / raw)
  To: almasrymina
  Cc: kuba, kaiyuanz, willemb, edumazet, pabeni, sdf, netdev,
	linux-kernel, dev, davem, horms, ncardwell, kuniyu, aconole,
	echaudro, i.maximets, kerneljasonxing, asml.silence,
	bobbyeshleman, fw

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/
---
net: core: propagate unreadable flag in skb_zerocopy

The patch makes skb_zerocopy() propagate the unreadable flag to the
destination skb, returns -EFAULT when a readable head payload would be
mixed with unreadable frags, and rejects unreadable skbs in openvswitch
queue_userspace_packet().

> diff --git a/net/core/skbuff.c b/net/core/skbuff.c
> index ba3dbac80fb49..d21af68156950 100644
> --- a/net/core/skbuff.c
> +++ b/net/core/skbuff.c

[ ... ]

> @@ -3905,10 +3906,17 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen)
>  		}
>  	}
>  
> +	if (!skb_frags_readable(from) && j > 0 && len) {
> +		put_page(virt_to_head_page(from->head));
> +		return -EFAULT;
> +	}
> +
>  	skb_len_add(to, len + plen);
>  
>  	if (unlikely(skb_orphan_frags(from, GFP_ATOMIC))) {
>  		skb_tx_error(from);
> +		if (j > 0)
> +			put_page(virt_to_head_page(from->head));
>  		return -ENOMEM;
>  	}

[Severity: Medium]
On this -ENOMEM path, is it safe to re-derive the head page from
from->head after skb_orphan_frags() has already run?

skb_orphan_frags() -> skb_copy_ubufs() drops the clone first:

net/core/skbuff.c:skb_copy_ubufs() {
	...
	if (skb_shared(skb) || skb_unclone(skb, gfp_mask))
		return -EINVAL;
	...
		page = alloc_pages(gfp_mask | __GFP_COMP, order);
		if (!page) {
			...
			return -ENOMEM;
		}
}

and skb_unclone() -> pskb_expand_head() replaces the buffer:

net/core/skbuff.c:pskb_expand_head() {
	...
	skb->head     = data;
	skb->head_frag = 0;
	...
}

So when the -ENOMEM comes from the alloc_pages() loop, from->head can
already point at a different (freshly allocated) buffer than the page that
was get_page()'d earlier in the hlen == 0 branch:

	page = virt_to_head_page(from->head);
	offset = from->data - (unsigned char *)page_address(page);
	__skb_fill_netmem_desc(to, 0, page_to_netmem(page), offset, plen);
	get_page(page);
	j = 1;

Does that leak the reference on the original head-fragment page, and apply
the put_page() to an unrelated buffer? Today the misdirected put_page() is
quiet because pskb_expand_head() installs slab/kmalloc memory and put_page()
early-returns for folio_test_slab()/folio_test_large_kmalloc() folios, but
the reference on the page that was actually taken is never returned.

Would it be more robust to keep the page pointer captured before get_page()
in a local and use put_page(page) in both new error paths?

I was not able to find an in-tree producer that combines head_frag == 1 with
skb_zcopy() set, so this path may not be reachable today.

[Severity: Low]
This hunk looks like it also fixes an older, unrelated head-page reference
leak. __skb_fill_netmem_desc() does not bump skb_shinfo(to)->nr_frags:

include/linux/skbuff.h:__skb_fill_netmem_desc() {
	__skb_fill_netmem_desc_noacc(skb_shinfo(skb), i, netmem, off, size);
	...
}

and skb_zerocopy() only sets it at the very end with
skb_shinfo(to)->nr_frags = j;. So before this patch, returning -ENOMEM here
left @to with nr_frags == 0 and the extra head-page reference was never
dropped when the caller freed it (consume_skb(user_skb) in
queue_userspace_packet(), kfree_skb() in nfqnl_build_packet_message()).

That leak dates back to 36d5fe6a0007 ("core, nfqueue, openvswitch: Orphan
frags in skb_zerocopy and handle errors") and is unrelated to unreadable
frags, while the changelog only mentions unreadable-flag propagation and the
new -EFAULT returns, and the Fixes: tag points at 65249feb6b3d.

Could this hunk be split into its own patch with Fixes: 36d5fe6a0007, or at
least described in the commit message, so that stable backports reach the
pre-6.12 kernels that also contain the leak?

> +	if (i > 0 && from->unreadable)
> +		to->unreadable = 1;
> +

[ ... ]
-- 
This is an AI-generated review.


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH net v4 2/2] net: tcp: block mixing readable and unreadable frags
  2026-08-14 19:13 ` [PATCH net v4 2/2] net: tcp: block mixing readable and unreadable frags Mina Almasry
@ 2026-08-18 10:26   ` Paolo Abeni
  2026-08-18 18:37     ` Mina Almasry
  0 siblings, 1 reply; 6+ messages in thread
From: Paolo Abeni @ 2026-08-18 10:26 UTC (permalink / raw)
  To: almasrymina
  Cc: kuba, kaiyuanz, willemb, edumazet, pabeni, sdf, netdev,
	linux-kernel, dev, davem, horms, ncardwell, kuniyu, aconole,
	echaudro, i.maximets, kerneljasonxing, asml.silence,
	bobbyeshleman, fw

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/
---
net: tcp: block mixing readable and unreadable frags

This adds a check in tcp_sendmsg_locked() so an existing tail SKB is not
collapsed into when its readability does not match the presence of a
devmem binding, forcing a new segment instead.

> diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
> index 455441f1b6949..b4237d0e994d6 100644
> --- a/net/ipv4/tcp.c
> +++ b/net/ipv4/tcp.c
> @@ -1240,7 +1240,8 @@ int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
>  
>  		trace_tcp_sendmsg_locked(sk, msg, skb, size_goal);
>  
> -		if (copy <= 0 || !tcp_skb_can_collapse_to(skb)) {
> +		if (copy <= 0 || !tcp_skb_can_collapse_to(skb) ||
> +		    unlikely(skb_frags_readable(skb) != !binding)) {
>  			bool first_skb;
>  
>  new_segment:

[Severity: Medium]
Is binding alone the right proxy here for "this send will append
unreadable frags"?

What actually selects the devmem filler is the zc dispatch in the loop
plus __zerocopy_sg_from_iter():

net/core/datagram.c:__zerocopy_sg_from_iter() {
	...
	else if (binding)
		ret = zerocopy_fill_skb_from_devmem(skb, from, length, binding);
	else
		ret = zerocopy_fill_skb_from_iter(skb, from, length);
}

and that is only reached from the zc == MSG_ZEROCOPY arm of
tcp_sendmsg_locked().

Can binding != NULL coexist with zc == 0?  In tcp_sendmsg_locked() the
binding is taken whenever the cmsg carries dmabuf_id:

		if (sk->sk_route_caps & NETIF_F_SG)
			zc = MSG_ZEROCOPY;
		else
			uarg_to_msgzc(uarg)->zerocopy = 0;

		if (!sockc_err && sockc.dmabuf_id) {
			binding = net_devmem_get_binding(sk, sockc.dmabuf_id);

while zc is only set under NETIF_F_SG.  The one dmabuf_id validation
does not look at SG at all:

	if (!sockc_err && sockc.dmabuf_id &&
	    (!(flags & MSG_ZEROCOPY) || !sock_flag(sk, SOCK_ZEROCOPY))) {
		err = -EINVAL;

And sk_setup_caps() in net/core/sock.c only force-adds NETIF_F_SG when
sk_can_gso() holds:

	if (unlikely(sk->sk_gso_disabled))
		sk->sk_route_caps &= ~NETIF_F_GSO_MASK;
	if (sk_can_gso(sk)) {
		if (dst->header_len && !xfrm_dst_offload_ok(dst)) {
			sk->sk_route_caps &= ~NETIF_F_GSO_MASK;
		} else {
			sk->sk_route_caps |= NETIF_F_SG | NETIF_F_HW_CSUM;

so a TCP-MD5/TCP-AO socket (sk_gso_disable()) or an IPsec dst on a device
with sg turned off ends up with binding != NULL and zc == 0.

In that state, does the new predicate go wrong in both directions?

If the tail is readable or newly allocated, skb_frags_readable(skb) !=
!binding evaluates to true != false, so every iteration takes
goto new_segment and the write is split into one skb per iteration, even
though the zc == 0 path only appends readable page frags via
skb_fill_page_desc().

If the tail is already unreadable from an earlier devmem send, the test
is false != false, collapsing is allowed, and skb_fill_page_desc() adds a
readable page frag to an skb that __skb_fill_netmem_desc() already marked
unreadable = 1 — the mixing this patch aims to prevent.  Would
validate_xmit_unreadable_skb() catch that?  It only looks at the first
frag:

net/core/dev.c:validate_xmit_unreadable_skb() {
	if (shinfo->nr_frags > 0) {
		niov = netmem_to_net_iov(skb_frag_netmem(&shinfo->frags[0]));

Would keying the new clause on binding && zc == MSG_ZEROCOPY, or
rejecting dmabuf_id when the route cannot do SG, line the predicate up
with the code that actually fills the frags?

Note the mixing described in the second direction pre-dates this patch
(there was no check at all before); the incorrect predicate and the
forced re-segmentation are new.  Reaching this also needs a local
privileged setup (dmabuf TX binding plus MD5/AO or IPsec plus an ethtool
feature change), and in that configuration a devmem send through the copy
path would likely already fail in skb_copy_to_page_nocache() since the
iovec holds dmabuf offsets rather than user addresses, so this is stated
as an observation rather than a demonstrated corruption path.
-- 
This is an AI-generated review.


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH net v4 1/2] net: core: propagate unreadable flag in skb_zerocopy
  2026-08-18 10:25 ` [PATCH net v4 1/2] net: core: propagate unreadable flag in skb_zerocopy Paolo Abeni
@ 2026-08-18 18:36   ` Mina Almasry
  0 siblings, 0 replies; 6+ messages in thread
From: Mina Almasry @ 2026-08-18 18:36 UTC (permalink / raw)
  To: Paolo Abeni
  Cc: kuba, kaiyuanz, willemb, edumazet, sdf, netdev, linux-kernel, dev,
	davem, horms, ncardwell, kuniyu, aconole, echaudro, i.maximets,
	kerneljasonxing, asml.silence, bobbyeshleman, fw

On Tue, Aug 18, 2026 at 3:26 AM Paolo Abeni <pabeni@redhat.com> wrote:
>
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> Full review at: https://netdev-ai.bots.linux.dev/sashiko/

Thanks Paolo. Responding to the specific feedback inline, but in
general, clashiko is pointing out pre-existing issues. They should be
fixed absolutely but I'd prefer to submit separate fixes for them. I'm
a bit worried about going down this rabbit hole forever. Let me know
what you think.

> ---
> net: core: propagate unreadable flag in skb_zerocopy
>
> The patch makes skb_zerocopy() propagate the unreadable flag to the
> destination skb, returns -EFAULT when a readable head payload would be
> mixed with unreadable frags, and rejects unreadable skbs in openvswitch
> queue_userspace_packet().
>
> > diff --git a/net/core/skbuff.c b/net/core/skbuff.c
> > index ba3dbac80fb49..d21af68156950 100644
> > --- a/net/core/skbuff.c
> > +++ b/net/core/skbuff.c
>
> [ ... ]
>
> > @@ -3905,10 +3906,17 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen)
> >               }
> >       }
> >
> > +     if (!skb_frags_readable(from) && j > 0 && len) {
> > +             put_page(virt_to_head_page(from->head));
> > +             return -EFAULT;
> > +     }
> > +
> >       skb_len_add(to, len + plen);
> >
> >       if (unlikely(skb_orphan_frags(from, GFP_ATOMIC))) {
> >               skb_tx_error(from);
> > +             if (j > 0)
> > +                     put_page(virt_to_head_page(from->head));
> >               return -ENOMEM;
> >       }
>
> [Severity: Medium]
> On this -ENOMEM path, is it safe to re-derive the head page from
> from->head after skb_orphan_frags() has already run?
>

Probably not. I think this is a pre-existing issue in skb_orphan_frags
though. I think skb_orphan_frags should check !skb_frags_readable()
before doing anything so that it doens't modify the skb and then
return -EFAULT on a modified skb.

> skb_orphan_frags() -> skb_copy_ubufs() drops the clone first:
>
> net/core/skbuff.c:skb_copy_ubufs() {
>         ...
>         if (skb_shared(skb) || skb_unclone(skb, gfp_mask))
>                 return -EINVAL;
>         ...
>                 page = alloc_pages(gfp_mask | __GFP_COMP, order);
>                 if (!page) {
>                         ...
>                         return -ENOMEM;
>                 }
> }
>
> and skb_unclone() -> pskb_expand_head() replaces the buffer:
>
> net/core/skbuff.c:pskb_expand_head() {
>         ...
>         skb->head     = data;
>         skb->head_frag = 0;
>         ...
> }
>
> So when the -ENOMEM comes from the alloc_pages() loop, from->head can
> already point at a different (freshly allocated) buffer than the page that
> was get_page()'d earlier in the hlen == 0 branch:
>
>         page = virt_to_head_page(from->head);
>         offset = from->data - (unsigned char *)page_address(page);
>         __skb_fill_netmem_desc(to, 0, page_to_netmem(page), offset, plen);
>         get_page(page);
>         j = 1;
>
> Does that leak the reference on the original head-fragment page, and apply
> the put_page() to an unrelated buffer? Today the misdirected put_page() is
> quiet because pskb_expand_head() installs slab/kmalloc memory and put_page()
> early-returns for folio_test_slab()/folio_test_large_kmalloc() folios, but
> the reference on the page that was actually taken is never returned.
>
> Would it be more robust to keep the page pointer captured before get_page()
> in a local and use put_page(page) in both new error paths?
>
> I was not able to find an in-tree producer that combines head_frag == 1 with
> skb_zcopy() set, so this path may not be reachable today.
>
> [Severity: Low]
> This hunk looks like it also fixes an older, unrelated head-page reference
> leak. __skb_fill_netmem_desc() does not bump skb_shinfo(to)->nr_frags:
>
> include/linux/skbuff.h:__skb_fill_netmem_desc() {
>         __skb_fill_netmem_desc_noacc(skb_shinfo(skb), i, netmem, off, size);
>         ...
> }
>
> and skb_zerocopy() only sets it at the very end with
> skb_shinfo(to)->nr_frags = j;. So before this patch, returning -ENOMEM here
> left @to with nr_frags == 0 and the extra head-page reference was never
> dropped when the caller freed it (consume_skb(user_skb) in
> queue_userspace_packet(), kfree_skb() in nfqnl_build_packet_message()).
>
> That leak dates back to 36d5fe6a0007 ("core, nfqueue, openvswitch: Orphan
> frags in skb_zerocopy and handle errors") and is unrelated to unreadable
> frags, while the changelog only mentions unreadable-flag propagation and the
> new -EFAULT returns, and the Fixes: tag points at 65249feb6b3d.
>
> Could this hunk be split into its own patch with Fixes: 36d5fe6a0007, or at
> least described in the commit message, so that stable backports reach the
> pre-6.12 kernels that also contain the leak?
>

Right, I added this hunk because sashkio reported this pre-existing
issue in a previous iteration. I could fork it into its own fix, but
for the follow up pre-existing issues it's reporting now, it maybe
makes sense to also fix those in follow up fixes.

--
Thanks,
Mina

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH net v4 2/2] net: tcp: block mixing readable and unreadable frags
  2026-08-18 10:26   ` Paolo Abeni
@ 2026-08-18 18:37     ` Mina Almasry
  0 siblings, 0 replies; 6+ messages in thread
From: Mina Almasry @ 2026-08-18 18:37 UTC (permalink / raw)
  To: Paolo Abeni
  Cc: kuba, kaiyuanz, willemb, edumazet, sdf, netdev, linux-kernel, dev,
	davem, horms, ncardwell, kuniyu, aconole, echaudro, i.maximets,
	kerneljasonxing, asml.silence, bobbyeshleman, fw

On Tue, Aug 18, 2026 at 3:26 AM Paolo Abeni <pabeni@redhat.com> wrote:
>
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> Full review at: https://netdev-ai.bots.linux.dev/sashiko/
> ---
> net: tcp: block mixing readable and unreadable frags
>
> This adds a check in tcp_sendmsg_locked() so an existing tail SKB is not
> collapsed into when its readability does not match the presence of a
> devmem binding, forcing a new segment instead.
>
> > diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
> > index 455441f1b6949..b4237d0e994d6 100644
> > --- a/net/ipv4/tcp.c
> > +++ b/net/ipv4/tcp.c
> > @@ -1240,7 +1240,8 @@ int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
> >
> >               trace_tcp_sendmsg_locked(sk, msg, skb, size_goal);
> >
> > -             if (copy <= 0 || !tcp_skb_can_collapse_to(skb)) {
> > +             if (copy <= 0 || !tcp_skb_can_collapse_to(skb) ||
> > +                 unlikely(skb_frags_readable(skb) != !binding)) {
> >                       bool first_skb;
> >
> >  new_segment:
>
> [Severity: Medium]
> Is binding alone the right proxy here for "this send will append
> unreadable frags"?
>
> What actually selects the devmem filler is the zc dispatch in the loop
> plus __zerocopy_sg_from_iter():
>
> net/core/datagram.c:__zerocopy_sg_from_iter() {
>         ...
>         else if (binding)
>                 ret = zerocopy_fill_skb_from_devmem(skb, from, length, binding);
>         else
>                 ret = zerocopy_fill_skb_from_iter(skb, from, length);
> }
>
> and that is only reached from the zc == MSG_ZEROCOPY arm of
> tcp_sendmsg_locked().
>
> Can binding != NULL coexist with zc == 0?  In tcp_sendmsg_locked() the
> binding is taken whenever the cmsg carries dmabuf_id:
>
>                 if (sk->sk_route_caps & NETIF_F_SG)
>                         zc = MSG_ZEROCOPY;
>                 else
>                         uarg_to_msgzc(uarg)->zerocopy = 0;
>
>                 if (!sockc_err && sockc.dmabuf_id) {
>                         binding = net_devmem_get_binding(sk, sockc.dmabuf_id);
>
> while zc is only set under NETIF_F_SG.  The one dmabuf_id validation
> does not look at SG at all:
>
>         if (!sockc_err && sockc.dmabuf_id &&
>             (!(flags & MSG_ZEROCOPY) || !sock_flag(sk, SOCK_ZEROCOPY))) {
>                 err = -EINVAL;
>
> And sk_setup_caps() in net/core/sock.c only force-adds NETIF_F_SG when
> sk_can_gso() holds:
>
>         if (unlikely(sk->sk_gso_disabled))
>                 sk->sk_route_caps &= ~NETIF_F_GSO_MASK;
>         if (sk_can_gso(sk)) {
>                 if (dst->header_len && !xfrm_dst_offload_ok(dst)) {
>                         sk->sk_route_caps &= ~NETIF_F_GSO_MASK;
>                 } else {
>                         sk->sk_route_caps |= NETIF_F_SG | NETIF_F_HW_CSUM;
>
> so a TCP-MD5/TCP-AO socket (sk_gso_disable()) or an IPsec dst on a device
> with sg turned off ends up with binding != NULL and zc == 0.
>
> In that state, does the new predicate go wrong in both directions?
>
> If the tail is readable or newly allocated, skb_frags_readable(skb) !=
> !binding evaluates to true != false, so every iteration takes
> goto new_segment and the write is split into one skb per iteration, even
> though the zc == 0 path only appends readable page frags via
> skb_fill_page_desc().
>
> If the tail is already unreadable from an earlier devmem send, the test
> is false != false, collapsing is allowed, and skb_fill_page_desc() adds a
> readable page frag to an skb that __skb_fill_netmem_desc() already marked
> unreadable = 1 — the mixing this patch aims to prevent.  Would
> validate_xmit_unreadable_skb() catch that?  It only looks at the first
> frag:
>
> net/core/dev.c:validate_xmit_unreadable_skb() {
>         if (shinfo->nr_frags > 0) {
>                 niov = netmem_to_net_iov(skb_frag_netmem(&shinfo->frags[0]));
>
> Would keying the new clause on binding && zc == MSG_ZEROCOPY, or
> rejecting dmabuf_id when the route cannot do SG, line the predicate up
> with the code that actually fills the frags?
>
> Note the mixing described in the second direction pre-dates this patch
> (there was no check at all before); the incorrect predicate and the
> forced re-segmentation are new.  Reaching this also needs a local
> privileged setup (dmabuf TX binding plus MD5/AO or IPsec plus an ethtool
> feature change), and in that configuration a devmem send through the copy
> path would likely already fail in skb_copy_to_page_nocache() since the
> iovec holds dmabuf offsets rather than user addresses, so this is stated
> as an observation rather than a demonstrated corruption path.

Pre-existing issue I think. We indeed assume binding != NULL means the
send is devmem and zc is set. We missed that we may not set zc if
NETIF_F_SG is not enabled. Probably the code setting binding to
non-NULL should check if zc is set. I think this is best reserved for
its own follow up fix, but let me know if not Paolo.

--
Thanks,
Mina

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-08-18 18:37 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-14 19:13 [PATCH net v4 1/2] net: core: propagate unreadable flag in skb_zerocopy Mina Almasry
2026-08-14 19:13 ` [PATCH net v4 2/2] net: tcp: block mixing readable and unreadable frags Mina Almasry
2026-08-18 10:26   ` Paolo Abeni
2026-08-18 18:37     ` Mina Almasry
2026-08-18 10:25 ` [PATCH net v4 1/2] net: core: propagate unreadable flag in skb_zerocopy Paolo Abeni
2026-08-18 18:36   ` Mina Almasry

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox