* [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal
@ 2026-11-03 2:28 Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 01/15] net: skbuff: make skb_copy_and_csum_bits() report unreadable frags Josef Bacik
` (15 more replies)
0 siblings, 16 replies; 24+ messages in thread
From: Josef Bacik @ 2026-11-03 2:28 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
v1: https://lore.kernel.org/all/20261019-b4-skbuff-bug-on-followups-v1-0-41dbef50b9f7@toxicpanda.com/
v2: https://lore.kernel.org/all/20261021-b4-skbuff-bug-on-followups-v2-0-2aa780a03524@toxicpanda.com/
v2->v3:
- 1, 2, 6: move the frag_list recursion into a noinline function, so
that taking the address of a local there doesn't give the helpers a
stack protector (Paolo). On x86_64 defconfig plus CONFIG_IP_SCTP
none of skb_copy_and_csum_bits(), skb_checksum_full(),
skb_crc32c_full() or the two wrappers has one now, and
__ip_append_data(), __ip6_append_data() and skb_segment() are
unchanged. The callers that pass a local do gain one. Besides the
seven I listed on v2, that's __skb_gro_checksum_complete() and
skb_checksum_help(), which are converted in this version, and
skb_crc32c_csum_help().
- 1: put the Fixes: tags back (Fernando, Sashiko). csump in the
prototype, and drop the "Both of above in one bottle" comment
(Fernando). skb_copy_and_csum_dev() zeroes the whole frame when the
copy fails, since leaving the checksum alone doesn't guarantee a bad
one (Sashiko).
- 2, 6: the kernel-doc describes the unreadable check as it is, the
wrappers don't blame unreadable frags for -EINVAL, and the commit
messages say what changes for a failing frag_list member (Fernando,
Sashiko).
- 2: convert skb_checksum_help() too, as 6 does for its crc32c twin, and
add Fixes: 20ddd0fd2fcd.
- 3: convert __skb_gro_checksum_complete() too (Fernando, Sashiko).
Take the skb off CHECKSUM_COMPLETE on failure. Otherwise conntrack,
which doesn't drop on a bad checksum, leaves the pseudo-header sum in
skb->csum and TCP's validation accepts the packet after all
(Sashiko). Update the comments, and add Fixes: 65249feb6b3d.
- 4: the commit message was wrong that devmem TX reaches this checksum.
Its skbs carry SKBFL_SHARED_FRAG, so the segment fails to linearize
first. The patch is hardening, and the commit message says so now.
- 5: when the tail can't be checksummed but what's left can be read,
set CHECKSUM_NONE instead of failing the trim, which dropped packets
that used to be accepted (Sashiko). Retitled, and add Fixes: tags.
- 7, 8: add the Fixes: tags for the original commits, and correct the
history in 8's commit message (Sashiko). 8 also says what it takes
to reach the memcpy().
- 7: reject a negative @len or @hlen on entry. The reorder left nothing
ahead of the skb_put(), which panics on one.
- 8: the truesize check refuses the coalesce instead of only warning,
and runs before the head is stolen. With len unsigned and nothing
else, an skb with a len above INT_MAX went from refused to coalesced.
- 9: check data_len > len directly and zero the frame. The old check
missed CHECKSUM_PARTIAL (Sashiko). Retitled.
- 10: drop both len > 0 tests, and correct what the commit message says
they covered (Sashiko).
- 9, 10: drop the Fixes: tags. Both problems are older than the commits
the tags named, and neither has a known trigger.
- 11: Willem, this one changed again after you OK'd the walk up front.
A nested frag_list is only one way for the walk to run out of members.
A len that counts bytes the skb doesn't hold gets to the same NULL
pointer dereference, and the walk up front doesn't catch that. So the
patch now checks for a NULL list_skb in the three places where the
walk moves on to it, and that's all it does: no pass up front, which
also takes care of the cost you pointed out for BIG TCP, and none of
v2's skb_has_frag_list() tests. You asked on v2 whether the first two
checks were needed. For a nested frag_list they weren't, for this
they are. 12 has a case for each, and taking any one of the three
checks out brings the oops back. The path without SG is left alone
again. The commit message says that IP reassembly does nest
frag_lists, just not on GSO skbs (Sashiko). Retitled.
- 12: add four cases for a len that counts bytes the skb doesn't hold.
Retitled.
- 13: correct what the commit message says about when bounce_skb() runs
and when skb_copy_bits() fails.
- 14: update the comment above skb_nsg() (Sashiko).
- 15: one more stale comment, in net_test.c.
- Picked up Fernando's Reviewed-by on 4. Dropped Mina's on 1 and
Fernando's on 2, since the frag_list loop moved in both, and Willem's
on 12, since it grew.
- Shortened the commit messages.
- Rebased onto current net-next.
v1->v2:
- skb_segment(): check each frag_list member as the walk takes it,
instead of an extra pass over the frag_list up front (Willem).
- KUnit test: braces around the suppressed skb_segment() call (Willem),
and the comment update for the non-uniform case moved into the same
patch (Mina).
- Picked up Willem's Reviewed-by and Mina's Acked-bys.
--- Original email (v1) ---
Hello,
Jakub applied v2 of my skbuff BUG_ON() series[1] before I got v3 out, so
this is what v3 would have changed, as fixes on top of what landed, plus
the skb_copy_and_csum_bits() patch I'd been sending to net on its own.
Patch 1 is that patch[2], moved to net-next as Mina suggested, with his
Reviewed-by. It makes skb_copy_and_csum_bits() report failure, and the
rest of the series builds on it. Please drop the net version.
Patches 2 through 6 deal with skb_checksum() and skb_crc32c(). My v2
had them return 0 when they run out of skb, and that turned out not to
be safe: 0 is a valid checksum, and it can make a packet that was never
fully checksummed pass verification. So they get versions that report
failure, skb_checksum_full() and skb_crc32c_full(), and the core users
switch to them: checksum validation now rejects a packet it can't
checksum, skb_segment() and pskb_trim_rcsum() fail instead of using a
bad sum, and skb_crc32c_csum_help() drops the packet. skb_checksum()
and skb_crc32c() stay as wrappers that behave as before, and I'll
convert the rest of their callers per subsystem in later series.
Patches 7 through 10 fix problems Sashiko and an LLM review found in
what was applied. skb_zerocopy() refuses a zero-length copy that Open
vSwitch can trigger from a user namespace, skb_try_coalesce() and
skb_copy_and_csum_bits() can be handed a negative length, and
skb_headlen() can underflow in skb_copy_and_csum_dev().
Patches 11 and 12 make skb_segment() refuse a nested frag_list, which
could still oops on a NULL list_skb, and add KUnit cases for the layouts
it refuses. Patches 13 and 14 get rid of the same BUG_ON()s in
xen-netfront's bounce_skb() and tls's __skb_nsg(), and patch 15 updates
comments that still describe the old BUG_ON()s.
I tested this with the net_core KUnit tests, a test module that drives
each of these paths under KASAN and fault injection, an Open vSwitch
reproducer for patch 7, and a stress run over veth with netem. Thanks,
Josef
[1] https://lore.kernel.org/all/20261007-b4-skbuff-bug-on-v2-0-b9a5f732895b@toxicpanda.com/
[2] https://lore.kernel.org/all/20261013-b4-skb-segment-nosg-unreadable-v2-1-6c28d8ffa51e@toxicpanda.com/
---
Changes in v3:
- Link to v2: https://patch.msgid.link/20261021-b4-skbuff-bug-on-followups-v2-0-2aa780a03524@toxicpanda.com
Changes in v2:
- Link to v1: https://patch.msgid.link/20261019-b4-skbuff-bug-on-followups-v1-0-41dbef50b9f7@toxicpanda.com
---
Josef Bacik (15):
net: skbuff: make skb_copy_and_csum_bits() report unreadable frags
net: skbuff: add skb_checksum_full() that reports failure
net: skbuff: fail checksum validation when the skb can't be checksummed
net: skbuff: fail segmentation when a segment can't be checksummed
net: skbuff: drop hw csum if trim can't checksum tail
net: skbuff: add skb_crc32c_full() that reports failure
net: skbuff: let skb_zerocopy() copy a short skb without head_frag
net: skbuff: make skb_try_coalesce()'s len unsigned
net: skbuff: reject data_len > len in copy_and_csum_dev
net: skbuff: reject a negative length in skb_copy_and_csum_bits()
net: skbuff: don't run off frag_list in skb_segment()
net: test the skb layouts skb_segment() refuses
xen-netfront: don't BUG() when skb_copy_bits() fails in bounce_skb()
net/tls: don't BUG() on leftover length in __skb_nsg()
net: update comments that still describe the skb BUG_ON()s
drivers/net/ethernet/sun/sunvnet_common.c | 10 +-
drivers/net/xen-netfront.c | 7 +-
include/linux/skbuff.h | 12 +-
net/core/dev.c | 14 +-
net/core/filter.c | 6 +-
net/core/gro.c | 11 +-
net/core/net_test.c | 100 ++++++-
net/core/skbuff.c | 447 +++++++++++++++++++++++-------
net/ipv4/icmp.c | 9 +-
net/ipv4/ip_output.c | 11 +-
net/ipv6/icmp.c | 7 +-
net/ipv6/ip6_output.c | 11 +-
net/sunrpc/socklib.c | 9 +-
net/tls/tls_sw.c | 8 +-
14 files changed, 515 insertions(+), 147 deletions(-)
---
base-commit: 4dd16e31135ea8437a720222df34e8745f385d6d
change-id: 20261016-b4-skbuff-bug-on-followups-ad11adf285a4
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH net-next v3 01/15] net: skbuff: make skb_copy_and_csum_bits() report unreadable frags
2026-11-03 2:28 [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Josef Bacik
@ 2026-11-03 2:28 ` Josef Bacik
2026-11-03 2:58 ` Mina Almasry
2026-11-03 2:28 ` [PATCH net-next v3 02/15] net: skbuff: add skb_checksum_full() that reports failure Josef Bacik
` (14 subsequent siblings)
15 siblings, 1 reply; 24+ messages in thread
From: Josef Bacik @ 2026-11-03 2:28 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
skb_copy_and_csum_bits() has no way to fail. When it reaches unreadable
frags, or runs out of skb before @len, it zero-fills the rest of the
buffer and returns 0. Its callers can't tell that the copy didn't
happen.
In the !sg && !csum branch of skb_segment() that turns a devmem TCP
payload into zeroes under a valid TCP checksum, and the peer accepts
them as stream data. It takes a device with both SG and TX checksum
offload turned off. A test module that segments an skb with 3000 bytes
in an unreadable frag gets 3 all-zero segments before this patch and
-EFAULT after it.
Return 0 or -EFAULT, hand the checksum back through a pointer, and mark
the function __must_check. Every caller takes the error path it already
has for a failed copy. skb_copy_and_csum_dev() can't return an error,
so it zeroes the whole frame. csum_partial_copy_to_xdr() no longer
checksums the bytes it didn't copy. That only ran when the function was
about to fail anyway, and skb_checksum() warns on an unreadable skb.
The frag_list loop takes the address of a local, so it moves into a
noinline function to keep a stack protector out of
skb_copy_and_csum_bits().
Found by the Sashiko AI review of commit ab9414ed70bd ("net: skbuff:
don't leave stale bytes in skb_copy_and_csum_bits()").
Fixes: ab9414ed70bd ("net: skbuff: don't leave stale bytes in skb_copy_and_csum_bits()")
Fixes: 417261779eab ("net: skbuff: don't BUG() on leftover length in skb_copy_and_csum_bits()")
Fixes: 65249feb6b3d ("net: add support for skbs with unreadable frags")
Link: https://lore.kernel.org/all/179148235783.434549.14322374227477832817@kernel.org/
Suggested-by: Mina Almasry <almasrymina@google.com>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
drivers/net/ethernet/sun/sunvnet_common.c | 10 ++-
include/linux/skbuff.h | 4 +-
net/core/skbuff.c | 133 ++++++++++++++++++++----------
net/ipv4/icmp.c | 9 +-
net/ipv4/ip_output.c | 11 ++-
net/ipv6/icmp.c | 7 +-
net/ipv6/ip6_output.c | 11 ++-
net/sunrpc/socklib.c | 9 +-
8 files changed, 127 insertions(+), 67 deletions(-)
diff --git a/drivers/net/ethernet/sun/sunvnet_common.c b/drivers/net/ethernet/sun/sunvnet_common.c
index 5cca95869052..4f229fcecc32 100644
--- a/drivers/net/ethernet/sun/sunvnet_common.c
+++ b/drivers/net/ethernet/sun/sunvnet_common.c
@@ -1167,9 +1167,13 @@ static inline struct sk_buff *vnet_skb_shape(struct sk_buff *skb, int ncookies)
/* copy the rest, with csum calculation */
*(__sum16 *)(skb->data + offset) = 0;
- csum = skb_copy_and_csum_bits(skb, start,
- nskb->data + start,
- skb->len - start);
+ if (skb_copy_and_csum_bits(skb, start,
+ nskb->data + start,
+ skb->len - start, &csum)) {
+ dev_kfree_skb(nskb);
+ dev_kfree_skb(skb);
+ return NULL;
+ }
/* add in the header checksums */
if (skb->protocol == htons(ETH_P_IP)) {
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 6787fcf6ac3e..f4c092b78006 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -4280,8 +4280,8 @@ void skb_free_datagram(struct sock *sk, struct sk_buff *skb);
int skb_kill_datagram(struct sock *sk, struct sk_buff *skb, unsigned int flags);
int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len);
int skb_store_bits(struct sk_buff *skb, int offset, const void *from, int len);
-__wsum skb_copy_and_csum_bits(const struct sk_buff *skb, int offset, u8 *to,
- int len);
+int __must_check skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
+ u8 *to, int len, __wsum *csump);
int skb_splice_bits(struct sk_buff *skb, struct sock *sk, unsigned int offset,
struct pipe_inode_info *pipe, unsigned int len,
unsigned int flags);
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 48f676411724..4ac2b476b0a9 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -3620,14 +3620,74 @@ __wsum skb_checksum(const struct sk_buff *skb, int offset, int len, __wsum csum)
}
EXPORT_SYMBOL(skb_checksum);
-/* Both of above in one bottle. */
+/* The frag_list part of skb_copy_and_csum_bits(). It's out of line so that
+ * taking the address of csum2 doesn't cost every caller a stack protector.
+ */
+static noinline int skb_copy_and_csum_frag_list(const struct sk_buff *skb,
+ int start, int offset, u8 *to,
+ int len, int pos, __wsum csum,
+ __wsum *csump)
+{
+ struct sk_buff *frag_iter;
+ int copy, err;
-__wsum skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
- u8 *to, int len)
+ skb_walk_frags(skb, frag_iter) {
+ __wsum csum2;
+ int end;
+
+ WARN_ON(start > offset + len);
+
+ end = start + frag_iter->len;
+ copy = end - offset;
+ if (copy > 0) {
+ if (copy > len)
+ copy = len;
+ err = skb_copy_and_csum_bits(frag_iter, offset - start,
+ to, copy, &csum2);
+ if (err) {
+ /* The child zeroed its part; zero the rest. */
+ memset(to + copy, 0, len - copy);
+ return err;
+ }
+ csum = csum_block_add(csum, csum2, pos);
+ len -= copy;
+ if (len == 0)
+ goto out;
+ offset += copy;
+ to += copy;
+ pos += copy;
+ }
+ start = end;
+ }
+ if (unlikely(len)) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ /* Don't hand the caller a buffer with stale bytes in it. */
+ if (len > 0)
+ memset(to, 0, len);
+ return -EFAULT;
+ }
+out:
+ *csump = csum;
+ return 0;
+}
+
+/**
+ * skb_copy_and_csum_bits - copy data out of an skb and checksum it
+ * @skb: source buffer
+ * @offset: offset in @skb to start copying from
+ * @to: destination buffer
+ * @len: number of bytes to copy
+ * @csump: where to store the checksum of the copied bytes
+ *
+ * Return: 0 on success, or -EFAULT if @skb has unreadable frags or runs
+ * out before @len does. On failure *@csump is not set and the part of
+ * @to that couldn't be filled is zeroed.
+ */
+int skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
+ u8 *to, int len, __wsum *csump)
{
int start = skb_headlen(skb);
int i, copy = start - offset;
- struct sk_buff *frag_iter;
int pos = 0;
__wsum csum = 0;
@@ -3638,7 +3698,7 @@ __wsum skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
csum = csum_partial_copy_nocheck(skb->data + offset, to,
copy);
if ((len -= copy) == 0)
- return csum;
+ goto out;
offset += copy;
to += copy;
pos = copy;
@@ -3648,7 +3708,7 @@ __wsum skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
/* Don't hand the caller a buffer with stale bytes in it. */
if (len > 0)
memset(to, 0, len);
- return 0;
+ return -EFAULT;
}
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
@@ -3680,43 +3740,18 @@ __wsum skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
}
if (!(len -= copy))
- return csum;
+ goto out;
offset += copy;
to += copy;
}
start = end;
}
- skb_walk_frags(skb, frag_iter) {
- __wsum csum2;
- int end;
-
- WARN_ON(start > offset + len);
-
- end = start + frag_iter->len;
- if ((copy = end - offset) > 0) {
- if (copy > len)
- copy = len;
- csum2 = skb_copy_and_csum_bits(frag_iter,
- offset - start,
- to, copy);
- csum = csum_block_add(csum, csum2, pos);
- if ((len -= copy) == 0)
- return csum;
- offset += copy;
- to += copy;
- pos += copy;
- }
- start = end;
- }
- if (unlikely(len)) {
- DEBUG_NET_WARN_ON_ONCE(1);
- /* Don't hand the caller a buffer with stale bytes in it. */
- if (len > 0)
- memset(to, 0, len);
- return 0;
- }
- return csum;
+ return skb_copy_and_csum_frag_list(skb, start, offset, to, len, pos,
+ csum, csump);
+out:
+ *csump = csum;
+ return 0;
}
EXPORT_SYMBOL(skb_copy_and_csum_bits);
@@ -4001,9 +4036,15 @@ void skb_copy_and_csum_dev(const struct sk_buff *skb, u8 *to)
skb_copy_from_linear_data(skb, to, csstart);
csum = 0;
- if (csstart != skb->len)
- csum = skb_copy_and_csum_bits(skb, csstart, to + csstart,
- skb->len - csstart);
+ if (csstart != skb->len &&
+ skb_copy_and_csum_bits(skb, csstart, to + csstart,
+ skb->len - csstart, &csum)) {
+ /* Send zeroes rather than intact headers on a payload that
+ * couldn't be copied.
+ */
+ memset(to, 0, skb->len);
+ return;
+ }
if (skb->ip_summed == CHECKSUM_PARTIAL) {
long csstuff = csstart + skb->csum_offset;
@@ -5057,11 +5098,13 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
if (!csum) {
if (!nskb->remcsum_offload)
nskb->ip_summed = CHECKSUM_NONE;
- SKB_GSO_CB(nskb)->csum =
- skb_copy_and_csum_bits(head_skb, offset,
- skb_put(nskb,
- len),
- len);
+ if (skb_copy_and_csum_bits(head_skb, offset,
+ skb_put(nskb, len),
+ len,
+ &SKB_GSO_CB(nskb)->csum)) {
+ err = -EFAULT;
+ goto err;
+ }
SKB_GSO_CB(nskb)->csum_start =
skb_headroom(nskb) + doffset;
} else {
diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
index 90c0e22c29be..35008785e3d2 100644
--- a/net/ipv4/icmp.c
+++ b/net/ipv4/icmp.c
@@ -364,12 +364,15 @@ static int icmp_glue_bits(void *from, char *to, int offset, int len, int odd,
DEFINE_RAW_FLEX(struct icmp_bxm, icmp_param, replyopts.opt.__data,
IP_OPTIONS_DATA_FIXED_SIZE);
__wsum csum;
+ int err;
icmp_param = from;
- csum = skb_copy_and_csum_bits(icmp_param->skb,
- icmp_param->offset + offset,
- to, len);
+ err = skb_copy_and_csum_bits(icmp_param->skb,
+ icmp_param->offset + offset,
+ to, len, &csum);
+ if (err)
+ return err;
skb->csum = csum_block_add(skb->csum, csum, odd);
if (icmp_param->data.icmph.type <= NR_ICMP_TYPES &&
diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c
index 3396f177a18d..f7da4538d05b 100644
--- a/net/ipv4/ip_output.c
+++ b/net/ipv4/ip_output.c
@@ -1162,9 +1162,14 @@ static int __ip_append_data(struct sock *sk,
data += fragheaderlen + exthdrlen;
if (fraggap) {
- skb->csum = skb_copy_and_csum_bits(
- skb_prev, maxfraglen,
- data + transhdrlen, fraggap);
+ if (skb_copy_and_csum_bits(skb_prev, maxfraglen,
+ data + transhdrlen,
+ fraggap,
+ &skb->csum)) {
+ err = -EFAULT;
+ kfree_skb(skb);
+ goto error;
+ }
skb_prev->csum = csum_sub(skb_prev->csum,
skb->csum);
data += fraggap;
diff --git a/net/ipv6/icmp.c b/net/ipv6/icmp.c
index a95b0351824f..31ca3da80b1f 100644
--- a/net/ipv6/icmp.c
+++ b/net/ipv6/icmp.c
@@ -322,9 +322,12 @@ static int icmpv6_getfrag(void *from, char *to, int offset, int len, int odd, st
struct icmpv6_msg *msg = (struct icmpv6_msg *) from;
struct sk_buff *org_skb = msg->skb;
__wsum csum;
+ int err;
- csum = skb_copy_and_csum_bits(org_skb, msg->offset + offset,
- to, len);
+ err = skb_copy_and_csum_bits(org_skb, msg->offset + offset,
+ to, len, &csum);
+ if (err)
+ return err;
skb->csum = csum_block_add(skb->csum, csum, odd);
if (!(msg->type & ICMPV6_INFOMSG_MASK))
nf_ct_attach(skb, org_skb);
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index 25fe0ba98b1a..d259e7dca8ba 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -1724,9 +1724,14 @@ static int __ip6_append_data(struct sock *sk,
skb->transport_header = (skb->network_header +
fragheaderlen);
if (fraggap) {
- skb->csum = skb_copy_and_csum_bits(
- skb_prev, maxfraglen,
- data + transhdrlen, fraggap);
+ if (skb_copy_and_csum_bits(skb_prev, maxfraglen,
+ data + transhdrlen,
+ fraggap,
+ &skb->csum)) {
+ err = -EFAULT;
+ kfree_skb(skb);
+ goto error;
+ }
skb_prev->csum = csum_sub(skb_prev->csum,
skb->csum);
data += fraggap;
diff --git a/net/sunrpc/socklib.c b/net/sunrpc/socklib.c
index d8d8842c7de5..5d780ad4ae3a 100644
--- a/net/sunrpc/socklib.c
+++ b/net/sunrpc/socklib.c
@@ -49,7 +49,9 @@ xdr_skb_read_bits(struct xdr_skb_reader *desc, void *to, size_t len)
if (desc->need_checksum) {
__wsum csum;
- csum = skb_copy_and_csum_bits(desc->skb, desc->offset, to, len);
+ if (unlikely(skb_copy_and_csum_bits(desc->skb, desc->offset,
+ to, len, &csum)))
+ return 0;
desc->csum = csum_block_add(desc->csum, csum, desc->offset);
} else {
if (unlikely(skb_copy_bits(desc->skb, desc->offset, to, len)))
@@ -142,11 +144,6 @@ int csum_partial_copy_to_xdr(struct xdr_buf *xdr, struct sk_buff *skb)
desc.csum = csum_partial(skb->data, desc.offset, skb->csum);
if (xdr_partial_copy_from_skb(xdr, &desc) < 0)
return -1;
- if (desc.offset != skb->len) {
- __wsum csum2;
- csum2 = skb_checksum(skb, desc.offset, skb->len - desc.offset, 0);
- desc.csum = csum_block_add(desc.csum, csum2, desc.offset);
- }
if (desc.count)
return -1;
if (csum_fold(desc.csum))
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v3 02/15] net: skbuff: add skb_checksum_full() that reports failure
2026-11-03 2:28 [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 01/15] net: skbuff: make skb_copy_and_csum_bits() report unreadable frags Josef Bacik
@ 2026-11-03 2:28 ` Josef Bacik
2026-11-03 2:44 ` Mina Almasry
2026-11-03 2:28 ` [PATCH net-next v3 03/15] net: skbuff: fail checksum validation when the skb can't be checksummed Josef Bacik
` (13 subsequent siblings)
15 siblings, 1 reply; 24+ messages in thread
From: Josef Bacik @ 2026-11-03 2:28 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
skb_checksum() can't fail. When the range runs into unreadable frags or
past the end of the skb, skb_checksum() returns 0, which is also a valid
checksum, so its callers can't tell a sum from a failure.
Add skb_checksum_full(), which adds the range's sum to *@csump and
returns 0. On failure it leaves *@csump alone and returns -EFAULT, or
-EINVAL for a negative length. It is __must_check. Its frag_list loop
sits in a noinline function so that taking the address of a local
doesn't give skb_checksum_full() a stack protector.
skb_checksum() becomes a wrapper for the callers that aren't converted
yet, and still returns 0 on failure. Three things change for them:
- When a frag_list member fails, skb_checksum() returns 0 for the whole
range instead of the sum of the parts it could read. Neither value
is the checksum of the range.
- skb_checksum() only warns about unreadable frags if @skb itself has
them.
- A negative length returns 0 instead of reaching csum_partial().
Convert skb_checksum_help(), which can already return an error, so that
a packet that can't be checksummed is dropped instead of sent with a
wrong checksum. The following patches convert checksum validation,
skb_segment() and pskb_trim_rcsum(). The rest will follow per
subsystem.
Fixes: 20ddd0fd2fcd ("net: skbuff: don't BUG() on leftover length in skb_checksum() and friends")
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
include/linux/skbuff.h | 2 +
net/core/dev.c | 7 +++-
net/core/skbuff.c | 111 ++++++++++++++++++++++++++++++++++++-------------
3 files changed, 90 insertions(+), 30 deletions(-)
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index f4c092b78006..608c1491597e 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -4282,6 +4282,8 @@ int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len);
int skb_store_bits(struct sk_buff *skb, int offset, const void *from, int len);
int __must_check skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
u8 *to, int len, __wsum *csump);
+int __must_check skb_checksum_full(const struct sk_buff *skb, int offset,
+ int len, __wsum *csump);
int skb_splice_bits(struct sk_buff *skb, struct sock *sk, unsigned int offset,
struct pipe_inode_info *pipe, unsigned int len,
unsigned int flags);
diff --git a/net/core/dev.c b/net/core/dev.c
index 3ac700d37e59..00a62c391056 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -3665,7 +3665,7 @@ void skb_warn_bad_offload(const struct sk_buff *skb)
*/
int skb_checksum_help(struct sk_buff *skb)
{
- __wsum csum;
+ __wsum csum = 0;
int ret = 0, offset;
if (skb->ip_summed == CHECKSUM_COMPLETE)
@@ -3697,13 +3697,16 @@ int skb_checksum_help(struct sk_buff *skb)
offset, skb_headlen(skb));
goto out;
}
- csum = skb_checksum(skb, offset, skb->len - offset, 0);
+ ret = skb_checksum_full(skb, offset, skb->len - offset, &csum);
+ if (ret)
+ goto out;
offset += skb->csum_offset;
if (unlikely(offset + sizeof(__sum16) > skb_headlen(skb))) {
DO_ONCE_LITE(skb_dump, KERN_ERR, skb, false);
WARN_ONCE(true, "offset+2 (%zu) > skb_headlen() (%u)\n",
offset + sizeof(__sum16), skb_headlen(skb));
+ ret = -EINVAL;
goto out;
}
ret = skb_ensure_writable(skb, offset + sizeof(__sum16));
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 4ac2b476b0a9..c5f8aca89221 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -3536,27 +3536,88 @@ int skb_store_bits(struct sk_buff *skb, int offset, const void *from, int len)
}
EXPORT_SYMBOL(skb_store_bits);
-/* Checksum skb data. */
-__wsum skb_checksum(const struct sk_buff *skb, int offset, int len, __wsum csum)
+/* The frag_list part of skb_checksum_full(). It's out of line so that taking
+ * the address of csum2 doesn't cost every caller a stack protector.
+ */
+static noinline int skb_checksum_frag_list(const struct sk_buff *skb, int start,
+ int offset, int len, int pos,
+ __wsum csum, __wsum *csump)
+{
+ struct sk_buff *frag_iter;
+ int copy, err;
+
+ skb_walk_frags(skb, frag_iter) {
+ int end;
+
+ WARN_ON(start > offset + len);
+
+ end = start + frag_iter->len;
+ copy = end - offset;
+ if (copy > 0) {
+ __wsum csum2 = 0;
+
+ if (copy > len)
+ copy = len;
+ err = skb_checksum_full(frag_iter, offset - start, copy,
+ &csum2);
+ if (err)
+ return err;
+ csum = csum_block_add(csum, csum2, pos);
+ len -= copy;
+ if (len == 0)
+ goto out;
+ offset += copy;
+ pos += copy;
+ }
+ start = end;
+ }
+ if (unlikely(len)) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ return -EFAULT;
+ }
+out:
+ *csump = csum;
+ return 0;
+}
+
+/**
+ * skb_checksum_full - checksum a range of an skb
+ * @skb: buffer to checksum
+ * @offset: offset in @skb to start at
+ * @len: number of bytes to checksum
+ * @csump: the sum to add to; on success the range's sum is added to it
+ *
+ * Return: 0 on success, -EINVAL if @len is negative, or -EFAULT if @skb
+ * runs out before @len does, or if the range goes past the linear area
+ * of an skb, @skb or one on its frag_list, that has unreadable frags.
+ * On failure *@csump is not changed.
+ */
+int skb_checksum_full(const struct sk_buff *skb, int offset, int len,
+ __wsum *csump)
{
int start = skb_headlen(skb);
int i, copy = start - offset;
- struct sk_buff *frag_iter;
+ __wsum csum = *csump;
int pos = 0;
+ if (unlikely(len < 0)) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+
/* Checksum header. */
if (copy > 0) {
if (copy > len)
copy = len;
csum = csum_partial(skb->data + offset, copy, csum);
if ((len -= copy) == 0)
- return csum;
+ goto out;
offset += copy;
pos = copy;
}
- if (WARN_ON_ONCE(!skb_frags_readable(skb)))
- return 0;
+ if (!skb_frags_readable(skb))
+ return -EFAULT;
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
int end;
@@ -3585,37 +3646,31 @@ __wsum skb_checksum(const struct sk_buff *skb, int offset, int len, __wsum csum)
}
if (!(len -= copy))
- return csum;
+ goto out;
offset += copy;
}
start = end;
}
- skb_walk_frags(skb, frag_iter) {
- int end;
+ return skb_checksum_frag_list(skb, start, offset, len, pos, csum,
+ csump);
+out:
+ *csump = csum;
+ return 0;
+}
+EXPORT_SYMBOL(skb_checksum_full);
- WARN_ON(start > offset + len);
+/* skb_checksum() has no way to report failure, so it returns 0, which is a
+ * valid checksum. Use skb_checksum_full() in new code.
+ */
+__wsum skb_checksum(const struct sk_buff *skb, int offset, int len, __wsum csum)
+{
+ int err = skb_checksum_full(skb, offset, len, &csum);
- end = start + frag_iter->len;
- if ((copy = end - offset) > 0) {
- __wsum csum2;
- if (copy > len)
- copy = len;
- csum2 = skb_checksum(frag_iter, offset - start, copy,
- 0);
- csum = csum_block_add(csum, csum2, pos);
- if ((len -= copy) == 0)
- return csum;
- offset += copy;
- pos += copy;
- }
- start = end;
- }
- if (unlikely(len)) {
- DEBUG_NET_WARN_ON_ONCE(1);
+ if (unlikely(err)) {
+ WARN_ON_ONCE(err == -EFAULT && !skb_frags_readable(skb));
return 0;
}
-
return csum;
}
EXPORT_SYMBOL(skb_checksum);
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v3 03/15] net: skbuff: fail checksum validation when the skb can't be checksummed
2026-11-03 2:28 [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 01/15] net: skbuff: make skb_copy_and_csum_bits() report unreadable frags Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 02/15] net: skbuff: add skb_checksum_full() that reports failure Josef Bacik
@ 2026-11-03 2:28 ` Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 04/15] net: skbuff: fail segmentation when a segment " Josef Bacik
` (12 subsequent siblings)
15 siblings, 0 replies; 24+ messages in thread
From: Josef Bacik @ 2026-11-03 2:28 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
__skb_checksum_complete() and __skb_gro_checksum_complete() verify a
packet with skb_checksum(), which returns 0 when the payload is in
unreadable frags or the skb's lengths don't add up. Both add that 0 to
the pseudo-header sum, so they accept the packet whenever the
pseudo-header sum folds to 0xffff. The sender controls that sum through
the addresses and the length.
Use skb_checksum_full() in both, and in __skb_checksum_complete_head(),
and report an skb that can't be checksummed as having a bad checksum.
Also take the skb off CHECKSUM_COMPLETE on that path. nf_ip_checksum()
stores the pseudo-header sum in skb->csum before it calls
__skb_checksum_complete(), and conntrack doesn't drop a packet with a
bad checksum. If the skb stayed CHECKSUM_COMPLETE,
__skb_checksum_validate_complete() would add the same pseudo-header sum
to skb->csum and accept the packet under the same condition.
A test module builds an skb that claims 50 more bytes than it holds,
with a pseudo-header sum of 0xffff. Both functions accept that skb
before this patch and reject it after.
Found by the Sashiko AI review of the BUG_ON() series.
Fixes: 20ddd0fd2fcd ("net: skbuff: don't BUG() on leftover length in skb_checksum() and friends")
Fixes: 65249feb6b3d ("net: add support for skbs with unreadable frags")
Link: https://lore.kernel.org/all/20261007-b4-skbuff-bug-on-v2-0-b9a5f732895b@toxicpanda.com/
Link: https://lore.kernel.org/all/179273111951.3134.3452028799437573017@kernel.org/
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
include/linux/skbuff.h | 4 ++--
net/core/gro.c | 11 +++++++++--
net/core/skbuff.c | 27 +++++++++++++++++++++++----
3 files changed, 34 insertions(+), 8 deletions(-)
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 608c1491597e..5a5354a6b6ba 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -4892,7 +4892,7 @@ static inline void skb_checksum_complete_unset(struct sk_buff *skb)
* 0: checksum is validated or try to in skb_checksum_complete. In the latter
* case the ip_summed will not be CHECKSUM_UNNECESSARY and the pseudo
* checksum is stored in skb->csum for use in __skb_checksum_complete
- * non-zero: value of invalid checksum
+ * non-zero: value of invalid checksum, or 1 if the skb can't be checksummed
*
*/
static inline __sum16 __skb_checksum_validate_complete(struct sk_buff *skb,
@@ -4932,7 +4932,7 @@ static inline __wsum null_compute_pseudo(struct sk_buff *skb, int proto)
*
* Return values:
* 0: checksum is validated or try to in skb_checksum_complete
- * non-zero: value of invalid checksum
+ * non-zero: value of invalid checksum, or 1 if the skb can't be checksummed
*/
#define __skb_checksum_validate(skb, proto, complete, \
zero_okay, check, compute_pseudo) \
diff --git a/net/core/gro.c b/net/core/gro.c
index 1b033855a20d..e2e847c95270 100644
--- a/net/core/gro.c
+++ b/net/core/gro.c
@@ -798,10 +798,17 @@ EXPORT_SYMBOL(napi_gro_frags);
*/
__sum16 __skb_gro_checksum_complete(struct sk_buff *skb)
{
- __wsum wsum;
+ __wsum wsum = 0;
__sum16 sum;
- wsum = skb_checksum(skb, skb_gro_offset(skb), skb_gro_len(skb), 0);
+ /* If the data can't be checksummed it can't be valid either, and
+ * NAPI_GRO_CB(skb)->csum only holds the pseudo checksum.
+ */
+ if (skb_checksum_full(skb, skb_gro_offset(skb), skb_gro_len(skb),
+ &wsum)) {
+ NAPI_GRO_CB(skb)->csum_valid = 0;
+ return (__force __sum16)1;
+ }
/* NAPI_GRO_CB(skb)->csum holds pseudo checksum */
sum = csum_fold(csum_add(NAPI_GRO_CB(skb)->csum, wsum));
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index c5f8aca89221..0d3f0f09c7d1 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -3885,11 +3885,28 @@ u32 skb_crc32c(const struct sk_buff *skb, int offset, int len, u32 crc)
EXPORT_SYMBOL(skb_crc32c);
#endif /* CONFIG_NET_CRC32C */
+/* The data can't be checksummed, so it can't be valid either. The caller has
+ * put the pseudo header's checksum in skb->csum, so don't leave the skb
+ * CHECKSUM_COMPLETE for a later validator to take that for the packet's.
+ */
+static __sum16 skb_checksum_complete_failed(struct sk_buff *skb)
+{
+ if (!skb_shared(skb)) {
+ skb_checksum_complete_unset(skb);
+ skb->csum_valid = 0;
+ }
+ return (__force __sum16)1;
+}
+
__sum16 __skb_checksum_complete_head(struct sk_buff *skb, int len)
{
+ __wsum csum = skb->csum;
__sum16 sum;
- sum = csum_fold(skb_checksum(skb, 0, len, skb->csum));
+ if (skb_checksum_full(skb, 0, len, &csum))
+ return skb_checksum_complete_failed(skb);
+
+ sum = csum_fold(csum);
/* See comments in __skb_checksum_complete(). */
if (likely(!sum)) {
if (unlikely(skb->ip_summed == CHECKSUM_COMPLETE) &&
@@ -3909,14 +3926,16 @@ EXPORT_SYMBOL(__skb_checksum_complete_head);
*
* It returns non-zero if the recomputed checksum is still invalid, otherwise
* zero. The new checksum is stored back into skb->csum unless the skb is
- * shared.
+ * shared. If the skb can't be checksummed it returns non-zero, skb->csum is
+ * left alone, and the skb is no longer CHECKSUM_COMPLETE unless it's shared.
*/
__sum16 __skb_checksum_complete(struct sk_buff *skb)
{
- __wsum csum;
+ __wsum csum = 0;
__sum16 sum;
- csum = skb_checksum(skb, 0, skb->len, 0);
+ if (skb_checksum_full(skb, 0, skb->len, &csum))
+ return skb_checksum_complete_failed(skb);
sum = csum_fold(csum_add(skb->csum, csum));
/* This check is inverted, because we already knew the hardware
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v3 04/15] net: skbuff: fail segmentation when a segment can't be checksummed
2026-11-03 2:28 [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Josef Bacik
` (2 preceding siblings ...)
2026-11-03 2:28 ` [PATCH net-next v3 03/15] net: skbuff: fail checksum validation when the skb can't be checksummed Josef Bacik
@ 2026-11-03 2:28 ` Josef Bacik
2026-11-03 2:36 ` Mina Almasry
2026-11-03 2:28 ` [PATCH net-next v3 05/15] net: skbuff: drop hw csum if trim can't checksum tail Josef Bacik
` (11 subsequent siblings)
15 siblings, 1 reply; 24+ messages in thread
From: Josef Bacik @ 2026-11-03 2:28 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
When the device can't checksum the protocol, skb_segment() checksums
each segment with skb_checksum(). If the segment has unreadable frags,
or its lengths don't add up, skb_checksum() returns 0 and the segment
goes out with a checksum that doesn't cover its payload.
Devmem TCP doesn't get that far today. Its skbs take SKBFL_SHARED_FRAG
from the zerocopy uarg, so skb_segment() tries to linearize the segment
first, and that fails with -ENOMEM for unreadable frags. No other path
is known to bring an unreadable segment here either.
Use skb_checksum_full() and fail the segmentation with -EFAULT, so that
the checksum doesn't depend on that flag.
Found by the Sashiko AI review of an earlier fix for the same path with
SG off.
Link: https://lore.kernel.org/all/20261009-b4-skb-segment-nosg-unreadable-v1-1-7e2334a55fe8@toxicpanda.com/
Reviewed-by: Fernando Fernandez Mancera <fmancera@suse.de>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
net/core/skbuff.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 0d3f0f09c7d1..5f997edf3519 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -5292,9 +5292,13 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
if (!nskb->remcsum_offload)
nskb->ip_summed = CHECKSUM_NONE;
- SKB_GSO_CB(nskb)->csum =
- skb_checksum(nskb, doffset,
- nskb->len - doffset, 0);
+ SKB_GSO_CB(nskb)->csum = 0;
+ if (skb_checksum_full(nskb, doffset,
+ nskb->len - doffset,
+ &SKB_GSO_CB(nskb)->csum)) {
+ err = -EFAULT;
+ goto err;
+ }
SKB_GSO_CB(nskb)->csum_start =
skb_headroom(nskb) + doffset;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v3 05/15] net: skbuff: drop hw csum if trim can't checksum tail
2026-11-03 2:28 [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Josef Bacik
` (3 preceding siblings ...)
2026-11-03 2:28 ` [PATCH net-next v3 04/15] net: skbuff: fail segmentation when a segment " Josef Bacik
@ 2026-11-03 2:28 ` Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 06/15] net: skbuff: add skb_crc32c_full() that reports failure Josef Bacik
` (10 subsequent siblings)
15 siblings, 0 replies; 24+ messages in thread
From: Josef Bacik @ 2026-11-03 2:28 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
For a CHECKSUM_COMPLETE skb, pskb_trim_rcsum() subtracts the checksum of
the trimmed bytes from skb->csum. It gets that checksum from
skb_checksum(), which returns 0 if the trimmed range runs into an
unreadable frag_list member or past the end of the skb. skb->csum then
still covers the trimmed bytes.
commit 2e357f002c61 ("net: Avoid checksumming unreadable skb tail on
trim") handles an skb that is itself unreadable, but not a readable head
with an unreadable frag_list member.
Use skb_checksum_full(). When it fails, do what pskb_trim_rcsum()
already does for an unreadable skb. Set CHECKSUM_NONE if the remaining
packet can be read, so that software checksums it, and fail the trim
with -EFAULT if it can't.
The stale skb->csum was only reproduced with a hand-built skb in a test
module.
Fixes: 65249feb6b3d ("net: add support for skbs with unreadable frags")
Fixes: 20ddd0fd2fcd ("net: skbuff: don't BUG() on leftover length in skb_checksum() and friends")
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
net/core/skbuff.c | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 5f997edf3519..68801a4f471b 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -2821,9 +2821,23 @@ static int pskb_trim_rcsum_complete(struct sk_buff *skb, unsigned int len)
int delta = skb->len - len;
if (skb_frags_readable(skb)) {
- skb->csum = csum_block_sub(skb->csum,
- skb_checksum(skb, len, delta, 0),
- len);
+ __wsum csum = 0;
+ int err;
+
+ if (!skb_checksum_full(skb, len, delta, &csum)) {
+ skb->csum = csum_block_sub(skb->csum, csum, len);
+ return 0;
+ }
+
+ /* The trimmed bytes can't be checksummed, so skb->csum can't
+ * be adjusted. Software can checksum the remaining packet
+ * after trimming, as long as it can read it.
+ */
+ csum = 0;
+ err = skb_checksum_full(skb, 0, len, &csum);
+ if (err)
+ return err;
+ skb->ip_summed = CHECKSUM_NONE;
return 0;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v3 06/15] net: skbuff: add skb_crc32c_full() that reports failure
2026-11-03 2:28 [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Josef Bacik
` (4 preceding siblings ...)
2026-11-03 2:28 ` [PATCH net-next v3 05/15] net: skbuff: drop hw csum if trim can't checksum tail Josef Bacik
@ 2026-11-03 2:28 ` Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 07/15] net: skbuff: let skb_zerocopy() copy a short skb without head_frag Josef Bacik
` (9 subsequent siblings)
15 siblings, 0 replies; 24+ messages in thread
From: Josef Bacik @ 2026-11-03 2:28 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
skb_crc32c() has the same problem as skb_checksum(). When the range
runs into unreadable frags or past the end of the skb, skb_crc32c()
returns 0, which is also a valid CRC. sctp_compute_cksum() and
skb_crc32c_csum_help() complement it, so a failed walk looks like a
checksum of 0xffffffff whatever the packet contains.
Add skb_crc32c_full(), which works like skb_checksum_full().
skb_crc32c() becomes a wrapper for the callers that aren't converted
yet. The wrapper still returns 0 on failure, but it no longer warns
when only a frag_list member has unreadable frags.
Convert skb_crc32c_csum_help(), which can already return an error, so
that an SCTP packet that can't be checksummed is dropped on transmit.
The other callers will follow with the skb_checksum() callers.
Fixes: 20ddd0fd2fcd ("net: skbuff: don't BUG() on leftover length in skb_checksum() and friends")
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
include/linux/skbuff.h | 2 +
net/core/dev.c | 7 +++-
net/core/skbuff.c | 100 +++++++++++++++++++++++++++++++++++++------------
3 files changed, 83 insertions(+), 26 deletions(-)
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 5a5354a6b6ba..5581ed3cbf47 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -4332,6 +4332,8 @@ static inline int memcpy_to_msg(struct msghdr *msg, void *data, int len)
__wsum skb_checksum(const struct sk_buff *skb, int offset, int len,
__wsum csum);
+int __must_check skb_crc32c_full(const struct sk_buff *skb, int offset,
+ int len, u32 *crcp);
u32 skb_crc32c(const struct sk_buff *skb, int offset, int len, u32 crc);
static inline void * __must_check
diff --git a/net/core/dev.c b/net/core/dev.c
index 00a62c391056..f9878db781df 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -3752,8 +3752,11 @@ int skb_crc32c_csum_help(struct sk_buff *skb)
if (ret)
goto out;
- crc = ~skb_crc32c(skb, start, skb->len - start, ~0);
- *(__le32 *)(skb->data + offset) = cpu_to_le32(crc);
+ crc = ~0;
+ ret = skb_crc32c_full(skb, start, skb->len - start, &crc);
+ if (ret)
+ goto out;
+ *(__le32 *)(skb->data + offset) = cpu_to_le32(~crc);
skb_reset_csum_not_inet(skb);
out:
return ret;
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 68801a4f471b..15d1f4142f29 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -3825,23 +3825,79 @@ int skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
EXPORT_SYMBOL(skb_copy_and_csum_bits);
#ifdef CONFIG_NET_CRC32C
-u32 skb_crc32c(const struct sk_buff *skb, int offset, int len, u32 crc)
+/* The frag_list part of skb_crc32c_full(). It's out of line so that taking
+ * the address of crc doesn't cost every caller a stack protector.
+ */
+static noinline int skb_crc32c_frag_list(const struct sk_buff *skb, int start,
+ int offset, int len, u32 crc,
+ u32 *crcp)
+{
+ struct sk_buff *frag_iter;
+ int copy, err;
+
+ skb_walk_frags(skb, frag_iter) {
+ int end;
+
+ WARN_ON(start > offset + len);
+
+ end = start + frag_iter->len;
+ copy = end - offset;
+ if (copy > 0) {
+ copy = min(copy, len);
+ err = skb_crc32c_full(frag_iter, offset - start, copy,
+ &crc);
+ if (err)
+ return err;
+ len -= copy;
+ if (len == 0)
+ goto out;
+ offset += copy;
+ }
+ start = end;
+ }
+ if (unlikely(len)) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ return -EFAULT;
+ }
+out:
+ *crcp = crc;
+ return 0;
+}
+
+/**
+ * skb_crc32c_full - CRC32C a range of an skb
+ * @skb: buffer to read
+ * @offset: offset in @skb to start at
+ * @len: number of bytes to include
+ * @crcp: the CRC to continue from; on success, updated with the range
+ *
+ * Return: 0 on success, -EINVAL if @len is negative, or -EFAULT if @skb
+ * runs out before @len does, or if the range goes past the linear area
+ * of an skb, @skb or one on its frag_list, that has unreadable frags.
+ * On failure *@crcp is not changed.
+ */
+int skb_crc32c_full(const struct sk_buff *skb, int offset, int len, u32 *crcp)
{
int start = skb_headlen(skb);
int i, copy = start - offset;
- struct sk_buff *frag_iter;
+ u32 crc = *crcp;
+
+ if (unlikely(len < 0)) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
if (copy > 0) {
copy = min(copy, len);
crc = crc32c(crc, skb->data + offset, copy);
len -= copy;
if (len == 0)
- return crc;
+ goto out;
offset += copy;
}
- if (WARN_ON_ONCE(!skb_frags_readable(skb)))
- return 0;
+ if (!skb_frags_readable(skb))
+ return -EFAULT;
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
int end;
@@ -3866,34 +3922,30 @@ u32 skb_crc32c(const struct sk_buff *skb, int offset, int len, u32 crc)
}
len -= copy;
if (len == 0)
- return crc;
+ goto out;
offset += copy;
}
start = end;
}
- skb_walk_frags(skb, frag_iter) {
- int end;
+ return skb_crc32c_frag_list(skb, start, offset, len, crc, crcp);
+out:
+ *crcp = crc;
+ return 0;
+}
+EXPORT_SYMBOL(skb_crc32c_full);
- WARN_ON(start > offset + len);
+/* skb_crc32c() has no way to report failure, so it returns 0, which is a
+ * valid CRC. Use skb_crc32c_full() in new code.
+ */
+u32 skb_crc32c(const struct sk_buff *skb, int offset, int len, u32 crc)
+{
+ int err = skb_crc32c_full(skb, offset, len, &crc);
- end = start + frag_iter->len;
- copy = end - offset;
- if (copy > 0) {
- copy = min(copy, len);
- crc = skb_crc32c(frag_iter, offset - start, copy, crc);
- len -= copy;
- if (len == 0)
- return crc;
- offset += copy;
- }
- start = end;
- }
- if (unlikely(len)) {
- DEBUG_NET_WARN_ON_ONCE(1);
+ if (unlikely(err)) {
+ WARN_ON_ONCE(err == -EFAULT && !skb_frags_readable(skb));
return 0;
}
-
return crc;
}
EXPORT_SYMBOL(skb_crc32c);
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v3 07/15] net: skbuff: let skb_zerocopy() copy a short skb without head_frag
2026-11-03 2:28 [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Josef Bacik
` (5 preceding siblings ...)
2026-11-03 2:28 ` [PATCH net-next v3 06/15] net: skbuff: add skb_crc32c_full() that reports failure Josef Bacik
@ 2026-11-03 2:28 ` Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 08/15] net: skbuff: make skb_try_coalesce()'s len unsigned Josef Bacik
` (8 subsequent siblings)
15 siblings, 0 replies; 24+ messages in thread
From: Josef Bacik @ 2026-11-03 2:28 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
skb_zerocopy() refuses an skb with no head_frag when the caller passes
hlen == 0, because it can't reference such a head as a page. That check
sits ahead of the small-payload path, which copies everything with
skb_copy_bits() and never references the head.
Open vSwitch gets there with an ordinary request.
OVS_PACKET_CMD_EXECUTE allocates the packet with a kmalloc'd head, a
POP_ETH action on a 14 byte frame leaves it empty, and a USERSPACE
action calls skb_zerocopy(user_skb, skb, 0, 0). The upcall fails, with
a WARN on CONFIG_DEBUG_NET kernels. Before
commit 433380f5ab8f ("net: skbuff: don't BUG() on a missing head_frag in
skb_zerocopy()") it hit a BUG(). The request only needs CAP_NET_ADMIN
in the user namespace that owns the network namespace.
Move the check below the small-payload path and document the -EFAULT
case. A genetlink reproducer run as an unprivileged user in its own
namespaces warns before this patch and doesn't after it.
The small-payload path hands @len to skb_put(), which panics on a
negative value, and nothing runs ahead of it now. Reject a negative
@len or @hlen on entry with -EINVAL. Neither caller passes one.
Found by the Sashiko AI review of the BUG_ON() series.
Fixes: 433380f5ab8f ("net: skbuff: don't BUG() on a missing head_frag in skb_zerocopy()")
Fixes: af2806f8f90a ("net: Export skb_zerocopy() to zerocopy from one skb to another")
Link: https://lore.kernel.org/all/179173804997.434549.4450793967361922984@kernel.org/
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
net/core/skbuff.c | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 15d1f4142f29..d79d8463c44e 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -4072,8 +4072,10 @@ 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, or readable head
- * payload would be mixed with unreadable frags.
+ * -EFAULT: skb_copy_bits() found some problem with skb geometry, @from has
+ * no head_frag and @hlen is 0 while @len doesn't fit in @to's tailroom,
+ * or readable head payload would be mixed with unreadable frags.
+ * -EINVAL: @len or @hlen is negative
*/
int
skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen)
@@ -4084,15 +4086,23 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen)
struct page *page;
unsigned int offset;
- if (unlikely(!from->head_frag && !hlen)) {
+ if (unlikely(len < 0 || hlen < 0)) {
DEBUG_NET_WARN_ON_ONCE(1);
- return -EFAULT;
+ return -EINVAL;
}
/* dont bother with small payloads */
if (len <= skb_tailroom(to))
return skb_copy_bits(from, 0, skb_put(to, len), len);
+ /* Without a head_frag the head can't be referenced as a page, so the
+ * caller has to ask for it to be copied.
+ */
+ if (unlikely(!from->head_frag && !hlen)) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ return -EFAULT;
+ }
+
to->pp_recycle = from->pp_recycle;
if (hlen) {
ret = skb_copy_bits(from, 0, skb_put(to, hlen), hlen);
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v3 08/15] net: skbuff: make skb_try_coalesce()'s len unsigned
2026-11-03 2:28 [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Josef Bacik
` (6 preceding siblings ...)
2026-11-03 2:28 ` [PATCH net-next v3 07/15] net: skbuff: let skb_zerocopy() copy a short skb without head_frag Josef Bacik
@ 2026-11-03 2:28 ` Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 09/15] net: skbuff: reject data_len > len in copy_and_csum_dev Josef Bacik
` (7 subsequent siblings)
15 siblings, 0 replies; 24+ messages in thread
From: Josef Bacik @ 2026-11-03 2:28 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
skb_try_coalesce() keeps from->len, which is unsigned, in an int. A
corrupt from->len above INT_MAX turns negative, gets past the
len <= skb_tailroom(to) check, and reaches skb_copy_bits() as a negative
length. skb_copy_bits() returns -EFAULT for most such skbs. If
skb_headlen(from) is still a small positive value, skb_copy_bits()
clamps the head copy to the negative length and memcpy() takes that as
2GB or more.
skb_put() used to run before the copy, and its overflow check caught
most of these lengths, but not the ones that wrap skb->tail back below
skb->end. commit 279877d86c82 ("net: skbuff: don't BUG() when
skb_copy_bits() fails in copy helpers") moved the copy ahead of
skb_put(), which left only the tailroom check.
Make len unsigned, like from->len, so that such an skb doesn't take the
copy path. It gets to the truesize sanity check instead, which sees
that from->len is more than from->truesize can hold. That check only
warns, and only after the head of @from has been added to @to as a frag
of that size. Make the check refuse the coalesce, and move it ahead of
the head steal so that @to isn't touched. The check compares the int
delta against len, so it now tests delta < 0 explicitly.
An skb from a driver that under-reports truesize used to warn once and
get coalesced. It isn't coalesced any more.
A test module that coalesces an skb with a page frag head and a len of
0x80000010 gets false and an untouched @to.
Found by the Sashiko AI review of the BUG_ON() series.
Fixes: 279877d86c82 ("net: skbuff: don't BUG() when skb_copy_bits() fails in copy helpers")
Fixes: bad43ca8325f ("net: introduce skb_try_coalesce()")
Link: https://lore.kernel.org/all/20261007-b4-skbuff-bug-on-v2-0-b9a5f732895b@toxicpanda.com/
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
net/core/skbuff.c | 27 +++++++++++++++------------
1 file changed, 15 insertions(+), 12 deletions(-)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index d79d8463c44e..e400990595f8 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -6478,7 +6478,8 @@ bool skb_try_coalesce(struct sk_buff *to, struct sk_buff *from,
bool *fragstolen, int *delta_truesize)
{
struct skb_shared_info *to_shinfo, *from_shinfo;
- int i, delta, len = from->len;
+ unsigned int len = from->len;
+ int i, delta;
*fragstolen = false;
@@ -6523,9 +6524,6 @@ bool skb_try_coalesce(struct sk_buff *to, struct sk_buff *from,
return false;
if (skb_headlen(from) != 0) {
- struct page *page;
- unsigned int offset;
-
if (to_shinfo->nr_frags +
from_shinfo->nr_frags >= MAX_SKB_FRAGS)
return false;
@@ -6534,13 +6532,6 @@ bool skb_try_coalesce(struct sk_buff *to, struct sk_buff *from,
return false;
delta = from->truesize - SKB_DATA_ALIGN(sizeof(struct sk_buff));
-
- page = virt_to_head_page(from->head);
- offset = from->data - (unsigned char *)page_address(page);
-
- skb_fill_page_desc(to, to_shinfo->nr_frags,
- page, offset, skb_headlen(from));
- *fragstolen = true;
} else {
if (to_shinfo->nr_frags +
from_shinfo->nr_frags > MAX_SKB_FRAGS)
@@ -6549,7 +6540,19 @@ bool skb_try_coalesce(struct sk_buff *to, struct sk_buff *from,
delta = from->truesize - SKB_TRUESIZE(skb_end_offset(from));
}
- WARN_ON_ONCE(delta < len);
+ /* @from claims more data than it has memory for. Leave @to alone. */
+ if (WARN_ON_ONCE(delta < 0 || delta < len))
+ return false;
+
+ if (skb_headlen(from) != 0) {
+ struct page *page = virt_to_head_page(from->head);
+ unsigned int offset;
+
+ offset = from->data - (unsigned char *)page_address(page);
+ skb_fill_page_desc(to, to_shinfo->nr_frags,
+ page, offset, skb_headlen(from));
+ *fragstolen = true;
+ }
memcpy(to_shinfo->frags + to_shinfo->nr_frags,
from_shinfo->frags,
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v3 09/15] net: skbuff: reject data_len > len in copy_and_csum_dev
2026-11-03 2:28 [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Josef Bacik
` (7 preceding siblings ...)
2026-11-03 2:28 ` [PATCH net-next v3 08/15] net: skbuff: make skb_try_coalesce()'s len unsigned Josef Bacik
@ 2026-11-03 2:28 ` Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 10/15] net: skbuff: reject a negative length in skb_copy_and_csum_bits() Josef Bacik
` (6 subsequent siblings)
15 siblings, 0 replies; 24+ messages in thread
From: Josef Bacik @ 2026-11-03 2:28 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
skb_headlen() is len - data_len in unsigned arithmetic, so it underflows
to about 4GB for an skb whose data_len is bigger than its len.
skb_copy_and_csum_dev() compares the checksum start against
skb_headlen(), so its checks don't catch such an skb:
- Without CHECKSUM_PARTIAL the checksum start is skb_headlen() itself.
On 64-bit the 4GB value becomes the length of the linear copy.
- With CHECKSUM_PARTIAL any checksum start below len passes, whatever
the linear area really holds.
The BUG_ON() that commit 56453199d28f ("net: skbuff: don't BUG() on a
bad csum_start in skb_copy_and_csum_dev()") replaced compared against
the same value, so this isn't new. No path is known to build such an
skb.
Check for data_len > len before anything uses skb_headlen(), and zero
the frame.
Link: https://lore.kernel.org/all/179273112823.3134.12241973533522574538@kernel.org/
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
net/core/skbuff.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index e400990595f8..b74d8a6ef0f9 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -4165,6 +4165,15 @@ void skb_copy_and_csum_dev(const struct sk_buff *skb, u8 *to)
__wsum csum;
long csstart;
+ /* skb_headlen() underflows if data_len is bigger than len, and then
+ * the checks below don't bound the copy from the linear area.
+ */
+ if (unlikely(skb->data_len > skb->len)) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ memset(to, 0, skb->len);
+ return;
+ }
+
if (skb->ip_summed == CHECKSUM_PARTIAL)
csstart = skb_checksum_start_offset(skb);
else
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v3 10/15] net: skbuff: reject a negative length in skb_copy_and_csum_bits()
2026-11-03 2:28 [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Josef Bacik
` (8 preceding siblings ...)
2026-11-03 2:28 ` [PATCH net-next v3 09/15] net: skbuff: reject data_len > len in copy_and_csum_dev Josef Bacik
@ 2026-11-03 2:28 ` Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 11/15] net: skbuff: don't run off frag_list in skb_segment() Josef Bacik
` (5 subsequent siblings)
15 siblings, 0 replies; 24+ messages in thread
From: Josef Bacik @ 2026-11-03 2:28 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
The head, frag and frag_list copies in skb_copy_and_csum_bits() each
clamp their length to @len. A negative @len from a broken caller
therefore becomes the copy length, and the copy takes it as a huge
unsigned value. The two len > 0 tests around the zero-fills only
protect the zero-fills.
Reject a negative @len on entry with -EINVAL and drop the two tests.
@to isn't touched in that case. Every caller already handles an error,
and skb_copy_and_csum_dev() zeroes its whole frame on one. No caller is
known to pass a negative length.
Found by the Sashiko AI review of the BUG_ON() series.
Link: https://lore.kernel.org/all/20261007-b4-skbuff-bug-on-v2-0-b9a5f732895b@toxicpanda.com/
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
net/core/skbuff.c | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index b74d8a6ef0f9..e3fc6291d223 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -3731,8 +3731,7 @@ static noinline int skb_copy_and_csum_frag_list(const struct sk_buff *skb,
if (unlikely(len)) {
DEBUG_NET_WARN_ON_ONCE(1);
/* Don't hand the caller a buffer with stale bytes in it. */
- if (len > 0)
- memset(to, 0, len);
+ memset(to, 0, len);
return -EFAULT;
}
out:
@@ -3748,9 +3747,10 @@ static noinline int skb_copy_and_csum_frag_list(const struct sk_buff *skb,
* @len: number of bytes to copy
* @csump: where to store the checksum of the copied bytes
*
- * Return: 0 on success, or -EFAULT if @skb has unreadable frags or runs
- * out before @len does. On failure *@csump is not set and the part of
- * @to that couldn't be filled is zeroed.
+ * Return: 0 on success, -EINVAL if @len is negative, or -EFAULT if @skb
+ * has unreadable frags or runs out before @len does. On failure *@csump
+ * is not set. On -EFAULT the part of @to that couldn't be filled is
+ * zeroed, and on -EINVAL @to is not touched.
*/
int skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
u8 *to, int len, __wsum *csump)
@@ -3760,6 +3760,14 @@ int skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
int pos = 0;
__wsum csum = 0;
+ /* Everything below clamps its copy to @len, so a negative one would
+ * turn into a huge copy.
+ */
+ if (unlikely(len < 0)) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+
/* Copy header. */
if (copy > 0) {
if (copy > len)
@@ -3775,8 +3783,7 @@ int skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
if (!skb_frags_readable(skb)) {
/* Don't hand the caller a buffer with stale bytes in it. */
- if (len > 0)
- memset(to, 0, len);
+ memset(to, 0, len);
return -EFAULT;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v3 11/15] net: skbuff: don't run off frag_list in skb_segment()
2026-11-03 2:28 [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Josef Bacik
` (9 preceding siblings ...)
2026-11-03 2:28 ` [PATCH net-next v3 10/15] net: skbuff: reject a negative length in skb_copy_and_csum_bits() Josef Bacik
@ 2026-11-03 2:28 ` Josef Bacik
2026-11-04 2:53 ` Willem de Bruijn
2026-11-03 2:28 ` [PATCH net-next v3 12/15] net: test the skb layouts skb_segment() refuses Josef Bacik
` (4 subsequent siblings)
15 siblings, 1 reply; 24+ messages in thread
From: Josef Bacik @ 2026-11-03 2:28 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
skb_segment() keeps going until it has segmented head_skb->len bytes.
It takes the data from head_skb and then from each member of the
frag_list. If head_skb->len counts data that this walk can't find, the
walk runs out of members while it still owes data and dereferences a
NULL list_skb. There are two ways to get there:
- head_skb->len is bigger than what head_skb and its frag_list hold.
- A member has a frag_list of its own. head_skb->len counts those
bytes, but for each member the walk only looks at the linear part and
frags[].
No path in the tree is known to build a GSO skb like either. GRO
flushes instead of chaining an skb that already has a frag_list. IP
reassembly does nest frag_lists, but a reassembled skb isn't GSO.
Check list_skb in the three places where the walk moves on to it, and
fail the segmentation with -EINVAL. The KUnit cases in the next patch
hit the NULL pointer dereference without this patch, and at least one of
them hits it again with any one of the three checks taken out.
Without SG the payload is copied by helpers that check the length
themselves and recurse into a nested frag_list. That path still fails
for a len that is too big, and still segments a nested frag_list.
Found by the Sashiko AI review of the BUG_ON() series.
Link: https://lore.kernel.org/all/20261007-b4-skbuff-bug-on-v2-0-b9a5f732895b@toxicpanda.com/
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
net/core/skbuff.c | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index e3fc6291d223..b2c44a58db23 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -5164,7 +5164,16 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
int hsize;
int size;
+ /* list_skb runs out early if head_skb->len counts data that
+ * this walk can't find: bytes that aren't there, or a member's
+ * own frag_list, which the walk never looks inside.
+ */
if (unlikely(gso_by_frags)) {
+ if (unlikely(!list_skb)) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ err = -EINVAL;
+ goto err;
+ }
len = list_skb->len;
} else {
len = head_skb->len - offset;
@@ -5174,7 +5183,8 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
hsize = skb_headlen(head_skb) - offset;
- if (hsize <= 0 && i >= nfrags && skb_headlen(list_skb) &&
+ if (hsize <= 0 && i >= nfrags && list_skb &&
+ skb_headlen(list_skb) &&
(skb_headlen(list_skb) == len || sg)) {
if (unlikely(skb_headlen(list_skb) > len)) {
DEBUG_NET_WARN_ON_ONCE(1);
@@ -5294,6 +5304,12 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
while (pos < offset + len) {
if (i >= nfrags) {
+ if (unlikely(!list_skb)) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ err = -EINVAL;
+ goto err;
+ }
+
if (skb_shinfo(nskb)->nr_frags &&
nskb->pp_recycle != list_skb->pp_recycle) {
nskb->data_len = pos - offset - hsize;
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v3 12/15] net: test the skb layouts skb_segment() refuses
2026-11-03 2:28 [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Josef Bacik
` (10 preceding siblings ...)
2026-11-03 2:28 ` [PATCH net-next v3 11/15] net: skbuff: don't run off frag_list in skb_segment() Josef Bacik
@ 2026-11-03 2:28 ` Josef Bacik
2026-11-04 2:57 ` Willem de Bruijn
2026-11-03 2:28 ` [PATCH net-next v3 13/15] xen-netfront: don't BUG() when skb_copy_bits() fails in bounce_skb() Josef Bacik
` (3 subsequent siblings)
15 siblings, 1 reply; 24+ messages in thread
From: Josef Bacik @ 2026-11-03 2:28 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
The GSO KUnit test only checks layouts skb_segment() can segment. Add
the ones it has to refuse with -EINVAL:
- The non-uniform frag_list with SG on, which used to hit the
BUG_ON(i >= nfrags) the existing test steps around.
- A frag_list member that has its own frag_list.
- An skb whose len counts bytes it doesn't hold. The data runs out on
a segment boundary, in the middle of a segment, after the last
frag_list member, and with GSO_BY_FRAGS.
They warn on CONFIG_DEBUG_NET kernels, so run them with the warning
backtrace suppressed. Also update the comment on the non-uniform case,
which still says SG would hit that BUG_ON().
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
net/core/net_test.c | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 95 insertions(+), 3 deletions(-)
diff --git a/net/core/net_test.c b/net/core/net_test.c
index 2b5080c22663..298ca240104c 100644
--- a/net/core/net_test.c
+++ b/net/core/net_test.c
@@ -41,6 +41,12 @@ enum gso_test_nr {
GSO_TEST_RESEGMENT_MULTI,
GSO_TEST_RESEGMENT_TAIL,
GSO_TEST_RESEGMENT_ONE_MSS,
+ GSO_TEST_FRAG_LIST_NON_UNIFORM_SG,
+ GSO_TEST_FRAG_LIST_NESTED,
+ GSO_TEST_SHORT_LINEAR,
+ GSO_TEST_SHORT_LINEAR_STRADDLE,
+ GSO_TEST_SHORT_FRAG_LIST,
+ GSO_TEST_SHORT_GSO_BY_FRAGS,
};
struct gso_test_case {
@@ -58,11 +64,16 @@ struct gso_test_case {
unsigned int gso_type;
/* skbs from alloc_skb() instead of build_skb(), head_frag clear */
bool no_head_frag;
+ /* bytes that skb->len counts but the skb doesn't hold */
+ unsigned int missing_len;
/* output as expected */
unsigned int nr_segs;
const unsigned int *segs;
const unsigned int *gso_segs;
+
+ /* skb_segment() is expected to fail with this error */
+ int err;
};
static struct gso_test_case cases[] = {
@@ -241,6 +252,61 @@ static struct gso_test_case cases[] = {
},
.gso_segs = (const unsigned int[]) { 0, 0, 0, 0 },
},
+ {
+ /* frag_list members that don't end on gso_size boundaries
+ * can't be segmented with SG
+ */
+ .id = GSO_TEST_FRAG_LIST_NON_UNIFORM_SG,
+ .name = "frag_list_non_uniform_sg",
+ .linear_len = GSO_TEST_SIZE,
+ .nr_frag_skbs = 4,
+ .frag_skbs = (const unsigned int[]) { GSO_TEST_SIZE, 1, GSO_TEST_SIZE, 2 },
+ .err = -EINVAL,
+ },
+ {
+ /* the second frag_list member hangs off the first one's
+ * frag_list instead of off the head's
+ */
+ .id = GSO_TEST_FRAG_LIST_NESTED,
+ .name = "frag_list_nested",
+ .linear_len = GSO_TEST_SIZE,
+ .nr_frag_skbs = 2,
+ .frag_skbs = (const unsigned int[]) { GSO_TEST_SIZE, GSO_TEST_SIZE },
+ .err = -EINVAL,
+ },
+ {
+ /* the data runs out on a segment boundary */
+ .id = GSO_TEST_SHORT_LINEAR,
+ .name = "short_linear",
+ .linear_len = GSO_TEST_SIZE,
+ .missing_len = 50,
+ .err = -EINVAL,
+ },
+ {
+ /* the data runs out in the middle of a segment */
+ .id = GSO_TEST_SHORT_LINEAR_STRADDLE,
+ .name = "short_linear_straddle",
+ .linear_len = GSO_TEST_SIZE + 500,
+ .missing_len = 700,
+ .err = -EINVAL,
+ },
+ {
+ .id = GSO_TEST_SHORT_FRAG_LIST,
+ .name = "short_frag_list",
+ .linear_len = GSO_TEST_SIZE,
+ .nr_frag_skbs = 1,
+ .frag_skbs = (const unsigned int[]) { GSO_TEST_SIZE },
+ .missing_len = 50,
+ .err = -EINVAL,
+ },
+ {
+ .id = GSO_TEST_SHORT_GSO_BY_FRAGS,
+ .name = "short_gso_by_frags",
+ .nr_frag_skbs = 2,
+ .frag_skbs = (const unsigned int[]) { 100, 200 },
+ .missing_len = 50,
+ .err = -EINVAL,
+ },
};
static void gso_test_case_to_desc(struct gso_test_case *t, char *desc)
@@ -337,21 +403,47 @@ static void gso_test_func(struct kunit *test)
skb->data_len += total_size;
skb->truesize += total_true_size;
- if (tcase->id == GSO_TEST_GSO_BY_FRAGS)
+ if (tcase->id == GSO_TEST_GSO_BY_FRAGS ||
+ tcase->id == GSO_TEST_SHORT_GSO_BY_FRAGS)
skb_shinfo(skb)->gso_size = GSO_BY_FRAGS;
+
+ if (tcase->id == GSO_TEST_FRAG_LIST_NESTED) {
+ struct sk_buff *first = skb_shinfo(skb)->frag_list;
+
+ skb_shinfo(first)->frag_list = first->next;
+ first->next = NULL;
+ first->len += tcase->frag_skbs[1];
+ first->data_len += tcase->frag_skbs[1];
+ }
}
+ skb->len += tcase->missing_len;
+ skb->data_len += tcase->missing_len;
+
features = NETIF_F_SG | NETIF_F_HW_CSUM;
if (tcase->id == GSO_TEST_GSO_PARTIAL)
features |= NETIF_F_GSO_PARTIAL;
- /* TODO: this should also work with SG,
- * rather than hit BUG_ON(i >= nfrags)
+ /* TODO: this should also work with SG, rather than fail the
+ * i >= nfrags check in skb_segment()
*/
if (tcase->id == GSO_TEST_FRAG_LIST_NON_UNIFORM)
features &= ~NETIF_F_SG;
SKB_GSO_CB(skb)->max_segs = tcase->max_segs;
+
+ if (tcase->err) {
+ /* the suppression block doesn't run if it can't be set up */
+ segs = NULL;
+ kunit_warning_suppress(test) {
+ segs = skb_segment(skb, features);
+ }
+ KUNIT_EXPECT_EQ(test, PTR_ERR_OR_ZERO(segs), tcase->err);
+ if (!IS_ERR_OR_NULL(segs))
+ kfree_skb_list(segs);
+ goto free_gso_skb;
+ }
+
segs = skb_segment(skb, features);
if (IS_ERR(segs)) {
KUNIT_FAIL(test, "segs error %pe", segs);
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v3 13/15] xen-netfront: don't BUG() when skb_copy_bits() fails in bounce_skb()
2026-11-03 2:28 [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Josef Bacik
` (11 preceding siblings ...)
2026-11-03 2:28 ` [PATCH net-next v3 12/15] net: test the skb layouts skb_segment() refuses Josef Bacik
@ 2026-11-03 2:28 ` Josef Bacik
2026-11-03 6:27 ` Jürgen Groß
2026-11-03 2:28 ` [PATCH net-next v3 14/15] net/tls: don't BUG() on leftover length in __skb_nsg() Josef Bacik
` (2 subsequent siblings)
15 siblings, 1 reply; 24+ messages in thread
From: Josef Bacik @ 2026-11-03 2:28 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
bounce_skb() is an open-coded skb_copy() into a page-aligned buffer, and
it has the same BUG_ON(skb_copy_bits()). skb_copy_bits() fails when the
skb's lengths don't add up or its frags are unreadable.
bounce_skb() already frees the new skb and returns NULL when the
allocation comes back misaligned, and xennet_start_xmit() drops the
packet in that case. Take the same path, with a
DEBUG_NET_WARN_ON_ONCE() for debug kernels.
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
drivers/net/xen-netfront.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/net/xen-netfront.c b/drivers/net/xen-netfront.c
index fdcb91042f29..17811d81937c 100644
--- a/drivers/net/xen-netfront.c
+++ b/drivers/net/xen-netfront.c
@@ -695,7 +695,12 @@ static struct sk_buff *bounce_skb(const struct sk_buff *skb)
/* Set the tail pointer and length */
skb_put(n, skb->len);
- BUG_ON(skb_copy_bits(skb, -headerlen, n->head, headerlen + skb->len));
+ if (unlikely(skb_copy_bits(skb, -headerlen, n->head,
+ headerlen + skb->len))) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ kfree_skb(n);
+ return NULL;
+ }
skb_copy_header(n, skb);
return n;
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v3 14/15] net/tls: don't BUG() on leftover length in __skb_nsg()
2026-11-03 2:28 [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Josef Bacik
` (12 preceding siblings ...)
2026-11-03 2:28 ` [PATCH net-next v3 13/15] xen-netfront: don't BUG() when skb_copy_bits() fails in bounce_skb() Josef Bacik
@ 2026-11-03 2:28 ` Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 15/15] net: update comments that still describe the skb BUG_ON()s Josef Bacik
2026-11-03 3:02 ` [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Mina Almasry
15 siblings, 0 replies; 24+ messages in thread
From: Josef Bacik @ 2026-11-03 2:28 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
__skb_nsg() counts the scatterlist entries for a range of an skb with a
copy of the __skb_to_sgvec() walk, including its BUG_ON(len) when the
skb runs out before the range does. tls_decrypt_sg() calls it on the
same range it then passes to skb_to_sgvec(), so a bad range would BUG()
here before it got to skb_to_sgvec()'s error return.
__skb_nsg() already returns -EMSGSIZE for a frag_list nested too deep,
and tls_decrypt_sg() fails the record on any return below 1. Return
-EINVAL for the leftover length too, with a DEBUG_NET_WARN_ON_ONCE() for
debug kernels, and say so in the comment above skb_nsg().
Acked-by: Mina Almasry <almasrymina@google.com>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
net/tls/tls_sw.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index 771e02bff519..748ae27f2b88 100644
--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -142,12 +142,16 @@ static int __skb_nsg(struct sk_buff *skb, int offset, int len,
start = end;
}
}
- BUG_ON(len);
+ if (unlikely(len)) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
return elt;
}
/* Return the number of scatterlist elements required to completely map the
- * skb, or -EMSGSIZE if the recursion depth is exceeded.
+ * skb, -EMSGSIZE if the recursion depth is exceeded, or -EINVAL if the skb
+ * runs out before @len does.
*/
static int skb_nsg(struct sk_buff *skb, int offset, int len)
{
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v3 15/15] net: update comments that still describe the skb BUG_ON()s
2026-11-03 2:28 [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Josef Bacik
` (13 preceding siblings ...)
2026-11-03 2:28 ` [PATCH net-next v3 14/15] net/tls: don't BUG() on leftover length in __skb_nsg() Josef Bacik
@ 2026-11-03 2:28 ` Josef Bacik
2026-11-03 3:02 ` [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Mina Almasry
15 siblings, 0 replies; 24+ messages in thread
From: Josef Bacik @ 2026-11-03 2:28 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
The skbuff BUG_ON() conversions left a few comments behind:
- bpf_skb_net_grow() says skb_segment() BUG()s on a frag_list with
head_frag set at the downgraded MSS. It fails the segmentation and
the packet is dropped, which is still why the skb gets linearized.
- The skb_to_sgvec() kernel-doc doesn't list the -EINVAL that
__skb_to_sgvec() returns when @offset and @len run past the end of
the skb.
- A comment in the netmem KUnit test names BUG_ON(i >= nfrags) in
skb_segment(), which is a check that fails the segmentation now.
No functional change.
Acked-by: Mina Almasry <almasrymina@google.com>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
net/core/filter.c | 6 +++---
net/core/net_test.c | 2 +-
net/core/skbuff.c | 5 +++--
3 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/net/core/filter.c b/net/core/filter.c
index 09c60a58c82a..9881ac89e94c 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -3723,9 +3723,9 @@ static int bpf_skb_net_grow(struct sk_buff *skb, u32 off, u32 len_diff,
shinfo->gso_segs = 0;
/* Due to header growth, MSS needs to be downgraded.
- * There is a BUG_ON() when segmenting the frag_list with
- * head_frag true, so linearize the skb after downgrading
- * the MSS.
+ * Segmenting a frag_list with head_frag true at the new MSS
+ * fails and the packet gets dropped, so linearize the skb
+ * after downgrading the MSS.
*/
if (!(flags & BPF_F_ADJ_ROOM_FIXED_GSO)) {
skb_decrease_gso_size(shinfo, len_diff);
diff --git a/net/core/net_test.c b/net/core/net_test.c
index 298ca240104c..7bfafc826b14 100644
--- a/net/core/net_test.c
+++ b/net/core/net_test.c
@@ -1466,7 +1466,7 @@ static void netmem_ref_test_skb_segment(struct kunit *test)
/* 5. Linear head_skb (nr_frags=0) + mixed pp_recycle frag_list where
* member A has linear + frag (pp_recycle=1) and tail member B has
* linear len == skb_headlen(A) (pp_recycle=0): keeps SG clone path
- * synchronized without hitting BUG_ON(i >= nfrags).
+ * synchronized without failing the i >= nfrags check.
*/
head = alloc_skb(64, GFP_KERNEL);
list = alloc_skb(64, GFP_KERNEL);
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index b2c44a58db23..bca69e8b3582 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -5639,8 +5639,9 @@ __skb_to_sgvec(struct sk_buff *skb, struct scatterlist *sg, int offset, int len,
*
* Fill the specified scatter-gather list with mappings/pointers into a
* region of the buffer space attached to a socket buffer. Returns either
- * the number of scatterlist items used, or -EMSGSIZE if the contents
- * could not fit.
+ * the number of scatterlist items used, -EMSGSIZE if the contents
+ * could not fit, or -EINVAL if @offset and @len run past the end of
+ * the skb.
*/
int skb_to_sgvec(struct sk_buff *skb, struct scatterlist *sg, int offset, int len)
{
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH net-next v3 04/15] net: skbuff: fail segmentation when a segment can't be checksummed
2026-11-03 2:28 ` [PATCH net-next v3 04/15] net: skbuff: fail segmentation when a segment " Josef Bacik
@ 2026-11-03 2:36 ` Mina Almasry
0 siblings, 0 replies; 24+ messages in thread
From: Mina Almasry @ 2026-11-03 2:36 UTC (permalink / raw)
To: Josef Bacik
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn, Juergen Gross, Stefano Stabellini,
Willem de Bruijn, Fernando Fernandez Mancera, David Laight,
netdev, xen-devel
On Mon, Nov 2, 2026 at 6:28 PM Josef Bacik <josef@toxicpanda.com> wrote:
>
> When the device can't checksum the protocol, skb_segment() checksums
> each segment with skb_checksum(). If the segment has unreadable frags,
> or its lengths don't add up, skb_checksum() returns 0 and the segment
> goes out with a checksum that doesn't cover its payload.
>
> Devmem TCP doesn't get that far today. Its skbs take SKBFL_SHARED_FRAG
> from the zerocopy uarg, so skb_segment() tries to linearize the segment
> first, and that fails with -ENOMEM for unreadable frags. No other path
> is known to bring an unreadable segment here either.
>
> Use skb_checksum_full() and fail the segmentation with -EFAULT, so that
> the checksum doesn't depend on that flag.
>
> Found by the Sashiko AI review of an earlier fix for the same path with
> SG off.
>
> Link: https://lore.kernel.org/all/20261009-b4-skb-segment-nosg-unreadable-v1-1-7e2334a55fe8@toxicpanda.com/
> Reviewed-by: Fernando Fernandez Mancera <fmancera@suse.de>
Acked-by: Mina Almasry <almasrymina@google.com>
--
Thanks,
Mina
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next v3 02/15] net: skbuff: add skb_checksum_full() that reports failure
2026-11-03 2:28 ` [PATCH net-next v3 02/15] net: skbuff: add skb_checksum_full() that reports failure Josef Bacik
@ 2026-11-03 2:44 ` Mina Almasry
2026-11-03 17:28 ` Josef Bacik
0 siblings, 1 reply; 24+ messages in thread
From: Mina Almasry @ 2026-11-03 2:44 UTC (permalink / raw)
To: Josef Bacik
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn, Juergen Gross, Stefano Stabellini,
Willem de Bruijn, Fernando Fernandez Mancera, David Laight,
netdev, xen-devel
On Mon, Nov 2, 2026 at 6:28 PM Josef Bacik <josef@toxicpanda.com> wrote:
>
> skb_checksum() can't fail. When the range runs into unreadable frags or
> past the end of the skb, skb_checksum() returns 0, which is also a valid
> checksum, so its callers can't tell a sum from a failure.
>
> Add skb_checksum_full(), which adds the range's sum to *@csump and
> returns 0. On failure it leaves *@csump alone and returns -EFAULT, or
> -EINVAL for a negative length. It is __must_check. Its frag_list loop
> sits in a noinline function so that taking the address of a local
> doesn't give skb_checksum_full() a stack protector.
>
> skb_checksum() becomes a wrapper for the callers that aren't converted
> yet, and still returns 0 on failure. Three things change for them:
>
Hmm, but do we really want this intermediate state where skb_checksum
and skb_checksum_full exist? Why not fix it in one go rather than
little by little?
--
Thanks,
Mina
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next v3 01/15] net: skbuff: make skb_copy_and_csum_bits() report unreadable frags
2026-11-03 2:28 ` [PATCH net-next v3 01/15] net: skbuff: make skb_copy_and_csum_bits() report unreadable frags Josef Bacik
@ 2026-11-03 2:58 ` Mina Almasry
0 siblings, 0 replies; 24+ messages in thread
From: Mina Almasry @ 2026-11-03 2:58 UTC (permalink / raw)
To: Josef Bacik
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn, Juergen Gross, Stefano Stabellini,
Willem de Bruijn, Fernando Fernandez Mancera, Mina Almasry,
David Laight, netdev, xen-devel
On Mon, Nov 2, 2026 at 6:28 PM Josef Bacik <josef@toxicpanda.com> wrote:
>
> skb_copy_and_csum_bits() has no way to fail. When it reaches unreadable
> frags, or runs out of skb before @len, it zero-fills the rest of the
> buffer and returns 0. Its callers can't tell that the copy didn't
> happen.
>
> In the !sg && !csum branch of skb_segment() that turns a devmem TCP
> payload into zeroes under a valid TCP checksum, and the peer accepts
> them as stream data. It takes a device with both SG and TX checksum
> offload turned off. A test module that segments an skb with 3000 bytes
> in an unreadable frag gets 3 all-zero segments before this patch and
> -EFAULT after it.
>
> Return 0 or -EFAULT, hand the checksum back through a pointer, and mark
> the function __must_check. Every caller takes the error path it already
> has for a failed copy. skb_copy_and_csum_dev() can't return an error,
> so it zeroes the whole frame. csum_partial_copy_to_xdr() no longer
> checksums the bytes it didn't copy. That only ran when the function was
> about to fail anyway, and skb_checksum() warns on an unreadable skb.
>
> The frag_list loop takes the address of a local, so it moves into a
> noinline function to keep a stack protector out of
> skb_copy_and_csum_bits().
>
> Found by the Sashiko AI review of commit ab9414ed70bd ("net: skbuff:
> don't leave stale bytes in skb_copy_and_csum_bits()").
>
> Fixes: ab9414ed70bd ("net: skbuff: don't leave stale bytes in skb_copy_and_csum_bits()")
> Fixes: 417261779eab ("net: skbuff: don't BUG() on leftover length in skb_copy_and_csum_bits()")
> Fixes: 65249feb6b3d ("net: add support for skbs with unreadable frags")
> Link: https://lore.kernel.org/all/179148235783.434549.14322374227477832817@kernel.org/
> Suggested-by: Mina Almasry <almasrymina@google.com>
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Mina Almasry <almasrymina@google.com>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal
2026-11-03 2:28 [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Josef Bacik
` (14 preceding siblings ...)
2026-11-03 2:28 ` [PATCH net-next v3 15/15] net: update comments that still describe the skb BUG_ON()s Josef Bacik
@ 2026-11-03 3:02 ` Mina Almasry
15 siblings, 0 replies; 24+ messages in thread
From: Mina Almasry @ 2026-11-03 3:02 UTC (permalink / raw)
To: Josef Bacik
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn, Juergen Gross, Stefano Stabellini,
Willem de Bruijn, Fernando Fernandez Mancera, David Laight,
netdev, xen-devel
On Mon, Nov 2, 2026 at 6:28 PM Josef Bacik <josef@toxicpanda.com> wrote:
>
> v1: https://lore.kernel.org/all/20261019-b4-skbuff-bug-on-followups-v1-0-41dbef50b9f7@toxicpanda.com/
> v2: https://lore.kernel.org/all/20261021-b4-skbuff-bug-on-followups-v2-0-2aa780a03524@toxicpanda.com/
>
> v2->v3:
> - 1, 2, 6: move the frag_list recursion into a noinline function, so
> that taking the address of a local there doesn't give the helpers a
> stack protector (Paolo). On x86_64 defconfig plus CONFIG_IP_SCTP
> none of skb_copy_and_csum_bits(), skb_checksum_full(),
> skb_crc32c_full() or the two wrappers has one now, and
> __ip_append_data(), __ip6_append_data() and skb_segment() are
> unchanged. The callers that pass a local do gain one. Besides the
> seven I listed on v2, that's __skb_gro_checksum_complete() and
> skb_checksum_help(), which are converted in this version, and
> skb_crc32c_csum_help().
> - 1: put the Fixes: tags back (Fernando, Sashiko). csump in the
> prototype, and drop the "Both of above in one bottle" comment
> (Fernando). skb_copy_and_csum_dev() zeroes the whole frame when the
> copy fails, since leaving the checksum alone doesn't guarantee a bad
> one (Sashiko).
> - 2, 6: the kernel-doc describes the unreadable check as it is, the
> wrappers don't blame unreadable frags for -EINVAL, and the commit
> messages say what changes for a failing frag_list member (Fernando,
> Sashiko).
> - 2: convert skb_checksum_help() too, as 6 does for its crc32c twin, and
> add Fixes: 20ddd0fd2fcd.
> - 3: convert __skb_gro_checksum_complete() too (Fernando, Sashiko).
> Take the skb off CHECKSUM_COMPLETE on failure. Otherwise conntrack,
> which doesn't drop on a bad checksum, leaves the pseudo-header sum in
> skb->csum and TCP's validation accepts the packet after all
> (Sashiko). Update the comments, and add Fixes: 65249feb6b3d.
> - 4: the commit message was wrong that devmem TX reaches this checksum.
> Its skbs carry SKBFL_SHARED_FRAG, so the segment fails to linearize
> first. The patch is hardening, and the commit message says so now.
> - 5: when the tail can't be checksummed but what's left can be read,
> set CHECKSUM_NONE instead of failing the trim, which dropped packets
> that used to be accepted (Sashiko). Retitled, and add Fixes: tags.
> - 7, 8: add the Fixes: tags for the original commits, and correct the
> history in 8's commit message (Sashiko). 8 also says what it takes
> to reach the memcpy().
> - 7: reject a negative @len or @hlen on entry. The reorder left nothing
> ahead of the skb_put(), which panics on one.
> - 8: the truesize check refuses the coalesce instead of only warning,
> and runs before the head is stolen. With len unsigned and nothing
> else, an skb with a len above INT_MAX went from refused to coalesced.
> - 9: check data_len > len directly and zero the frame. The old check
> missed CHECKSUM_PARTIAL (Sashiko). Retitled.
> - 10: drop both len > 0 tests, and correct what the commit message says
> they covered (Sashiko).
> - 9, 10: drop the Fixes: tags. Both problems are older than the commits
> the tags named, and neither has a known trigger.
> - 11: Willem, this one changed again after you OK'd the walk up front.
> A nested frag_list is only one way for the walk to run out of members.
> A len that counts bytes the skb doesn't hold gets to the same NULL
> pointer dereference, and the walk up front doesn't catch that. So the
> patch now checks for a NULL list_skb in the three places where the
> walk moves on to it, and that's all it does: no pass up front, which
> also takes care of the cost you pointed out for BIG TCP, and none of
> v2's skb_has_frag_list() tests. You asked on v2 whether the first two
> checks were needed. For a nested frag_list they weren't, for this
> they are. 12 has a case for each, and taking any one of the three
> checks out brings the oops back. The path without SG is left alone
> again. The commit message says that IP reassembly does nest
> frag_lists, just not on GSO skbs (Sashiko). Retitled.
> - 12: add four cases for a len that counts bytes the skb doesn't hold.
> Retitled.
> - 13: correct what the commit message says about when bounce_skb() runs
> and when skb_copy_bits() fails.
> - 14: update the comment above skb_nsg() (Sashiko).
> - 15: one more stale comment, in net_test.c.
> - Picked up Fernando's Reviewed-by on 4. Dropped Mina's on 1 and
> Fernando's on 2, since the frag_list loop moved in both, and Willem's
> on 12, since it grew.
> - Shortened the commit messages.
> - Rebased onto current net-next.
> v1->v2:
> - skb_segment(): check each frag_list member as the walk takes it,
> instead of an extra pass over the frag_list up front (Willem).
> - KUnit test: braces around the suppressed skb_segment() call (Willem),
> and the comment update for the non-uniform case moved into the same
> patch (Mina).
> - Picked up Willem's Reviewed-by and Mina's Acked-bys.
>
> --- Original email (v1) ---
>
> Hello,
>
> Jakub applied v2 of my skbuff BUG_ON() series[1] before I got v3 out, so
> this is what v3 would have changed, as fixes on top of what landed, plus
> the skb_copy_and_csum_bits() patch I'd been sending to net on its own.
>
> Patch 1 is that patch[2], moved to net-next as Mina suggested, with his
> Reviewed-by. It makes skb_copy_and_csum_bits() report failure, and the
> rest of the series builds on it. Please drop the net version.
>
> Patches 2 through 6 deal with skb_checksum() and skb_crc32c(). My v2
> had them return 0 when they run out of skb, and that turned out not to
> be safe: 0 is a valid checksum, and it can make a packet that was never
> fully checksummed pass verification. So they get versions that report
> failure, skb_checksum_full() and skb_crc32c_full(), and the core users
> switch to them: checksum validation now rejects a packet it can't
> checksum, skb_segment() and pskb_trim_rcsum() fail instead of using a
> bad sum, and skb_crc32c_csum_help() drops the packet. skb_checksum()
> and skb_crc32c() stay as wrappers that behave as before, and I'll
> convert the rest of their callers per subsystem in later series.
>
> Patches 7 through 10 fix problems Sashiko and an LLM review found in
> what was applied. skb_zerocopy() refuses a zero-length copy that Open
> vSwitch can trigger from a user namespace, skb_try_coalesce() and
> skb_copy_and_csum_bits() can be handed a negative length, and
> skb_headlen() can underflow in skb_copy_and_csum_dev().
>
> Patches 11 and 12 make skb_segment() refuse a nested frag_list, which
> could still oops on a NULL list_skb, and add KUnit cases for the layouts
> it refuses. Patches 13 and 14 get rid of the same BUG_ON()s in
> xen-netfront's bounce_skb() and tls's __skb_nsg(), and patch 15 updates
> comments that still describe the old BUG_ON()s.
>
> I tested this with the net_core KUnit tests, a test module that drives
> each of these paths under KASAN and fault injection, an Open vSwitch
> reproducer for patch 7, and a stress run over veth with netem. Thanks,
>
> Josef
>
> [1] https://lore.kernel.org/all/20261007-b4-skbuff-bug-on-v2-0-b9a5f732895b@toxicpanda.com/
> [2] https://lore.kernel.org/all/20261013-b4-skb-segment-nosg-unreadable-v2-1-6c28d8ffa51e@toxicpanda.com/
>
> ---
> Changes in v3:
> - Link to v2: https://patch.msgid.link/20261021-b4-skbuff-bug-on-followups-v2-0-2aa780a03524@toxicpanda.com
>
> Changes in v2:
> - Link to v1: https://patch.msgid.link/20261019-b4-skbuff-bug-on-followups-v1-0-41dbef50b9f7@toxicpanda.com
>
> ---
> Josef Bacik (15):
> net: skbuff: make skb_copy_and_csum_bits() report unreadable frags
> net: skbuff: add skb_checksum_full() that reports failure
> net: skbuff: fail checksum validation when the skb can't be checksummed
> net: skbuff: fail segmentation when a segment can't be checksummed
> net: skbuff: drop hw csum if trim can't checksum tail
> net: skbuff: add skb_crc32c_full() that reports failure
> net: skbuff: let skb_zerocopy() copy a short skb without head_frag
> net: skbuff: make skb_try_coalesce()'s len unsigned
> net: skbuff: reject data_len > len in copy_and_csum_dev
> net: skbuff: reject a negative length in skb_copy_and_csum_bits()
> net: skbuff: don't run off frag_list in skb_segment()
> net: test the skb layouts skb_segment() refuses
> xen-netfront: don't BUG() when skb_copy_bits() fails in bounce_skb()
> net/tls: don't BUG() on leftover length in __skb_nsg()
> net: update comments that still describe the skb BUG_ON()s
>
> drivers/net/ethernet/sun/sunvnet_common.c | 10 +-
> drivers/net/xen-netfront.c | 7 +-
> include/linux/skbuff.h | 12 +-
> net/core/dev.c | 14 +-
> net/core/filter.c | 6 +-
> net/core/gro.c | 11 +-
> net/core/net_test.c | 100 ++++++-
> net/core/skbuff.c | 447 +++++++++++++++++++++++-------
> net/ipv4/icmp.c | 9 +-
> net/ipv4/ip_output.c | 11 +-
> net/ipv6/icmp.c | 7 +-
> net/ipv6/ip6_output.c | 11 +-
> net/sunrpc/socklib.c | 9 +-
> net/tls/tls_sw.c | 8 +-
> 14 files changed, 515 insertions(+), 147 deletions(-)
> ---
> base-commit: 4dd16e31135ea8437a720222df34e8745f385d6d
> change-id: 20261016-b4-skbuff-bug-on-followups-ad11adf285a4
>
Honestly there is nothing super objectionable about the series, but it
seems a bunch of unrelated fixes, capped at 15 because netdev doesn't
let you have more than 15 patches out at once. I wish you broke it up
into less ambitious problems that the human reviewers can follow. But
maybe that's just me.
Like crc checksum failing in 1 series we can follow, and maybe 1
series for a full failable skb_checksum changes in 1 go so we don't
add the new thing before removing the old? Or is that too infeasible?
--
Thanks,
Mina
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next v3 13/15] xen-netfront: don't BUG() when skb_copy_bits() fails in bounce_skb()
2026-11-03 2:28 ` [PATCH net-next v3 13/15] xen-netfront: don't BUG() when skb_copy_bits() fails in bounce_skb() Josef Bacik
@ 2026-11-03 6:27 ` Jürgen Groß
0 siblings, 0 replies; 24+ messages in thread
From: Jürgen Groß @ 2026-11-03 6:27 UTC (permalink / raw)
To: Josef Bacik, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Andrew Lunn
Cc: Stefano Stabellini, Willem de Bruijn, Fernando Fernandez Mancera,
Mina Almasry, David Laight, netdev, xen-devel
[-- Attachment #1.1.1: Type: text/plain, Size: 610 bytes --]
On 03.11.26 03:28, Josef Bacik wrote:
> bounce_skb() is an open-coded skb_copy() into a page-aligned buffer, and
> it has the same BUG_ON(skb_copy_bits()). skb_copy_bits() fails when the
> skb's lengths don't add up or its frags are unreadable.
>
> bounce_skb() already frees the new skb and returns NULL when the
> allocation comes back misaligned, and xennet_start_xmit() drops the
> packet in that case. Take the same path, with a
> DEBUG_NET_WARN_ON_ONCE() for debug kernels.
>
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Juergen Gross <jgross@suse.com>
Juergen
[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 3743 bytes --]
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 495 bytes --]
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next v3 02/15] net: skbuff: add skb_checksum_full() that reports failure
2026-11-03 2:44 ` Mina Almasry
@ 2026-11-03 17:28 ` Josef Bacik
0 siblings, 0 replies; 24+ messages in thread
From: Josef Bacik @ 2026-11-03 17:28 UTC (permalink / raw)
To: Mina Almasry
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrew Lunn, Juergen Gross, Stefano Stabellini,
Willem de Bruijn, Fernando Fernandez Mancera, David Laight,
netdev, xen-devel
On Mon, Nov 02, 2026 at 06:44:46PM -0800, Mina Almasry wrote:
> Hmm, but do we really want this intermediate state where skb_checksum
> and skb_checksum_full exist? Why not fix it in one go rather than
> little by little?
I don't want to keep it, the wrappers go away once the last caller is
converted. I just can't get there in one go.
After this series skb_checksum() still has 44 call sites in 28 files:
11 in netfilter and ipvs, 13 in ipv4 and ipv6, 13 in six drivers, 2 each
in mptcp, bpf and pktgen, and 1 in xfrm. Changing the prototype means
converting all of them in the same patch, and that one patch would go
through netfilter, ipsec, mptcp, bpf and the drivers at once.
It isn't a mechanical change either. Each caller has to do something
with the error, and 17 of the 44 are in functions that return void, for
example udp4_hwcsum(), ip_cmsg_recv_checksum(),
mptcp_update_data_checksum() and nft_payload_set_eval(). Those need
their own callers looked at first.
skb_crc32c() looks smaller with three callers left, but one of them is
sctp_compute_cksum(), which has 11 callers of its own in sctp,
netfilter, openvswitch and act_csum. The other two are siw and
batman-adv.
So this series converts the core users, because patch 3 closes a hole
where a packet that couldn't be checksummed passes validation and I
didn't want that to wait for the rest. After that it's one small series
per subsystem, and the last one removes the wrapper.
This is my plan for everything, in response to your cover letter. I've
got a 15 patch limit and a one series per net/net-next. I'm trying to
make this as easy as possible so that they can be spot checked and
merged quickly. I started with core and I'm slowly working my way out.
I'm purposely avoiding "change all the callers" style patches because
that's harder to review. This is slower but easier for everybody, at
least I think it's easier.
Thanks,
Josef
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next v3 11/15] net: skbuff: don't run off frag_list in skb_segment()
2026-11-03 2:28 ` [PATCH net-next v3 11/15] net: skbuff: don't run off frag_list in skb_segment() Josef Bacik
@ 2026-11-04 2:53 ` Willem de Bruijn
0 siblings, 0 replies; 24+ messages in thread
From: Willem de Bruijn @ 2026-11-04 2:53 UTC (permalink / raw)
To: Josef Bacik, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
Josef Bacik wrote:
> skb_segment() keeps going until it has segmented head_skb->len bytes.
> It takes the data from head_skb and then from each member of the
> frag_list. If head_skb->len counts data that this walk can't find, the
> walk runs out of members while it still owes data and dereferences a
> NULL list_skb. There are two ways to get there:
>
> - head_skb->len is bigger than what head_skb and its frag_list hold.
> - A member has a frag_list of its own. head_skb->len counts those
> bytes, but for each member the walk only looks at the linear part and
> frags[].
>
> No path in the tree is known to build a GSO skb like either. GRO
> flushes instead of chaining an skb that already has a frag_list. IP
> reassembly does nest frag_lists, but a reassembled skb isn't GSO.
>
> Check list_skb in the three places where the walk moves on to it, and
> fail the segmentation with -EINVAL. The KUnit cases in the next patch
> hit the NULL pointer dereference without this patch, and at least one of
> them hits it again with any one of the three checks taken out.
>
> Without SG the payload is copied by helpers that check the length
> themselves and recurse into a nested frag_list. That path still fails
> for a len that is too big, and still segments a nested frag_list.
>
> Found by the Sashiko AI review of the BUG_ON() series.
>
> Link: https://lore.kernel.org/all/20261007-b4-skbuff-bug-on-v2-0-b9a5f732895b@toxicpanda.com/
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Willem de Bruijn <willemb@google.com>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next v3 12/15] net: test the skb layouts skb_segment() refuses
2026-11-03 2:28 ` [PATCH net-next v3 12/15] net: test the skb layouts skb_segment() refuses Josef Bacik
@ 2026-11-04 2:57 ` Willem de Bruijn
0 siblings, 0 replies; 24+ messages in thread
From: Willem de Bruijn @ 2026-11-04 2:57 UTC (permalink / raw)
To: Josef Bacik, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Andrew Lunn
Cc: Juergen Gross, Stefano Stabellini, Willem de Bruijn,
Fernando Fernandez Mancera, Mina Almasry, David Laight, netdev,
xen-devel, Josef Bacik
Josef Bacik wrote:
> The GSO KUnit test only checks layouts skb_segment() can segment. Add
> the ones it has to refuse with -EINVAL:
>
> - The non-uniform frag_list with SG on, which used to hit the
> BUG_ON(i >= nfrags) the existing test steps around.
> - A frag_list member that has its own frag_list.
> - An skb whose len counts bytes it doesn't hold. The data runs out on
> a segment boundary, in the middle of a segment, after the last
> frag_list member, and with GSO_BY_FRAGS.
>
> They warn on CONFIG_DEBUG_NET kernels, so run them with the warning
> backtrace suppressed. Also update the comment on the non-uniform case,
> which still says SG would hit that BUG_ON().
>
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Willem de Bruijn <willemb@google.com>
> - /* TODO: this should also work with SG,
> - * rather than hit BUG_ON(i >= nfrags)
> + /* TODO: this should also work with SG, rather than fail the
> + * i >= nfrags check in skb_segment()
> */
> if (tcase->id == GSO_TEST_FRAG_LIST_NON_UNIFORM)
> features &= ~NETIF_F_SG;
The previous patch also addresses non-SG.
Do you want to run the new tests in both SG and non-SG mode?
>
> SKB_GSO_CB(skb)->max_segs = tcase->max_segs;
> +
> + if (tcase->err) {
> + /* the suppression block doesn't run if it can't be set up */
> + segs = NULL;
> + kunit_warning_suppress(test) {
> + segs = skb_segment(skb, features);
> + }
> + KUNIT_EXPECT_EQ(test, PTR_ERR_OR_ZERO(segs), tcase->err);
> + if (!IS_ERR_OR_NULL(segs))
> + kfree_skb_list(segs);
> + goto free_gso_skb;
> + }
> +
^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2026-11-04 2:57 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-03 2:28 [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 01/15] net: skbuff: make skb_copy_and_csum_bits() report unreadable frags Josef Bacik
2026-11-03 2:58 ` Mina Almasry
2026-11-03 2:28 ` [PATCH net-next v3 02/15] net: skbuff: add skb_checksum_full() that reports failure Josef Bacik
2026-11-03 2:44 ` Mina Almasry
2026-11-03 17:28 ` Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 03/15] net: skbuff: fail checksum validation when the skb can't be checksummed Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 04/15] net: skbuff: fail segmentation when a segment " Josef Bacik
2026-11-03 2:36 ` Mina Almasry
2026-11-03 2:28 ` [PATCH net-next v3 05/15] net: skbuff: drop hw csum if trim can't checksum tail Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 06/15] net: skbuff: add skb_crc32c_full() that reports failure Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 07/15] net: skbuff: let skb_zerocopy() copy a short skb without head_frag Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 08/15] net: skbuff: make skb_try_coalesce()'s len unsigned Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 09/15] net: skbuff: reject data_len > len in copy_and_csum_dev Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 10/15] net: skbuff: reject a negative length in skb_copy_and_csum_bits() Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 11/15] net: skbuff: don't run off frag_list in skb_segment() Josef Bacik
2026-11-04 2:53 ` Willem de Bruijn
2026-11-03 2:28 ` [PATCH net-next v3 12/15] net: test the skb layouts skb_segment() refuses Josef Bacik
2026-11-04 2:57 ` Willem de Bruijn
2026-11-03 2:28 ` [PATCH net-next v3 13/15] xen-netfront: don't BUG() when skb_copy_bits() fails in bounce_skb() Josef Bacik
2026-11-03 6:27 ` Jürgen Groß
2026-11-03 2:28 ` [PATCH net-next v3 14/15] net/tls: don't BUG() on leftover length in __skb_nsg() Josef Bacik
2026-11-03 2:28 ` [PATCH net-next v3 15/15] net: update comments that still describe the skb BUG_ON()s Josef Bacik
2026-11-03 3:02 ` [PATCH net-next v3 00/15] net: skbuff: follow-ups to the BUG_ON() removal Mina Almasry
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox