[PATCH net-next 3/9] net: skbuff: don't BUG() on a bad frag_list layout in skb_segment()

From: Josef Bacik

Date: Tue Oct 06 2026 - 13:12:48 EST


skb_segment()'s frag_list walk assumes the GRO-shaped layout it expects
and BUG()s when the layout doesn't match. Anybody who can get a
malformed GSO skb to a segmentation point gets to crash the box. Most
recently commit d5dc1e69fd72 ("inet: frags: strip GSO state from
fragments before reassembly") fixed one that an unprivileged user could
trigger with two writes to a tap device in their own user namespace.
commit 3382a1ed7f77 ("net: fix udp gso skb_segment after pull from
frag_list") fixed another.

skb_segment() already has an error path for a bad layout: the
too-many-frags check sets -EINVAL and frees the partial segment list.
Warn once and take that path for the four layout checks. The packet
gets dropped, which is what should happen to a packet we can't segment.

The one check that runs after skb_clone() and before the clone is
linked into the segment list frees the clone itself.

Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
---
net/core/skbuff.c | 21 +++++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)

diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 5d856948cef9..405d27e9bc9d 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -4916,7 +4916,10 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,

if (hsize <= 0 && i >= nfrags && skb_headlen(list_skb) &&
(skb_headlen(list_skb) == len || sg)) {
- BUG_ON(skb_headlen(list_skb) > len);
+ if (WARN_ON_ONCE(skb_headlen(list_skb) > len)) {
+ err = -EINVAL;
+ goto err;
+ }

nskb = skb_clone(list_skb, GFP_ATOMIC);
if (unlikely(!nskb))
@@ -4929,7 +4932,11 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
pos += skb_headlen(list_skb);

while (pos < offset + len) {
- BUG_ON(i >= nfrags);
+ if (WARN_ON_ONCE(i >= nfrags)) {
+ kfree_skb(nskb);
+ err = -EINVAL;
+ goto err;
+ }

size = skb_frag_size(frag);
if (pos + size > offset + len)
@@ -5036,9 +5043,15 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
skb_shinfo(nskb)->flags |= skb_shinfo(frag_skb)->flags & SKBFL_SHARED_FRAG;

if (!skb_headlen(list_skb)) {
- BUG_ON(!nfrags);
+ if (WARN_ON_ONCE(!nfrags)) {
+ err = -EINVAL;
+ goto err;
+ }
} else {
- BUG_ON(!list_skb->head_frag);
+ if (WARN_ON_ONCE(!list_skb->head_frag)) {
+ err = -EINVAL;
+ goto err;
+ }

/* to make room for head_frag. */
i--;

--
2.55.0