From: Jerome Mohm <[email protected]>
vsock_bpf_recvmsg() holds lock_sock() while vsock_msg_wait_data() sleeps
in wait_woken(). The transport delivers data in
virtio_transport_recv_pkt(), which takes the same lock, so the reader
and the delivery worker deadlock: recv() hangs until a signal or
SO_RCVTIMEO, and all vsock rx handled by that worker stops.
Release the lock around wait_woken(), as unix_bpf and tcp_bpf release
theirs. That alone ends the deadlock but makes a blocking recv()
return -EAGAIN early: the reader is also woken without data, for
example by a credit update. So the wait also loops until data arrives
or one of the native exits applies: a socket error, a local or peer
shutdown, a socket that is no longer connected, the timeout or a
pending signal. The loop alone, with the lock still held, keeps the
deadlock, so the fix needs both halves and stays one patch.
For a non-blocking read, a socket error, a peer shutdown and a socket
that is not connected are now reported before -EAGAIN, as on the native
path. A blocking read interrupted by a signal returns sock_intr_errno()
instead of -EAGAIN. A recv() that starts after a virtio transport reset
returns -ECONNRESET until SO_ERROR has read the error, where the native
path returns -ENOTCONN.
The deadlock was found with the help of Anthropic's Claude; Sashiko
reviews of two earlier sockmap patches had already noted it as a
pre-existing issue. The other cases were reported by the netdev Sashiko
review. Each was reproduced in a VM with a vsock socket in a sockmap,
without kernel changes or error injection; the reset case unbinds the
guest's virtio-vsock device. With the series applied they behave as on
a native socket, except for the reset case above, and the vsock
vmtest.sh single-VM tests and the BPF sockmap test_progs pass.
Fixes: 634f1a7110b4 ("vsock: support sockmap")
Cc: [email protected]
Signed-off-by: Jerome Mohm <[email protected]>
---
net/vmw_vsock/vsock_bpf.c | 68 +++++++++++++++++++++++++++++++++++++----------
1 file changed, 54 insertions(+), 14 deletions(-)
diff --git a/net/vmw_vsock/vsock_bpf.c b/net/vmw_vsock/vsock_bpf.c
index 9049d2648646..6d96ff03f28c 100644
--- a/net/vmw_vsock/vsock_bpf.c
+++ b/net/vmw_vsock/vsock_bpf.c
@@ -34,24 +34,63 @@ static bool vsock_has_data(struct sock *sk, struct sk_psock
*psock)
return vsock_sk_has_data(sk, psock);
}
-static bool vsock_msg_wait_data(struct sock *sk, struct sk_psock *psock, long
timeo)
+/* Returns 1 if data is ready, 0 on EOF/shutdown, or a negative error. */
+static int vsock_msg_wait_data(struct sock *sk, struct sk_psock *psock, long
timeo)
{
- bool ret;
+ struct vsock_sock *vsk = vsock_sk(sk);
+ int ret;
DEFINE_WAIT_FUNC(wait, woken_wake_function);
- if (sk->sk_shutdown & RCV_SHUTDOWN)
- return true;
-
- if (!timeo)
- return false;
-
add_wait_queue(sk_sleep(sk), &wait);
sk_set_bit(SOCKWQ_ASYNC_WAITDATA, sk);
- ret = vsock_has_data(sk, psock);
- if (!ret) {
- wait_woken(&wait, TASK_INTERRUPTIBLE, timeo);
- ret = vsock_has_data(sk, psock);
+ while (1) {
+ if (vsock_has_data(sk, psock)) {
+ ret = 1;
+ break;
+ }
+
+ /* Stop on a socket error, as vsock_connectible_wait_data()
+ * does, so that recv() returns it; the checks below would
+ * end the wait with 0 or -ENOTCONN instead.
+ */
+ if (sk->sk_err) {
+ ret = -sk->sk_err;
+ break;
+ }
+
+ if ((sk->sk_shutdown & RCV_SHUTDOWN) ||
+ (vsk->peer_shutdown & SEND_SHUTDOWN)) {
+ ret = 0;
+ break;
+ }
+
+ /* virtio_vsock_reset_sock() sets no shutdown bits, and
+ * SO_ERROR clears sk_err: check the state the way
+ * __vsock_connectible_recvmsg() does.
+ */
+ if (sk->sk_state != TCP_ESTABLISHED) {
+ ret = sock_flag(sk, SOCK_DONE) ? 0 : -ENOTCONN;
+ break;
+ }
+
+ if (!timeo) {
+ ret = -EAGAIN;
+ break;
+ }
+
+ release_sock(sk);
+ timeo = wait_woken(&wait, TASK_INTERRUPTIBLE, timeo);
+ lock_sock(sk);
+
+ /* With a signal pending, wait_woken() returns at once, so
+ * without this check the loop would spin until the timeout,
+ * or forever without one.
+ */
+ if (signal_pending(current)) {
+ ret = sock_intr_errno(timeo);
+ break;
+ }
}
sk_clear_bit(SOCKWQ_ASYNC_WAITDATA, sk);
remove_wait_queue(sk_sleep(sk), &wait);
@@ -101,9 +140,10 @@ static int vsock_bpf_recvmsg(struct sock *sk, struct
msghdr *msg,
copied = sk_msg_recvmsg(sk, psock, msg, len, flags);
while (copied == 0) {
long timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);
+ int data = vsock_msg_wait_data(sk, psock, timeo);
- if (!vsock_msg_wait_data(sk, psock, timeo)) {
- copied = -EAGAIN;
+ if (data <= 0) {
+ copied = data;
break;
}
--
2.56.0