ndisc: extend RCU protection in ndisc_send_skb()
authorEric Dumazet <edumazet@google.com>
Fri, 7 Feb 2025 13:58:39 +0000 (13:58 +0000)
committerJakub Kicinski <kuba@kernel.org>
Tue, 11 Feb 2025 02:09:10 +0000 (18:09 -0800)
ndisc_send_skb() can be called without RTNL or RCU held.

Acquire rcu_read_lock() earlier, so that we can use dev_net_rcu()
and avoid a potential UAF.

Fixes: 1762f7e88eb3 ("[NETNS][IPV6] ndisc - make socket control per namespace")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: David Ahern <dsahern@kernel.org>
Reviewed-by: Kuniyuki Iwashima <kuniyu@amazon.com>
Link: https://patch.msgid.link/20250207135841.1948589-8-edumazet@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
net/ipv6/ndisc.c

index 90f8aa2d7af2ec1103220378c155b7b724efc575..8699d1a188dc4a15ac0b65229c4dd19240c9c054 100644 (file)
@@ -471,16 +471,20 @@ static void ip6_nd_hdr(struct sk_buff *skb,
 void ndisc_send_skb(struct sk_buff *skb, const struct in6_addr *daddr,
                    const struct in6_addr *saddr)
 {
+       struct icmp6hdr *icmp6h = icmp6_hdr(skb);
        struct dst_entry *dst = skb_dst(skb);
-       struct net *net = dev_net(skb->dev);
-       struct sock *sk = net->ipv6.ndisc_sk;
        struct inet6_dev *idev;
+       struct net *net;
+       struct sock *sk;
        int err;
-       struct icmp6hdr *icmp6h = icmp6_hdr(skb);
        u8 type;
 
        type = icmp6h->icmp6_type;
 
+       rcu_read_lock();
+
+       net = dev_net_rcu(skb->dev);
+       sk = net->ipv6.ndisc_sk;
        if (!dst) {
                struct flowi6 fl6;
                int oif = skb->dev->ifindex;
@@ -488,6 +492,7 @@ void ndisc_send_skb(struct sk_buff *skb, const struct in6_addr *daddr,
                icmpv6_flow_init(sk, &fl6, type, saddr, daddr, oif);
                dst = icmp6_dst_alloc(skb->dev, &fl6);
                if (IS_ERR(dst)) {
+                       rcu_read_unlock();
                        kfree_skb(skb);
                        return;
                }
@@ -502,7 +507,6 @@ void ndisc_send_skb(struct sk_buff *skb, const struct in6_addr *daddr,
 
        ip6_nd_hdr(skb, saddr, daddr, READ_ONCE(inet6_sk(sk)->hop_limit), skb->len);
 
-       rcu_read_lock();
        idev = __in6_dev_get(dst->dev);
        IP6_INC_STATS(net, idev, IPSTATS_MIB_OUTREQUESTS);