commit 002541ef650b ("vsock: Ignore signal/timeout on connect() if
already established") stopped connect() from resetting an established
socket. The check only looks at whether sk_state is TCP_ESTABLISHED at
that moment, and the state can change while connect() sleeps.

A peer RST moves the socket to TCP_CLOSING, and it is not removed from
vsock_connected_table on that path. connect() then wakes up, fails the
check, and resets a socket that had actually connected to TCP_CLOSE and
SS_UNCONNECTED.

The socket can now be connected again while it is still on the table.
Reconnecting to an address served by a different transport makes
vsock_assign_transport() drop the transport from a live socket and free
vsk->trans, even if skbs it already sent are still in flight.
Reconnecting to the same address inserts a node that is already on the
table, provided shutdown() has cleared SOCK_DONE in between.

sock->state cannot be used for the check either. shutdown() overwrites
SS_CONNECTED with SS_DISCONNECTING.

Record on the socket that the connection completed, and check that
instead. The sk_err path after the loop does the same reset, so guard it
as well.

Fixes: d021c344051a ("VSOCK: Introduce VM Sockets")
Cc: [email protected]
Signed-off-by: Hyunwoo Kim <[email protected]>
---
 include/net/af_vsock.h   |  2 ++
 net/vmw_vsock/af_vsock.c | 12 ++++++++----
 2 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index 30046a3c20f735..d7ec976ed6e272 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -63,6 +63,8 @@ struct vsock_sock {
        struct delayed_work pending_work;
        struct delayed_work close_work;
        bool close_work_scheduled;
+       /* Set once the connection completed; never cleared. */
+       bool ever_connected;
        u32 peer_shutdown;
        bool sent_request;
        bool ignore_connecting_rst;
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index 622dbd04679944..735c91bb762242 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -351,6 +351,8 @@ void vsock_insert_connected(struct vsock_sock *vsk)
        struct list_head *list = vsock_connected_sockets(
                &vsk->remote_addr, &vsk->local_addr);
 
+       vsk->ever_connected = true;
+
        spin_lock_bh(&vsock_table_lock);
        __vsock_insert_connected(list, vsk);
        spin_unlock_bh(&vsock_table_lock);
@@ -1814,14 +1816,14 @@ static int vsock_connect(struct socket *sock, struct 
sockaddr_unsized *addr,
                 * Note that allowing to "reset" an already established socket
                 * here is racy and insecure.
                 */
-               if (sk->sk_state == TCP_ESTABLISHED)
+               if (vsk->ever_connected)
                        break;
 
                /* If connection was _not_ established and a signal/timeout came
                 * to be, we want the socket's state reset. User space may want
                 * to retry.
                 *
-                * sk_state != TCP_ESTABLISHED implies that socket is not on
+                * !ever_connected implies that socket is not on
                 * vsock_connected_table. We keep the binding and the transport
                 * assigned.
                 */
@@ -1849,8 +1851,10 @@ static int vsock_connect(struct socket *sock, struct 
sockaddr_unsized *addr,
 
        if (sk->sk_err) {
                err = -sk->sk_err;
-               sk->sk_state = TCP_CLOSE;
-               sock->state = SS_UNCONNECTED;
+               if (!vsk->ever_connected) {
+                       sk->sk_state = TCP_CLOSE;
+                       sock->state = SS_UNCONNECTED;
+               }
        } else {
                err = 0;
        }
-- 
2.43.0


Reply via email to