Message ID | 20240614200715.93150-5-kuniyu@amazon.com (mailing list archive) |
---|---|
State | Superseded |
Delegated to: | Netdev Maintainers |
Headers | show |
Series | af_unix: Remove spin_lock_nested() and convert to lock_cmp_fn. | expand |
On Fri, 2024-06-14 at 13:07 -0700, Kuniyuki Iwashima wrote: > 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> > --- > include/net/af_unix.h | 1 - > net/unix/af_unix.c | 26 +++++++++++++++++++++++++- > 2 files changed, 25 insertions(+), 2 deletions(-) > > diff --git a/include/net/af_unix.h b/include/net/af_unix.h > index b6eedf7650da..fd813ad73ab8 100644 > --- a/include/net/af_unix.h > +++ b/include/net/af_unix.h > @@ -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. > diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c > index 88f2c5d039c4..5d2728e33f3f 100644 > --- a/net/unix/af_unix.c > +++ b/net/unix/af_unix.c > @@ -143,6 +143,30 @@ 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; > + } I'm sorry for missing this before, but I'm wondering if we need to enforce even this case to be symmetric? that is, explicitly check for b state == TCP_LISTEN, ... Kent, WDYT? Thanks! Paolo
diff --git a/include/net/af_unix.h b/include/net/af_unix.h index b6eedf7650da..fd813ad73ab8 100644 --- a/include/net/af_unix.h +++ b/include/net/af_unix.h @@ -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. diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 88f2c5d039c4..5d2728e33f3f 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -143,6 +143,30 @@ 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; + } + } + /* unix_state_double_lock(): ascending address order. */ return cmp_ptr(a, b); } @@ -1585,7 +1609,7 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr *uaddr, 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;
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> --- include/net/af_unix.h | 1 - net/unix/af_unix.c | 26 +++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-)