Thread (3 messages) flat view 3 messages, 2 authors, 5h ago
HOTtoday

[PATCH net] net: fix use-after-free in pskb_expand_head()

From: Eric Dumazet <edumazet@google.com>
Date: 2026-08-31 09:59:55
Subsystem: networking [general], the rest · Maintainers: "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds

pskb_expand_head() and pskb_carve_inside_header() decide whether to drop
the old head gracefully via skb_release_data() or directly free it via
skb_free_head().

Blamed commit replaced the check against dataref == delta with skb_cloned(skb).

However, skb_cloned(skb) masks out the upper 16 bits (SKB_DATAREF_SHIFT)
of dataref using SKB_DATAREF_MASK. For an skb with nohdr == 1 or a clone
referencing a shared buffer where only one regular reference exists,
skb_cloned(skb) evaluates to false even when dataref is shared with other
skbs.

When pskb_expand_head() is called on such a buffer (e.g. during bridge
flooding, generic XDP, or VLAN tag insertion), it falsely assumes the head
is unshared and calls skb_free_head(skb) directly. This drops the underlying
head/frag reference immediately, while other clones or references still
point to it, causing a use-after-free in subsequent packet processing.

Introduce skb_dataref_bias() and skb_data_is_shared() to accurately test
if dataref is shared by checking atomic_read(&skb_shinfo(skb)->dataref) != bias.

Use skb_dataref_bias() in skb_data_unref() and skb_data_is_shared() in
pskb_expand_head() and pskb_carve_inside_header().

Fixes: 3e24591a19bb ("skb: Drop "fastpath" variable for skb_cloned check in pskb_expand_head")
Reported-by: syzbot+22c4f9a7026c86bcc3b8@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/netdev/6a954e27.4d659fcc.734b4.0046.GAE@google.com/ (local)
Reported-by: syzbot+128e9f5a0f85a51215b1@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=128e9f5a0f85a51215b1
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
 include/linux/skbuff.h | 13 ++++++++++++-
 net/core/skbuff.c      |  4 ++--
 2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 421f6fc454511ea6c7b83d5227481840a7c4bcc0..efeef148e1ba50cbb0d480975dc6c3c59acb98c7 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -1295,6 +1295,11 @@ static inline bool skb_unref(struct sk_buff *skb)
 	return true;
 }
 
+static inline int skb_dataref_bias(const struct sk_buff *skb)
+{
+	return skb->nohdr ? (1 << SKB_DATAREF_SHIFT) + 1 : 1;
+}
+
 static inline bool skb_data_unref(const struct sk_buff *skb,
 				  struct skb_shared_info *shinfo)
 {
@@ -1303,7 +1308,7 @@ static inline bool skb_data_unref(const struct sk_buff *skb,
 	if (!skb->cloned)
 		return true;
 
-	bias = skb->nohdr ? (1 << SKB_DATAREF_SHIFT) + 1 : 1;
+	bias = skb_dataref_bias(skb);
 
 	if (atomic_read(&shinfo->dataref) == bias)
 		smp_rmb();
@@ -2019,6 +2024,12 @@ static inline struct sk_buff *skb_get(struct sk_buff *skb)
  * If users == 1, we are the only owner and can avoid redundant atomic changes.
  */
 
+static inline bool skb_data_is_shared(const struct sk_buff *skb)
+{
+	return skb->cloned &&
+	       atomic_read(&skb_shinfo(skb)->dataref) != skb_dataref_bias(skb);
+}
+
 /**
  *	skb_cloned - is the buffer a clone
  *	@skb: buffer to check
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 966af3beed94d0a9f0387cec70b5297eccfcf19f..f4c0645f98d3f2f0c8765403e0184769e928d7b0 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -2331,7 +2331,7 @@ int pskb_expand_head(struct sk_buff *skb, int nhead, int ntail,
 	 * is not we can just drop the old head and let the existing refcount
 	 * be since all we did is relocate the values
 	 */
-	if (skb_cloned(skb)) {
+	if (skb_data_is_shared(skb)) {
 		if (skb_orphan_frags(skb, gfp_mask))
 			goto nofrags;
 		if (skb_zcopy(skb))
@@ -6855,7 +6855,7 @@ static int pskb_carve_inside_header(struct sk_buff *skb, const u32 off,
 	       skb_shinfo(skb),
 	       offsetof(struct skb_shared_info,
 			frags[skb_shinfo(skb)->nr_frags]));
-	if (skb_cloned(skb)) {
+	if (skb_data_is_shared(skb)) {
 		/* drop the old head gracefully */
 		if (skb_orphan_frags(skb, gfp_mask)) {
 			skb_kfree_head(data);
-- 
2.55.0.897.gb25b4bd76c-goog
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help