* [PATCH net-next v5 0/7] vsock: assign the guest vsock device to a network namespace
@ 2026-11-04 1:53 Bobby Eshleman
2026-11-04 1:53 ` [PATCH net-next v5 1/7] vsock: constify the transport in vsock_for_each_connected_socket() Bobby Eshleman
` (6 more replies)
0 siblings, 7 replies; 10+ messages in thread
From: Bobby Eshleman @ 2026-11-04 1:53 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Shuah Khan, Randy Dunlap, Donald Hunter, Eric Dumazet,
Eric Dumazet
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman, Bobby Eshleman
vsock network namespaces let a host put each VM in a namespace of its
own. A guest has no equivalent yet. It has a single G2H device that
cannot be assigned to a network namespace.
This series lets a guest move that device into a network namespace. A
new generic netlink family, "vsock", supports the command
VSOCK_CMD_DEV_NETNS_SET which assigns the device to the namespace the
request was sent from. The namespace's existing ns_mode then decides who
may use it: a "global" namespace shares the device with every other
global namespace, and a "local" namespace keeps the host connection to
itself. The device starts out in the initial namespace, so until the
command is issued nothing has moved and no mode has changed. There is no
explicit unassign as assigning the device back to the initial namespace
is equivalent.
The command requires CAP_NET_ADMIN in the initial user namespace, so
that an unprivileged user namespace cannot claim the device.
Connections that can no longer reach the device after a move are reset,
so that a namespace which has lost access cannot keep using a socket it
opened while it still had access. The assignment holds a reference on
the namespace, so deleting that namespace does not move the device; only
another assignment does.
Transports opt in through a new netns_assign_allow flag. Only
virtio-vsock sets it here, because it is the only guest transport
that I am able to test against.
Based off of Stefano's original series:
https://lore.kernel.org/all/20200116172428.311437-1-sgarzare@redhat.com/
Suggested-by: Stefano Garzarella <sgarzare@redhat.com>
Link: https://lore.kernel.org/all/20200427142518.uwssa6dtasrp3bfc@steredhat/
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Changes in v5:
- Rename the uapi header to linux/vsock_netlink.h (Stefano)
- Drop the VSOCK_CMD_DEV_NETNS_SET reply and rely on the netlink ack
(Stefano)
- Keep vsock_register_mutex static, export vsock_cid_lock() and
vsock_cid_unlock() for vhost_vsock_set_cid() instead (Stefano)
- Replace vsock_g2h_next_unreachable() with a generic
vsock_next_connected_socket() next to vsock_for_each_connected_socket(),
which cannot be reused because the sweep takes lock_sock() (Stefano)
- Use rcu_dereference_protected() in vsock_nl_dev_netns_fill(), drop the
RCU read lock and allocate the nsid with GFP_KERNEL (Stefano)
- Point to the vsock_g2h_assign_mutex comment for the lock ordering in
vsock_g2h_net_assign() (Stefano)
- Reverse xmas tree in vsock_dev_do_ioctl() (Stefano)
- Set SOCK_DONE and peer_shutdown on swept sockets, so recv() returns EOF
and send() fails with -EPIPE/-ECONNRESET instead of -ENOTCONN (Sashiko)
- Only move sock->state back to SS_UNCONNECTED in vsock_connect() if it is
still SS_CONNECTING, via the new vsock_connect_close(), so a socket
swept while connect() sleeps cannot be connect()ed again and have
vsk->trans freed under in-flight skbs (Michal)
- Send the RSTs in virtio_transport_recv_listen() and
virtio_transport_recv_connecting() to the netns the packet arrived on,
not the socket's: after an assign the socket's netns may no longer
reach the device, so the RST would be dropped by the reachability
check this patch adds to virtio_transport_send_pkt() (Stefano, Sashiko)
- Pick up Stefano's RoBs on patches 2 and 3
- Link to v4: https://lore.kernel.org/r/20261020-vsock-guest-ns-v4-0-047527cfccfc@meta.com
Changes in v4:
- All Sashiko fixes (see individual patch change lists)
- Link to v3: https://lore.kernel.org/r/20261012-vsock-guest-ns-v3-0-b32447c66b23@meta.com
Changes in v3:
- Lots of fixes for race conditions found by local and upstream Sashiko.
Sorry for the delayed reposting, it took significant time to track
down and confirm that these were real issues, and come up with a
decent solution for each...
- Pin the assigned netns instead of returning the device to init_net
when it is deleted, and refuse an assign that would make the device's
CID collide with a nested H2G vhost-vsock with the same CID
- Drop the idea of sending RST to the peer from the reset sweep. Sending
a RST required a lock_sock, which can't be acquired underneath the
vsock_table_lock. Splitting into two phases of locking opened up new
races, I couldn't find a solution I was really happy with. Probably
not worth the complexity
- New prep patch passing the netns to ->has_remote_cid(), which is used
in a later patch in the series for avoid CID collisions between nested
H2G guests and the G2H device
- Various fixes (see individual patch change lists for more details)
- Link to v2: https://lore.kernel.org/r/20260921-vsock-guest-ns-v2-0-693bd78fde9e@meta.com
Changes in v2:
- Replace the IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS ioctl with the "vsock"
genl family and VSOCK_CMD_DEV_NETNS_SET
- RST the peer from the reset sweep, v1 only set TCP_CLOSE locally, so
the peer waited on a connection the guest had abandoned (Stefano)
- Drop the vsock_assign_g2h_netns helper patch, the tests use ynl cli.py
- Note why only virtio-vsock opts in (Stefano)
- Add a getter to the uAPI
- Various fixes (see individual patch change list for more details)
- Link to v1: https://lore.kernel.org/r/20260902-vsock-guest-ns-v1-0-9995383e9a8b@meta.com
---
Bobby Eshleman (7):
vsock: constify the transport in vsock_for_each_connected_socket()
vsock: rename the vsock pernet operations
vsock: pass the netns to ->has_remote_cid()
vsock: add a netlink command to assign the g2h device to a netns
vsock/virtio: support guest device network namespace
selftests/vsock: test the guest vsock device network namespace
selftests/vsock: test the netns assign privilege checks
Documentation/admin-guide/sysctl/net.rst | 42 +++
Documentation/netlink/specs/vsock.yaml | 72 ++++
MAINTAINERS | 2 +
drivers/vhost/vsock.c | 23 +-
include/net/af_vsock.h | 18 +-
include/uapi/linux/vsock_netlink.h | 28 ++
net/vmw_vsock/Makefile | 2 +-
net/vmw_vsock/af_vsock.c | 523 +++++++++++++++++++++++++++--
net/vmw_vsock/virtio_transport.c | 34 +-
net/vmw_vsock/virtio_transport_common.c | 40 ++-
net/vmw_vsock/vsock_nl_gen.c | 36 ++
net/vmw_vsock/vsock_nl_gen.h | 20 ++
tools/net/ynl/Makefile.deps | 1 +
tools/testing/selftests/vsock/Makefile | 5 +
tools/testing/selftests/vsock/config | 1 +
tools/testing/selftests/vsock/vmtest.sh | 550 ++++++++++++++++++++++++++++++-
16 files changed, 1326 insertions(+), 71 deletions(-)
---
base-commit: 599d0e97610f09162d84c4ede4164628c066921e
change-id: 20260831-vsock-guest-ns-d06af451da67
Best regards,
--
Bobby Eshleman <bobbyeshleman@meta.com>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v5 1/7] vsock: constify the transport in vsock_for_each_connected_socket()
2026-11-04 1:53 [PATCH net-next v5 0/7] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
@ 2026-11-04 1:53 ` Bobby Eshleman
2026-11-04 1:53 ` [PATCH net-next v5 2/7] vsock: rename the vsock pernet operations Bobby Eshleman
` (5 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Bobby Eshleman @ 2026-11-04 1:53 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Shuah Khan, Randy Dunlap, Donald Hunter, Eric Dumazet,
Eric Dumazet
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman, Bobby Eshleman
Allow const transports to be passed too. The function only compares the
pointer against vsk->transport, which is itself const, and never writes
through it.
No functional change.
Reviewed-by: Stefano Garzarella <sgarzare@redhat.com>
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
include/net/af_vsock.h | 2 +-
net/vmw_vsock/af_vsock.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index 5549298c1ec6..370fcd3ddabc 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -233,7 +233,7 @@ bool vsock_check_source(const struct vsock_sock *vsk,
const struct vsock_transport *transport,
const struct sockaddr_vm *src);
void vsock_remove_sock(struct vsock_sock *vsk);
-void vsock_for_each_connected_socket(struct vsock_transport *transport,
+void vsock_for_each_connected_socket(const struct vsock_transport *transport,
void (*fn)(struct sock *sk));
int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk);
bool vsock_find_cid(unsigned int cid);
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index 483af15a5109..5a0ba068bfd1 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -480,7 +480,7 @@ void vsock_remove_sock(struct vsock_sock *vsk)
}
EXPORT_SYMBOL_GPL(vsock_remove_sock);
-void vsock_for_each_connected_socket(struct vsock_transport *transport,
+void vsock_for_each_connected_socket(const struct vsock_transport *transport,
void (*fn)(struct sock *sk))
{
int i;
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v5 2/7] vsock: rename the vsock pernet operations
2026-11-04 1:53 [PATCH net-next v5 0/7] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
2026-11-04 1:53 ` [PATCH net-next v5 1/7] vsock: constify the transport in vsock_for_each_connected_socket() Bobby Eshleman
@ 2026-11-04 1:53 ` Bobby Eshleman
2026-11-04 1:53 ` [PATCH net-next v5 3/7] vsock: pass the netns to ->has_remote_cid() Bobby Eshleman
` (4 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Bobby Eshleman @ 2026-11-04 1:53 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Shuah Khan, Randy Dunlap, Donald Hunter, Eric Dumazet,
Eric Dumazet
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman, Bobby Eshleman
vsock_sysctl_ops and its two callbacks are named for the sysctl tables
they register, but the init callback already does more than that: it
also initialises the per-namespace vsock state through vsock_net_init().
Rename them to vsock_pernet_ops, vsock_pernet_init and
vsock_pernet_exit, so that later per-namespace work can be placed here
too.
No functional change.
Reviewed-by: Stefano Garzarella <sgarzare@redhat.com>
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
net/vmw_vsock/af_vsock.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index 5a0ba068bfd1..aa4732c3599f 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -3037,7 +3037,7 @@ static void vsock_net_init(struct net *net)
net->vsock.g2h_fallback = 1;
}
-static __net_init int vsock_sysctl_init_net(struct net *net)
+static __net_init int vsock_pernet_init(struct net *net)
{
vsock_net_init(net);
@@ -3047,14 +3047,14 @@ static __net_init int vsock_sysctl_init_net(struct net *net)
return 0;
}
-static __net_exit void vsock_sysctl_exit_net(struct net *net)
+static __net_exit void vsock_pernet_exit(struct net *net)
{
vsock_sysctl_unregister(net);
}
-static struct pernet_operations vsock_sysctl_ops = {
- .init = vsock_sysctl_init_net,
- .exit = vsock_sysctl_exit_net,
+static struct pernet_operations vsock_pernet_ops = {
+ .init = vsock_pernet_init,
+ .exit = vsock_pernet_exit,
};
static int __init vsock_init(void)
@@ -3084,7 +3084,7 @@ static int __init vsock_init(void)
goto err_unregister_proto;
}
- if (register_pernet_subsys(&vsock_sysctl_ops)) {
+ if (register_pernet_subsys(&vsock_pernet_ops)) {
err = -ENOMEM;
goto err_unregister_sock;
}
@@ -3108,7 +3108,7 @@ static void __exit vsock_exit(void)
misc_deregister(&vsock_device);
sock_unregister(AF_VSOCK);
proto_unregister(&vsock_proto);
- unregister_pernet_subsys(&vsock_sysctl_ops);
+ unregister_pernet_subsys(&vsock_pernet_ops);
}
const struct vsock_transport *vsock_core_get_transport(struct vsock_sock *vsk)
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v5 3/7] vsock: pass the netns to ->has_remote_cid()
2026-11-04 1:53 [PATCH net-next v5 0/7] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
2026-11-04 1:53 ` [PATCH net-next v5 1/7] vsock: constify the transport in vsock_for_each_connected_socket() Bobby Eshleman
2026-11-04 1:53 ` [PATCH net-next v5 2/7] vsock: rename the vsock pernet operations Bobby Eshleman
@ 2026-11-04 1:53 ` Bobby Eshleman
2026-11-04 1:53 ` [PATCH net-next v5 4/7] vsock: add a netlink command to assign the g2h device to a netns Bobby Eshleman
` (3 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Bobby Eshleman @ 2026-11-04 1:53 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Shuah Khan, Randy Dunlap, Donald Hunter, Eric Dumazet,
Eric Dumazet
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman, Bobby Eshleman
The transports only use the socket to find its network namespace. Pass
the namespace instead, so that a later patch can ask whether a CID is
taken in a namespace without necessarily having a socket on hand. This
will help with avoiding CID collisions when devices move between
namespaces.
No functional change.
Reviewed-by: Stefano Garzarella <sgarzare@redhat.com>
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
drivers/vhost/vsock.c | 4 +---
include/net/af_vsock.h | 2 +-
net/vmw_vsock/af_vsock.c | 6 ++++--
net/vmw_vsock/virtio_transport.c | 2 +-
4 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
index abed1fbcf66c..01f8930ca51f 100644
--- a/drivers/vhost/vsock.c
+++ b/drivers/vhost/vsock.c
@@ -91,10 +91,8 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid, struct net *net)
return NULL;
}
-static bool vhost_transport_has_remote_cid(struct vsock_sock *vsk, u32 cid)
+static bool vhost_transport_has_remote_cid(struct net *net, u32 cid)
{
- struct sock *sk = sk_vsock(vsk);
- struct net *net = sock_net(sk);
bool found;
rcu_read_lock();
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index 370fcd3ddabc..1c38a27fadf2 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -183,7 +183,7 @@ struct vsock_transport {
* CIDs via the hypervisor (i.e. supports the fallback overlay). VMCI
* does not implement this as it only serves CIDs 0 and 2.
*/
- bool (*has_remote_cid)(struct vsock_sock *vsk, u32 remote_cid);
+ bool (*has_remote_cid)(struct net *net, u32 remote_cid);
/* Read a single skb */
int (*read_skb)(struct vsock_sock *, skb_read_actor_t);
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index aa4732c3599f..c9b28e34b1b3 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -633,11 +633,13 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
new_transport = transport_g2h;
else if (transport_h2g &&
(!transport_h2g->has_remote_cid ||
- transport_h2g->has_remote_cid(vsk, remote_cid)))
+ transport_h2g->has_remote_cid(sock_net(sk),
+ remote_cid)))
new_transport = transport_h2g;
else if (sock_net(sk)->vsock.g2h_fallback &&
transport_g2h && transport_g2h->has_remote_cid &&
- transport_g2h->has_remote_cid(vsk, remote_cid)) {
+ transport_g2h->has_remote_cid(sock_net(sk),
+ remote_cid)) {
vsk->remote_addr.svm_flags |= VMADDR_FLAG_TO_HOST;
new_transport = transport_g2h;
} else {
diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
index 4f9aa9c4c3aa..eced6929405d 100644
--- a/net/vmw_vsock/virtio_transport.c
+++ b/net/vmw_vsock/virtio_transport.c
@@ -548,7 +548,7 @@ bool virtio_transport_stream_allow(struct vsock_sock *vsk, u32 cid, u32 port)
static bool virtio_transport_seqpacket_allow(struct vsock_sock *vsk,
u32 remote_cid);
-static bool virtio_transport_has_remote_cid(struct vsock_sock *vsk, u32 cid)
+static bool virtio_transport_has_remote_cid(struct net *net, u32 cid)
{
/* The CID could be implemented by the host. Always assume it is. */
return true;
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v5 4/7] vsock: add a netlink command to assign the g2h device to a netns
2026-11-04 1:53 [PATCH net-next v5 0/7] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
` (2 preceding siblings ...)
2026-11-04 1:53 ` [PATCH net-next v5 3/7] vsock: pass the netns to ->has_remote_cid() Bobby Eshleman
@ 2026-11-04 1:53 ` Bobby Eshleman
2026-11-05 1:54 ` sashiko-bot
2026-11-04 1:53 ` [PATCH net-next v5 5/7] vsock/virtio: support guest device network namespace Bobby Eshleman
` (2 subsequent siblings)
6 siblings, 1 reply; 10+ messages in thread
From: Bobby Eshleman @ 2026-11-04 1:53 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Shuah Khan, Randy Dunlap, Donald Hunter, Eric Dumazet,
Eric Dumazet
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman, Bobby Eshleman
Namespaces let a host isolate a VM's vsock traffic to a specific
namespace, but in a guest vsock traffic cannot be isolated to a
namespace. The vsock device is hardcoded to global mode and can't be
moved into a local-mode namespace.
Introduce a vsock generic netlink family with VSOCK_CMD_DEV_NETNS_SET,
which moves the device to the namespace the request was sent from.
VSOCK_CMD_DEV_NETNS_GET reads the assignment back, reporting the
namespace as an nsid relative to the caller. VSOCK_CMD_DEV_NETNS_SET
only returns the netlink ack.
The command requires CAP_NET_ADMIN in the initial user namespace. A
privileged user wishing to "unassign" the device can move it to the
init_net, which is hardcoded to global mode (so no unassign call is
necessary).
Connected sockets are closed with ECONNRESET when the underlying device
moves to a different namespace, so as to prevent reachability from the
previous and now disallowed namespace.
Device netns assignment is rejected when it would cause a CID collision
(e.g., moving a g2h device with CID 10 into a namespace with a nested
H2G VM with CID 10). On namespace destruction, the netdev approach
cannot be emulated. A netdev's IP addresses are removed before it
returns to init_net, which makes an address collision impossible, but
the vsock device keeps its CID. Instead, namespace destruction is
deferred until the device has moved. Again, CAP_NET_ADMIN in the init
user namespace is required for the assignment.
Suggested-by: Stefano Garzarella <sgarzare@redhat.com>
Link: https://lore.kernel.org/all/20200427142518.uwssa6dtasrp3bfc@steredhat/
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Changes in v5:
- Rename the uapi header to linux/vsock_netlink.h (Stefano)
- Drop the VSOCK_CMD_DEV_NETNS_SET reply and rely on the netlink ack
(Stefano)
- Keep vsock_register_mutex static, export vsock_cid_lock() and
vsock_cid_unlock() for vhost_vsock_set_cid() instead (Stefano)
- Replace vsock_g2h_next_unreachable() with a generic
vsock_next_connected_socket() next to vsock_for_each_connected_socket(),
which cannot be reused because the sweep takes lock_sock() (Stefano)
- Use rcu_dereference_protected() in vsock_nl_dev_netns_fill(), drop the
RCU read lock and allocate the nsid with GFP_KERNEL (Stefano)
- Point to the vsock_g2h_assign_mutex comment for the lock ordering in
vsock_g2h_net_assign() (Stefano)
- Reverse xmas tree in vsock_dev_do_ioctl() (Stefano)
- Set SOCK_DONE and peer_shutdown on swept sockets, so recv() returns EOF
and send() fails with -EPIPE/-ECONNRESET instead of -ENOTCONN (Sashiko)
- Only move sock->state back to SS_UNCONNECTED in vsock_connect() if it is
still SS_CONNECTING, via the new vsock_connect_close(), so a socket
swept while connect() sleeps cannot be connect()ed again and have
vsk->trans freed under in-flight skbs (Michal)
Changes in v4:
- Treat an unreachable g2h device as absent in vsock_use_local_transport(),
so a local ns can connect to the CID it reports as local (Sashiko)
- Reset swept sockets under lock_sock() (Sashiko), and serialize assigns
with vsock_g2h_assign_mutex now that the sweep runs outside
vsock_register_mutex
Changes in v3:
- Try to use net_tracker when taking net references
- Take a reference on the net when assigning a device to it. This keeps
the net alive even after deletion. Drop the concept of moving the
device back to init_net on net destruction. This avoids the problem
of the device CID colliding with a nested h2g CID.
- Refuse the assign with -EADDRINUSE when a guest in the target netns
already answers to the device's CID, via the h2g transport's
->has_remote_cid()
- Hold vsock_register_mutex across the CID check and commit in
vhost_vsock_set_cid(), so it cannot race an assign. We also add a
lockdep assertion into vsock_find_cid() because it is a requirement to
avoid racing between netns assignment of a g2h device and setting a
nested h2g CID (I know, not ideal looking)
- Unlink swept sockets from vsock_connected_table, so a retried
connect() cannot insert the same node twice
- Drop the RST to the peer from the sweep, along with the ->reset op and
the pending_reset list (Sashiko)
- Reply to VSOCK_CMD_DEV_NETNS_SET with the netns-id instead a plain ACK
(Sashiko)
Changes in v2:
- New patch, replaces the ioctl with a genl family and
VSOCK_CMD_DEV_NETNS_SET (Stefano)
- Make netns_assign_allow a bool, not a callback (Stefano)
- One vsock_netns_assignable(t) helper for both call sites (Stefano)
- Make vsock_g2h_reachable_sk() static, drop the export (Stefano)
- RST the peer when socket loses access to the device's net (Stefano)
- Move the netns reset out of the sysctl exit hook, into the renamed
vsock_pernet_ops (Stefano).
- Remove the synchronize_rcu() from the pernet exit path. The
net_namespace documentation states that synchronize_rcu() should be
avoided in the pernet exit path.
- Run the socket reset in the pernet pre_exit() hook, as the
net_namespace management code (and documentation) guarantees
synchronize_rcu() between pre_exit() and exit()
- Drop the conditional synchronize_rcu() in vsock_core_unregister()
(Stefano)
- Specifically state "G2H transport" in the netns_assign_allow comment (Stefano)
- Avoid leaking the g2h CID to a namespace that cannot reach it;
GET_LOCAL_CID, bind() and loopback transport selection all read it
through vsock_registered_transport_cid(), which now takes a netns
- Add VSOCK_CMD_DEV_NETNS_GET
---
Documentation/admin-guide/sysctl/net.rst | 42 +++
Documentation/netlink/specs/vsock.yaml | 72 +++++
MAINTAINERS | 2 +
drivers/vhost/vsock.c | 19 +-
include/net/af_vsock.h | 14 +-
include/uapi/linux/vsock_netlink.h | 28 ++
net/vmw_vsock/Makefile | 2 +-
net/vmw_vsock/af_vsock.c | 501 +++++++++++++++++++++++++++++--
net/vmw_vsock/vsock_nl_gen.c | 36 +++
net/vmw_vsock/vsock_nl_gen.h | 20 ++
tools/net/ynl/Makefile.deps | 1 +
11 files changed, 708 insertions(+), 29 deletions(-)
diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst
index fe43e8595958..eb60903e4eb2 100644
--- a/Documentation/admin-guide/sysctl/net.rst
+++ b/Documentation/admin-guide/sysctl/net.rst
@@ -529,6 +529,48 @@ their hosts. The behavior of VSOCK sockets in a network namespace is determined
by the namespace's mode (``global`` or ``local``), which controls how CIDs
(Context IDs) are allocated and how sockets interact across namespaces.
+In a guest, the vsock device owned by the guest-to-host (G2H) transport belongs
+to one network namespace at a time. The ``VSOCK_CMD_DEV_NETNS_SET`` command of
+the ``vsock`` netlink family, described in
+Documentation/netlink/specs/vsock.yaml, moves it to the namespace the request
+was sent from, which requires ``CAP_NET_ADMIN`` in the initial user namespace.
+The namespace's mode decides who may then use the device:
+
+- ``global`` - every ``global`` mode namespace may use it.
+- ``local`` - only that namespace may use it, which reserves the connection to
+ the host for it alone.
+
+The device starts out in init_net, so until the command is issued nothing has
+moved and no mode has changed.
+
+Support is transport dependent. A G2H transport that does not implement the
+move refuses the command with ``EOPNOTSUPP``. Of the in-tree guest transports,
+only virtio-vsock implements it.
+
+A namespace that cannot reach the device sees the vsock addresses it would see
+if no G2H transport were loaded: the device's CID is not one of its local CIDs,
+and with ``vsock_loopback`` loaded, ``VMADDR_CID_HOST`` (2) is served by
+loopback.
+
+Connections established before the move, from a namespace that can no longer
+reach the device, are closed with ``ECONNRESET``. Nothing is sent to the peer,
+which is what already happens when the device is removed altogether: the peer
+finds out on its next send, which no longer matches a socket and so is answered
+with a reset. A peer that only receives waits for its own timeout. A connection
+still being set up when the device moves is refused with a reset once the peer
+answers it.
+
+An assignment keeps the namespace alive, the same way an open socket does, so
+deleting that namespace does not move the device. Assigning the device to
+another namespace, such as init_net, is the only way to undo an assignment, and
+releases the reference the assignment held on the previous namespace.
+
+The move is refused with ``EADDRINUSE`` if a guest registered in the target
+namespace already answers to the device's CID, since the CID would otherwise
+name two different peers there. If a loaded H2G transport cannot check that,
+the move is refused with ``EOPNOTSUPP``. Of the in-tree host transports, only
+vhost-vsock can.
+
ns_mode
-------
diff --git a/Documentation/netlink/specs/vsock.yaml b/Documentation/netlink/specs/vsock.yaml
new file mode 100644
index 000000000000..3a13c88bdd08
--- /dev/null
+++ b/Documentation/netlink/specs/vsock.yaml
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+#
+# Copyright (c) 2026 Meta Platforms, Inc. and affiliates
+#
+---
+name: vsock
+
+doc: |
+ Control interface for the vsock (AF_VSOCK) core.
+
+protocol: genetlink
+
+uapi-header: linux/vsock_netlink.h
+
+attribute-sets:
+ -
+ name: vsock
+ attributes:
+ -
+ name: netns-id
+ type: s32
+ doc: |
+ Network namespace the guest's vsock device is assigned to, as an
+ nsid relative to the caller.
+
+ Absent when the device is in the caller's own namespace, and
+ NETNSA_NSID_NOT_ASSIGNED when the g2h transport supports netns
+ assignment and the caller's namespace cannot reach the device under
+ the mode rules, or when no nsid could be allocated for it.
+
+operations:
+ list:
+ -
+ name: dev-netns-set
+ doc: |
+ Set the network namespace of the guest's vsock device.
+
+ The device is moved to the namespace the request was sent from. It
+ starts out in init_net, and moving it back there is how an
+ assignment is undone.
+
+ A g2h transport that does not support netns assignment returns
+ EOPNOTSUPP. If the netns assignment would cause a CID collision, it
+ is rejected with EADDRINUSE.
+
+ The namespace's mode decides who may then use the device: a global
+ mode namespace shares it with every other global mode namespace,
+ while a local mode namespace reserves it for itself.
+
+ Movement to an unreachable local namespace results in sockets being
+ closed with ECONNRESET. Peers are not guaranteed to be notified via
+ an RST.
+
+ The assignment holds a reference on the namespace.
+ attribute-set: vsock
+ flags: [admin-perm]
+ do: {}
+ -
+ name: dev-netns-get
+ doc: |
+ Get the network namespace the guest's vsock device is assigned to.
+
+ The namespace is reported as an nsid relative to the caller, so the
+ attribute is absent when the device is already in the caller's own
+ namespace. If the g2h transport supports netns assignment, a caller
+ whose namespace cannot reach the device is told
+ NETNSA_NSID_NOT_ASSIGNED rather than where the device went.
+ attribute-set: vsock
+ do:
+ reply:
+ attributes:
+ - netns-id
diff --git a/MAINTAINERS b/MAINTAINERS
index 7636ab50d44f..95c8882669bb 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29169,10 +29169,12 @@ M: Stefano Garzarella <sgarzare@redhat.com>
L: virtualization@lists.linux.dev
L: netdev@vger.kernel.org
S: Maintained
+F: Documentation/netlink/specs/vsock.yaml
F: drivers/net/vsockmon.c
F: include/net/af_vsock.h
F: include/uapi/linux/vm_sockets.h
F: include/uapi/linux/vm_sockets_diag.h
+F: include/uapi/linux/vsock_netlink.h
F: include/uapi/linux/vsockmon.h
F: net/vmw_vsock/
F: tools/testing/selftests/vsock/
diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
index 01f8930ca51f..1a03cff56c5a 100644
--- a/drivers/vhost/vsock.c
+++ b/drivers/vhost/vsock.c
@@ -815,6 +815,7 @@ static int vhost_vsock_dev_release(struct inode *inode, struct file *file)
static int vhost_vsock_set_cid(struct vhost_vsock *vsock, u64 guest_cid)
{
struct vhost_vsock *other;
+ int ret;
/* Refuse reserved CIDs */
if (guest_cid <= VMADDR_CID_HOST ||
@@ -828,15 +829,20 @@ static int vhost_vsock_set_cid(struct vhost_vsock *vsock, u64 guest_cid)
/* Refuse if CID is assigned to the guest->host transport (i.e. nested
* VM), to make the loopback work.
*/
- if (vsock_find_cid(guest_cid))
- return -EADDRINUSE;
+ vsock_cid_lock();
+
+ if (__vsock_find_cid(vsock->net, guest_cid)) {
+ ret = -EADDRINUSE;
+ goto unlock;
+ }
/* Refuse if CID is already in use */
mutex_lock(&vhost_vsock_mutex);
other = vhost_vsock_get(guest_cid, vsock->net);
if (other && other != vsock) {
mutex_unlock(&vhost_vsock_mutex);
- return -EADDRINUSE;
+ ret = -EADDRINUSE;
+ goto unlock;
}
if (vsock->guest_cid)
@@ -846,7 +852,12 @@ static int vhost_vsock_set_cid(struct vhost_vsock *vsock, u64 guest_cid)
hash_add_rcu(vhost_vsock_hash, &vsock->hash, vsock->guest_cid);
mutex_unlock(&vhost_vsock_mutex);
- return 0;
+ ret = 0;
+
+unlock:
+ vsock_cid_unlock();
+
+ return ret;
}
static int vhost_vsock_set_features(struct vhost_vsock *vsock, u64 features)
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index 1c38a27fadf2..a35ab9e41bbb 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -9,6 +9,7 @@
#define __AF_VSOCK_H__
#include <linux/kernel.h>
+#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <net/netns/vsock.h>
#include <net/sock.h>
@@ -190,6 +191,9 @@ struct vsock_transport {
/* Zero-copy. */
bool (*msgzerocopy_allow)(void);
+
+ /* True if the G2H transport honours VSOCK_CMD_DEV_NETNS_SET. */
+ bool netns_assign_allow;
};
/**** CORE ****/
@@ -236,8 +240,16 @@ void vsock_remove_sock(struct vsock_sock *vsk);
void vsock_for_each_connected_socket(const struct vsock_transport *transport,
void (*fn)(struct sock *sk));
int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk);
-bool vsock_find_cid(unsigned int cid);
+bool vsock_find_cid(struct net *net, unsigned int cid);
+void vsock_cid_lock(void);
+void vsock_cid_unlock(void);
+/* Caller must hold vsock_cid_lock(). */
+bool __vsock_find_cid(struct net *net, unsigned int cid);
void vsock_linger(struct sock *sk);
+struct net *vsock_g2h_net_get(netns_tracker *tracker);
+bool vsock_g2h_net_eq(const struct net *net);
+bool vsock_g2h_net_reachable(struct net *net);
+bool vsock_maybe_set_connected(struct vsock_sock *vsk);
/**** TAP ****/
diff --git a/include/uapi/linux/vsock_netlink.h b/include/uapi/linux/vsock_netlink.h
new file mode 100644
index 000000000000..adf0fbc50ad5
--- /dev/null
+++ b/include/uapi/linux/vsock_netlink.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/vsock.yaml */
+/* YNL-GEN uapi header */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#ifndef _UAPI_LINUX_VSOCK_NETLINK_H
+#define _UAPI_LINUX_VSOCK_NETLINK_H
+
+#define VSOCK_FAMILY_NAME "vsock"
+#define VSOCK_FAMILY_VERSION 1
+
+enum {
+ VSOCK_A_NETNS_ID = 1,
+
+ __VSOCK_A_MAX,
+ VSOCK_A_MAX = (__VSOCK_A_MAX - 1)
+};
+
+enum {
+ VSOCK_CMD_DEV_NETNS_SET = 1,
+ VSOCK_CMD_DEV_NETNS_GET,
+
+ __VSOCK_CMD_MAX,
+ VSOCK_CMD_MAX = (__VSOCK_CMD_MAX - 1)
+};
+
+#endif /* _UAPI_LINUX_VSOCK_NETLINK_H */
diff --git a/net/vmw_vsock/Makefile b/net/vmw_vsock/Makefile
index 5da74c4a9f1d..97e2a559e2bf 100644
--- a/net/vmw_vsock/Makefile
+++ b/net/vmw_vsock/Makefile
@@ -7,7 +7,7 @@ obj-$(CONFIG_VIRTIO_VSOCKETS_COMMON) += vmw_vsock_virtio_transport_common.o
obj-$(CONFIG_HYPERV_VSOCKETS) += hv_sock.o
obj-$(CONFIG_VSOCKETS_LOOPBACK) += vsock_loopback.o
-vsock-y += af_vsock.o af_vsock_tap.o vsock_addr.o
+vsock-y += af_vsock.o af_vsock_tap.o vsock_addr.o vsock_nl_gen.o
vsock-$(CONFIG_BPF_SYSCALL) += vsock_bpf.o
vsock_diag-y += diag.o
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index c9b28e34b1b3..7140a3c70838 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -130,6 +130,27 @@
* a different transport that *does* support local mode. For
* example, virtio-vsock may not support local mode, but the socket
* may still accept a connection from vhost-vsock which does.
+ *
+ * - A guest has a single vsock device, owned by the guest->host transport.
+ * The VSOCK_CMD_DEV_NETNS_SET netlink command moves it to the namespace the
+ * request was sent from. It starts out in init_net. The mode rules then
+ * decide who may use it, and which namespace packets from the host are
+ * delivered to:
+ *
+ * - assigned to a global mode namespace - every global mode namespace may
+ * use it. Until the command is issued nothing has moved and no mode has
+ * changed, so the default is the behaviour that predates it.
+ * - assigned to a local mode namespace - only that namespace may use it.
+ * This is how a nested VM is isolated from the rest of the guest.
+ *
+ * Connections made before an assignment, from a namespace that can no
+ * longer reach the device, are closed with ECONNRESET. Nothing is sent to
+ * the peer. As with device removal, it finds out on its next send.
+ *
+ * The assignment holds a reference on the namespace, the way an open socket
+ * does, so deleting that namespace does not move the device: its destruction
+ * is deferred until the device moves to another namespace, on another
+ * assignment or when the transport unregisters.
*/
#include <linux/compat.h>
@@ -161,10 +182,14 @@
#include <linux/workqueue.h>
#include <net/sock.h>
#include <net/af_vsock.h>
+#include <linux/net_namespace.h>
+#include <net/genetlink.h>
#include <net/netns/vsock.h>
#include <uapi/linux/vm_sockets.h>
#include <uapi/asm-generic/ioctls.h>
+#include "vsock_nl_gen.h"
+
#define VSOCK_NET_MODE_STR_GLOBAL "global"
#define VSOCK_NET_MODE_STR_LOCAL "local"
@@ -206,8 +231,21 @@ static const struct vsock_transport *transport_g2h;
static const struct vsock_transport *transport_dgram;
/* Transport used for local communication */
static const struct vsock_transport *transport_local;
+/* Protects transport registration and the g2h device's netns. */
static DEFINE_MUTEX(vsock_register_mutex);
+/* Network namespace of the g2h device. Protected by
+ * vsock_register_mutex/RCU.
+ */
+static struct net __rcu *vsock_g2h_net = RCU_INITIALIZER(&init_net);
+static netns_tracker vsock_g2h_net_tracker;
+
+/* Held across an assign's swap and reset sweep, so that a later assign cannot
+ * move the device back before the sweep has closed every socket that lost it.
+ * Must be taken before vsock_register_mutex.
+ */
+static DEFINE_MUTEX(vsock_g2h_assign_mutex);
+
/**** UTILS ****/
/* Each bound VSocket is stored in the bind hash table and each connected
@@ -502,6 +540,37 @@ void vsock_for_each_connected_socket(const struct vsock_transport *transport,
}
EXPORT_SYMBOL_GPL(vsock_for_each_connected_socket);
+/* Return the next connected socket of @transport that @match accepts, with a
+ * reference held. The scan starts at bucket *@bucket and leaves it where it
+ * stopped, so calling again resumes there. Unlike
+ * vsock_for_each_connected_socket(), the caller acts on the socket after
+ * vsock_table_lock is dropped, so it can take lock_sock().
+ */
+static struct sock *
+vsock_next_connected_socket(const struct vsock_transport *transport,
+ unsigned int *bucket,
+ bool (*match)(struct vsock_sock *vsk))
+{
+ struct vsock_sock *vsk;
+
+ spin_lock_bh(&vsock_table_lock);
+
+ for (; *bucket < ARRAY_SIZE(vsock_connected_table); (*bucket)++) {
+ list_for_each_entry(vsk, &vsock_connected_table[*bucket],
+ connected_table) {
+ if (vsk->transport == transport && match(vsk)) {
+ sock_hold(sk_vsock(vsk));
+ spin_unlock_bh(&vsock_table_lock);
+ return sk_vsock(vsk);
+ }
+ }
+ }
+
+ spin_unlock_bh(&vsock_table_lock);
+
+ return NULL;
+}
+
void vsock_add_pending(struct sock *listener, struct sock *pending)
{
struct vsock_sock *vlistener;
@@ -553,7 +622,42 @@ void vsock_enqueue_accept(struct sock *listener, struct sock *connected)
}
EXPORT_SYMBOL_GPL(vsock_enqueue_accept);
-static bool vsock_use_local_transport(unsigned int remote_cid)
+/* Return true if @t honours namespace assignment. One that does not keeps the
+ * reachability rules it had before VSOCK_CMD_DEV_NETNS_SET existed.
+ */
+static bool vsock_net_assignable(const struct vsock_transport *t)
+{
+ return t && t->netns_assign_allow;
+}
+
+static bool vsock_g2h_unreachable(const struct vsock_transport *t,
+ struct net *net)
+{
+ return t == transport_g2h && vsock_net_assignable(t) &&
+ !vsock_g2h_net_reachable(net);
+}
+
+/* Return the CID of the transport in @transport, as seen from @net.
+ *
+ * Returns VMADDR_CID_ANY if the transport supports net assignment but is not
+ * reachable from the net.
+ */
+static u32
+__vsock_registered_transport_cid(const struct vsock_transport **transport,
+ struct net *net)
+{
+ lockdep_assert_held(&vsock_register_mutex);
+
+ if (!*transport)
+ return VMADDR_CID_ANY;
+
+ if (vsock_g2h_unreachable(*transport, net))
+ return VMADDR_CID_ANY;
+
+ return (*transport)->get_local_cid();
+}
+
+static bool vsock_use_local_transport(struct net *net, unsigned int remote_cid)
{
lockdep_assert_held(&vsock_register_mutex);
@@ -563,11 +667,10 @@ static bool vsock_use_local_transport(unsigned int remote_cid)
if (remote_cid == VMADDR_CID_LOCAL)
return true;
- if (transport_g2h) {
+ if (transport_g2h && !vsock_g2h_unreachable(transport_g2h, net))
return remote_cid == transport_g2h->get_local_cid();
- } else {
- return remote_cid == VMADDR_CID_HOST;
- }
+
+ return remote_cid == VMADDR_CID_HOST;
}
static void vsock_deassign_transport(struct vsock_sock *vsk)
@@ -626,7 +729,7 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
break;
case SOCK_STREAM:
case SOCK_SEQPACKET:
- if (vsock_use_local_transport(remote_cid))
+ if (vsock_use_local_transport(sock_net(sk), remote_cid))
new_transport = transport_local;
else if (remote_cid <= VMADDR_CID_HOST ||
(remote_flags & VMADDR_FLAG_TO_HOST))
@@ -651,6 +754,11 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
goto err;
}
+ if (vsock_g2h_unreachable(new_transport, sock_net(sk))) {
+ ret = -ENETUNREACH;
+ goto err;
+ }
+
if (vsk->transport && vsk->transport == new_transport) {
ret = 0;
goto err;
@@ -717,21 +825,39 @@ EXPORT_SYMBOL_GPL(vsock_assign_transport);
* Provide safe access to static transport_{h2g,g2h,dgram,local} callbacks.
* Otherwise we may race with module removal. Do not use on `vsk->transport`.
*/
-static u32 vsock_registered_transport_cid(const struct vsock_transport **transport)
+static u32
+vsock_registered_transport_cid(const struct vsock_transport **transport,
+ struct net *net)
{
- u32 cid = VMADDR_CID_ANY;
+ u32 cid;
mutex_lock(&vsock_register_mutex);
- if (*transport)
- cid = (*transport)->get_local_cid();
+ cid = __vsock_registered_transport_cid(transport, net);
mutex_unlock(&vsock_register_mutex);
return cid;
}
-bool vsock_find_cid(unsigned int cid)
+/* Keep the CIDs checked by __vsock_find_cid() stable: transport registration
+ * and g2h device netns assignment both wait for vsock_cid_unlock().
+ */
+void vsock_cid_lock(void)
{
- if (cid == vsock_registered_transport_cid(&transport_g2h))
+ mutex_lock(&vsock_register_mutex);
+}
+EXPORT_SYMBOL_GPL(vsock_cid_lock);
+
+void vsock_cid_unlock(void)
+{
+ mutex_unlock(&vsock_register_mutex);
+}
+EXPORT_SYMBOL_GPL(vsock_cid_unlock);
+
+bool __vsock_find_cid(struct net *net, unsigned int cid)
+{
+ lockdep_assert_held(&vsock_register_mutex);
+
+ if (cid == __vsock_registered_transport_cid(&transport_g2h, net))
return true;
if (transport_h2g && cid == VMADDR_CID_HOST)
@@ -742,8 +868,269 @@ bool vsock_find_cid(unsigned int cid)
return false;
}
+EXPORT_SYMBOL_GPL(__vsock_find_cid);
+
+bool vsock_find_cid(struct net *net, unsigned int cid)
+{
+ bool found;
+
+ mutex_lock(&vsock_register_mutex);
+ found = __vsock_find_cid(net, cid);
+ mutex_unlock(&vsock_register_mutex);
+
+ return found;
+}
EXPORT_SYMBOL_GPL(vsock_find_cid);
+/* Return the namespace the g2h device is assigned to, with a reference held.
+ *
+ * The assignment itself holds a reference, so the namespace cannot be going
+ * away here and this never fails.
+ */
+struct net *vsock_g2h_net_get(netns_tracker *tracker)
+{
+ struct net *net;
+
+ rcu_read_lock();
+ net = rcu_dereference(vsock_g2h_net);
+ get_net_track(net, tracker, GFP_ATOMIC);
+ rcu_read_unlock();
+
+ return net;
+}
+EXPORT_SYMBOL_GPL(vsock_g2h_net_get);
+
+/* True if @net is still the namespace the device is assigned to. Cheap enough
+ * to call per packet on the RX path.
+ */
+bool vsock_g2h_net_eq(const struct net *net)
+{
+ return rcu_access_pointer(vsock_g2h_net) == net;
+}
+EXPORT_SYMBOL_GPL(vsock_g2h_net_eq);
+
+bool vsock_g2h_net_reachable(struct net *net)
+{
+ bool reachable;
+
+ rcu_read_lock();
+ reachable = vsock_net_check_mode(net, rcu_dereference(vsock_g2h_net));
+ rcu_read_unlock();
+
+ return reachable;
+}
+EXPORT_SYMBOL_GPL(vsock_g2h_net_reachable);
+
+static bool vsock_g2h_reachable_sk(struct vsock_sock *vsk)
+{
+ if (!vsock_net_assignable(vsk->transport))
+ return true;
+
+ return vsock_g2h_net_reachable(sock_net(sk_vsock(vsk)));
+}
+
+/* Move @vsk to TCP_ESTABLISHED and into the connected table, unless the device
+ * has moved to a namespace @vsk cannot reach. Returns false without doing
+ * either in that case.
+ *
+ * The reset sweep walks the same table under the same lock, so an assign
+ * cannot land between the check and the insert: either the sweep finds @vsk
+ * and closes it, or @vsk is never added.
+ */
+bool vsock_maybe_set_connected(struct vsock_sock *vsk)
+{
+ struct list_head *list;
+ bool reachable;
+
+ list = vsock_connected_sockets(&vsk->remote_addr, &vsk->local_addr);
+
+ spin_lock_bh(&vsock_table_lock);
+ reachable = vsock_g2h_reachable_sk(vsk);
+ if (reachable) {
+ sk_vsock(vsk)->sk_state = TCP_ESTABLISHED;
+ __vsock_insert_connected(list, vsk);
+ }
+ spin_unlock_bh(&vsock_table_lock);
+
+ return reachable;
+}
+EXPORT_SYMBOL_GPL(vsock_maybe_set_connected);
+
+static bool vsock_g2h_should_reset(struct vsock_sock *vsk)
+{
+ return READ_ONCE(sk_vsock(vsk)->sk_state) != TCP_CLOSE &&
+ !READ_ONCE(vsk->close_work_scheduled) &&
+ !vsock_g2h_reachable_sk(vsk);
+}
+
+/* Close every connected socket of @t that can no longer reach the g2h device.
+ */
+static void vsock_g2h_reset_unreachable(const struct vsock_transport *t)
+{
+ struct vsock_sock *vsk;
+ unsigned int i = 0;
+ struct sock *sk;
+ bool reset;
+
+ lockdep_assert_held(&vsock_g2h_assign_mutex);
+ lockdep_assert_not_held(&vsock_register_mutex);
+
+ /* lock_sock() cannot be taken under vsock_table_lock, so pick the
+ * sockets one at a time, then recheck each under both locks.
+ */
+ while ((sk = vsock_next_connected_socket(t, &i,
+ vsock_g2h_should_reset))) {
+ vsk = vsock_sk(sk);
+
+ lock_sock(sk);
+
+ reset = false;
+ spin_lock_bh(&vsock_table_lock);
+ /* It may have been closed since we dropped the table lock. */
+ if (__vsock_in_connected_table(vsk) && vsk->transport == t &&
+ vsock_g2h_should_reset(vsk)) {
+ __vsock_remove_connected(vsk);
+ reset = true;
+ }
+ spin_unlock_bh(&vsock_table_lock);
+
+ if (reset) {
+ sock_set_flag(sk, SOCK_DONE);
+ WRITE_ONCE(vsk->peer_shutdown, SHUTDOWN_MASK);
+ sk->sk_state = TCP_CLOSE;
+ sk->sk_err = ECONNRESET;
+ sk_error_report(sk);
+ }
+
+ release_sock(sk);
+ sock_put(sk);
+ }
+}
+
+/* Point the g2h device at @net, taking the reference that keeps @net alive,
+ * and return the namespace it leaves behind.
+ */
+static struct net *vsock_g2h_net_swap(struct net *net, netns_tracker *old)
+{
+ lockdep_assert_held(&vsock_register_mutex);
+
+ *old = vsock_g2h_net_tracker;
+ get_net_track(net, &vsock_g2h_net_tracker, GFP_KERNEL);
+
+ return rcu_replace_pointer(vsock_g2h_net, net,
+ lockdep_is_held(&vsock_register_mutex));
+}
+
+/* Drop the namespace the device just left, once the readers that may still
+ * see it in vsock_g2h_net_get() are gone. Runs without the mutex held.
+ */
+static void vsock_g2h_net_release(struct net *old, netns_tracker *tracker)
+{
+ synchronize_rcu();
+ put_net_track(old, tracker);
+}
+
+static int vsock_nl_dev_netns_fill(struct sk_buff *msg, struct genl_info *info)
+{
+ struct net *net = genl_info_net(info);
+ struct net *g2h;
+ bool equal;
+ void *hdr;
+ s32 id;
+
+ g2h = rcu_dereference_protected(vsock_g2h_net,
+ lockdep_is_held(&vsock_register_mutex));
+ equal = net_eq(net, g2h);
+ if (!equal) {
+ if (vsock_net_assignable(transport_g2h) &&
+ !vsock_net_check_mode(net, g2h))
+ id = NETNSA_NSID_NOT_ASSIGNED;
+ else
+ id = peernet2id_alloc(net, g2h, GFP_KERNEL);
+ }
+
+ hdr = genlmsg_iput(msg, info);
+ if (!hdr)
+ return -EMSGSIZE;
+
+ if (!equal && nla_put_s32(msg, VSOCK_A_NETNS_ID, id)) {
+ genlmsg_cancel(msg, hdr);
+ return -EMSGSIZE;
+ }
+
+ genlmsg_end(msg, hdr);
+
+ return 0;
+}
+
+/* Move the g2h device to @net, which keeps @net alive until the device leaves
+ * again. Returns -ENODEV if no g2h transport is loaded, -EOPNOTSUPP if the
+ * loaded one cannot be moved, and -EADDRINUSE if a guest in @net already uses
+ * the device's CID.
+ */
+static int vsock_g2h_net_assign(struct net *net,
+ struct netlink_ext_ack *extack)
+{
+ const struct vsock_transport *t;
+ netns_tracker old_tracker;
+ struct net *old;
+ int ret;
+
+ /* See vsock_g2h_assign_mutex for the lock ordering. */
+ mutex_lock(&vsock_g2h_assign_mutex);
+ mutex_lock(&vsock_register_mutex);
+
+ ret = -ENODEV;
+ if (!transport_g2h) {
+ NL_SET_ERR_MSG(extack, "no guest-to-host transport is loaded");
+ goto unlock;
+ }
+
+ ret = -EOPNOTSUPP;
+ if (!vsock_net_assignable(transport_g2h)) {
+ NL_SET_ERR_MSG(extack,
+ "the loaded guest-to-host transport does not support namespace assignment");
+ goto unlock;
+ }
+
+ if (transport_h2g && !transport_h2g->has_remote_cid) {
+ NL_SET_ERR_MSG(extack,
+ "the loaded host-to-guest transport cannot check the device's CID");
+ goto unlock;
+ }
+
+ ret = -EADDRINUSE;
+ if (transport_h2g &&
+ transport_h2g->has_remote_cid(net,
+ transport_g2h->get_local_cid())) {
+ NL_SET_ERR_MSG(extack,
+ "a guest in the target namespace already uses the device's CID");
+ goto unlock;
+ }
+
+ old = vsock_g2h_net_swap(net, &old_tracker);
+ t = transport_g2h;
+
+ mutex_unlock(&vsock_register_mutex);
+
+ /* See vsock_maybe_set_connected() comment about synchronizing with
+ * connecting sockets.
+ */
+ vsock_g2h_reset_unreachable(t);
+
+ mutex_unlock(&vsock_g2h_assign_mutex);
+
+ vsock_g2h_net_release(old, &old_tracker);
+
+ return 0;
+
+unlock:
+ mutex_unlock(&vsock_register_mutex);
+ mutex_unlock(&vsock_g2h_assign_mutex);
+
+ return ret;
+}
+
static struct sock *vsock_dequeue_accept(struct sock *listener)
{
struct vsock_sock *vlistener;
@@ -910,7 +1297,8 @@ static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr)
* like AF_INET prevents binding to a non-local IP address (in most
* cases), we only allow binding to a local CID.
*/
- if (addr->svm_cid != VMADDR_CID_ANY && !vsock_find_cid(addr->svm_cid))
+ if (addr->svm_cid != VMADDR_CID_ANY &&
+ !vsock_find_cid(sock_net(sk_vsock(vsk)), addr->svm_cid))
return -EADDRNOTAVAIL;
switch (sk->sk_socket->type) {
@@ -1705,6 +2093,16 @@ static void vsock_connect_timeout(struct work_struct *work)
sock_put(sk);
}
+/* Avoid breaking the socket when another thread has changed its sock->state
+ * while we were asleep.
+ */
+static void vsock_connect_close(struct socket *sock)
+{
+ sock->sk->sk_state = TCP_CLOSE;
+ if (sock->state == SS_CONNECTING)
+ sock->state = SS_UNCONNECTED;
+}
+
static int vsock_connect(struct socket *sock, struct sockaddr_unsized *addr,
int addr_len, int flags)
{
@@ -1863,8 +2261,7 @@ static int vsock_connect(struct socket *sock, struct sockaddr_unsized *addr,
* sk_state == TCP_SYN_SENT, which hereby we break.
* In such case VIRTIO_VSOCK_OP_RST will follow.
*/
- sk->sk_state = TCP_CLOSE;
- sock->state = SS_UNCONNECTED;
+ vsock_connect_close(sock);
/* Try to cancel VIRTIO_VSOCK_OP_REQUEST skb sent out by
* transport->connect().
@@ -1878,10 +2275,8 @@ static int vsock_connect(struct socket *sock, struct sockaddr_unsized *addr,
}
err = sock_error(sk);
- if (err) {
- sk->sk_state = TCP_CLOSE;
- sock->state = SS_UNCONNECTED;
- }
+ if (err)
+ vsock_connect_close(sock);
out_wait:
finish_wait(sk_sleep(sk), &wait);
@@ -2797,6 +3192,7 @@ static long vsock_dev_do_ioctl(struct file *filp,
unsigned int cmd, void __user *ptr)
{
u32 __user *p = ptr;
+ struct net *net;
int retval = 0;
u32 cid;
@@ -2805,11 +3201,14 @@ static long vsock_dev_do_ioctl(struct file *filp,
/* To be compatible with the VMCI behavior, we prioritize the
* guest CID instead of well-know host CID (VMADDR_CID_HOST).
*/
- cid = vsock_registered_transport_cid(&transport_g2h);
+ net = current->nsproxy->net_ns;
+ cid = vsock_registered_transport_cid(&transport_g2h, net);
if (cid == VMADDR_CID_ANY)
- cid = vsock_registered_transport_cid(&transport_h2g);
+ cid = vsock_registered_transport_cid(&transport_h2g,
+ net);
if (cid == VMADDR_CID_ANY)
- cid = vsock_registered_transport_cid(&transport_local);
+ cid = vsock_registered_transport_cid(&transport_local,
+ net);
if (put_user(cid, p) != 0)
retval = -EFAULT;
@@ -2850,6 +3249,38 @@ static struct miscdevice vsock_device = {
.fops = &vsock_device_ops,
};
+int vsock_nl_dev_netns_get_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct sk_buff *msg;
+ int err;
+
+ msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ mutex_lock(&vsock_register_mutex);
+ if (transport_g2h) {
+ err = vsock_nl_dev_netns_fill(msg, info);
+ } else {
+ NL_SET_ERR_MSG(info->extack,
+ "no guest-to-host transport is loaded");
+ err = -ENODEV;
+ }
+ mutex_unlock(&vsock_register_mutex);
+
+ if (err) {
+ nlmsg_free(msg);
+ return err;
+ }
+
+ return genlmsg_reply(msg, info);
+}
+
+int vsock_nl_dev_netns_set_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ return vsock_g2h_net_assign(genl_info_net(info), info->extack);
+}
+
static int __vsock_net_mode_string(const struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos,
enum vsock_net_mode mode,
@@ -3065,6 +3496,8 @@ static int __init vsock_init(void)
vsock_init_tables();
+ get_net_track(&init_net, &vsock_g2h_net_tracker, GFP_KERNEL);
+
vsock_proto.owner = THIS_MODULE;
vsock_device.minor = MISC_DYNAMIC_MINOR;
err = misc_register(&vsock_device);
@@ -3091,10 +3524,18 @@ static int __init vsock_init(void)
goto err_unregister_sock;
}
+ err = genl_register_family(&vsock_nl_family);
+ if (err) {
+ pr_err("Cannot register vsock netlink family: %d\n", err);
+ goto err_unregister_pernet;
+ }
+
vsock_bpf_build_proto();
return 0;
+err_unregister_pernet:
+ unregister_pernet_subsys(&vsock_pernet_ops);
err_unregister_sock:
sock_unregister(AF_VSOCK);
err_unregister_proto:
@@ -3102,15 +3543,20 @@ static int __init vsock_init(void)
err_deregister_misc:
misc_deregister(&vsock_device);
err_reset_transport:
+ put_net_track(&init_net, &vsock_g2h_net_tracker);
return err;
}
static void __exit vsock_exit(void)
{
+ genl_unregister_family(&vsock_nl_family);
misc_deregister(&vsock_device);
sock_unregister(AF_VSOCK);
proto_unregister(&vsock_proto);
unregister_pernet_subsys(&vsock_pernet_ops);
+
+ put_net_track(rcu_dereference_protected(vsock_g2h_net, true),
+ &vsock_g2h_net_tracker);
}
const struct vsock_transport *vsock_core_get_transport(struct vsock_sock *vsk)
@@ -3177,13 +3623,18 @@ EXPORT_SYMBOL_GPL(vsock_core_register);
void vsock_core_unregister(const struct vsock_transport *t)
{
+ netns_tracker old_tracker;
+ struct net *old = NULL;
+
mutex_lock(&vsock_register_mutex);
if (transport_h2g == t)
transport_h2g = NULL;
- if (transport_g2h == t)
+ if (transport_g2h == t) {
transport_g2h = NULL;
+ old = vsock_g2h_net_swap(&init_net, &old_tracker);
+ }
if (transport_dgram == t)
transport_dgram = NULL;
@@ -3192,6 +3643,10 @@ void vsock_core_unregister(const struct vsock_transport *t)
transport_local = NULL;
mutex_unlock(&vsock_register_mutex);
+
+ /* The device is gone, so release the namespace it was holding. */
+ if (old)
+ vsock_g2h_net_release(old, &old_tracker);
}
EXPORT_SYMBOL_GPL(vsock_core_unregister);
diff --git a/net/vmw_vsock/vsock_nl_gen.c b/net/vmw_vsock/vsock_nl_gen.c
new file mode 100644
index 000000000000..85004f780775
--- /dev/null
+++ b/net/vmw_vsock/vsock_nl_gen.c
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/vsock.yaml */
+/* YNL-GEN kernel source */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include "vsock_nl_gen.h"
+
+#include <uapi/linux/vsock_netlink.h>
+
+/* Ops table for vsock */
+static const struct genl_split_ops vsock_nl_ops[] = {
+ {
+ .cmd = VSOCK_CMD_DEV_NETNS_SET,
+ .doit = vsock_nl_dev_netns_set_doit,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = VSOCK_CMD_DEV_NETNS_GET,
+ .doit = vsock_nl_dev_netns_get_doit,
+ .flags = GENL_CMD_CAP_DO,
+ },
+};
+
+struct genl_family vsock_nl_family __ro_after_init = {
+ .name = VSOCK_FAMILY_NAME,
+ .version = VSOCK_FAMILY_VERSION,
+ .netnsok = true,
+ .parallel_ops = true,
+ .module = THIS_MODULE,
+ .split_ops = vsock_nl_ops,
+ .n_split_ops = ARRAY_SIZE(vsock_nl_ops),
+};
diff --git a/net/vmw_vsock/vsock_nl_gen.h b/net/vmw_vsock/vsock_nl_gen.h
new file mode 100644
index 000000000000..a2fd87344581
--- /dev/null
+++ b/net/vmw_vsock/vsock_nl_gen.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/vsock.yaml */
+/* YNL-GEN kernel header */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#ifndef _LINUX_VSOCK_GEN_H
+#define _LINUX_VSOCK_GEN_H
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include <uapi/linux/vsock_netlink.h>
+
+int vsock_nl_dev_netns_set_doit(struct sk_buff *skb, struct genl_info *info);
+int vsock_nl_dev_netns_get_doit(struct sk_buff *skb, struct genl_info *info);
+
+extern struct genl_family vsock_nl_family;
+
+#endif /* _LINUX_VSOCK_GEN_H */
diff --git a/tools/net/ynl/Makefile.deps b/tools/net/ynl/Makefile.deps
index 3eee34e0efcf..cd1450f1cac0 100644
--- a/tools/net/ynl/Makefile.deps
+++ b/tools/net/ynl/Makefile.deps
@@ -62,4 +62,5 @@ CFLAGS_tc:= $(call get_hdr_inc,__LINUX_RTNETLINK_H,rtnetlink.h) \
$(call get_hdr_inc,_TC_SKBEDIT_H,tc_act/tc_skbedit.h) \
$(call get_hdr_inc,_TC_TUNNEL_KEY_H,tc_act/tc_tunnel_key.h)
CFLAGS_tcp_metrics:=$(call get_hdr_inc,_LINUX_TCP_METRICS_H,tcp_metrics.h)
+CFLAGS_vsock:=$(call get_hdr_inc,_LINUX_VSOCK_NETLINK_H,vsock_netlink.h)
CFLAGS_wireguard:=$(call get_hdr_inc2,_LINUX_WIREGUARD_H,_WG_UAPI_WIREGUARD_H,wireguard.h)
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v5 5/7] vsock/virtio: support guest device network namespace
2026-11-04 1:53 [PATCH net-next v5 0/7] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
` (3 preceding siblings ...)
2026-11-04 1:53 ` [PATCH net-next v5 4/7] vsock: add a netlink command to assign the g2h device to a netns Bobby Eshleman
@ 2026-11-04 1:53 ` Bobby Eshleman
2026-11-05 1:54 ` sashiko-bot
2026-11-04 1:53 ` [PATCH net-next v5 6/7] selftests/vsock: test the guest vsock " Bobby Eshleman
2026-11-04 1:53 ` [PATCH net-next v5 7/7] selftests/vsock: test the netns assign privilege checks Bobby Eshleman
6 siblings, 1 reply; 10+ messages in thread
From: Bobby Eshleman @ 2026-11-04 1:53 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Shuah Khan, Randy Dunlap, Donald Hunter, Eric Dumazet,
Eric Dumazet
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman, Bobby Eshleman
virtio-vsock did not have namespace support (the device was always
accessible to any global namespace).
Make the virtio-vsock device assignable to a namespace and initialize it
to init_net. Because virtio-vsock and init_net are both hardcoded to
global mode, nothing changes until the assign command is issued.
An assign can land while virtio_transport_rx_work() is draining a
batch, so the assigned namespace is re-read per packet rather than once
per batch. Matching the rest of the batch against the namespace the
device has left would reset connections that belong to the new one.
When a device is assigned to a namespace, every already established
vsock socket that is no longer able to reach the device is forcibly
closed. For that reason, adding new sockets to the connected table must
be performed atomically with regards to namespace assignment. This
ensures that when the socket is added to the connected table that it
actually passes the new reachability conditions set by ns assignment. If
it wins the race to the table and does NOT pass the reachability tests,
then it will be reset. This is the purpose of the new helper
'vsock_maybe_set_connected()'.
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Changes in v5:
- Send the RSTs in virtio_transport_recv_listen() and
virtio_transport_recv_connecting() to the netns the packet arrived on,
not the socket's: after an assign the socket's netns may no longer
reach the device, so the RST would be dropped by the reachability
check this patch adds to virtio_transport_send_pkt() (Stefano, Sashiko)
Changes in v4:
- Drop packets in virtio_transport_send_pkt() from a netns that cannot
reach the device, e.g. shutdown() or credit updates on a swept socket
(Sashiko)
Changes in v3:
- Re-read the assigned netns per packet in virtio_transport_rx_work()
rather than once per batch, so a mid-batch assign cannot reset the new
namespace's connections (Sashiko)
- Drop the NULL netns reset in virtio_transport_rx_work(), the assigned
netns is now pinned
- Drop the .reset op and the virtio_transport_reset{,_no_sock}() exports,
the sweep no longer sends a RST (Sashiko)
Changes in v2:
- Export virtio_transport_reset(), wire it to the new .reset op (Stefano)
- netns_assign_allow is now a bool (Stefano)
- Pass NULL, not &init_net, in virtio_transport_rx_work(), and
comment why (Stefano)
- Drop the if (net) guard around put_net() (Stefano)
- Drop the comments at the vsock_maybe_set_connected() call sites,
the commit msg seems sufficient
---
net/vmw_vsock/virtio_transport.c | 32 +++++++++++++++++++++-----
net/vmw_vsock/virtio_transport_common.c | 40 ++++++++++++++++++++-------------
2 files changed, 52 insertions(+), 20 deletions(-)
diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
index eced6929405d..06df82b130cf 100644
--- a/net/vmw_vsock/virtio_transport.c
+++ b/net/vmw_vsock/virtio_transport.c
@@ -256,6 +256,15 @@ virtio_transport_send_pkt(struct sk_buff *skb, struct net *net)
goto out_rcu;
}
+ /* A socket reset by an assign can still send credit updates from
+ * recvmsg() and setsockopt(SO_RCVLOWAT).
+ */
+ if (!vsock_g2h_net_reachable(net)) {
+ kfree_skb(skb);
+ len = -ENETUNREACH;
+ goto out_rcu;
+ }
+
/* If send_pkt_queue is empty, we can safely bypass this queue
* because packet order is maintained and (try) to put the packet
* on the virtqueue using virtio_transport_send_skb_fast_path.
@@ -542,7 +551,7 @@ static bool virtio_transport_msgzerocopy_allow(void)
bool virtio_transport_stream_allow(struct vsock_sock *vsk, u32 cid, u32 port)
{
- return vsock_net_mode_global(vsk);
+ return vsock_g2h_net_reachable(sock_net(sk_vsock(vsk)));
}
static bool virtio_transport_seqpacket_allow(struct vsock_sock *vsk,
@@ -587,6 +596,7 @@ static struct virtio_transport virtio_transport = {
.seqpacket_has_data = virtio_transport_seqpacket_has_data,
.msgzerocopy_allow = virtio_transport_msgzerocopy_allow,
+ .netns_assign_allow = true,
.notify_poll_in = virtio_transport_notify_poll_in,
.notify_poll_out = virtio_transport_notify_poll_out,
@@ -616,7 +626,7 @@ virtio_transport_seqpacket_allow(struct vsock_sock *vsk, u32 remote_cid)
struct virtio_vsock *vsock;
bool seqpacket_allow;
- if (!vsock_net_mode_global(vsk))
+ if (!vsock_g2h_net_reachable(sock_net(sk_vsock(vsk))))
return false;
seqpacket_allow = false;
@@ -633,7 +643,12 @@ static void virtio_transport_rx_work(struct work_struct *work)
{
struct virtio_vsock *vsock =
container_of(work, struct virtio_vsock, rx_work);
+ struct virtio_transport *t = &virtio_transport;
+ netns_tracker ns_tracker;
struct virtqueue *vq;
+ struct net *net;
+
+ net = vsock_g2h_net_get(&ns_tracker);
mutex_lock(&vsock->rx_lock);
@@ -682,10 +697,15 @@ static void virtio_transport_rx_work(struct work_struct *work)
virtio_transport_deliver_tap_pkt(skb);
- /* Force virtio-transport into global mode since it
- * does not yet support local-mode namespacing.
+ /* Only grab a reference if the net changes underneath
+ * us.
*/
- virtio_transport_recv_pkt(&virtio_transport, skb, NULL);
+ if (unlikely(!vsock_g2h_net_eq(net))) {
+ put_net_track(net, &ns_tracker);
+ net = vsock_g2h_net_get(&ns_tracker);
+ }
+
+ virtio_transport_recv_pkt(t, skb, net);
}
} while (!virtqueue_enable_cb(vq));
@@ -694,6 +714,8 @@ static void virtio_transport_rx_work(struct work_struct *work)
virtio_vsock_rx_fill(vsock);
out_nofill:
mutex_unlock(&vsock->rx_lock);
+
+ put_net_track(net, &ns_tracker);
}
static int virtio_vsock_vqs_init(struct virtio_vsock *vsock)
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index 1e762a480df4..7efcbdc7e04f 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -1468,8 +1468,8 @@ void virtio_transport_release(struct vsock_sock *vsk)
EXPORT_SYMBOL_GPL(virtio_transport_release);
static int
-virtio_transport_recv_connecting(struct sock *sk,
- struct sk_buff *skb)
+virtio_transport_recv_connecting(struct sock *sk, struct sk_buff *skb,
+ struct virtio_transport *t, struct net *net)
{
struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
struct vsock_sock *vsk = vsock_sk(sk);
@@ -1478,9 +1478,14 @@ virtio_transport_recv_connecting(struct sock *sk,
switch (le16_to_cpu(hdr->op)) {
case VIRTIO_VSOCK_OP_RESPONSE:
- sk->sk_state = TCP_ESTABLISHED;
+ if (!vsock_maybe_set_connected(vsk)) {
+ virtio_transport_reset_no_sock(t, skb, net);
+ skerr = ECONNRESET;
+ err = -ENETUNREACH;
+ goto close;
+ }
+
sk->sk_socket->state = SS_CONNECTED;
- vsock_insert_connected(vsk);
sk->sk_state_change(sk);
break;
case VIRTIO_VSOCK_OP_INVALID:
@@ -1498,6 +1503,7 @@ virtio_transport_recv_connecting(struct sock *sk,
destroy:
virtio_transport_reset(vsk, skb);
+close:
sk->sk_state = TCP_CLOSE;
sk->sk_err = skerr;
sk_error_report(sk);
@@ -1702,7 +1708,7 @@ static bool virtio_transport_space_update(struct sock *sk,
/* Handle server socket */
static int
virtio_transport_recv_listen(struct sock *sk, struct sk_buff *skb,
- struct virtio_transport *t)
+ struct virtio_transport *t, struct net *net)
{
struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
struct vsock_sock *vsk = vsock_sk(sk);
@@ -1711,12 +1717,12 @@ virtio_transport_recv_listen(struct sock *sk, struct sk_buff *skb,
int ret;
if (le16_to_cpu(hdr->op) != VIRTIO_VSOCK_OP_REQUEST) {
- virtio_transport_reset_no_sock(t, skb, sock_net(sk));
+ virtio_transport_reset_no_sock(t, skb, net);
return -EINVAL;
}
if (sk_acceptq_is_full(sk)) {
- virtio_transport_reset_no_sock(t, skb, sock_net(sk));
+ virtio_transport_reset_no_sock(t, skb, net);
return -ENOMEM;
}
@@ -1724,20 +1730,18 @@ virtio_transport_recv_listen(struct sock *sk, struct sk_buff *skb,
* Subsequent enqueues would lead to a memory leak.
*/
if (sk->sk_shutdown == SHUTDOWN_MASK) {
- virtio_transport_reset_no_sock(t, skb, sock_net(sk));
+ virtio_transport_reset_no_sock(t, skb, net);
return -ESHUTDOWN;
}
child = vsock_create_connected(sk);
if (!child) {
- virtio_transport_reset_no_sock(t, skb, sock_net(sk));
+ virtio_transport_reset_no_sock(t, skb, net);
return -ENOMEM;
}
lock_sock_nested(child, SINGLE_DEPTH_NESTING);
- child->sk_state = TCP_ESTABLISHED;
-
vchild = vsock_sk(child);
vsock_addr_init(&vchild->local_addr, le64_to_cpu(hdr->dst_cid),
le32_to_cpu(hdr->dst_port));
@@ -1750,7 +1754,7 @@ virtio_transport_recv_listen(struct sock *sk, struct sk_buff *skb,
*/
if (ret || vchild->transport != &t->transport) {
release_sock(child);
- virtio_transport_reset_no_sock(t, skb, sock_net(sk));
+ virtio_transport_reset_no_sock(t, skb, net);
sock_put(child);
return ret;
}
@@ -1758,7 +1762,13 @@ virtio_transport_recv_listen(struct sock *sk, struct sk_buff *skb,
if (virtio_transport_space_update(child, skb))
child->sk_write_space(child);
- vsock_insert_connected(vchild);
+ if (!vsock_maybe_set_connected(vchild)) {
+ release_sock(child);
+ virtio_transport_reset_no_sock(t, skb, net);
+ sock_put(child);
+ return -ENETUNREACH;
+ }
+
vsock_enqueue_accept(sk, child);
virtio_transport_send_response(vchild, skb);
@@ -1855,11 +1865,11 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,
switch (sk->sk_state) {
case TCP_LISTEN:
- virtio_transport_recv_listen(sk, skb, t);
+ virtio_transport_recv_listen(sk, skb, t, net);
kfree_skb(skb);
break;
case TCP_SYN_SENT:
- virtio_transport_recv_connecting(sk, skb);
+ virtio_transport_recv_connecting(sk, skb, t, net);
kfree_skb(skb);
break;
case TCP_ESTABLISHED:
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v5 6/7] selftests/vsock: test the guest vsock device network namespace
2026-11-04 1:53 [PATCH net-next v5 0/7] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
` (4 preceding siblings ...)
2026-11-04 1:53 ` [PATCH net-next v5 5/7] vsock/virtio: support guest device network namespace Bobby Eshleman
@ 2026-11-04 1:53 ` Bobby Eshleman
2026-11-04 1:53 ` [PATCH net-next v5 7/7] selftests/vsock: test the netns assign privilege checks Bobby Eshleman
6 siblings, 0 replies; 10+ messages in thread
From: Bobby Eshleman @ 2026-11-04 1:53 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Shuah Khan, Randy Dunlap, Donald Hunter, Eric Dumazet,
Eric Dumazet
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman, Bobby Eshleman
Add tests for guest vsock device namespace assignment, including ns
destruction and the device moving between namespaces.
The tests drive the device through ynl's cli.py and create guest
namespaces with unshare, so they need python3, PyYAML and unshare. A
missing one skips only these tests, not the whole script.
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Changes in v4:
- Start the old_conn_send_fails stopwatch before the sender, so a slow
guest skips rather than fails (Sashiko)
Changes in v3:
- Replace reset_on_ns_delete_ok with pins_ns_ok, to cover the netns
pinning
- Skip only the ns_guest_ tests when PyYAML or the ynl CLI or spec is
missing, and ship the ynl bits via TEST_FILES for installed trees
(Sashiko)
- Also skip only the ns_guest_ tests when unshare or python3 is missing
- Make vm_ns_start() wait for the namespace and report failure (Sashiko)
- Poll for FIRST in old_conn_send_fails instead of sleeping, and report
an over-long assign as a skip (Sashiko)
- Kill the guest-side socat by name, $! names a local subshell (Sashiko)
- Wait for the host listener to exit before the next test binds its
port (Sashiko)
Changes in v2:
- Drive the assign with ynl cli.py, drop the vsock_assign_g2h_netns
ioctl helper and its patch
- Terminate the guest socat sender too, it sometimes outlived the test
and kept holding on to a port the next test binds
---
tools/testing/selftests/vsock/Makefile | 5 +
tools/testing/selftests/vsock/vmtest.sh | 502 +++++++++++++++++++++++++++++++-
2 files changed, 501 insertions(+), 6 deletions(-)
diff --git a/tools/testing/selftests/vsock/Makefile b/tools/testing/selftests/vsock/Makefile
index c407c0afd938..2b066ba91bcd 100644
--- a/tools/testing/selftests/vsock/Makefile
+++ b/tools/testing/selftests/vsock/Makefile
@@ -13,5 +13,10 @@ $(VSOCK_TEST_DIR)/vsock_test: $(VSOCK_TEST_SRCS)
TEST_PROGS += vmtest.sh
TEST_GEN_FILES := vsock_test
+TEST_FILES := \
+ ../../../net/ynl/pyynl/cli.py \
+ ../../../net/ynl/pyynl/lib \
+ ../../../../Documentation/netlink/specs/vsock.yaml
+
include ../lib.mk
diff --git a/tools/testing/selftests/vsock/vmtest.sh b/tools/testing/selftests/vsock/vmtest.sh
index 310dfc2a39ad..f8aee1336a09 100755
--- a/tools/testing/selftests/vsock/vmtest.sh
+++ b/tools/testing/selftests/vsock/vmtest.sh
@@ -17,6 +17,20 @@ readonly KERNEL_CHECKOUT=$(realpath "${SCRIPT_DIR}"/../../../../)
source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh
readonly VSOCK_TEST="${SCRIPT_DIR}"/vsock_test
+
+# "make install" copies the ynl CLI, its lib package and the spec next to this
+# script (see TEST_FILES); in a source tree they stay where they are built.
+if [[ -r "${SCRIPT_DIR}"/cli.py ]]; then
+ readonly YNL_CLI="${SCRIPT_DIR}"/cli.py
+ readonly YNL_LIB="${SCRIPT_DIR}"/lib
+ readonly VSOCK_SPEC="${SCRIPT_DIR}"/vsock.yaml
+else
+ readonly YNL_DIR="${KERNEL_CHECKOUT}"/tools/net/ynl/pyynl
+ readonly YNL_CLI="${YNL_DIR}"/cli.py
+ readonly YNL_LIB="${YNL_DIR}"/lib
+ readonly SPEC_DIR="${KERNEL_CHECKOUT}"/Documentation/netlink/specs
+ readonly VSOCK_SPEC="${SPEC_DIR}"/vsock.yaml
+fi
readonly TEST_GUEST_PORT=51000
readonly TEST_HOST_PORT=50000
readonly TEST_HOST_PORT_LISTENER=50001
@@ -73,6 +87,12 @@ readonly TEST_NAMES=(
ns_delete_vm_ok
ns_delete_host_ok
ns_delete_both_ok
+ ns_guest_local_connect_to_host_fails
+ ns_guest_assign_g2h_netns_connect_to_host_ok
+ ns_guest_assign_g2h_netns_init_ns_connect_fails
+ ns_guest_assign_g2h_netns_host_connect_ok
+ ns_guest_assign_g2h_netns_pins_ns_ok
+ ns_guest_assign_g2h_netns_old_conn_send_fails
)
readonly TEST_DESCS=(
# vm_server_host_client
@@ -149,12 +169,36 @@ readonly TEST_DESCS=(
# ns_delete_both_ok
"Check that deleting the VM and host's namespaces does not break the socket connection"
+
+ # ns_guest_local_connect_to_host_fails
+ "Check a guest process in a local ns cannot reach the host without the netns assign."
+
+ # ns_guest_assign_g2h_netns_connect_to_host_ok
+ "Check a guest process in a local ns reaches the host once the vsock device is assigned to it."
+
+ # ns_guest_assign_g2h_netns_init_ns_connect_fails
+ "Check the guest's initial ns loses vsock once the device is assigned to another ns."
+
+ # ns_guest_assign_g2h_netns_host_connect_ok
+ "Check the host reaches a guest listener in the ns the vsock device is assigned to."
+
+ # ns_guest_assign_g2h_netns_pins_ns_ok
+ "Check the assignment keeps the ns alive when deleted, and that reassigning releases it."
+
+ # ns_guest_assign_g2h_netns_old_conn_send_fails
+ "Check connections made before the assign stop sending once they lose the device."
)
readonly USE_SHARED_VM=(
vm_server_host_client
vm_client_host_server
vm_loopback
+ ns_guest_local_connect_to_host_fails
+ ns_guest_assign_g2h_netns_connect_to_host_ok
+ ns_guest_assign_g2h_netns_init_ns_connect_fails
+ ns_guest_assign_g2h_netns_host_connect_ok
+ ns_guest_assign_g2h_netns_pins_ns_ok
+ ns_guest_assign_g2h_netns_old_conn_send_fails
)
readonly NS_MODES=("local" "global")
@@ -316,6 +360,34 @@ check_deps() {
fi
}
+check_ns_guest_deps() {
+ local tname=$1
+ local dep
+
+ if [[ ! "${tname}" =~ ^ns_guest_ ]]; then
+ return 0
+ fi
+
+ for dep in unshare python3; do
+ if [[ ! -x $(command -v "${dep}") ]]; then
+ log_host "no ${dep} for test ${tname}"
+ return 1
+ fi
+ done
+
+ if ! python3 -c "import yaml" &>/dev/null; then
+ log_host "no python3 yaml module for test ${tname}"
+ return 1
+ fi
+
+ if [[ ! -r "${YNL_CLI}" ]] || [[ ! -r "${VSOCK_SPEC}" ]]; then
+ log_host "no ynl cli or vsock spec for test ${tname}"
+ return 1
+ fi
+
+ return 0
+}
+
check_netns() {
local tname=$1
@@ -401,6 +473,19 @@ setup_home() {
mkdir -p "$(dirname "${SSH_KEY_PATH}")"
ssh-keygen -t ed25519 -f "${SSH_KEY_PATH}" -N "" -q
cp "${VSOCK_TEST}" "${TEST_HOME}"/vsock_test
+
+ mkdir -p "${TEST_HOME}"/ynl
+ if [[ -r "${YNL_CLI}" ]]; then
+ cp "${YNL_CLI}" "${TEST_HOME}"/ynl/
+ cp -r "${YNL_LIB}" "${TEST_HOME}"/ynl/
+ cp "${VSOCK_SPEC}" "${TEST_HOME}"/ynl/
+ fi
+
+ # One of the tests runs the CLI as an unprivileged user, so the CLI
+ # has to be reachable by one.
+ chmod -R a+rX "${TEST_HOME}"/ynl
+
+ chmod 755 "${TEST_HOME}"
}
create_pidfile() {
@@ -528,6 +613,82 @@ vm_wait_for_ssh() {
done
}
+# Create a local mode namespace in the VM and echo the pid holding it open.
+vm_ns_start() {
+ local ns=$1
+ local initns
+ local nspid
+ local newns
+ local i
+
+ local mode=/proc/sys/net/vsock/child_ns_mode
+
+ if ! vm_ssh "${ns}" -- "echo local > ${mode}" &>/dev/null; then
+ return
+ fi
+
+ nspid=$(vm_ssh "${ns}" -- "unshare -n sleep infinity" \
+ '>/dev/null 2>&1 & echo $!')
+ if [[ -z "${nspid}" ]]; then
+ return
+ fi
+
+ initns=$(vm_ssh "${ns}" -- "readlink /proc/self/ns/net")
+ for ((i = 0; i < WAIT_PERIOD_MAX; i++)); do
+ newns=$(vm_ssh "${ns}" -- "readlink /proc/${nspid}/ns/net" \
+ 2>/dev/null)
+ if [[ -n "${newns}" ]] && [[ "${newns}" != "${initns}" ]]; then
+ echo "${nspid}"
+ return
+ fi
+ sleep 1
+ done
+
+ vm_ns_stop "${ns}" "${nspid}"
+}
+
+# Returns once the holder is gone, so that the namespace is unreferenced and
+# the kernel can start tearing it down.
+vm_ns_stop() {
+ local ns=$1
+ local nspid=$2
+
+ vm_ssh "${ns}" <<-EOF &>/dev/null
+ kill ${nspid}
+ for ((i = 0; i < ${WAIT_PERIOD_MAX}; i++)); do
+ kill -0 ${nspid} 2>/dev/null || break
+ sleep 1
+ done
+ EOF
+}
+
+# Runs in the guest's initial namespace when <nspid> is empty. The command must
+# not contain single quotes.
+vm_ns_exec() {
+ local ns=$1
+ local nspid=$2
+ local cmd=$3
+
+ if [[ -z "${nspid}" ]]; then
+ vm_ssh "${ns}" -- "${cmd}"
+ return
+ fi
+
+ vm_ssh "${ns}" -- nsenter -t "${nspid}" -n sh -c "'${cmd}'"
+}
+
+vm_ns_assign_g2h() {
+ local ns=$1
+ local nspid=$2
+
+ vm_ns_exec "${ns}" "${nspid}" "python3 /root/ynl/cli.py --no-schema \
+ --spec /root/ynl/vsock.yaml --do dev-netns-set"
+}
+
+vm_reset_g2h() {
+ vm_ns_assign_g2h "init_ns" "" &>/dev/null
+}
+
# derived from selftests/net/net_helper.sh
wait_for_listener()
{
@@ -564,17 +725,33 @@ wait_for_listener()
done
}
-vm_wait_for_listener() {
+# Runs in the guest's initial namespace when <nspid> is empty.
+vm_ns_wait_for_listener() {
local ns=$1
- local port=$2
- local protocol=$3
+ local nspid=$2
+ local port=$3
+ local protocol=$4
+ local nsenter=
+ local args
+
+ [[ -n "${nspid}" ]] && nsenter="nsenter -t ${nspid} -n"
+ args="${port} ${WAIT_PERIOD} ${WAIT_PERIOD_MAX} ${protocol}"
vm_ssh "${ns}" <<EOF
$(declare -f wait_for_listener)
-wait_for_listener ${port} ${WAIT_PERIOD} ${WAIT_PERIOD_MAX} ${protocol}
+export -f wait_for_listener
+${nsenter} bash -c "wait_for_listener ${args}"
EOF
}
+vm_wait_for_listener() {
+ local ns=$1
+ local port=$2
+ local protocol=$3
+
+ vm_ns_wait_for_listener "${ns}" "" "${port}" "${protocol}"
+}
+
host_wait_for_listener() {
local ns=$1
local port=$2
@@ -1421,6 +1598,317 @@ test_ns_delete_both_ok() {
check_ns_delete_doesnt_break_connection "both"
}
+# Send a string from the guest to a host listener and leave what the host
+# received in <outfile>.
+guest_send_to_host() {
+ local ns=$1
+ local nspid=$2
+ local port=$3
+ local outfile=$4
+ local cmd="echo TEST | socat -u STDIN VSOCK-CONNECT:2:${port}"
+ local pid
+
+ socat -u VSOCK-LISTEN:"${port}" STDOUT > "${outfile}" 2>/dev/null &
+ pid=$!
+ host_wait_for_listener "${ns}" "${port}" "vsock"
+
+ vm_ns_exec "${ns}" "${nspid}" "${cmd}" 2>/dev/null
+
+ timeout "${WAIT_PERIOD}" \
+ bash -c 'while [[ ! -s '"${outfile}"' ]]; do sleep 1; done'
+
+ # Wait for the listener to actually go, or the next test binding the
+ # same port races it and silently reads nothing.
+ terminate_pids "${pid}"
+ wait "${pid}" 2>/dev/null
+}
+
+# Send a string from the host to a listener in the guest and leave what the
+# guest received in <outfile>.
+host_send_to_guest() {
+ local ns=$1
+ local nspid=$2
+ local port=$3
+ local outfile=$4
+ local cmd="socat -u VSOCK-LISTEN:${port} STDOUT"
+ local dst="VSOCK-CONNECT:${VSOCK_CID}:${port}"
+ local pid
+
+ vm_ns_exec "${ns}" "${nspid}" "${cmd}" > "${outfile}" 2>/dev/null &
+ pid=$!
+ vm_ns_wait_for_listener "${ns}" "${nspid}" "${port}" "vsock"
+
+ echo TEST | socat -u STDIN "${dst}" 2>/dev/null
+
+ timeout "${WAIT_PERIOD}" \
+ bash -c 'while [[ ! -s '"${outfile}"' ]]; do sleep 1; done'
+
+ vm_ns_exec "${ns}" "${nspid}" \
+ "pkill -f VSOCK-LISTEN:${port}" &>/dev/null
+ terminate_pids "${pid}"
+ wait "${pid}" 2>/dev/null
+}
+
+ns_old_conn_cleanup() {
+ local port=$1
+ local pid=$2
+ local sender=$3
+ local outfile=$4
+ local nspid=$5
+
+ vm_ns_exec "init_ns" "" "pkill -f VSOCK-CONNECT:2:${port}" &>/dev/null
+ terminate_pids "${pid}" "${sender}"
+ wait "${pid}" "${sender}" 2>/dev/null
+ rm -f "${outfile}"
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+}
+
+test_ns_guest_assign_g2h_netns_old_conn_send_fails() {
+ local gap=$(( WAIT_PERIOD * 3 ))
+ local port=12346
+ local elapsed
+ local started
+ local outfile
+ local result
+ local sender
+ local nspid
+ local pid
+
+ nspid=$(vm_ns_start "init_ns")
+ if [[ -z "${nspid}" ]]; then
+ log_host "failed to create a namespace inside the guest"
+ return "${KSFT_FAIL}"
+ fi
+
+ outfile=$(mktemp)
+ socat -u VSOCK-LISTEN:"${port}" STDOUT > "${outfile}" 2>/dev/null &
+ pid=$!
+ host_wait_for_listener "init_ns" "${port}" "vsock"
+
+ # Send a message, wait, then send another. While waiting, assign the
+ # device to a namespace. Confirm the second message does not arrive.
+ started=${SECONDS}
+ vm_ssh "init_ns" -- \
+ "(echo FIRST; sleep ${gap}; echo SECOND) |" \
+ "socat -u STDIN VSOCK-CONNECT:2:${port}" &>/dev/null &
+ sender=$!
+
+ timeout "$(( WAIT_PERIOD * WAIT_PERIOD_MAX ))" \
+ bash -c 'while [[ ! -s '"${outfile}"' ]]; do sleep 1; done'
+
+ if ! vm_ns_assign_g2h "init_ns" "${nspid}"; then
+ log_host "failed to assign the vsock device to the guest ns"
+ ns_old_conn_cleanup "${port}" "${pid}" "${sender}" \
+ "${outfile}" "${nspid}"
+ return "${KSFT_FAIL}"
+ fi
+ elapsed=$(( SECONDS - started ))
+
+ sleep $(( gap + WAIT_PERIOD ))
+
+ result=$(cat "${outfile}")
+ ns_old_conn_cleanup "${port}" "${pid}" "${sender}" \
+ "${outfile}" "${nspid}"
+
+ if [[ "${result}" != *FIRST* ]]; then
+ log_host "no connection before the assign: [${result}]"
+ return "${KSFT_FAIL}"
+ fi
+
+ if [[ $(( elapsed + 1 )) -ge ${gap} ]]; then
+ log_host "connect and assign took ${elapsed}s of a ${gap}s gap"
+ return "${KSFT_SKIP}"
+ fi
+
+ if [[ "${result}" == *SECOND* ]]; then
+ log_host "old connection still delivered after the assign"
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
+test_ns_guest_local_connect_to_host_fails() {
+ local port=12345
+ local outfile
+ local result
+ local nspid
+
+ nspid=$(vm_ns_start "init_ns")
+ if [[ -z "${nspid}" ]]; then
+ log_host "failed to create a namespace inside the guest"
+ return "${KSFT_FAIL}"
+ fi
+
+ outfile=$(mktemp)
+ guest_send_to_host "init_ns" "${nspid}" "${port}" "${outfile}"
+
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+
+ result=$(cat "${outfile}")
+ rm -f "${outfile}"
+
+ if [[ "${result}" == TEST ]]; then
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
+test_ns_guest_assign_g2h_netns_connect_to_host_ok() {
+ local port=12345
+ local outfile
+ local result
+ local nspid
+
+ nspid=$(vm_ns_start "init_ns")
+ if [[ -z "${nspid}" ]]; then
+ log_host "failed to create a namespace inside the guest"
+ return "${KSFT_FAIL}"
+ fi
+
+ if ! vm_ns_assign_g2h "init_ns" "${nspid}"; then
+ log_host "failed to assign the vsock device to the guest ns"
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+ return "${KSFT_FAIL}"
+ fi
+
+ outfile=$(mktemp)
+ guest_send_to_host "init_ns" "${nspid}" "${port}" "${outfile}"
+
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+
+ result=$(cat "${outfile}")
+ rm -f "${outfile}"
+
+ if [[ "${result}" != TEST ]]; then
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
+test_ns_guest_assign_g2h_netns_init_ns_connect_fails() {
+ local port=12345
+ local outfile
+ local result
+ local nspid
+
+ nspid=$(vm_ns_start "init_ns")
+ if [[ -z "${nspid}" ]]; then
+ log_host "failed to create a namespace inside the guest"
+ return "${KSFT_FAIL}"
+ fi
+
+ if ! vm_ns_assign_g2h "init_ns" "${nspid}"; then
+ log_host "failed to assign the vsock device to the guest ns"
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+ return "${KSFT_FAIL}"
+ fi
+
+ outfile=$(mktemp)
+ guest_send_to_host "init_ns" "" "${port}" "${outfile}"
+
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+
+ result=$(cat "${outfile}")
+ rm -f "${outfile}"
+
+ if [[ "${result}" == TEST ]]; then
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
+test_ns_guest_assign_g2h_netns_host_connect_ok() {
+ local port=12345
+ local outfile
+ local result
+ local nspid
+
+ nspid=$(vm_ns_start "init_ns")
+ if [[ -z "${nspid}" ]]; then
+ log_host "failed to create a namespace inside the guest"
+ return "${KSFT_FAIL}"
+ fi
+
+ if ! vm_ns_assign_g2h "init_ns" "${nspid}"; then
+ log_host "failed to assign the vsock device to the guest ns"
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+ return "${KSFT_FAIL}"
+ fi
+
+ outfile=$(mktemp)
+ host_send_to_guest "init_ns" "${nspid}" "${port}" "${outfile}"
+
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+
+ result=$(cat "${outfile}")
+ rm -f "${outfile}"
+
+ if [[ "${result}" != TEST ]]; then
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
+test_ns_guest_assign_g2h_netns_pins_ns_ok() {
+ local port=12345
+ local outfile
+ local result
+ local nspid
+
+ nspid=$(vm_ns_start "init_ns")
+ if [[ -z "${nspid}" ]]; then
+ log_host "failed to create a namespace inside the guest"
+ return "${KSFT_FAIL}"
+ fi
+
+ if ! vm_ns_assign_g2h "init_ns" "${nspid}"; then
+ log_host "failed to assign the vsock device to the guest ns"
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+ return "${KSFT_FAIL}"
+ fi
+
+ vm_ns_stop "init_ns" "${nspid}"
+ sleep "${WAIT_PERIOD}"
+
+ outfile=$(mktemp)
+ guest_send_to_host "init_ns" "" "${port}" "${outfile}"
+ result=$(cat "${outfile}")
+ if [[ "${result}" == TEST ]]; then
+ log_host "device returned to the init ns instead of pinning it"
+ rm -f "${outfile}"
+ vm_reset_g2h
+ return "${KSFT_FAIL}"
+ fi
+
+ # Assigning it back is what releases the deleted namespace.
+ vm_reset_g2h
+
+ guest_send_to_host "init_ns" "" "$(( port + 1 ))" "${outfile}"
+ result=$(cat "${outfile}")
+ rm -f "${outfile}"
+
+ if [[ "${result}" != TEST ]]; then
+ log_host "device did not come back on reassign: [${result}]"
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
shared_vm_test() {
local tname
@@ -1453,7 +1941,8 @@ run_shared_vm_tests() {
continue
fi
- if ! check_netns "${arg}"; then
+ if ! check_netns "${arg}" ||
+ ! check_ns_guest_deps "${arg}"; then
check_result "${KSFT_SKIP}" "${arg}"
continue
fi
@@ -1517,7 +2006,8 @@ run_ns_tests() {
continue
fi
- if ! check_netns "${arg}"; then
+ if ! check_netns "${arg}" ||
+ ! check_ns_guest_deps "${arg}"; then
check_result "${KSFT_SKIP}" "${arg}"
continue
fi
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v5 7/7] selftests/vsock: test the netns assign privilege checks
2026-11-04 1:53 [PATCH net-next v5 0/7] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
` (5 preceding siblings ...)
2026-11-04 1:53 ` [PATCH net-next v5 6/7] selftests/vsock: test the guest vsock " Bobby Eshleman
@ 2026-11-04 1:53 ` Bobby Eshleman
6 siblings, 0 replies; 10+ messages in thread
From: Bobby Eshleman @ 2026-11-04 1:53 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Shuah Khan, Randy Dunlap, Donald Hunter, Eric Dumazet,
Eric Dumazet
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman, Bobby Eshleman
VSOCK_CMD_DEV_NETNS_SET refuses callers without CAP_NET_ADMIN in the
init user namespace.
Add two tests: one confirms that CAP_NET_ADMIN is required even by a
privileged user and the other confirms that CAP_NET_ADMIN in an
unprivileged user ns alone is insufficient.
CONFIG_USER_NS is needed to test the CAP_NET_ADMIN + unprivileged user
ns case.
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Changes in v4:
- Require EPERM in the privilege tests, cli.py does print the errno, and
probe unshare -Urn so a setup failure skips (Sashiko)
Changes in v3:
- Require setpriv only for the ns_guest_ tests, not the whole script
Changes in v2:
- Assert only that the command fails, not EPERM: ynl's cli.py does not
report the errno (Sashiko)
- Use ynl cli.py for the assign
---
tools/testing/selftests/vsock/config | 1 +
tools/testing/selftests/vsock/vmtest.sh | 50 ++++++++++++++++++++++++++++++++-
2 files changed, 50 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/vsock/config b/tools/testing/selftests/vsock/config
index 5f0a4f17dfc9..4b31085558fa 100644
--- a/tools/testing/selftests/vsock/config
+++ b/tools/testing/selftests/vsock/config
@@ -109,3 +109,4 @@ CONFIG_FS_DAX=y
CONFIG_MEMORY_HOTPLUG=y
CONFIG_MEMORY_HOTREMOVE=y
CONFIG_ZONE_DEVICE=y
+CONFIG_USER_NS=y
diff --git a/tools/testing/selftests/vsock/vmtest.sh b/tools/testing/selftests/vsock/vmtest.sh
index f8aee1336a09..5bd90a0c2380 100755
--- a/tools/testing/selftests/vsock/vmtest.sh
+++ b/tools/testing/selftests/vsock/vmtest.sh
@@ -93,6 +93,8 @@ readonly TEST_NAMES=(
ns_guest_assign_g2h_netns_host_connect_ok
ns_guest_assign_g2h_netns_pins_ns_ok
ns_guest_assign_g2h_netns_old_conn_send_fails
+ ns_guest_assign_g2h_netns_no_cap_net_admin_fails
+ ns_guest_assign_g2h_netns_unpriv_user_ns_fails
)
readonly TEST_DESCS=(
# vm_server_host_client
@@ -187,6 +189,12 @@ readonly TEST_DESCS=(
# ns_guest_assign_g2h_netns_old_conn_send_fails
"Check connections made before the assign stop sending once they lose the device."
+
+ # ns_guest_assign_g2h_netns_no_cap_net_admin_fails
+ "Check assigning the guest's vsock device to a namespace needs CAP_NET_ADMIN."
+
+ # ns_guest_assign_g2h_netns_unpriv_user_ns_fails
+ "Check an unprivileged user cannot claim the guest's vsock device via a user ns."
)
readonly USE_SHARED_VM=(
@@ -199,6 +207,8 @@ readonly USE_SHARED_VM=(
ns_guest_assign_g2h_netns_host_connect_ok
ns_guest_assign_g2h_netns_pins_ns_ok
ns_guest_assign_g2h_netns_old_conn_send_fails
+ ns_guest_assign_g2h_netns_no_cap_net_admin_fails
+ ns_guest_assign_g2h_netns_unpriv_user_ns_fails
)
readonly NS_MODES=("local" "global")
@@ -368,7 +378,7 @@ check_ns_guest_deps() {
return 0
fi
- for dep in unshare python3; do
+ for dep in unshare setpriv python3; do
if [[ ! -x $(command -v "${dep}") ]]; then
log_host "no ${dep} for test ${tname}"
return 1
@@ -1909,6 +1919,44 @@ test_ns_guest_assign_g2h_netns_pins_ns_ok() {
return "${KSFT_PASS}"
}
+check_assign_g2h_eperm() {
+ local wrapper=$1
+ local out
+
+ if out=$(vm_ssh "init_ns" -- "${wrapper}" python3 /root/ynl/cli.py \
+ --no-schema --spec /root/ynl/vsock.yaml \
+ --do dev-netns-set 2>&1); then
+ log_host "assign unexpectedly succeeded under '${wrapper}'"
+ return "${KSFT_FAIL}"
+ fi
+
+ if [[ "${out}" != *"Operation not permitted"* ]]; then
+ log_host "no EPERM under '${wrapper}': [${out}]"
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
+test_ns_guest_assign_g2h_netns_no_cap_net_admin_fails() {
+ check_assign_g2h_eperm "unshare -n setpriv --bounding-set=-net_admin"
+}
+
+test_ns_guest_assign_g2h_netns_unpriv_user_ns_fails() {
+ local unpriv_uid=65534
+ local unpriv
+
+ unpriv="setpriv --reuid=${unpriv_uid} --regid=${unpriv_uid}"
+ unpriv="${unpriv} --clear-groups"
+
+ if ! vm_ssh "init_ns" -- "${unpriv} unshare -Urn true"; then
+ log_host "unprivileged user namespaces unavailable, skipping"
+ return "${KSFT_SKIP}"
+ fi
+
+ check_assign_g2h_eperm "${unpriv} unshare -Urn"
+}
+
shared_vm_test() {
local tname
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v5 4/7] vsock: add a netlink command to assign the g2h device to a netns
2026-11-04 1:53 ` [PATCH net-next v5 4/7] vsock: add a netlink command to assign the g2h device to a netns Bobby Eshleman
@ 2026-11-05 1:54 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-11-05 1:54 UTC (permalink / raw)
To: Bobby Eshleman; +Cc: virtualization, kvm, Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] vsock: connect() corrupts state and bytes_unsent on swept established socket
--
commit 1f8c2b3f7e5cf0bf0bba26600e236377dd6b1fe3
Author: Bobby Eshleman <bobbyeshleman@meta.com>
vsock: add a netlink command to assign the g2h device to a netns
This commit introduces a vsock generic netlink family with
VSOCK_CMD_DEV_NETNS_SET and VSOCK_CMD_DEV_NETNS_GET to assign the
guest-to-host vsock device to a network namespace and query its assignment.
Sockets that can no longer reach the device after an assignment are closed
with ECONNRESET.
Link: https://lore.kernel.org/all/20200427142518.uwssa6dtasrp3bfc@steredhat/
> diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
> index c9b28e34b1b3c..7140a3c708389 100644
> --- a/net/vmw_vsock/af_vsock.c
> +++ b/net/vmw_vsock/af_vsock.c
[ ... ]
> @@ -742,8 +868,269 @@ bool vsock_find_cid(unsigned int cid)
[ ... ]
> +/* Close every connected socket of @t that can no longer reach the g2h device.
> + */
> +static void vsock_g2h_reset_unreachable(const struct vsock_transport *t)
> +{
> + struct vsock_sock *vsk;
> + unsigned int i = 0;
> + struct sock *sk;
> + bool reset;
> +
> + lockdep_assert_held(&vsock_g2h_assign_mutex);
> + lockdep_assert_not_held(&vsock_register_mutex);
> +
> + /* lock_sock() cannot be taken under vsock_table_lock, so pick the
> + * sockets one at a time, then recheck each under both locks.
> + */
> + while ((sk = vsock_next_connected_socket(t, &i,
> + vsock_g2h_should_reset))) {
> + vsk = vsock_sk(sk);
> +
> + lock_sock(sk);
> +
> + reset = false;
> + spin_lock_bh(&vsock_table_lock);
> + /* It may have been closed since we dropped the table lock. */
> + if (__vsock_in_connected_table(vsk) && vsk->transport == t &&
> + vsock_g2h_should_reset(vsk)) {
> + __vsock_remove_connected(vsk);
> + reset = true;
> + }
> + spin_unlock_bh(&vsock_table_lock);
> +
> + if (reset) {
> + sock_set_flag(sk, SOCK_DONE);
> + WRITE_ONCE(vsk->peer_shutdown, SHUTDOWN_MASK);
> + sk->sk_state = TCP_CLOSE;
> + sk->sk_err = ECONNRESET;
> + sk_error_report(sk);
> + }
> +
> + release_sock(sk);
> + sock_put(sk);
> + }
> +}
[ ... ]
> @@ -1705,6 +2093,16 @@ static void vsock_connect_timeout(struct work_struct *work)
> sock_put(sk);
> }
>
> +/* Avoid breaking the socket when another thread has changed its sock->state
> + * while we were asleep.
> + */
> +static void vsock_connect_close(struct socket *sock)
> +{
> + sock->sk->sk_state = TCP_CLOSE;
> + if (sock->state == SS_CONNECTING)
> + sock->state = SS_UNCONNECTED;
> +}
> +
> static int vsock_connect(struct socket *sock, struct sockaddr_unsized *addr,
> int addr_len, int flags)
> {
> @@ -1863,8 +2261,7 @@ static int vsock_connect(struct socket *sock, struct sockaddr_unsized *addr,
> * sk_state == TCP_SYN_SENT, which hereby we break.
> * In such case VIRTIO_VSOCK_OP_RST will follow.
> */
> - sk->sk_state = TCP_CLOSE;
> - sock->state = SS_UNCONNECTED;
> + vsock_connect_close(sock);
>
> /* Try to cancel VIRTIO_VSOCK_OP_REQUEST skb sent out by
> * transport->connect().
> @@ -1878,10 +2275,8 @@ static int vsock_connect(struct socket *sock, struct sockaddr_unsized *addr,
> }
>
> err = sock_error(sk);
> - if (err) {
> - sk->sk_state = TCP_CLOSE;
> - sock->state = SS_UNCONNECTED;
> - }
> + if (err)
> + vsock_connect_close(sock);
[Severity: Medium]
What happens in vsock_connect() when a socket reaches TCP_ESTABLISHED while
sleeping in schedule_timeout(), and is then swept by
vsock_g2h_reset_unreachable() before vsock_connect() reacquires
lock_sock(sk)?
While vsock_connect() has released lock_sock(sk) in schedule_timeout(),
virtio_transport_recv_connecting() can receive VIRTIO_VSOCK_OP_RESPONSE,
transition sk->sk_state to TCP_ESTABLISHED, set sock->state to SS_CONNECTED,
and insert vsk into vsock_connected_table. Once established, a concurrent
vsock_connectible_sendmsg() on another thread can acquire credits via
virtio_transport_get_credit() (incrementing vvs->bytes_unsent) and enqueue
VIRTIO_VSOCK_OP_RW skbs onto send_pkt_queue.
If VSOCK_CMD_DEV_NETNS_SET then runs vsock_g2h_reset_unreachable() before
vsock_connect() reacquires lock_sock(sk), it sets sk->sk_state to TCP_CLOSE
and sk->sk_err to ECONNRESET while leaving sock->state as SS_CONNECTED. When
vsock_connect() wakes up, the once-established check:
if (sk->sk_state == TCP_ESTABLISHED ||
sk->sk_state == TCP_CLOSING) {
err = 0;
goto out_wait;
}
does not match because sk->sk_state is TCP_CLOSE rather than TCP_CLOSING (unlike
virtio_transport_do_close()) and sock->state == SS_CONNECTED is not checked.
If signal_pending(current) || timeout == 0 holds, vsock_connect() proceeds to
call vsock_transport_cancel_pkt(vsk) -> virtio_transport_purge_skbs(), which
purges the post-connection VIRTIO_VSOCK_OP_RW skbs without calling
virtio_transport_consume_skb_sent() to decrement vvs->bytes_unsent. Doesn't
this leak vvs->bytes_unsent and cause vsock_linger() on close() to block until
sk_lingertime expires, similar to the issue fixed by commit 002541ef650b
("vsock: Do not allow signal/timeout on established sockets")?
And if no signal or timeout is pending, vsock_connect() exits the wait loop,
consumes sk->sk_err via sock_error(sk), calls vsock_connect_close(sock), and
returns -ECONNRESET while leaving sock->state as SS_CONNECTED with sk->sk_err
cleared to 0.
Should vsock_g2h_reset_unreachable() set sk->sk_state = TCP_CLOSING like
virtio_transport_do_close(), or should vsock_connect() also check for
sock->state == SS_CONNECTED after schedule_timeout()?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261103-vsock-guest-ns-v5-0-48179b9b66b8@meta.com?part=4
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v5 5/7] vsock/virtio: support guest device network namespace
2026-11-04 1:53 ` [PATCH net-next v5 5/7] vsock/virtio: support guest device network namespace Bobby Eshleman
@ 2026-11-05 1:54 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-11-05 1:54 UTC (permalink / raw)
To: Bobby Eshleman; +Cc: virtualization, kvm, Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] vsock/virtio: virtio_transport_send_pkt() drops RST replies on netns change
--
commit b8eab48b6f555e1cbf805a0b83f5c8f89bdd6029
Author: Bobby Eshleman <bobbyeshleman@meta.com>
vsock/virtio: support guest device network namespace
This commit adds network namespace assignment support to the guest
virtio-vsock driver, restricting reachability to the assigned namespace and
atomically checking reachability when inserting sockets into the connected
table.
> diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
> index eced6929405df..06df82b130cff 100644
> --- a/net/vmw_vsock/virtio_transport.c
> +++ b/net/vmw_vsock/virtio_transport.c
> @@ -256,6 +256,15 @@ virtio_transport_send_pkt(struct sk_buff *skb, struct net *net)
> goto out_rcu;
> }
>
> + /* A socket reset by an assign can still send credit updates from
> + * recvmsg() and setsockopt(SO_RCVLOWAT).
> + */
> + if (!vsock_g2h_net_reachable(net)) {
> + kfree_skb(skb);
> + len = -ENETUNREACH;
> + goto out_rcu;
> + }
> +
> /* If send_pkt_queue is empty, we can safely bypass this queue
> * because packet order is maintained and (try) to put the packet
> * on the virtqueue using virtio_transport_send_skb_fast_path.
[ ... ]
> @@ -682,10 +697,15 @@ static void virtio_transport_rx_work(struct work_struct *work)
>
> virtio_transport_deliver_tap_pkt(skb);
>
> - /* Force virtio-transport into global mode since it
> - * does not yet support local-mode namespacing.
> + /* Only grab a reference if the net changes underneath
> + * us.
> */
> - virtio_transport_recv_pkt(&virtio_transport, skb, NULL);
> + if (unlikely(!vsock_g2h_net_eq(net))) {
> + put_net_track(net, &ns_tracker);
> + net = vsock_g2h_net_get(&ns_tracker);
> + }
> +
> + virtio_transport_recv_pkt(t, skb, net);
> }
> } while (!virtqueue_enable_cb(vq));
>
> diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
> index 1e762a480df49..7efcbdc7e04fc 100644
> --- a/net/vmw_vsock/virtio_transport_common.c
> +++ b/net/vmw_vsock/virtio_transport_common.c
> @@ -1468,8 +1468,8 @@ void virtio_transport_release(struct vsock_sock *vsk)
> EXPORT_SYMBOL_GPL(virtio_transport_release);
>
> static int
> -virtio_transport_recv_connecting(struct sock *sk,
> - struct sk_buff *skb)
> +virtio_transport_recv_connecting(struct sock *sk, struct sk_buff *skb,
> + struct virtio_transport *t, struct net *net)
> {
> struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
> struct vsock_sock *vsk = vsock_sk(sk);
> @@ -1478,9 +1478,14 @@ virtio_transport_recv_connecting(struct sock *sk,
>
> switch (le16_to_cpu(hdr->op)) {
> case VIRTIO_VSOCK_OP_RESPONSE:
> - sk->sk_state = TCP_ESTABLISHED;
> + if (!vsock_maybe_set_connected(vsk)) {
> + virtio_transport_reset_no_sock(t, skb, net);
> + skerr = ECONNRESET;
> + err = -ENETUNREACH;
> + goto close;
> + }
> +
> sk->sk_socket->state = SS_CONNECTED;
> - vsock_insert_connected(vsk);
> sk->sk_state_change(sk);
> break;
[ ... ]
> @@ -1750,7 +1754,7 @@ virtio_transport_recv_listen(struct sock *sk, struct sk_buff *skb,
> */
> if (ret || vchild->transport != &t->transport) {
> release_sock(child);
> - virtio_transport_reset_no_sock(t, skb, sock_net(sk));
> + virtio_transport_reset_no_sock(t, skb, net);
> sock_put(child);
> return ret;
> }
> @@ -1758,7 +1762,13 @@ virtio_transport_recv_listen(struct sock *sk, struct sk_buff *skb,
> if (virtio_transport_space_update(child, skb))
> child->sk_write_space(child);
>
> - vsock_insert_connected(vchild);
> + if (!vsock_maybe_set_connected(vchild)) {
> + release_sock(child);
> + virtio_transport_reset_no_sock(t, skb, net);
> + sock_put(child);
> + return -ENETUNREACH;
> + }
[Severity: Medium]
As reported by Stefano Garzarella on v4 [1], will the VIRTIO_VSOCK_OP_RST
replies sent by virtio_transport_reset_no_sock(t, skb, net) still get dropped
by virtio_transport_send_pkt() when a namespace assignment races with an
incoming packet?
In virtio_transport_rx_work(), net is snapshotted via vsock_g2h_net_get()
before calling virtio_transport_recv_pkt(t, skb, net). Inside
virtio_transport_recv_pkt(), the socket sk is looked up with
vsock_find_connected_socket_net() or vsock_find_bound_socket_net(), both of
which require vsock_net_check_mode(sock_net(sk), net) to be true.
Because a namespace's vsock mode is fixed after initialization, sock_net(sk)
(as well as sock_net(child) in virtio_transport_recv_listen()) and the
snapshot net passed to virtio_transport_recv_pkt() always have the same
reachability with respect to vsock_g2h_net.
If a concurrent vsock_g2h_net_assign() changes vsock_g2h_net to an
unreachable namespace after the socket lookup in virtio_transport_recv_pkt()
(for instance while acquiring lock_sock(sk) or setting up the child socket):
1. In virtio_transport_recv_connecting(), vsock_maybe_set_connected(vsk)
returns false because sock_net(sk) is no longer reachable from the new
vsock_g2h_net.
2. In virtio_transport_recv_listen(), vsock_assign_transport(vchild, vsk) or
vsock_maybe_set_connected(vchild) fails for the same reason.
3. In virtio_transport_recv_pkt(), if vsock_g2h_reset_unreachable() swept sk
before lock_sock(sk) completed, sock_flag(sk, SOCK_DONE) is set.
All of these paths call:
virtio_transport_reset_no_sock(t, skb, net)
-> t->send_pkt(reply, net)
-> virtio_transport_send_pkt(reply, net)
However, net is still the pre-assignment namespace snapshot from
virtio_transport_rx_work(). When virtio_transport_send_pkt() checks
vsock_g2h_net_reachable(net) against the newly assigned vsock_g2h_net, it
evaluates to false, freeing the RST skb and returning -ENETUNREACH without
sending the reset to the peer.
Should virtio_transport_send_pkt() allow outgoing VIRTIO_VSOCK_OP_RST packets
(or should virtio_transport_reset_no_sock() bypass the net reachability
check) so the peer receives the reset when a handshake or connection races
with namespace assignment?
[1] https://lore.kernel.org/r/atjvzn-zPYJjLAhy@sgarzare-redhat
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261103-vsock-guest-ns-v5-0-48179b9b66b8@meta.com?part=5
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-11-05 1:54 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-04 1:53 [PATCH net-next v5 0/7] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
2026-11-04 1:53 ` [PATCH net-next v5 1/7] vsock: constify the transport in vsock_for_each_connected_socket() Bobby Eshleman
2026-11-04 1:53 ` [PATCH net-next v5 2/7] vsock: rename the vsock pernet operations Bobby Eshleman
2026-11-04 1:53 ` [PATCH net-next v5 3/7] vsock: pass the netns to ->has_remote_cid() Bobby Eshleman
2026-11-04 1:53 ` [PATCH net-next v5 4/7] vsock: add a netlink command to assign the g2h device to a netns Bobby Eshleman
2026-11-05 1:54 ` sashiko-bot
2026-11-04 1:53 ` [PATCH net-next v5 5/7] vsock/virtio: support guest device network namespace Bobby Eshleman
2026-11-05 1:54 ` sashiko-bot
2026-11-04 1:53 ` [PATCH net-next v5 6/7] selftests/vsock: test the guest vsock " Bobby Eshleman
2026-11-04 1:53 ` [PATCH net-next v5 7/7] selftests/vsock: test the netns assign privilege checks Bobby Eshleman
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox