Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ipv6: use RCU in ip6_xmit()

Use RCU in ip6_xmit() in order to use dst_dev_rcu() to prevent
possible UAF.

Fixes: 4a6ce2b6f2ec ("net: introduce a new function dst_dev_put()")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: David Ahern <dsahern@kernel.org>
Link: https://patch.msgid.link/20250828195823.3958522-4-edumazet@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Eric Dumazet and committed by
Jakub Kicinski
9085e565 b775ecf1

+21 -14
+21 -14
net/ipv6/ip6_output.c
··· 268 268 int ip6_xmit(const struct sock *sk, struct sk_buff *skb, struct flowi6 *fl6, 269 269 __u32 mark, struct ipv6_txoptions *opt, int tclass, u32 priority) 270 270 { 271 - struct net *net = sock_net(sk); 272 271 const struct ipv6_pinfo *np = inet6_sk(sk); 273 272 struct in6_addr *first_hop = &fl6->daddr; 274 273 struct dst_entry *dst = skb_dst(skb); 275 - struct net_device *dev = dst_dev(dst); 276 274 struct inet6_dev *idev = ip6_dst_idev(dst); 277 275 struct hop_jumbo_hdr *hop_jumbo; 278 276 int hoplen = sizeof(*hop_jumbo); 277 + struct net *net = sock_net(sk); 279 278 unsigned int head_room; 279 + struct net_device *dev; 280 280 struct ipv6hdr *hdr; 281 281 u8 proto = fl6->flowi6_proto; 282 282 int seg_len = skb->len; 283 - int hlimit = -1; 283 + int ret, hlimit = -1; 284 284 u32 mtu; 285 285 286 + rcu_read_lock(); 287 + 288 + dev = dst_dev_rcu(dst); 286 289 head_room = sizeof(struct ipv6hdr) + hoplen + LL_RESERVED_SPACE(dev); 287 290 if (opt) 288 291 head_room += opt->opt_nflen + opt->opt_flen; 289 292 290 293 if (unlikely(head_room > skb_headroom(skb))) { 291 - /* Make sure idev stays alive */ 292 - rcu_read_lock(); 294 + /* idev stays alive while we hold rcu_read_lock(). */ 293 295 skb = skb_expand_head(skb, head_room); 294 296 if (!skb) { 295 297 IP6_INC_STATS(net, idev, IPSTATS_MIB_OUTDISCARDS); 296 - rcu_read_unlock(); 297 - return -ENOBUFS; 298 + ret = -ENOBUFS; 299 + goto unlock; 298 300 } 299 - rcu_read_unlock(); 300 301 } 301 302 302 303 if (opt) { ··· 359 358 * skb to its handler for processing 360 359 */ 361 360 skb = l3mdev_ip6_out((struct sock *)sk, skb); 362 - if (unlikely(!skb)) 363 - return 0; 361 + if (unlikely(!skb)) { 362 + ret = 0; 363 + goto unlock; 364 + } 364 365 365 366 /* hooks should never assume socket lock is held. 366 367 * we promote our socket to non const 367 368 */ 368 - return NF_HOOK(NFPROTO_IPV6, NF_INET_LOCAL_OUT, 369 - net, (struct sock *)sk, skb, NULL, dev, 370 - dst_output); 369 + ret = NF_HOOK(NFPROTO_IPV6, NF_INET_LOCAL_OUT, 370 + net, (struct sock *)sk, skb, NULL, dev, 371 + dst_output); 372 + goto unlock; 371 373 } 372 374 375 + ret = -EMSGSIZE; 373 376 skb->dev = dev; 374 377 /* ipv6_local_error() does not require socket lock, 375 378 * we promote our socket to non const ··· 382 377 383 378 IP6_INC_STATS(net, idev, IPSTATS_MIB_FRAGFAILS); 384 379 kfree_skb(skb); 385 - return -EMSGSIZE; 380 + unlock: 381 + rcu_read_unlock(); 382 + return ret; 386 383 } 387 384 EXPORT_SYMBOL(ip6_xmit); 388 385