TIPC assumes that the received skb is not shared with other users. However, syzbot reports a bug triggered by receiving a shared skb: ... RIP: 0010:pskb_expand_head+0x135a/0x13a0 net/core/skbuff.c:2308 __pskb_pull_tail+0x111/0x1870 net/core/skbuff.c:2890 skb_cow_data+0x2ed/0xc50 net/core/skbuff.c:5398 tipc_aead_decrypt net/tipc/crypto.c:892 [inline] tipc_crypto_rcv+0x346/0x23d0 net/tipc/crypto.c:1843 tipc_rcv+0x4b7/0x1040 net/tipc/node.c:2119 tipc_l2_rcv_msg+0x2d9/0x470 net/tipc/bearer.c:669 deliver_ptype_list_skb net/core/dev.c:2533 [inline] ... The root cause is that local delivery hooks in the kernel (e.g., deliver_skb()) may increment the skb's reference count (`users`) before TIPC processes it on ingress. Fix this by copying the shared or cloned skb to ensure exclusive ownership before passing it to tipc_rcv(), as TIPC needs to modify the skb's data. Reported-by: syzbot+0017742620a9d06f06ef@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=0017742620a9d06f06ef Closes: https://lore.kernel.org/all/20261018145928.58317-1-ngocthang2710.1999@gmail.com/T/ Fixes: 6e967adf798a ("tipc: relocate common functions from media to bearer") Fixes: d0f91938bede ("tipc: add ip/udp media type") Signed-off-by: Tung Nguyen --- v2: Fix typo in function comment of tipc_skb_unshare() and call skb_cloned() to ensure exclusive ownership as sashiko's feedback v1: https://lore.kernel.org/all/20261019050648.287309-1-tung.quang.nguyen@est.tech/T/ net/tipc/bearer.c | 4 ++++ net/tipc/msg.h | 26 ++++++++++++++++++++++++++ net/tipc/udp_media.c | 4 ++++ 3 files changed, 34 insertions(+) diff --git a/net/tipc/bearer.c b/net/tipc/bearer.c index 05dcd2f9e887..1f8d85e083e1 100644 --- a/net/tipc/bearer.c +++ b/net/tipc/bearer.c @@ -659,6 +659,10 @@ static int tipc_l2_rcv_msg(struct sk_buff *skb, struct net_device *dev, { struct tipc_bearer *b; + skb = tipc_skb_unshare(skb); + if (!skb) + return NET_RX_DROP; + rcu_read_lock(); b = rcu_dereference(dev->tipc_ptr) ?: rcu_dereference(orig_dev->tipc_ptr); diff --git a/net/tipc/msg.h b/net/tipc/msg.h index c5eec16213d7..4ee2faf5d86e 100644 --- a/net/tipc/msg.h +++ b/net/tipc/msg.h @@ -1307,4 +1307,30 @@ static inline struct sk_buff *__tipc_skb_dequeue(struct sk_buff_head *list, return NULL; } +/** + * tipc_skb_unshare - make a copy of a shared buffer + * @skb: buffer to check + * + * Return: + * + * * skb - Original skb or its copy with the reference count at 1 + * * NULL - Memory allocation failure + * + */ +static inline struct sk_buff *tipc_skb_unshare(struct sk_buff *skb) +{ + if (unlikely(skb_shared(skb)) || unlikely(skb_cloned(skb))) { + struct sk_buff *nskb = skb_copy(skb, GFP_ATOMIC); + + if (likely(nskb)) + consume_skb(skb); + else + kfree_skb(skb); + + skb = nskb; + } + + return skb; +} + #endif diff --git a/net/tipc/udp_media.c b/net/tipc/udp_media.c index 230645cc01c9..ea1122510d97 100644 --- a/net/tipc/udp_media.c +++ b/net/tipc/udp_media.c @@ -372,6 +372,10 @@ static int tipc_udp_recv(struct sock *sk, struct sk_buff *skb) struct tipc_msg *hdr; int err; + skb = tipc_skb_unshare(skb); + if (!skb) + return 0; + ub = rcu_dereference_sk_user_data(sk); if (!ub) { pr_err_ratelimited("Failed to get UDP bearer reference"); -- 2.53.0