lkml.org 
[lkml]   [2018]   [Feb]   [9]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH 4.9 28/92] ipv6: Fix SO_REUSEPORT UDP socket with implicit sk_ipv6only
    Date
    4.9-stable review patch.  If anyone has any objections, please let me know.

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

    From: Martin KaFai Lau <kafai@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 <calvinowens@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 <kafai@fb.com>
    Signed-off-by: David S. Miller <davem@davemloft.net>
    Signed-off-by: Greg Kroah-Hartman <gregkh@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);

    \
     
     \ /
      Last update: 2018-02-09 15:15    [W:2.458 / U:0.640 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site