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 --- 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