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

ipv6: use RCU in ip6_output()

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

We can remove rcu_read_lock()/rcu_read_unlock() pairs
from ip6_finish_output2().

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-5-edumazet@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Eric Dumazet and committed by
Jakub Kicinski
11709573 9085e565

+15 -14
+15 -14
net/ipv6/ip6_output.c
··· 60 60 static int ip6_finish_output2(struct net *net, struct sock *sk, struct sk_buff *skb) 61 61 { 62 62 struct dst_entry *dst = skb_dst(skb); 63 - struct net_device *dev = dst_dev(dst); 63 + struct net_device *dev = dst_dev_rcu(dst); 64 64 struct inet6_dev *idev = ip6_dst_idev(dst); 65 65 unsigned int hh_len = LL_RESERVED_SPACE(dev); 66 66 const struct in6_addr *daddr, *nexthop; ··· 70 70 71 71 /* Be paranoid, rather than too clever. */ 72 72 if (unlikely(hh_len > skb_headroom(skb)) && dev->header_ops) { 73 - /* Make sure idev stays alive */ 74 - rcu_read_lock(); 73 + /* idev stays alive because we hold rcu_read_lock(). */ 75 74 skb = skb_expand_head(skb, hh_len); 76 75 if (!skb) { 77 76 IP6_INC_STATS(net, idev, IPSTATS_MIB_OUTDISCARDS); 78 - rcu_read_unlock(); 79 77 return -ENOMEM; 80 78 } 81 - rcu_read_unlock(); 82 79 } 83 80 84 81 hdr = ipv6_hdr(skb); ··· 120 123 121 124 IP6_UPD_PO_STATS(net, idev, IPSTATS_MIB_OUT, skb->len); 122 125 123 - rcu_read_lock(); 124 126 nexthop = rt6_nexthop(dst_rt6_info(dst), daddr); 125 127 neigh = __ipv6_neigh_lookup_noref(dev, nexthop); 126 128 ··· 127 131 if (unlikely(!neigh)) 128 132 neigh = __neigh_create(&nd_tbl, nexthop, dev, false); 129 133 if (IS_ERR(neigh)) { 130 - rcu_read_unlock(); 131 134 IP6_INC_STATS(net, idev, IPSTATS_MIB_OUTNOROUTES); 132 135 kfree_skb_reason(skb, SKB_DROP_REASON_NEIGH_CREATEFAIL); 133 136 return -EINVAL; ··· 134 139 } 135 140 sock_confirm_neigh(skb, neigh); 136 141 ret = neigh_output(neigh, skb, false); 137 - rcu_read_unlock(); 138 142 return ret; 139 143 } 140 144 ··· 227 233 int ip6_output(struct net *net, struct sock *sk, struct sk_buff *skb) 228 234 { 229 235 struct dst_entry *dst = skb_dst(skb); 230 - struct net_device *dev = dst_dev(dst), *indev = skb->dev; 231 - struct inet6_dev *idev = ip6_dst_idev(dst); 236 + struct net_device *dev, *indev = skb->dev; 237 + struct inet6_dev *idev; 238 + int ret; 232 239 233 240 skb->protocol = htons(ETH_P_IPV6); 241 + rcu_read_lock(); 242 + dev = dst_dev_rcu(dst); 243 + idev = ip6_dst_idev(dst); 234 244 skb->dev = dev; 235 245 236 246 if (unlikely(!idev || READ_ONCE(idev->cnf.disable_ipv6))) { 237 247 IP6_INC_STATS(net, idev, IPSTATS_MIB_OUTDISCARDS); 248 + rcu_read_unlock(); 238 249 kfree_skb_reason(skb, SKB_DROP_REASON_IPV6DISABLED); 239 250 return 0; 240 251 } 241 252 242 - return NF_HOOK_COND(NFPROTO_IPV6, NF_INET_POST_ROUTING, 243 - net, sk, skb, indev, dev, 244 - ip6_finish_output, 245 - !(IP6CB(skb)->flags & IP6SKB_REROUTED)); 253 + ret = NF_HOOK_COND(NFPROTO_IPV6, NF_INET_POST_ROUTING, 254 + net, sk, skb, indev, dev, 255 + ip6_finish_output, 256 + !(IP6CB(skb)->flags & IP6SKB_REROUTED)); 257 + rcu_read_unlock(); 258 + return ret; 246 259 } 247 260 EXPORT_SYMBOL(ip6_output); 248 261