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

Paolo Abeni <[email protected]>
Newsgroups dev.linux.lists.virtualization,org.kernel.vger.kvm,org.kernel.vger.linux-kernel,org.kernel.vger.netdev
Message-ID <[email protected]>
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/[email protected]/

> 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.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.