af_unix: Define locking order for U_LOCK_SECOND in unix_stream_connect().

While a SOCK_(STREAM|SEQPACKET) socket connect()s to another, we hold
two locks of them by unix_state_lock() and unix_state_lock_nested() in
unix_stream_connect().

Before unix_state_lock_nested(), the following is guaranteed by checking
sk->sk_state:

  1. The first socket is TCP_LISTEN
  2. The second socket is not the first one
  3. Simultaneous connect() must fail

So, the client state can be TCP_CLOSE or TCP_LISTEN or TCP_ESTABLISHED.

Let's define the expected states as unix_state_lock_cmp_fn() instead of
using unix_state_lock_nested().

Note that 2. is detected by debug_spin_lock_before() and 3. cannot be
expressed as lock_cmp_fn.

Signed-off-by: Kuniyuki Iwashima <kuniyu@amazon.com>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Kuniyuki Iwashima 2024-06-20 13:56:16 -07:00 committed by Paolo Abeni
parent 1ca27e0c8c
commit 98f706de44
2 changed files with 36 additions and 2 deletions

View File

@ -98,7 +98,6 @@ struct unix_sock {
#define unix_state_unlock(s) spin_unlock(&unix_sk(s)->lock)
enum unix_socket_lock_class {
U_LOCK_NORMAL,
U_LOCK_SECOND, /* for double locking, see unix_state_double_lock(). */
U_LOCK_DIAG, /* used while dumping icons, see sk_diag_dump_icons(). */
U_LOCK_GC_LISTENER, /* used for listening socket while determining gc
* candidates to close a small race window.

View File

@ -143,6 +143,41 @@ static int unix_state_lock_cmp_fn(const struct lockdep_map *_a,
a = container_of(_a, struct unix_sock, lock.dep_map);
b = container_of(_b, struct unix_sock, lock.dep_map);
if (a->sk.sk_state == TCP_LISTEN) {
/* unix_stream_connect(): Before the 2nd unix_state_lock(),
*
* 1. a is TCP_LISTEN.
* 2. b is not a.
* 3. concurrent connect(b -> a) must fail.
*
* Except for 2. & 3., the b's state can be any possible
* value due to concurrent connect() or listen().
*
* 2. is detected in debug_spin_lock_before(), and 3. cannot
* be expressed as lock_cmp_fn.
*/
switch (b->sk.sk_state) {
case TCP_CLOSE:
case TCP_ESTABLISHED:
case TCP_LISTEN:
return -1;
default:
/* Invalid case. */
return 0;
}
}
/* Should never happen. Just to be symmetric. */
if (b->sk.sk_state == TCP_LISTEN) {
switch (b->sk.sk_state) {
case TCP_CLOSE:
case TCP_ESTABLISHED:
return 1;
default:
return 0;
}
}
/* unix_state_double_lock(): ascending address order. */
return cmp_ptr(a, b);
}
@ -1585,7 +1620,7 @@ restart:
goto out_unlock;
}
unix_state_lock_nested(sk, U_LOCK_SECOND);
unix_state_lock(sk);
if (unlikely(sk->sk_state != TCP_CLOSE)) {
err = sk->sk_state == TCP_ESTABLISHED ? -EISCONN : -EINVAL;