• Kuniyuki Iwashima's avatar
    af_unix: Don't retry after unix_state_lock_nested() in unix_stream_connect(). · 1ca27e0c
    Kuniyuki Iwashima authored
    When a SOCK_(STREAM|SEQPACKET) socket connect()s to another one, we need
    to lock the two sockets to check their states in unix_stream_connect().
    
    We use unix_state_lock() for the server and unix_state_lock_nested() for
    client with tricky sk->sk_state check to avoid deadlock.
    
    The possible deadlock scenario are the following:
    
      1) Self connect()
      2) Simultaneous connect()
    
    The former is simple, attempt to grab the same lock, and the latter is
    AB-BA deadlock.
    
    After the server's unix_state_lock(), we check the server socket's state,
    and if it's not TCP_LISTEN, connect() fails with -EINVAL.
    
    Then, we avoid the former deadlock by checking the client's state before
    unix_state_lock_nested().  If its state is not TCP_LISTEN, we can make
    sure that the client and the server are not identical based on the state.
    
    Also, the latter deadlock can be avoided in the same way.  Due to the
    server sk->sk_state requirement, AB-BA deadlock could happen only with
    TCP_LISTEN sockets.  So, if the client's state is TCP_LISTEN, we can
    give up the second lock to avoid the deadlock.
    
      CPU 1                 CPU 2                  CPU 3
      connect(A -> B)       connect(B -> A)        listen(A)
      ---                   ---                    ---
      unix_state_lock(B)
      B->sk_state == TCP_LISTEN
      READ_ONCE(A->sk_state) == TCP_CLOSE
                                ^^^^^^^^^
                                ok, will lock A    unix_state_lock(A)
                 .--------------'                  WRITE_ONCE(A->sk_state, TCP_LISTEN)
                 |                                 unix_state_unlock(A)
                 |
                 |          unix_state_lock(A)
                 |          A->sk_sk_state == TCP_LISTEN
                 |          READ_ONCE(B->sk_state) == TCP_LISTEN
                 v                                    ^^^^^^^^^^
      unix_state_lock_nested(A)                       Don't lock B !!
    
    Currently, while checking the client's state, we also check if it's
    TCP_ESTABLISHED, but this is unlikely and can be checked after we know
    the state is not TCP_CLOSE.
    
    Moreover, if it happens after the second lock, we now jump to the restart
    label, but it's unlikely that the server is not found during the retry,
    so the jump is mostly to revist the client state check.
    
    Let's remove the retry logic and check the state against TCP_CLOSE first.
    Signed-off-by: default avatarKuniyuki Iwashima <kuniyu@amazon.com>
    Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
    1ca27e0c
af_unix.c 86.7 KB