Re: [PATCH net v3 2/2] vsock/vmci: validate packet source for connected sockets

From: Paolo Abeni

Date: Tue Aug 25 2026 - 05:23:51 EST


This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/
---
vsock/vmci: validate packet source for connected sockets

This adds a vsock_check_source() test to the VMCI stream receive paths so
that a non-listening socket found by the destination-only bound-table
lookup no longer processes packets from a context other than its stored
peer. The check is applied both under bh_lock_sock() in
vmci_transport_recv_stream_cb() and after lock_sock() in
vmci_transport_recv_pkt_work().

Closes: https://lore.kernel.org/netdev/20260814121255.6B5001F000E9@xxxxxxxxxxxxxxx/

> diff --git a/net/vmw_vsock/vmci_transport.c b/net/vmw_vsock/vmci_transport.c
> index 1c4ee039c1662..b612a9893856a 100644
> --- a/net/vmw_vsock/vmci_transport.c
> +++ b/net/vmw_vsock/vmci_transport.c

[ ... ]

> @@ -765,17 +767,26 @@ static int vmci_transport_recv_stream_cb(void *data, struct vmci_datagram *dg)
> bh_lock_sock(sk);
>
> if (!sock_owned_by_user(sk)) {
> - /* The local context ID may be out of date, update it. */
> - vsk->local_addr.svm_cid = dst.svm_cid;
> + if (sk->sk_state != TCP_LISTEN &&
> + !vsock_check_source(vsk, &vmci_transport, &src)) {
> + drop_pkt = true;
> + err = VMCI_ERROR_NO_ACCESS;
> + } else {
> + /* The local context ID may be out of date, update it. */
> + vsk->local_addr.svm_cid = dst.svm_cid;
>
> - if (sk->sk_state == TCP_ESTABLISHED)
> - vmci_trans(vsk)->notify_ops->handle_notify_pkt(
> - sk, pkt, true, &dst, &src,
> - &bh_process_pkt);
> + if (sk->sk_state == TCP_ESTABLISHED)
> + vmci_trans(vsk)->notify_ops->handle_notify_pkt(sk, pkt, true,
> + &dst, &src,
> + &bh_process_pkt);
> + }
> }
>
> bh_unlock_sock(sk);
>
> + if (drop_pkt)
> + goto out;
> +

[Severity: Medium]
Should these two new exits still emit an RST before dropping the packet?

Consider a stream socket that called bind() but never listen().
__vsock_bind_connectible() puts it in the bound table with sk_state
TCP_CLOSE, remote_addr left as {VMADDR_CID_ANY, VMADDR_PORT_ANY}, and
vsk->transport still NULL, because vsock_assign_transport() is only
called for connectible sockets from vsock_connect() (and at create time
for SOCK_DGRAM).

A REQUEST from a remote context to that port misses
vsock_find_connected_socket(), is matched by vsock_find_bound_socket(),
and then reaches the new check. vsock_check_source() fails on its very
first test:

net/vmw_vsock/af_vsock.c:vsock_check_source() {
if (vsk->transport != transport)
return false;
...
}

So drop_pkt is set and "goto out" skips the kmalloc/schedule_work()
handoff entirely. vmci_transport_send_reset_bh() is only reached on the
"no socket found" and allocation-failure paths, so nothing is sent back.

The same happens on the deferred path below, where the new "goto out"
jumps over the whole sk_state switch, including the default: arm whose
in-tree comment says the reset is required:

> @@ -908,6 +920,10 @@ static void vmci_transport_recv_pkt_work(struct work_struct *work)
> pkt = &recv_pkt_info->pkt;
>
> lock_sock(sk);
> + vsock_addr_init(&src, pkt->dg.src.context, pkt->src_port);
> + if (sk->sk_state != TCP_LISTEN &&
> + !vsock_check_source(vsock_sk(sk), &vmci_transport, &src))
> + goto out;
>
> /* The local context ID may be out of date. */
> vsock_sk(sk)->local_addr.svm_cid = pkt->dg.dst.context;

net/vmw_vsock/vmci_transport.c:vmci_transport_recv_pkt_work() {
...
default:
/* Because this function does not run in the same context as
* vmci_transport_recv_stream_cb it is possible that the
* socket has closed. We need to let the other side know or it
* could be sitting in a connect and hang forever. Send a
* reset to prevent that.
*/
vmci_transport_send_reset(sk, pkt);
break;
}

Before this change that arm did transmit an RST for exactly this case,
since vmci_transport_send_reset() handles an unbound remote_addr by
replying to the packet's own source:

net/vmw_vsock/vmci_transport.c:vmci_transport_send_reset() {
...
if (vsock_addr_bound(&vsk->remote_addr)) {
dst_ptr = &vsk->remote_addr;
} else {
vsock_addr_init(&dst, pkt->dg.src.context,
pkt->src_port);
dst_ptr = &dst;
}
...
}

With the reply gone, does the remote connect() now block for
vsk->connect_timeout and return -ETIMEDOUT instead of -ECONNRESET? The
same loss of the reply would apply to a socket disconnected via
connect(AF_UNSPEC), which resets remote_addr to {VMADDR_CID_ANY,
VMADDR_PORT_ANY}.

Returning VMCI_ERROR_NO_ACCESS from the datagram callback does not turn
into a protocol reply, so is there anything left that notifies the peer?

For comparison, the virtio side runs the same source test but resets the
sender before dropping:

net/vmw_vsock/virtio_transport_common.c:virtio_transport_recv_pkt() {
...
if (sock_flag(sk, SOCK_DONE) ||
(sk->sk_state != TCP_LISTEN &&
!vsock_check_source(vsk, &t->transport, &src))) {
(void)virtio_transport_reset_no_sock(t, skb, net);
...
}

Would it make sense to follow that convention here, and to mention the
new drop-without-reply semantics in the changelog?
--
This is an AI-generated review.