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

From: Josef Bacik

Date: Wed Oct 07 2026 - 13:37:45 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.
Take that path for the four layout checks, with a
DEBUG_NET_WARN_ON_ONCE() for debug kernels. 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 | 25 +++++++++++++++++++++----
1 file changed, 21 insertions(+), 4 deletions(-)

diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index d226fe77d484..a6821ab13969 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -4916,7 +4916,11 @@ 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 (unlikely(skb_headlen(list_skb) > len)) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ err = -EINVAL;
+ goto err;
+ }

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

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

size = skb_frag_size(frag);
if (pos + size > offset + len)
@@ -5036,9 +5045,17 @@ 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 (unlikely(!nfrags)) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ err = -EINVAL;
+ goto err;
+ }
} else {
- BUG_ON(!list_skb->head_frag);
+ if (unlikely(!list_skb->head_frag)) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ err = -EINVAL;
+ goto err;
+ }

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

--
2.55.0