From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=pass header.oldest-pass=0 smtp.remote-ip=165.173.180.15 ARC-Seal: i=2; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=pass; t=1793170366; b=MPB4YcMbK4fY67QQvb3BalxGAGymeCNFe7xgnDBtzaIR08RkKp2+q9zx2lAAmqoucLUF EM5Wr4CyduQY4kFFke+P7BvdhX6NgbnA6/noKRzv5YzafpN9VPrJlQmWAZWTxxKhyW65I XweY3qu16+VgqkorYPHJSS2BWNqBbR4mogNA3hPxmelMCK5I+tmVeNvhWhqAfxI3wDu/V NzuatWvrWxrDBhLMswUS0HNHfYq4fi6lkh02W3MviLgKKqAWUBNPX0DrY3k2Kn6th5Hcb 95CmbWkM+2GQVrheNIlCmJRdCDPZ29XH3PnqXh/FeAaZ5Cwq3N1d2VxFNRoXLk3Y85w== ARC-Message-Signature: i=2; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793170366; h=DMARC-Filter:DKIM-Signature:Received:From:To:Cc:Subject:Date: Message-ID:X-Mailer:MIME-Version:Content-Transfer-Encoding; bh=Lzr9r/kB9PGsjX+/QhWa77JRthkHZLmh08VLPaFIXyw=; b=QilHpAe6cNNBWd/+68+nmTICuNauMYCCQniAJYEd8OveQyaEs6RytIX35Kms6PmzMOdA BIVFU/z+xUBsQ6Dx0cDD98BBRbLr864nINUNVhBcr01TTVHT3TBR5TZV55EoFRmH957rF Zy7K03FEByg20+mvdkmSG3bRiOpEXjPVHqHysgp8kug9jURzoIVMCaiqpZsO5j9IR305n 3jqXPTK8h7vIpVIUcT5s6adoI8Q7rzz8XNf+Y28qM6+AXWj9y+oYrW6xfPtRPP4id3ekc 4VvrpRx5jjP89RrCETouhZ9VOAGDcWFwQewxO3TuFWvu8af5qmSbdvZeE/EHMcY/5uw== ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass header.from=ikuyo.dev; spf=pass smtp.mailfrom=ikuyo.dev; dkim=pass header.d=ikuyo.dev header.i=eritque-arcus@ikuyo.dev header.a=rsa-sha256 header.s=zmail header.b=VXlWZuyn; arc=pass header.oldest-pass=0 smtp.remote-ip=165.173.180.15 Received: from sender6-op-o15.zoho.com (sender6-op-o15.zoho.com [165.173.180.15]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 25B45606A31A; Wed, 28 Oct 2026 06:52:45 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 25B45606A31A Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=ikuyo.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ikuyo.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key, unprotected) header.d=ikuyo.dev header.i=eritque-arcus@ikuyo.dev header.a=rsa-sha256 header.s=zmail header.b=VXlWZuyn ARC-Seal: i=1; a=rsa-sha256; t=1793170355; cv=none; d=zohomail.com; s=zohoarc; b=HT1Xz+ofkMStnXESpgd/h0Uo2sePaeKv9eWHFat0YifYx+Qhb3A7jwcyfZo9DCXbDid2LoUoe7DVEaJUlDPaPZAds8nj2I8quTMHikCOVbAdj8Lwn048z2ewgg6IlSPo4g7aB1cru21w1IrT+/cw+8v6CgAyq3b8zRJQfd+yVAk= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1793170355; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:MIME-Version:Message-ID:Subject:Subject:To:To:Message-Id:Reply-To; bh=Lzr9r/kB9PGsjX+/QhWa77JRthkHZLmh08VLPaFIXyw=; b=Sw1lLQ0dAkpUN4R3uHTrsZ2K1OnmsKIQOyPqWD3GxCAlVu85s0EfrTVmCY4u1cxOow9cOcatBLpjPvGlNi+AkXudIgWXqpPGHJFrOGQJ41vuDtVJdcW5eQKyNFlmLlqCQBUCWfC14irTprcJl4P017TZqtrLD/HBjuIZT5TxuaE= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=ikuyo.dev; spf=pass smtp.mailfrom=eritque-arcus@ikuyo.dev; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1793170355; s=zmail; d=ikuyo.dev; i=eritque-arcus@ikuyo.dev; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:MIME-Version:Content-Transfer-Encoding:Message-Id:Reply-To; bh=Lzr9r/kB9PGsjX+/QhWa77JRthkHZLmh08VLPaFIXyw=; b=VXlWZuynhRqZ9ap4ImqLVuWEMW+kDdyouUFeDEmZ3D+qwOxLVkAvFMtoDZ1m1Oi2 Jq9oVzyMNKnLzunyCQD1e/TjbcWyrFGY1lzExXOeQlkdBhHs7NgyZXLraLmn07o/Bye FbtsAJU3Nhd8Rj4t5ZckKK1PWff53tVxnVO+3b8A= Received: by smtp.zohomail.com with SMTPS id 1793170353223347.53982264335957; Tue, 27 Oct 2026 23:52:33 -0700 (PDT) From: Yiran Qiu To: Stefano Garzarella Cc: Bobby Eshleman , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Stefan Hajnoczi , virtualization@lists.linux.dev, netdev@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH RFC net] vsock: make local-transport CIDs conflict on the same port Date: Wed, 28 Oct 2026 02:52:31 -0400 Message-ID: <20261028065231.655701-1-eritque-arcus@ikuyo.dev> X-Mailer: git-send-email 2.56.0 X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit The local transport serves VMADDR_CID_LOCAL and the guest->host transport's CID (VMADDR_CID_HOST if there is none), and sends every packet with source CID VMADDR_CID_LOCAL. A port bound on either CID is therefore one port on the wire, but the bind conflict check compares full addresses, so (guest_cid, P) and (VMADDR_CID_LOCAL, P) can both be bound. Because __vsock_find_connected_socket_net() compares only the destination port (deliberately, since 990454b5a48b), a child accepted on one CID then matches packets addressed to the other. vsock_check_source() already treats these CIDs as aliases (dee44f41f206); bind() does not. An unprivileged process can use this to reset another process's connect() when that client is bound to a specific local CID. Comparing the destination CID in the lookup instead turns the reset into data injection, as vsock_check_source() accepts the rewritten source CID. Reject a bind whose port is already held on another local-transport CID. User-visible change: a guest can no longer serve the host on (guest_cid, P) and local clients on (VMADDR_CID_LOCAL, P) at once; the second bind fails with EADDRINUSE. Found with afb-afl, an LLM-driven kernel audit/repro pipeline; the fix and this changelog were also drafted with LLM assistance. Fixes: 408624af4c89 ("vsock: use local transport when it is loaded") Signed-off-by: Yiran Qiu --- Tested on net/main 4dca97647a3e (vsock unchanged through net/main 5d7f3375cd57 and mainline 65ba8f34ec88) in a QEMU/KVM guest with vhost-vsock (guest CID 3) and vsock_loopback; 3 runs per case: case before after peer on (LOCAL, P), attacker listener ECONNRESET attacker bind fails, victim connects same, attacker at another uid ECONNRESET victim connects peer on (ANY, P), no attacker listen. ECONNRESET victim connects no attacker connects connects With only a destination-CID compare instead: no reset, but the victim read 18 attacker bytes (3/3). vsock_test 44/44 with --peer-cid=1 and 3, before and after; no WARN/BUG (KASAN off). Not tested: VMCI, the host-side alias. RFC: bind time vs. lookup is your call. Reproducer available on request. net/vmw_vsock/af_vsock.c | 63 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index d0508887a2fb..47fd00fdac6d 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -294,8 +294,31 @@ static void __vsock_remove_connected(struct vsock_sock *vsk) sock_put(&vsk->sk); } +/* + * CIDs served by the local transport are aliases of one another on the wire: + * an outgoing packet uses transport->get_local_cid() as its source CID, no + * matter which of those CIDs the socket was bound to. Two binds of the same + * port on different local CIDs therefore reach the same peer and must + * conflict, just like a VMADDR_CID_ANY bind conflicts with any CID. + * + * @local_cid is the second local CID (the guest->host transport's own CID, or + * VMADDR_CID_HOST when there is no guest->host transport), or + * VMADDR_CID_ANY when the local transport is not loaded. + */ +static bool vsock_addr_aliases_local(const struct sockaddr_vm *a, + const struct sockaddr_vm *b, + u32 local_cid) +{ + return local_cid != VMADDR_CID_ANY && + a->svm_port == b->svm_port && + a->svm_cid != b->svm_cid && + (a->svm_cid == VMADDR_CID_LOCAL || a->svm_cid == local_cid) && + (b->svm_cid == VMADDR_CID_LOCAL || b->svm_cid == local_cid); +} + static struct sock *__vsock_find_bound_socket_net(struct sockaddr_vm *addr, - struct net *net) + struct net *net, + u32 local_cid) { struct vsock_sock *vsk; @@ -311,6 +334,10 @@ static struct sock *__vsock_find_bound_socket_net(struct sockaddr_vm *addr, addr->svm_cid == VMADDR_CID_ANY) && vsock_net_check_mode(sock_net(sk), net)) return sk; + + if (vsock_addr_aliases_local(addr, &vsk->local_addr, local_cid) && + vsock_net_check_mode(sock_net(sk), net)) + return sk; } return NULL; @@ -383,7 +410,7 @@ struct sock *vsock_find_bound_socket_net(struct sockaddr_vm *addr, struct sock *sk; spin_lock_bh(&vsock_table_lock); - sk = __vsock_find_bound_socket_net(addr, net); + sk = __vsock_find_bound_socket_net(addr, net, VMADDR_CID_ANY); if (sk) sock_hold(sk); @@ -727,6 +754,27 @@ static u32 vsock_registered_transport_cid(const struct vsock_transport **transpo return cid; } +/* + * Return the CID that the local transport aliases with VMADDR_CID_LOCAL, or + * VMADDR_CID_ANY when the local transport is not loaded or has no second CID. + * Must be called before taking vsock_table_lock. + */ +static u32 vsock_local_alias_cid(void) +{ + u32 cid = VMADDR_CID_ANY; + + mutex_lock(&vsock_register_mutex); + if (transport_local) { + if (transport_g2h) + cid = transport_g2h->get_local_cid(); + else + cid = VMADDR_CID_HOST; + } + mutex_unlock(&vsock_register_mutex); + + return cid; +} + bool vsock_find_cid(unsigned int cid) { if (cid == vsock_registered_transport_cid(&transport_g2h)) @@ -833,7 +881,8 @@ static void vsock_pending_work(struct work_struct *work) /**** SOCKET OPERATIONS ****/ static int __vsock_bind_connectible(struct vsock_sock *vsk, - struct sockaddr_vm *addr) + struct sockaddr_vm *addr, + u32 local_cid) { struct net *net = sock_net(sk_vsock(vsk)); struct sockaddr_vm new_addr; @@ -854,7 +903,7 @@ static int __vsock_bind_connectible(struct vsock_sock *vsk, new_addr.svm_port = net->vsock.port++; - if (!__vsock_find_bound_socket_net(&new_addr, net)) { + if (!__vsock_find_bound_socket_net(&new_addr, net, local_cid)) { found = true; break; } @@ -871,7 +920,7 @@ static int __vsock_bind_connectible(struct vsock_sock *vsk, return -EACCES; } - if (__vsock_find_bound_socket_net(&new_addr, net)) + if (__vsock_find_bound_socket_net(&new_addr, net, local_cid)) return -EADDRINUSE; } @@ -897,6 +946,7 @@ static int __vsock_bind_dgram(struct vsock_sock *vsk, static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr) { struct vsock_sock *vsk = vsock_sk(sk); + u32 local_cid; int retval; /* First ensure this socket isn't already bound. */ @@ -914,8 +964,9 @@ static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr) switch (sk->sk_socket->type) { case SOCK_STREAM: case SOCK_SEQPACKET: + local_cid = vsock_local_alias_cid(); spin_lock_bh(&vsock_table_lock); - retval = __vsock_bind_connectible(vsk, addr); + retval = __vsock_bind_connectible(vsk, addr, local_cid); spin_unlock_bh(&vsock_table_lock); break; -- 2.56.0