4.9-stable review patch.  If anyone has any objections, please let me know.

------------------

From: Martin KaFai Lau <ka...@fb.com>


[ Upstream commit 7ece54a60ee2ba7a386308cae73c790bd580589c ]

If a sk_v6_rcv_saddr is !IPV6_ADDR_ANY and !IPV6_ADDR_MAPPED, it
implicitly implies it is an ipv6only socket.  However, in inet6_bind(),
this addr_type checking and setting sk->sk_ipv6only to 1 are only done
after sk->sk_prot->get_port(sk, snum) has been completed successfully.

This inconsistency between sk_v6_rcv_saddr and sk_ipv6only confuses
the 'get_port()'.

In particular, when binding SO_REUSEPORT UDP sockets,
udp_reuseport_add_sock(sk,...) is called.  udp_reuseport_add_sock()
checks "ipv6_only_sock(sk2) == ipv6_only_sock(sk)" before adding sk to
sk2->sk_reuseport_cb.  In this case, ipv6_only_sock(sk2) could be
1 while ipv6_only_sock(sk) is still 0 here.  The end result is,
reuseport_alloc(sk) is called instead of adding sk to the existing
sk2->sk_reuseport_cb.

It can be reproduced by binding two SO_REUSEPORT UDP sockets on an
IPv6 address (!ANY and !MAPPED).  Only one of the socket will
receive packet.

The fix is to set the implicit sk_ipv6only before calling get_port().
The original sk_ipv6only has to be saved such that it can be restored
in case get_port() failed.  The situation is similar to the
inet_reset_saddr(sk) after get_port() has failed.

Thanks to Calvin Owens <calvinow...@fb.com> who created an easy
reproduction which leads to a fix.

Fixes: e32ea7e74727 ("soreuseport: fast reuseport UDP socket selection")
Signed-off-by: Martin KaFai Lau <ka...@fb.com>
Signed-off-by: David S. Miller <da...@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gre...@linuxfoundation.org>
---
 net/ipv6/af_inet6.c |   11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -274,6 +274,7 @@ int inet6_bind(struct socket *sock, stru
        struct net *net = sock_net(sk);
        __be32 v4addr = 0;
        unsigned short snum;
+       bool saved_ipv6only;
        int addr_type = 0;
        int err = 0;
 
@@ -378,19 +379,21 @@ int inet6_bind(struct socket *sock, stru
        if (!(addr_type & IPV6_ADDR_MULTICAST))
                np->saddr = addr->sin6_addr;
 
+       saved_ipv6only = sk->sk_ipv6only;
+       if (addr_type != IPV6_ADDR_ANY && addr_type != IPV6_ADDR_MAPPED)
+               sk->sk_ipv6only = 1;
+
        /* Make sure we are allowed to bind here. */
        if ((snum || !inet->bind_address_no_port) &&
            sk->sk_prot->get_port(sk, snum)) {
+               sk->sk_ipv6only = saved_ipv6only;
                inet_reset_saddr(sk);
                err = -EADDRINUSE;
                goto out;
        }
 
-       if (addr_type != IPV6_ADDR_ANY) {
+       if (addr_type != IPV6_ADDR_ANY)
                sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
-               if (addr_type != IPV6_ADDR_MAPPED)
-                       sk->sk_ipv6only = 1;
-       }
        if (snum)
                sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
        inet->inet_sport = htons(inet->inet_num);


Reply via email to