skb_split() and skb_shift() do not accurately maintain skb->unreadable
when moving fragments between skbs:
- skb_split_inside_header() moves all fragments out of skb into skb1
(leaving skb with nr_frags == 0 and only host-readable linear memory),
but leaves skb->unreadable set on skb.
- skb_split_no_header() unconditionally copies skb->unreadable to skb1
even if 0 fragments were moved to skb1 (k == 0), and leaves
skb->unreadable set on skb when splitting at skb_headlen(skb) moves
all fragments into skb1 (!skb_shinfo(skb)->nr_frags).
- skb_shift() neither propagates skb->unreadable to target skb when
shifting unreadable fragments into an empty skb, nor clears
skb->unreadable after all fragments are shifted out of skb.
Fix all three cases so skb->unreadable accurately tracks whether the
remaining fragments in each skb are unreadable.
This issue is theoretical and was found by manual code inspection and
LLM review; it was not triggered in production. Verified with the
net_core KUnit test suite; not tested on real hardware.
Fixes: 65249feb6b3d ("net: add support for skbs with unreadable frags")
Signed-off-by: Mina Almasry <redacted>
---
net/core/skbuff.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 48f6764117243..dc761ceb4d204 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -4225,6 +4225,7 @@ static inline void skb_split_inside_header(struct sk_buff *skb,
skb_shinfo(skb1)->nr_frags = skb_shinfo(skb)->nr_frags;
skb1->unreadable = skb->unreadable;
+ skb->unreadable = 0;
skb_shinfo(skb)->nr_frags = 0;
skb1->data_len = skb->data_len;
skb1->len += skb1->data_len;
@@ -4273,7 +4274,10 @@ static inline void skb_split_no_header(struct sk_buff *skb,
}
skb_shinfo(skb1)->nr_frags = k;
- skb1->unreadable = skb->unreadable;
+ if (k)
+ skb1->unreadable = skb->unreadable;
+ if (!skb_shinfo(skb)->nr_frags)
+ skb->unreadable = 0;
}
/**
@@ -4447,6 +4451,9 @@ int skb_shift(struct sk_buff *tgt, struct sk_buff *skb, int shiftlen)
skb->ip_summed = CHECKSUM_PARTIAL;
skb_shinfo(tgt)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG;
+ tgt->unreadable |= skb->unreadable;
+ if (!skb_shinfo(skb)->nr_frags && !skb_has_frag_list(skb))
+ skb->unreadable = 0;
skb_len_add(skb, -shiftlen);
skb_len_add(tgt, shiftlen);
--
2.56.0.385.gac671c819a-goog