diff options
| author | Paolo Abeni <pabeni@redhat.com> | 2026-08-18 14:58:55 +0200 |
|---|---|---|
| committer | Paolo Abeni <pabeni@redhat.com> | 2026-08-18 14:58:55 +0200 |
| commit | a3dee9bb902ee4357fa02e49b415d7724ee0140a (patch) | |
| tree | d0b5f65a3f36aa9665c563c6c0058a3c8cd08f96 | |
| parent | e36ce6e78fe3fc3c071a26750783b7ba081ce10d (diff) | |
| parent | 96cbf89993091a163bfedec52a3bd683dc94b3b4 (diff) | |
Merge branch 'vsock-fix-stale-sk_err-handling-after-a-failed-connect'
Nguyen Dinh Phi says:
====================
vsock: fix stale sk_err handling after a failed connect
A socket whose connect() failed keeps sk_err set. If that socket is
later reused as a listener, vsock_accept() rejects an unrelated
incoming connection, and on virtio/hyperv the resulting child socket
is leaked.
Patch 1 removes the listener's sk_err check from vsock_accept(), since
no vsock transport ever sets sk_err on a TCP_LISTEN socket. This will
fix what the syzbot reported.
Patch 2 removes vsock_sock.rejected, now unreachable after patch 1.
Patch 3 is a related but separate fix: vsock_connect() now consumes
sk_err via sock_error() once it has been returned to userspace, so a
failed blocking connect() doesn't keep reporting the same error a
second time.
====================
Link: https://patch.msgid.link/20260813173024.2362935-1-phind.uet@gmail.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
| -rw-r--r-- | include/net/af_vsock.h | 5 | ||||
| -rw-r--r-- | net/vmw_vsock/af_vsock.c | 62 |
2 files changed, 21 insertions, 46 deletions
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h index 30046a3c20f7..3357ee62d10b 100644 --- a/include/net/af_vsock.h +++ b/include/net/af_vsock.h @@ -52,13 +52,10 @@ struct vsock_sock { * The listening socket is the head for both lists. Sockets created * for connection requests are placed in the pending list until they * are connected, at which point they are put in the accept queue list - * so they can be accepted in accept(). If accept() cannot accept the - * connection, it is marked as rejected so the cleanup function knows - * to clean up the socket. + * so they can be accepted in accept(). */ struct list_head pending_links; struct list_head accept_queue; - bool rejected; struct delayed_work connect_work; struct delayed_work pending_work; struct delayed_work close_work; diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index 622dbd046799..e89cb84b8d73 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -38,10 +38,9 @@ * pending socket. When that socket reaches the connected state, it is removed * from the listener socket's pending list and enqueued in the listener * socket's accept queue. Callers of accept(2) will accept connected sockets - * from the listener socket's accept queue. If the socket cannot be accepted - * for some reason then it is marked rejected. Once the connection is - * accepted, it is owned by the user process and the responsibility for cleanup - * falls with that user process. + * from the listener socket's accept queue. Once the connection is accepted, + * it is owned by the user process and the responsibility for cleanup falls + * with that user process. * * - It is possible that these pending sockets will never reach the connected * state; in fact, we may never receive another packet after the connection @@ -49,9 +48,7 @@ * future, after some amount of time passes where a connection should have been * established. This function ensures that the socket is off all lists so it * cannot be retrieved, then drops all references to the socket so it is cleaned - * up (sock_put() -> sk_free() -> our sk_destruct implementation). Note this - * function will also cleanup rejected sockets, those that reach the connected - * state but leave it before they have been accepted. + * up (sock_put() -> sk_free() -> our sk_destruct implementation). * * - Lock ordering for pending or accept queue sockets is: * @@ -774,11 +771,10 @@ static void vsock_pending_work(struct work_struct *work) if (vsock_is_pending(sk)) { vsock_remove_pending(listener, sk); - } else if (!vsk->rejected) { - /* We are not on the pending list and accept() did not reject - * us, so we must have been accepted by our user process. We - * just need to drop our references to the sockets and be on - * our way. + } else { + /* We are not on the pending list so we must have been accepted + * by our user process. We just need to drop our references to + * the sockets and be on our way. */ cleanup = false; goto out; @@ -942,7 +938,6 @@ static struct sock *__vsock_create(struct net *net, vsk->listener = NULL; INIT_LIST_HEAD(&vsk->pending_links); INIT_LIST_HEAD(&vsk->accept_queue); - vsk->rejected = false; vsk->sent_request = false; vsk->ignore_connecting_rst = false; WRITE_ONCE(vsk->peer_shutdown, 0); @@ -1847,12 +1842,10 @@ static int vsock_connect(struct socket *sock, struct sockaddr_unsized *addr, prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE); } - if (sk->sk_err) { - err = -sk->sk_err; + err = sock_error(sk); + if (err) { sk->sk_state = TCP_CLOSE; sock->state = SS_UNCONNECTED; - } else { - err = 0; } out_wait: @@ -1893,7 +1886,7 @@ static int vsock_accept(struct socket *sock, struct socket *newsock, timeout = sock_rcvtimeo(listener, arg->flags & O_NONBLOCK); while ((connected = vsock_dequeue_accept(listener)) == NULL && - listener->sk_err == 0 && timeout != 0) { + timeout != 0) { prepare_to_wait(sk_sleep(listener), &wait, TASK_INTERRUPTIBLE); release_sock(listener); timeout = schedule_timeout(timeout); @@ -1906,38 +1899,23 @@ static int vsock_accept(struct socket *sock, struct socket *newsock, } } - if (listener->sk_err) { - err = -listener->sk_err; - } else if (!connected) { + if (!connected) { err = -EAGAIN; - } - - if (connected) { + } else { sk_acceptq_removed(listener); lock_sock_nested(connected, SINGLE_DEPTH_NESTING); vconnected = vsock_sk(connected); - /* If the listener socket has received an error, then we should - * reject this socket and return. Note that we simply mark the - * socket rejected, drop our reference, and let the cleanup - * function handle the cleanup; the fact that we found it in - * the listener's accept queue guarantees that the cleanup - * function hasn't run yet. - */ - if (err) { - vconnected->rejected = true; - } else { - newsock->state = SS_CONNECTED; - sock_graft(connected, newsock); + newsock->state = SS_CONNECTED; + sock_graft(connected, newsock); - set_bit(SOCK_CUSTOM_SOCKOPT, - &connected->sk_socket->flags); + set_bit(SOCK_CUSTOM_SOCKOPT, + &connected->sk_socket->flags); - if (vsock_msgzerocopy_allow(vconnected->transport)) - set_bit(SOCK_SUPPORT_ZC, - &connected->sk_socket->flags); - } + if (vsock_msgzerocopy_allow(vconnected->transport)) + set_bit(SOCK_SUPPORT_ZC, + &connected->sk_socket->flags); release_sock(connected); sock_put(connected); |
