ipv6: lockless IPV6_UNICAST_HOPS implementation
authorEric Dumazet <edumazet@google.com>
Tue, 12 Sep 2023 16:01:59 +0000 (16:01 +0000)
committerDavid S. Miller <davem@davemloft.net>
Fri, 15 Sep 2023 09:33:46 +0000 (10:33 +0100)
Some np->hop_limit accesses are racy, when socket lock is not held.

Add missing annotations and switch to full lockless implementation.

Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: David Ahern <dsahern@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/ipv6.h
include/net/ipv6.h
net/ipv6/ip6_output.c
net/ipv6/ipv6_sockglue.c
net/ipv6/mcast.c
net/ipv6/ndisc.c

index af8a771a053c51eed297516f927a5fd003315ef4..c2e0870713849fbbf1a8ec2d60cca80caab0cb98 100644 (file)
@@ -213,17 +213,7 @@ struct ipv6_pinfo {
        __be32                  flow_label;
        __u32                   frag_size;
 
-       /*
-        * Packed in 16bits.
-        * Omit one shift by putting the signed field at MSB.
-        */
-#if defined(__BIG_ENDIAN_BITFIELD)
-       __s16                   hop_limit:9;
-       __u16                   __unused_1:7;
-#else
-       __u16                   __unused_1:7;
-       __s16                   hop_limit:9;
-#endif
+       s16                     hop_limit;
 
 #if defined(__BIG_ENDIAN_BITFIELD)
        /* Packed in 16bits. */
index c6932d1a3fa806ab9d72948994dbf3b7ac742b1c..2e8e7e31e02eaf54963f100f103b48be70e82155 100644 (file)
@@ -916,7 +916,7 @@ static inline int ip6_sk_dst_hoplimit(struct ipv6_pinfo *np, struct flowi6 *fl6,
        if (ipv6_addr_is_multicast(&fl6->daddr))
                hlimit = np->mcast_hops;
        else
-               hlimit = np->hop_limit;
+               hlimit = READ_ONCE(np->hop_limit);
        if (hlimit < 0)
                hlimit = ip6_dst_hoplimit(dst);
        return hlimit;
index 54fc4c711f2c545f2ca625d6b0e09f2bb8e6d513..1e16d56d8c38ac51bd999038ae4e8478bf2f5f8c 100644 (file)
@@ -309,7 +309,7 @@ int ip6_xmit(const struct sock *sk, struct sk_buff *skb, struct flowi6 *fl6,
         *      Fill in the IPv6 header
         */
        if (np)
-               hlimit = np->hop_limit;
+               hlimit = READ_ONCE(np->hop_limit);
        if (hlimit < 0)
                hlimit = ip6_dst_hoplimit(dst);
 
index 0e2a0847b387f0f6f50211b89f92ac1e00a0b07a..f27993a1470dddd876f34f65c1f171c576eca272 100644 (file)
@@ -415,6 +415,16 @@ int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
        if (ip6_mroute_opt(optname))
                return ip6_mroute_setsockopt(sk, optname, optval, optlen);
 
+       /* Handle options that can be set without locking the socket. */
+       switch (optname) {
+       case IPV6_UNICAST_HOPS:
+               if (optlen < sizeof(int))
+                       return -EINVAL;
+               if (val > 255 || val < -1)
+                       return -EINVAL;
+               WRITE_ONCE(np->hop_limit, val);
+               return 0;
+       }
        if (needs_rtnl)
                rtnl_lock();
        sockopt_lock_sock(sk);
@@ -733,14 +743,6 @@ done:
                }
                break;
        }
-       case IPV6_UNICAST_HOPS:
-               if (optlen < sizeof(int))
-                       goto e_inval;
-               if (val > 255 || val < -1)
-                       goto e_inval;
-               np->hop_limit = val;
-               retv = 0;
-               break;
 
        case IPV6_MULTICAST_HOPS:
                if (sk->sk_type == SOCK_STREAM)
@@ -1347,7 +1349,7 @@ int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
                struct dst_entry *dst;
 
                if (optname == IPV6_UNICAST_HOPS)
-                       val = np->hop_limit;
+                       val = READ_ONCE(np->hop_limit);
                else
                        val = np->mcast_hops;
 
index 421264a69e972e1ddae67b4aaee4f83a2b2e7e09..4a7967623909412bc7e63edd0f0e20ecda901c6e 100644 (file)
@@ -1716,7 +1716,7 @@ static void ip6_mc_hdr(const struct sock *sk, struct sk_buff *skb,
 
        hdr->payload_len = htons(len);
        hdr->nexthdr = proto;
-       hdr->hop_limit = inet6_sk(sk)->hop_limit;
+       hdr->hop_limit = READ_ONCE(inet6_sk(sk)->hop_limit);
 
        hdr->saddr = *saddr;
        hdr->daddr = *daddr;
index 553c8664e0a7a37d7858393ab6a30616ab13a3bf..b554fd40bdc3787eb3bafa1d9923076d6078217e 100644 (file)
@@ -500,7 +500,7 @@ void ndisc_send_skb(struct sk_buff *skb, const struct in6_addr *daddr,
                                              csum_partial(icmp6h,
                                                           skb->len, 0));
 
-       ip6_nd_hdr(skb, saddr, daddr, inet6_sk(sk)->hop_limit, skb->len);
+       ip6_nd_hdr(skb, saddr, daddr, READ_ONCE(inet6_sk(sk)->hop_limit), skb->len);
 
        rcu_read_lock();
        idev = __in6_dev_get(dst->dev);