* [PATCH net-next 0/6] vsock: assign the guest vsock device to a network namespace
@ 2026-09-02 23:00 Bobby Eshleman
2026-09-02 23:00 ` [PATCH net-next 1/6] vsock: constify the transport in vsock_for_each_connected_socket() Bobby Eshleman
` (6 more replies)
0 siblings, 7 replies; 16+ messages in thread
From: Bobby Eshleman @ 2026-09-02 23:00 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Eric Dumazet, 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
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, 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 ioctl on /dev/vsock, IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS, assigns the
device to the namespace of the calling process. 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 ioctl 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 ioctl requires CAP_NET_ADMIN in the initial user namespace.
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. Following netdevs, the device returns
to the initial namespace when the namespace it was moved to is deleted.
Transports opt in through a new netns_assign_allow callback. Only
virtio-vsock implements it here.
Patch 1 is just a const cleanup that patch 2 needs. The remaining
patches are actual implementation and tests.
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>
---
Bobby Eshleman (6):
vsock: constify the transport in vsock_for_each_connected_socket()
vsock: add IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS
vsock/virtio: support guest device network namespace
selftests/vsock: add a helper to assign the g2h device to a netns
selftests/vsock: test the guest vsock device network namespace
selftests/vsock: test the assign ioctl privilege checks
Documentation/admin-guide/sysctl/net.rst | 18 +
include/linux/virtio_vsock.h | 2 +
include/net/af_vsock.h | 9 +-
include/uapi/linux/vm_sockets.h | 6 +
net/vmw_vsock/af_vsock.c | 200 ++++++++-
net/vmw_vsock/virtio_transport.c | 28 +-
net/vmw_vsock/virtio_transport_common.c | 28 +-
tools/testing/selftests/vsock/.gitignore | 1 +
tools/testing/selftests/vsock/Makefile | 3 +-
tools/testing/selftests/vsock/config | 1 +
tools/testing/selftests/vsock/vmtest.sh | 461 ++++++++++++++++++++-
.../selftests/vsock/vsock_assign_g2h_netns.c | 45 ++
12 files changed, 774 insertions(+), 28 deletions(-)
---
base-commit: d0ec95a8a4e79f2fd6063fc8932415db8c227689
change-id: 20260831-vsock-guest-ns-d06af451da67
Best regards,
--
Bobby Eshleman <bobbyeshleman@meta.com>
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH net-next 1/6] vsock: constify the transport in vsock_for_each_connected_socket()
2026-09-02 23:00 [PATCH net-next 0/6] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
@ 2026-09-02 23:00 ` Bobby Eshleman
2026-09-02 23:00 ` [PATCH net-next 2/6] vsock: add IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS Bobby Eshleman
` (5 subsequent siblings)
6 siblings, 0 replies; 16+ messages in thread
From: Bobby Eshleman @ 2026-09-02 23:00 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Eric Dumazet, 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
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman
From: Bobby Eshleman <bobbyeshleman@meta.com>
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.
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 3357ee62d10b..87fdec60ba45 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -230,7 +230,7 @@ struct sock *vsock_find_connected_socket_net(struct sockaddr_vm *src,
struct sockaddr_vm *dst,
struct net *net);
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 a33b2a2d381d..29cde17e08f3 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -448,7 +448,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] 16+ messages in thread
* [PATCH net-next 2/6] vsock: add IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS
2026-09-02 23:00 [PATCH net-next 0/6] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
2026-09-02 23:00 ` [PATCH net-next 1/6] vsock: constify the transport in vsock_for_each_connected_socket() Bobby Eshleman
@ 2026-09-02 23:00 ` Bobby Eshleman
2026-09-02 23:35 ` Randy Dunlap
2026-09-06 17:03 ` netdev-bot+sashiko
2026-09-02 23:00 ` [PATCH net-next 3/6] vsock/virtio: support guest device network namespace Bobby Eshleman
` (4 subsequent siblings)
6 siblings, 2 replies; 16+ messages in thread
From: Bobby Eshleman @ 2026-09-02 23:00 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Eric Dumazet, 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
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman
From: Bobby Eshleman <bobbyeshleman@meta.com>
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 ioctl IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS on /dev/vsock that
gives userspace a way to move the device to the calling pid's namespace.
The call requires CAP_NET_ADMIN in the root user namespace. A privileged
user wishing to "unassign" the device can move it to the init_netns,
which is hardcoded to global mode (so no unassign call is necessary).
A getter to read the current assignment back was considered, returning
either the namespace's net_cookie or its nsfs inode number, but neither
seemed useful enough to bake into the uAPI now. It can be added later if
a user turns up that needs it.
Add a transport hook to indicate support for guest namespacing, so that
transports may opt in/out. A transport that opts out keeps the
reachability rules it had before this ioctl existed.
Sockets are reset when the underlying device moves to a different
namespace, so as to prevent reachability from the previous and now
disallowed namespace.
Following the approach of netdevs, the device returns to init_net when
its namespace is removed. Care is taken to not break flows when the
device is inside a global namespace that is being torn down and alive
sockets are in a different global namespace. In this scenario, the
device's netns getter pre-emptively falls back to the init_net (always
global) so that these flows are not disrupted. If init_netns ever
supports local-mode in the future, this logic will have to be changed.
Suggested-by: Stefano Garzarella <sgarzare@redhat.com>
Link: https://lore.kernel.org/all/20200427142518.uwssa6dtasrp3bfc@steredhat/
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Documentation/admin-guide/sysctl/net.rst | 18 +++
include/net/af_vsock.h | 7 ++
include/uapi/linux/vm_sockets.h | 6 +
net/vmw_vsock/af_vsock.c | 198 ++++++++++++++++++++++++++++++-
4 files changed, 228 insertions(+), 1 deletion(-)
diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst
index e586e17fc7a5..1e9c0d2be7b8 100644
--- a/Documentation/admin-guide/sysctl/net.rst
+++ b/Documentation/admin-guide/sysctl/net.rst
@@ -515,6 +515,24 @@ 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 ``IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS``
+ioctl on ``/dev/vsock`` moves it to the namespace of the calling process, 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 the initial namespace, so until the ioctl is issued
+nothing has moved and no mode has changed.
+
+Connections made before the move, from a namespace that can no longer reach the
+device, are reset. The device returns to the initial namespace when the
+namespace it was moved to is deleted, so assigning it to the initial namespace
+is how an assignment is undone.
+
ns_mode
-------
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index 87fdec60ba45..64c4b205a11b 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -190,6 +190,9 @@ struct vsock_transport {
/* Zero-copy. */
bool (*msgzerocopy_allow)(void);
+
+ /* True if the transport honours IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS. */
+ bool (*netns_assign_allow)(void);
};
/**** CORE ****/
@@ -235,6 +238,10 @@ void vsock_for_each_connected_socket(const struct vsock_transport *transport,
int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk);
bool vsock_find_cid(unsigned int cid);
void vsock_linger(struct sock *sk);
+struct net *vsock_g2h_net_get(void);
+bool vsock_g2h_net_reachable(struct net *net);
+bool vsock_g2h_reachable_sk(struct vsock_sock *vsk);
+bool vsock_maybe_set_connected(struct vsock_sock *vsk);
/**** TAP ****/
diff --git a/include/uapi/linux/vm_sockets.h b/include/uapi/linux/vm_sockets.h
index e05280e41522..894b0d65b458 100644
--- a/include/uapi/linux/vm_sockets.h
+++ b/include/uapi/linux/vm_sockets.h
@@ -195,6 +195,12 @@ struct sockaddr_vm {
#define IOCTL_VM_SOCKETS_GET_LOCAL_CID _IO(7, 0xb9)
+/* Assign the guest's vsock device to the network namespace of the calling
+ * process. Requires CAP_NET_ADMIN in the initial user namespace. To undo an
+ * assignment, assign the device to the initial network namespace.
+ */
+#define IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS _IO(7, 0xba)
+
/* MSG_ZEROCOPY notifications are encoded in the standard error format,
* sock_extended_err. See Documentation/networking/msg_zerocopy.rst in
* kernel source tree for more details.
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index 29cde17e08f3..ad11f0f56eb8 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -130,6 +130,24 @@
* 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.
+ * IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS on /dev/vsock assigns it to the
+ * namespace of the caller. 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 ioctl 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 reset.
+ *
+ * No reference is taken on the assigned namespace. As is done for netdevs,
+ * the device is moved back to init_net when that namespace is destroyed.
*/
#include <linux/compat.h>
@@ -208,6 +226,11 @@ static const struct vsock_transport *transport_dgram;
static const struct vsock_transport *transport_local;
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);
+
/**** UTILS ****/
/* Each bound VSocket is stored in the bind hash table and each connected
@@ -548,6 +571,17 @@ static void vsock_deassign_transport(struct vsock_sock *vsk)
vsk->transport = NULL;
}
+/* Return true if the loaded g2h transport honours namespace assignment. One
+ * that does not keeps the reachability rules it had before the ioctl existed.
+ *
+ * Must be called with vsock_register_mutex held.
+ */
+static bool vsock_g2h_netns_assignable(void)
+{
+ return transport_g2h && transport_g2h->netns_assign_allow &&
+ transport_g2h->netns_assign_allow();
+}
+
/* Assign a transport to a socket and call the .init transport callback.
*
* Note: for connection oriented socket this must be called when vsk->remote_addr
@@ -622,6 +656,13 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
goto err;
}
+ if (new_transport && new_transport == transport_g2h &&
+ vsock_g2h_netns_assignable() &&
+ !vsock_g2h_net_reachable(sock_net(sk))) {
+ ret = -ENETUNREACH;
+ goto err;
+ }
+
/* We increase the module refcnt to prevent the transport unloading
* while there are open sockets assigned to it.
*/
@@ -710,6 +751,140 @@ bool vsock_find_cid(unsigned int cid)
}
EXPORT_SYMBOL_GPL(vsock_find_cid);
+/* Return the g2h devices' namespace with a reference held, or NULL if that
+ * namespace is being destroyed.
+ */
+struct net *vsock_g2h_net_get(void)
+{
+ struct net *assigned;
+ struct net *net;
+
+ rcu_read_lock();
+ assigned = rcu_dereference(vsock_g2h_net);
+ net = maybe_get_net(assigned);
+
+ /* !net means the net is about to be destroyed, at which point the g2h
+ * device will move to the init_net. If the init_net and the dying net
+ * are both global mode, we use the init_net as a fallback to avoid
+ * disrupting global-mode flows. The per-net destructor hook will
+ * eventually move the g2h device to the init_net anyway.
+ */
+ if (!net && vsock_net_check_mode(&init_net, assigned))
+ net = get_net(&init_net);
+ rcu_read_unlock();
+
+ return net;
+}
+EXPORT_SYMBOL_GPL(vsock_g2h_net_get);
+
+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);
+
+bool vsock_g2h_reachable_sk(struct vsock_sock *vsk)
+{
+ const struct vsock_transport *t = vsk->transport;
+
+ if (!t || !t->netns_assign_allow || !t->netns_assign_allow())
+ return true;
+
+ return vsock_g2h_net_reachable(sock_net(sk_vsock(vsk)));
+}
+EXPORT_SYMBOL_GPL(vsock_g2h_reachable_sk);
+
+/* 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.
+ *
+ * vsock_g2h_net_assign() resets the sockets it finds in the same table under
+ * the same lock. Either vsock_g2h_net_assign() sees the vsk in the table and
+ * resets it, or it does not see the @vsk in the table and this function
+ * refuses to add it. This avoids netns assignment racing with outstanding
+ * connection responses and incoming connection requests.
+ */
+bool vsock_maybe_set_connected(struct vsock_sock *vsk)
+{
+ struct list_head *list = vsock_connected_sockets(&vsk->remote_addr,
+ &vsk->local_addr);
+ bool reachable;
+
+ 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 void vsock_reset_unreachable_sock(struct sock *sk)
+{
+ if (vsock_g2h_net_reachable(sock_net(sk)))
+ return;
+
+ sk->sk_state = TCP_CLOSE;
+ sk->sk_err = ECONNRESET;
+ sk_error_report(sk);
+}
+
+/* Move the g2h device to @net. Returns -ENODEV if no g2h transport is loaded
+ * and -EOPNOTSUPP if the loaded one cannot be moved.
+ */
+static int vsock_g2h_net_assign(struct net *net)
+{
+ int ret = 0;
+
+ mutex_lock(&vsock_register_mutex);
+ if (!transport_g2h) {
+ ret = -ENODEV;
+ } else if (!vsock_g2h_netns_assignable()) {
+ ret = -EOPNOTSUPP;
+ } else {
+ /* See vsock_maybe_set_connected() comment about synchronizing
+ * with connecting sockets.
+ */
+ rcu_assign_pointer(vsock_g2h_net, net);
+ vsock_for_each_connected_socket(transport_g2h,
+ vsock_reset_unreachable_sock);
+ }
+ mutex_unlock(&vsock_register_mutex);
+
+ return ret;
+}
+
+/* Move the g2h device back to init_net if it lives in @net, which is about to
+ * be destroyed.
+ */
+static void vsock_g2h_net_reset(struct net *net)
+{
+ bool reset = false;
+
+ /* Avoid taking the mutex if the namespaces don't match. */
+ if (likely(rcu_access_pointer(vsock_g2h_net) != net))
+ return;
+
+ mutex_lock(&vsock_register_mutex);
+ if (rcu_access_pointer(vsock_g2h_net) == net) {
+ rcu_assign_pointer(vsock_g2h_net, &init_net);
+ reset = true;
+ }
+ mutex_unlock(&vsock_register_mutex);
+
+ if (reset)
+ synchronize_rcu();
+}
+
static struct sock *vsock_dequeue_accept(struct sock *listener)
{
struct vsock_sock *vlistener;
@@ -2745,6 +2920,15 @@ static long vsock_dev_do_ioctl(struct file *filp,
retval = -EFAULT;
break;
+ case IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS:
+ if (!capable(CAP_NET_ADMIN)) {
+ retval = -EPERM;
+ break;
+ }
+
+ retval = vsock_g2h_net_assign(current->nsproxy->net_ns);
+ break;
+
default:
retval = -ENOIOCTLCMD;
}
@@ -2978,6 +3162,7 @@ static __net_init int vsock_sysctl_init_net(struct net *net)
static __net_exit void vsock_sysctl_exit_net(struct net *net)
{
+ vsock_g2h_net_reset(net);
vsock_sysctl_unregister(net);
}
@@ -3104,13 +3289,21 @@ EXPORT_SYMBOL_GPL(vsock_core_register);
void vsock_core_unregister(const struct vsock_transport *t)
{
+ bool g2h_net_reset = false;
+
mutex_lock(&vsock_register_mutex);
if (transport_h2g == t)
transport_h2g = NULL;
- if (transport_g2h == t)
+ if (transport_g2h == t) {
transport_g2h = NULL;
+ /* The device is gone, so is its namespace assignment. */
+ if (rcu_access_pointer(vsock_g2h_net) != &init_net) {
+ rcu_assign_pointer(vsock_g2h_net, &init_net);
+ g2h_net_reset = true;
+ }
+ }
if (transport_dgram == t)
transport_dgram = NULL;
@@ -3119,6 +3312,9 @@ void vsock_core_unregister(const struct vsock_transport *t)
transport_local = NULL;
mutex_unlock(&vsock_register_mutex);
+
+ if (g2h_net_reset)
+ synchronize_rcu();
}
EXPORT_SYMBOL_GPL(vsock_core_unregister);
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH net-next 3/6] vsock/virtio: support guest device network namespace
2026-09-02 23:00 [PATCH net-next 0/6] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
2026-09-02 23:00 ` [PATCH net-next 1/6] vsock: constify the transport in vsock_for_each_connected_socket() Bobby Eshleman
2026-09-02 23:00 ` [PATCH net-next 2/6] vsock: add IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS Bobby Eshleman
@ 2026-09-02 23:00 ` Bobby Eshleman
2026-09-06 17:04 ` netdev-bot+sashiko
2026-09-02 23:00 ` [PATCH net-next 4/6] selftests/vsock: add a helper to assign the g2h device to a netns Bobby Eshleman
` (3 subsequent siblings)
6 siblings, 1 reply; 16+ messages in thread
From: Bobby Eshleman @ 2026-09-02 23:00 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Eric Dumazet, 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
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman
From: Bobby Eshleman <bobbyeshleman@meta.com>
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 ioctl is issued.
When the device's local-mode namespace is being destroyed, received
packets are reset until new valid a namespace has been assigned and/or
automatically returned to, and the next RX batch begins (in
virtio_transport_rx_work). They are reset rather than dropped because
vsock does not retransmit, so a silent drop would leave the host waiting
for a timeout, and a connection request arriving in that window has no
socket whose teardown would tell it otherwise. This requires making
virtio_transport_reset_no_sock() available outside of the common code.
When a device is assigned to a namespace, every already established
vsock socket that is no longer able to reach the device is forcibly
reset. 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 the reset sweep will correctly catch it. This is the purpose
of the new helper 'vsock_maybe_set_connected()'.
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
include/linux/virtio_vsock.h | 2 ++
net/vmw_vsock/virtio_transport.c | 28 ++++++++++++++++++++++------
net/vmw_vsock/virtio_transport_common.c | 28 +++++++++++++++++++++-------
3 files changed, 45 insertions(+), 13 deletions(-)
diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h
index f91704731057..9c68ce1d7fb4 100644
--- a/include/linux/virtio_vsock.h
+++ b/include/linux/virtio_vsock.h
@@ -286,6 +286,8 @@ void virtio_transport_inc_tx_pkt(struct virtio_vsock_sock *vvs, struct sk_buff *
u32 virtio_transport_get_credit(struct virtio_vsock_sock *vvs, u32 wanted);
void virtio_transport_put_credit(struct virtio_vsock_sock *vvs, u32 credit);
void virtio_transport_deliver_tap_pkt(struct sk_buff *skb);
+int virtio_transport_reset_no_sock(const struct virtio_transport *t,
+ struct sk_buff *skb, struct net *net);
int virtio_transport_purge_skbs(void *vsk, struct sk_buff_head *list);
int virtio_transport_read_skb(struct vsock_sock *vsk, skb_read_actor_t read_actor);
int virtio_transport_notify_set_rcvlowat(struct vsock_sock *vsk, int val);
diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
index 4f9aa9c4c3aa..a453a4f828dc 100644
--- a/net/vmw_vsock/virtio_transport.c
+++ b/net/vmw_vsock/virtio_transport.c
@@ -540,9 +540,14 @@ static bool virtio_transport_msgzerocopy_allow(void)
return true;
}
+static bool virtio_transport_netns_assign_allow(void)
+{
+ return true;
+}
+
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 +592,7 @@ static struct virtio_transport virtio_transport = {
.seqpacket_has_data = virtio_transport_seqpacket_has_data,
.msgzerocopy_allow = virtio_transport_msgzerocopy_allow,
+ .netns_assign_allow = virtio_transport_netns_assign_allow,
.notify_poll_in = virtio_transport_notify_poll_in,
.notify_poll_out = virtio_transport_notify_poll_out,
@@ -616,7 +622,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;
@@ -634,6 +640,9 @@ static void virtio_transport_rx_work(struct work_struct *work)
struct virtio_vsock *vsock =
container_of(work, struct virtio_vsock, rx_work);
struct virtqueue *vq;
+ struct net *net;
+
+ net = vsock_g2h_net_get();
mutex_lock(&vsock->rx_lock);
@@ -682,10 +691,14 @@ 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.
- */
- virtio_transport_recv_pkt(&virtio_transport, skb, NULL);
+ if (unlikely(!net)) {
+ virtio_transport_reset_no_sock(
+ &virtio_transport, skb, &init_net);
+ kfree_skb(skb);
+ continue;
+ }
+
+ virtio_transport_recv_pkt(&virtio_transport, skb, net);
}
} while (!virtqueue_enable_cb(vq));
@@ -694,6 +707,9 @@ static void virtio_transport_rx_work(struct work_struct *work)
virtio_vsock_rx_fill(vsock);
out_nofill:
mutex_unlock(&vsock->rx_lock);
+
+ if (net)
+ put_net(net);
}
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 88df82364f77..313ef263fd2d 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -1315,8 +1315,8 @@ static int virtio_transport_reset(struct vsock_sock *vsk,
* loopback, this is the namespace of the socket. For vhost, this is the
* namespace of the VM (i.e., vhost_vsock).
*/
-static int virtio_transport_reset_no_sock(const struct virtio_transport *t,
- struct sk_buff *skb, struct net *net)
+int virtio_transport_reset_no_sock(const struct virtio_transport *t,
+ struct sk_buff *skb, struct net *net)
{
struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
struct virtio_vsock_pkt_info info = {
@@ -1355,6 +1355,7 @@ static int virtio_transport_reset_no_sock(const struct virtio_transport *t,
return t->send_pkt(reply, net);
}
+EXPORT_SYMBOL_GPL(virtio_transport_reset_no_sock);
/* This function should be called with sk_lock held and SOCK_DONE set */
static void virtio_transport_remove_sock(struct vsock_sock *vsk)
@@ -1478,9 +1479,14 @@ virtio_transport_recv_connecting(struct sock *sk,
switch (le16_to_cpu(hdr->op)) {
case VIRTIO_VSOCK_OP_RESPONSE:
- sk->sk_state = TCP_ESTABLISHED;
+ /* An assign cannot see a socket that is not connected yet. */
+ if (!vsock_maybe_set_connected(vsk)) {
+ skerr = ECONNRESET;
+ err = -ENETUNREACH;
+ goto destroy;
+ }
+
sk->sk_socket->state = SS_CONNECTED;
- vsock_insert_connected(vsk);
sk->sk_state_change(sk);
break;
case VIRTIO_VSOCK_OP_INVALID:
@@ -1736,8 +1742,6 @@ virtio_transport_recv_listen(struct sock *sk, struct sk_buff *skb,
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));
@@ -1758,7 +1762,17 @@ 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);
+ /* An assign cannot see a socket that is not connected yet, and the
+ * check in vsock_assign_transport() above has since dropped
+ * vsock_register_mutex.
+ */
+ if (!vsock_maybe_set_connected(vchild)) {
+ release_sock(child);
+ virtio_transport_reset_no_sock(t, skb, sock_net(sk));
+ sock_put(child);
+ return -ENETUNREACH;
+ }
+
vsock_enqueue_accept(sk, child);
virtio_transport_send_response(vchild, skb);
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH net-next 4/6] selftests/vsock: add a helper to assign the g2h device to a netns
2026-09-02 23:00 [PATCH net-next 0/6] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
` (2 preceding siblings ...)
2026-09-02 23:00 ` [PATCH net-next 3/6] vsock/virtio: support guest device network namespace Bobby Eshleman
@ 2026-09-02 23:00 ` Bobby Eshleman
2026-09-06 17:04 ` netdev-bot+sashiko
2026-09-02 23:00 ` [PATCH net-next 5/6] selftests/vsock: test the guest vsock device network namespace Bobby Eshleman
` (2 subsequent siblings)
6 siblings, 1 reply; 16+ messages in thread
From: Bobby Eshleman @ 2026-09-02 23:00 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Eric Dumazet, 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
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman
From: Bobby Eshleman <bobbyeshleman@meta.com>
No shell tool can issue IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS, so anything
that wants to move the guest's vsock device from a script needs a small
program to do it.
It exits with the ioctl's errno so a caller can differentiate the
reasons for failure.
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
tools/testing/selftests/vsock/.gitignore | 1 +
tools/testing/selftests/vsock/Makefile | 3 +-
.../selftests/vsock/vsock_assign_g2h_netns.c | 45 ++++++++++++++++++++++
3 files changed, 47 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/vsock/.gitignore b/tools/testing/selftests/vsock/.gitignore
index 9c5bf379480f..ffca0d9c34b1 100644
--- a/tools/testing/selftests/vsock/.gitignore
+++ b/tools/testing/selftests/vsock/.gitignore
@@ -1,2 +1,3 @@
vmtest.log
+vsock_assign_g2h_netns
vsock_test
diff --git a/tools/testing/selftests/vsock/Makefile b/tools/testing/selftests/vsock/Makefile
index c407c0afd938..d7170a15150f 100644
--- a/tools/testing/selftests/vsock/Makefile
+++ b/tools/testing/selftests/vsock/Makefile
@@ -11,7 +11,6 @@ $(OUTPUT)/vsock_test: $(VSOCK_TEST_DIR)/vsock_test
$(VSOCK_TEST_DIR)/vsock_test: $(VSOCK_TEST_SRCS)
$(MAKE) -C $(VSOCK_TEST_DIR) vsock_test
TEST_PROGS += vmtest.sh
-TEST_GEN_FILES := vsock_test
+TEST_GEN_FILES := vsock_test vsock_assign_g2h_netns
include ../lib.mk
-
diff --git a/tools/testing/selftests/vsock/vsock_assign_g2h_netns.c b/tools/testing/selftests/vsock/vsock_assign_g2h_netns.c
new file mode 100644
index 000000000000..6f15629af607
--- /dev/null
+++ b/tools/testing/selftests/vsock/vsock_assign_g2h_netns.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Assign the guest->host vsock transport, i.e. the guest's virtio-vsock
+ * device, to the network namespace of the invoking process.
+ *
+ * Exits with the ioctl's errno, so that callers can tell why it was refused.
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include <linux/vm_sockets.h>
+
+#ifndef IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS
+#define IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS _IO(7, 0xba)
+#endif
+
+int main(void)
+{
+ int fd, ret;
+
+ fd = open("/dev/vsock", O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "open /dev/vsock: %s\n", strerror(errno));
+ return -1;
+ }
+
+ ret = ioctl(fd, IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS);
+ if (ret < 0) {
+ ret = errno;
+ fprintf(stderr,
+ "IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS: %s (errno %d)\n",
+ strerror(errno), errno);
+ }
+
+ close(fd);
+
+ return ret;
+}
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH net-next 5/6] selftests/vsock: test the guest vsock device network namespace
2026-09-02 23:00 [PATCH net-next 0/6] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
` (3 preceding siblings ...)
2026-09-02 23:00 ` [PATCH net-next 4/6] selftests/vsock: add a helper to assign the g2h device to a netns Bobby Eshleman
@ 2026-09-02 23:00 ` Bobby Eshleman
2026-09-06 17:04 ` netdev-bot+sashiko
2026-09-02 23:00 ` [PATCH net-next 6/6] selftests/vsock: test the assign ioctl privilege checks Bobby Eshleman
2026-09-04 8:55 ` [PATCH net-next 0/6] vsock: assign the guest vsock device to a network namespace Stefano Garzarella
6 siblings, 1 reply; 16+ messages in thread
From: Bobby Eshleman @ 2026-09-02 23:00 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Eric Dumazet, 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
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman
From: Bobby Eshleman <bobbyeshleman@meta.com>
Add six tests covering basic cases and hopefully most edge cases: normal
transfer in both directions, namespaces being deleted, device movement
between namespaces mid-socket-lifetime.
The namespaces are created with "unshare -n" and held open by a sleeping
process rather than by ip netns because bind-mounting namespaces via ip
netns is incompatible with the guest 9p rootfs.
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
tools/testing/selftests/vsock/vmtest.sh | 404 +++++++++++++++++++++++++++++++-
1 file changed, 394 insertions(+), 10 deletions(-)
diff --git a/tools/testing/selftests/vsock/vmtest.sh b/tools/testing/selftests/vsock/vmtest.sh
index 310dfc2a39ad..53591fa07f1a 100755
--- a/tools/testing/selftests/vsock/vmtest.sh
+++ b/tools/testing/selftests/vsock/vmtest.sh
@@ -17,6 +17,7 @@ readonly KERNEL_CHECKOUT=$(realpath "${SCRIPT_DIR}"/../../../../)
source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh
readonly VSOCK_TEST="${SCRIPT_DIR}"/vsock_test
+readonly VSOCK_ASSIGN_G2H_NETNS="${SCRIPT_DIR}"/vsock_assign_g2h_netns
readonly TEST_GUEST_PORT=51000
readonly TEST_HOST_PORT=50000
readonly TEST_HOST_PORT_LISTENER=50001
@@ -73,6 +74,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_reset_on_ns_delete_ok
+ ns_guest_assign_g2h_netns_old_conn_send_fails
)
readonly TEST_DESCS=(
# vm_server_host_client
@@ -149,12 +156,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 assign ioctl."
+
+ # 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_reset_on_ns_delete_ok
+ "Check the guest's vsock device returns to the initial ns when its ns is deleted."
+
+ # 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_reset_on_ns_delete_ok
+ ns_guest_assign_g2h_netns_old_conn_send_fails
)
readonly NS_MODES=("local" "global")
@@ -302,18 +333,20 @@ check_args() {
}
check_deps() {
- for dep in vng ${QEMU} busybox pkill ssh ss socat nsenter; do
+ for dep in vng ${QEMU} busybox pkill ssh ss socat nsenter unshare; do
if [[ ! -x $(command -v "${dep}") ]]; then
echo -e "skip: dependency ${dep} not found!\n"
exit "${KSFT_SKIP}"
fi
done
- if [[ ! -x $(command -v "${VSOCK_TEST}") ]]; then
- printf "skip: %s not found!" "${VSOCK_TEST}"
- printf " Please build the kselftest vsock target.\n"
- exit "${KSFT_SKIP}"
- fi
+ for prog in "${VSOCK_TEST}" "${VSOCK_ASSIGN_G2H_NETNS}"; do
+ if [[ ! -x $(command -v "${prog}") ]]; then
+ printf "skip: %s not found!" "${prog}"
+ printf " Please build the kselftest vsock target.\n"
+ exit "${KSFT_SKIP}"
+ fi
+ done
}
check_netns() {
@@ -401,6 +434,7 @@ 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
+ cp "${VSOCK_ASSIGN_G2H_NETNS}" "${TEST_HOME}"/vsock_assign_g2h_netns
}
create_pidfile() {
@@ -528,6 +562,58 @@ 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
+
+ vm_ssh "${ns}" -- \
+ "echo local > /proc/sys/net/vsock/child_ns_mode" &>/dev/null
+
+ vm_ssh "${ns}" -- "unshare -n sleep infinity" \
+ '>/dev/null 2>&1 & echo $!'
+}
+
+# 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}" ./vsock_assign_g2h_netns
+}
+
+vm_reset_g2h() {
+ vm_ns_assign_g2h "init_ns" "" &>/dev/null
+}
+
# derived from selftests/net/net_helper.sh
wait_for_listener()
{
@@ -564,17 +650,31 @@ 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=
+
+ [[ -n "${nspid}" ]] && nsenter="nsenter -t ${nspid} -n"
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 ${port} ${WAIT_PERIOD} ${WAIT_PERIOD_MAX} ${protocol}"
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 +1521,290 @@ 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'
+
+ terminate_pids "${pid}"
+}
+
+# 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'
+
+ terminate_pids "${pid}"
+}
+
+test_ns_guest_assign_g2h_netns_old_conn_send_fails() {
+ local gap=$(( WAIT_PERIOD * 3 ))
+ local port=12346
+ local outfile
+ local result
+ 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.
+ vm_ssh "init_ns" -- \
+ "(echo FIRST; sleep ${gap}; echo SECOND) |" \
+ "socat -u STDIN VSOCK-CONNECT:2:${port}" &>/dev/null &
+
+ sleep "${WAIT_PERIOD}"
+
+ if ! vm_ns_assign_g2h "init_ns" "${nspid}"; then
+ log_host "failed to assign the vsock device to the guest ns"
+ terminate_pids "${pid}"
+ rm -f "${outfile}"
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+ return "${KSFT_FAIL}"
+ fi
+
+ # Let the second write happen and land, if it is going to.
+ sleep $(( gap + WAIT_PERIOD ))
+
+ terminate_pids "${pid}"
+ result=$(cat "${outfile}")
+ rm -f "${outfile}"
+
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+
+ if [[ "${result}" != *FIRST* ]]; then
+ log_host "connection did not work before the assign: [${result}]"
+ return "${KSFT_FAIL}"
+ 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
+
+ # The device now belongs to a local-mode namespace, so the guest's
+ # initial namespace must no longer reach the host.
+ 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_reset_on_ns_delete_ok() {
+ local port=12345
+ local outfile
+ local result
+ local nspid
+ local i
+
+ 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}"
+
+ # The holder is gone, but the namespace itself is dismantled from a
+ # workqueue, so the device does not come back the same instant. Retry
+ # until it does, rather than expecting the first send to succeed.
+ outfile=$(mktemp)
+ for ((i = 0; i < 5; i++)); do
+ sleep "${WAIT_PERIOD}"
+ guest_send_to_host "init_ns" "" "$(( port + i ))" "${outfile}"
+ result=$(cat "${outfile}")
+ if [[ "${result}" == TEST ]]; then
+ break
+ fi
+ done
+
+ rm -f "${outfile}"
+ vm_reset_g2h
+
+ if [[ "${result}" != TEST ]]; then
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
shared_vm_test() {
local tname
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH net-next 6/6] selftests/vsock: test the assign ioctl privilege checks
2026-09-02 23:00 [PATCH net-next 0/6] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
` (4 preceding siblings ...)
2026-09-02 23:00 ` [PATCH net-next 5/6] selftests/vsock: test the guest vsock device network namespace Bobby Eshleman
@ 2026-09-02 23:00 ` Bobby Eshleman
2026-09-06 17:04 ` netdev-bot+sashiko
2026-09-04 8:55 ` [PATCH net-next 0/6] vsock: assign the guest vsock device to a network namespace Stefano Garzarella
6 siblings, 1 reply; 16+ messages in thread
From: Bobby Eshleman @ 2026-09-02 23:00 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Eric Dumazet, 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
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman
From: Bobby Eshleman <bobbyeshleman@meta.com>
/dev/vsock IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS has 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>
---
tools/testing/selftests/vsock/config | 1 +
tools/testing/selftests/vsock/vmtest.sh | 59 ++++++++++++++++++++++++++++++++-
2 files changed, 59 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 53591fa07f1a..efb94d17d997 100755
--- a/tools/testing/selftests/vsock/vmtest.sh
+++ b/tools/testing/selftests/vsock/vmtest.sh
@@ -28,6 +28,7 @@ readonly WAIT_PERIOD=3
readonly WAIT_PERIOD_MAX=60
readonly WAIT_QEMU=5
readonly PIDFILE_TEMPLATE=/tmp/vsock_vmtest_XXXX.pid
+readonly EPERM=1
declare -A PIDFILES
# virtme-ng offers a netdev for ssh when using "--ssh", but we also need a
@@ -80,6 +81,8 @@ readonly TEST_NAMES=(
ns_guest_assign_g2h_netns_host_connect_ok
ns_guest_assign_g2h_netns_reset_on_ns_delete_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
@@ -174,6 +177,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=(
@@ -186,6 +195,8 @@ readonly USE_SHARED_VM=(
ns_guest_assign_g2h_netns_host_connect_ok
ns_guest_assign_g2h_netns_reset_on_ns_delete_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")
@@ -333,7 +344,8 @@ check_args() {
}
check_deps() {
- for dep in vng ${QEMU} busybox pkill ssh ss socat nsenter unshare; do
+ for dep in vng ${QEMU} busybox pkill ssh ss socat nsenter unshare \
+ setpriv; do
if [[ ! -x $(command -v "${dep}") ]]; then
echo -e "skip: dependency ${dep} not found!\n"
exit "${KSFT_SKIP}"
@@ -1805,6 +1817,51 @@ test_ns_guest_assign_g2h_netns_reset_on_ns_delete_ok() {
return "${KSFT_PASS}"
}
+test_ns_guest_assign_g2h_netns_no_cap_net_admin_fails() {
+ local cmd="unshare -n setpriv --bounding-set=-net_admin"
+ local rc
+
+ vm_ssh "init_ns" -- "${cmd}" ./vsock_assign_g2h_netns &>/dev/null
+ rc=$?
+
+ if [[ "${rc}" -ne "${EPERM}" ]]; then
+ log_host "expected EPERM (${EPERM}) without CAP_NET_ADMIN, got ${rc}"
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
+test_ns_guest_assign_g2h_netns_unpriv_user_ns_fails() {
+ local helper=/tmp/vsock_assign_g2h_netns
+ local unpriv_uid=65534
+ local unpriv
+ local rc
+
+ unpriv="setpriv --reuid=${unpriv_uid} --regid=${unpriv_uid}"
+ unpriv="${unpriv} --clear-groups"
+
+ if ! vm_ssh "init_ns" -- "${unpriv} unshare -U true"; then
+ log_host "unprivileged user namespaces unavailable, skipping"
+ return "${KSFT_SKIP}"
+ fi
+
+ # The home shared with the guest is root-only, so place the helper where
+ # an unprivileged user can execute it.
+ vm_ssh "init_ns" -- \
+ "cp ./vsock_assign_g2h_netns ${helper} && chmod 755 ${helper}"
+
+ vm_ssh "init_ns" -- "${unpriv} unshare -Urn ${helper}" &>/dev/null
+ rc=$?
+
+ if [[ "${rc}" -ne "${EPERM}" ]]; then
+ log_host "expected EPERM (${EPERM}) for an unprivileged user, got ${rc}"
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
shared_vm_test() {
local tname
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH net-next 2/6] vsock: add IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS
2026-09-02 23:00 ` [PATCH net-next 2/6] vsock: add IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS Bobby Eshleman
@ 2026-09-02 23:35 ` Randy Dunlap
2026-09-02 23:58 ` Bobby Eshleman
2026-09-06 17:03 ` netdev-bot+sashiko
1 sibling, 1 reply; 16+ messages in thread
From: Randy Dunlap @ 2026-09-02 23:35 UTC (permalink / raw)
To: Bobby Eshleman, Stefano Garzarella, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Stefan Hajnoczi, Michael S. Tsirkin, Jason Wang,
Xuan Zhuo, Eugenio Pérez, Shuah Khan
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman
Hi,
On 9/2/26 4:00 PM, Bobby Eshleman wrote:
> From: Bobby Eshleman <bobbyeshleman@meta.com>
>
> 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 ioctl IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS on /dev/vsock that
> gives userspace a way to move the device to the calling pid's namespace.
> The call requires CAP_NET_ADMIN in the root user namespace. A privileged
> user wishing to "unassign" the device can move it to the init_netns,
> which is hardcoded to global mode (so no unassign call is necessary).
>
> A getter to read the current assignment back was considered, returning
> either the namespace's net_cookie or its nsfs inode number, but neither
> seemed useful enough to bake into the uAPI now. It can be added later if
> a user turns up that needs it.
>
> Add a transport hook to indicate support for guest namespacing, so that
> transports may opt in/out. A transport that opts out keeps the
> reachability rules it had before this ioctl existed.
>
> Sockets are reset when the underlying device moves to a different
> namespace, so as to prevent reachability from the previous and now
> disallowed namespace.
>
> Following the approach of netdevs, the device returns to init_net when
I'm confused by the use of "init_net" several times and "init_netns" at
least 2 times. "init_net" is the initial, boot-time net namespace.
And is one of these what is referred to in the Documentation/ file below
as "initial namespace"?
> its namespace is removed. Care is taken to not break flows when the
> device is inside a global namespace that is being torn down and alive
> sockets are in a different global namespace. In this scenario, the
> device's netns getter pre-emptively falls back to the init_net (always
> global) so that these flows are not disrupted. If init_netns ever
maybe init_netns()
if you are referring to a function...
> supports local-mode in the future, this logic will have to be changed.
>
> Suggested-by: Stefano Garzarella <sgarzare@redhat.com>
> Link: https://lore.kernel.org/all/20200427142518.uwssa6dtasrp3bfc@steredhat/
> Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
> ---
> Documentation/admin-guide/sysctl/net.rst | 18 +++
> include/net/af_vsock.h | 7 ++
> include/uapi/linux/vm_sockets.h | 6 +
> net/vmw_vsock/af_vsock.c | 198 ++++++++++++++++++++++++++++++-
> 4 files changed, 228 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst
> index e586e17fc7a5..1e9c0d2be7b8 100644
> --- a/Documentation/admin-guide/sysctl/net.rst
> +++ b/Documentation/admin-guide/sysctl/net.rst
> @@ -515,6 +515,24 @@ 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 ``IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS``
> +ioctl on ``/dev/vsock`` moves it to the namespace of the calling process, which
> +requires ``CAP_NET_ADMIN`` in the initial user namespace. The namespace's mode
Is this the caller's namespace?
> +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 the initial namespace, so until the ioctl is issued
> +nothing has moved and no mode has changed.
> +
> +Connections made before the move, from a namespace that can no longer reach the
> +device, are reset. The device returns to the initial namespace when the
> +namespace it was moved to is deleted, so assigning it to the initial namespace
> +is how an assignment is undone.
> +
> ns_mode
> -------
>
thanks.
--
~Randy
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH net-next 2/6] vsock: add IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS
2026-09-02 23:35 ` Randy Dunlap
@ 2026-09-02 23:58 ` Bobby Eshleman
0 siblings, 0 replies; 16+ messages in thread
From: Bobby Eshleman @ 2026-09-02 23:58 UTC (permalink / raw)
To: Randy Dunlap
Cc: Stefano Garzarella, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan,
Stefan Hajnoczi, Michael S. Tsirkin, Jason Wang, Xuan Zhuo,
Eugenio Pérez, Shuah Khan, virtualization, netdev,
linux-kernel, linux-doc, kvm, linux-kselftest, sargun, jlinbox,
Bobby Eshleman
On Wed, Sep 02, 2026 at 04:35:59PM -0700, Randy Dunlap wrote:
> Hi,
>
> On 9/2/26 4:00 PM, Bobby Eshleman wrote:
> > From: Bobby Eshleman <bobbyeshleman@meta.com>
> >
> > 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 ioctl IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS on /dev/vsock that
> > gives userspace a way to move the device to the calling pid's namespace.
> > The call requires CAP_NET_ADMIN in the root user namespace. A privileged
> > user wishing to "unassign" the device can move it to the init_netns,
> > which is hardcoded to global mode (so no unassign call is necessary).
> >
> > A getter to read the current assignment back was considered, returning
> > either the namespace's net_cookie or its nsfs inode number, but neither
> > seemed useful enough to bake into the uAPI now. It can be added later if
> > a user turns up that needs it.
> >
> > Add a transport hook to indicate support for guest namespacing, so that
> > transports may opt in/out. A transport that opts out keeps the
> > reachability rules it had before this ioctl existed.
> >
> > Sockets are reset when the underlying device moves to a different
> > namespace, so as to prevent reachability from the previous and now
> > disallowed namespace.
> >
> > Following the approach of netdevs, the device returns to init_net when
>
> I'm confused by the use of "init_net" several times and "init_netns" at
> least 2 times. "init_net" is the initial, boot-time net namespace.
>
> And is one of these what is referred to in the Documentation/ file below
> as "initial namespace"?
Good point, init_netns should be init_net everywhere here (and in the
Documentation/).
>
> > its namespace is removed. Care is taken to not break flows when the
> > device is inside a global namespace that is being torn down and alive
> > sockets are in a different global namespace. In this scenario, the
> > device's netns getter pre-emptively falls back to the init_net (always
> > global) so that these flows are not disrupted. If init_netns ever
>
> maybe init_netns()
> if you are referring to a function...
Same here, should be init_net.
>
> > supports local-mode in the future, this logic will have to be changed.
> >
> > Suggested-by: Stefano Garzarella <sgarzare@redhat.com>
> > Link: https://lore.kernel.org/all/20200427142518.uwssa6dtasrp3bfc@steredhat/
> > Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
> > ---
> > Documentation/admin-guide/sysctl/net.rst | 18 +++
> > include/net/af_vsock.h | 7 ++
> > include/uapi/linux/vm_sockets.h | 6 +
> > net/vmw_vsock/af_vsock.c | 198 ++++++++++++++++++++++++++++++-
> > 4 files changed, 228 insertions(+), 1 deletion(-)
> >
> > diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst
> > index e586e17fc7a5..1e9c0d2be7b8 100644
> > --- a/Documentation/admin-guide/sysctl/net.rst
> > +++ b/Documentation/admin-guide/sysctl/net.rst
> > @@ -515,6 +515,24 @@ 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 ``IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS``
> > +ioctl on ``/dev/vsock`` moves it to the namespace of the calling process, which
> > +requires ``CAP_NET_ADMIN`` in the initial user namespace. The namespace's mode
>
> Is this the caller's namespace?
Yes. I'll clarify that in the next revision.
>
> > +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 the initial namespace, so until the ioctl is issued
> > +nothing has moved and no mode has changed.
> > +
> > +Connections made before the move, from a namespace that can no longer reach the
> > +device, are reset. The device returns to the initial namespace when the
> > +namespace it was moved to is deleted, so assigning it to the initial namespace
> > +is how an assignment is undone.
> > +
> > ns_mode
> > -------
> >
> thanks.
> --
> ~Randy
>
Thanks for the review.
Best,
Bobby
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH net-next 0/6] vsock: assign the guest vsock device to a network namespace
2026-09-02 23:00 [PATCH net-next 0/6] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
` (5 preceding siblings ...)
2026-09-02 23:00 ` [PATCH net-next 6/6] selftests/vsock: test the assign ioctl privilege checks Bobby Eshleman
@ 2026-09-04 8:55 ` Stefano Garzarella
2026-09-04 17:30 ` Bobby Eshleman
6 siblings, 1 reply; 16+ messages in thread
From: Stefano Garzarella @ 2026-09-04 8:55 UTC (permalink / raw)
To: Bobby Eshleman
Cc: David S. Miller, Eric Dumazet, 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, virtualization, netdev, linux-kernel,
linux-doc, kvm, linux-kselftest, sargun, jlinbox, Bobby Eshleman
On Wed, Sep 02, 2026 at 04:00:46PM -0700, Bobby Eshleman wrote:
>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.
Thanks for this, I'll do a proper review next week, in the mean time
some comments below:
>
>This series lets a guest move that device into a network namespace. A
>new ioctl on /dev/vsock, IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS, assigns the
>device to the namespace of the calling process. The namespace's existing
Why an ioctl?
I'm asking because I'd like to know if you've already considered any
alternatives (sysfs, netlink, etc.)
How do you think the ioctl should be used? Should we provide an
userspace tool, or extending some existing tools?
Thanks,
Stefano
>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 ioctl 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 ioctl requires CAP_NET_ADMIN in the initial user namespace.
>
>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. Following netdevs, the device returns
>to the initial namespace when the namespace it was moved to is deleted.
>
>Transports opt in through a new netns_assign_allow callback. Only
>virtio-vsock implements it here.
Why? (Not asking to support all the others, asking to explain the reason
or ask helps from others to extend it)
Thanks,
Stefano
>
>Patch 1 is just a const cleanup that patch 2 needs. The remaining
>patches are actual implementation and tests.
>
>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>
>---
>Bobby Eshleman (6):
> vsock: constify the transport in vsock_for_each_connected_socket()
> vsock: add IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS
> vsock/virtio: support guest device network namespace
> selftests/vsock: add a helper to assign the g2h device to a netns
> selftests/vsock: test the guest vsock device network namespace
> selftests/vsock: test the assign ioctl privilege checks
>
> Documentation/admin-guide/sysctl/net.rst | 18 +
> include/linux/virtio_vsock.h | 2 +
> include/net/af_vsock.h | 9 +-
> include/uapi/linux/vm_sockets.h | 6 +
> net/vmw_vsock/af_vsock.c | 200 ++++++++-
> net/vmw_vsock/virtio_transport.c | 28 +-
> net/vmw_vsock/virtio_transport_common.c | 28 +-
> tools/testing/selftests/vsock/.gitignore | 1 +
> tools/testing/selftests/vsock/Makefile | 3 +-
> tools/testing/selftests/vsock/config | 1 +
> tools/testing/selftests/vsock/vmtest.sh | 461 ++++++++++++++++++++-
> .../selftests/vsock/vsock_assign_g2h_netns.c | 45 ++
> 12 files changed, 774 insertions(+), 28 deletions(-)
>---
>base-commit: d0ec95a8a4e79f2fd6063fc8932415db8c227689
>change-id: 20260831-vsock-guest-ns-d06af451da67
>
>Best regards,
>--
>Bobby Eshleman <bobbyeshleman@meta.com>
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH net-next 0/6] vsock: assign the guest vsock device to a network namespace
2026-09-04 8:55 ` [PATCH net-next 0/6] vsock: assign the guest vsock device to a network namespace Stefano Garzarella
@ 2026-09-04 17:30 ` Bobby Eshleman
0 siblings, 0 replies; 16+ messages in thread
From: Bobby Eshleman @ 2026-09-04 17:30 UTC (permalink / raw)
To: Stefano Garzarella
Cc: David S. Miller, Eric Dumazet, 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, virtualization, netdev, linux-kernel,
linux-doc, kvm, linux-kselftest, sargun, jlinbox, Bobby Eshleman
On Fri, Sep 04, 2026 at 10:55:17AM +0200, Stefano Garzarella wrote:
> On Wed, Sep 02, 2026 at 04:00:46PM -0700, Bobby Eshleman wrote:
> > 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.
>
> Thanks for this, I'll do a proper review next week, in the mean time some
> comments below:
>
> >
> > This series lets a guest move that device into a network namespace. A
> > new ioctl on /dev/vsock, IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS, assigns the
> > device to the namespace of the calling process. The namespace's existing
>
> Why an ioctl?
>
> I'm asking because I'd like to know if you've already considered any
> alternatives (sysfs, netlink, etc.)
>
> How do you think the ioctl should be used? Should we provide an userspace
> tool, or extending some existing tools?
>
> Thanks,
> Stefano
Really only because /dev/vsock exists and the prior series used it.
Considering netlink, it might be the better option because there is a
lot of prior art solving problems we might have in the future. For
example, I was thinking about when users suddenly lose access to vsock,
with just the current assign ioctl there is no way for apps or users to
figure out why this happened. We can have an ioctl() setter for user,
but in netdev world users can actually get a notification via netlink as
to which namespace the device went to and what its ifindex is there (see
__dev_change_net_namespace() for the RTM_DELLINK and RTM_NEWLINK
messages). There is probably more, but that's the case that comes to
mind.
>
> > 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 ioctl 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 ioctl requires CAP_NET_ADMIN in the initial user namespace.
> >
> > 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. Following netdevs, the device returns
> > to the initial namespace when the namespace it was moved to is deleted.
> >
> > Transports opt in through a new netns_assign_allow callback. Only
> > virtio-vsock implements it here.
>
> Why? (Not asking to support all the others, asking to explain the reason or
> ask helps from others to extend it)
>
> Thanks,
> Stefano
Sure, can add a note here about extending it. I just didn't want to
implement code without having an environment allowing me to test it.
Best,
Bobby
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH net-next 2/6] vsock: add IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS
2026-09-02 23:00 ` [PATCH net-next 2/6] vsock: add IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS Bobby Eshleman
2026-09-02 23:35 ` Randy Dunlap
@ 2026-09-06 17:03 ` netdev-bot+sashiko
1 sibling, 0 replies; 16+ messages in thread
From: netdev-bot+sashiko @ 2026-09-06 17:03 UTC (permalink / raw)
To: bobbyeshleman
Cc: sgarzare, davem, edumazet, kuba, pabeni, horms, corbet, skhan,
stefanha, mst, jasowangio, xuanzhuo, eperezma, shuah, rdunlap,
virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, bobbyeshleman
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 0 · Low: 3
- [Low] Claim: the new ioctl is inert because no in-tree transport
implements `netns_assign_allow`, and `vsock_g2h_net_get()` /…
- [Low] Claim: TOCTOU between `vsock_g2h_net_assign()` (which only sweeps
the connected hash table) and in-flight connections — a socket in…
- [Low] Claim: the reachability gate in `vsock_assign_transport()` is
skipped by the `vsk->transport == new_transport` early return (so a…
--
vsock: add IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS
This adds an ioctl on /dev/vsock that moves the guest's vsock device into
the network namespace of the calling process, gated on CAP_NET_ADMIN in the
initial user namespace, plus a transport hook so transports can opt in.
Sockets that can no longer reach the device after a move are reset, and the
device falls back to init_net when the assigned namespace is destroyed.
Link: https://lore.kernel.org/all/20200427142518.uwssa6dtasrp3bfc@steredhat/
No regressions to report on this one. A few areas were looked at closely
and appear fine; they are noted below only so the review record is
complete.
> diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
> index 29cde17e08f3..ad11f0f56eb8 100644
> --- a/net/vmw_vsock/af_vsock.c
> +++ b/net/vmw_vsock/af_vsock.c
[ ... ]
> @@ -548,6 +571,17 @@ static void vsock_deassign_transport(struct vsock_sock *vsk)
> vsk->transport = NULL;
> }
>
> +/* Return true if the loaded g2h transport honours namespace assignment. One
> + * that does not keeps the reachability rules it had before the ioctl existed.
> + *
> + * Must be called with vsock_register_mutex held.
> + */
> +static bool vsock_g2h_netns_assignable(void)
> +{
> + return transport_g2h && transport_g2h->netns_assign_allow &&
> + transport_g2h->netns_assign_allow();
> +}
At this commit no in-tree transport sets .netns_assign_allow, so
vsock_g2h_netns_assignable() is always false and vsock_g2h_net_assign()
returns -EOPNOTSUPP, while vsock_g2h_net_get(), vsock_g2h_reachable_sk()
and vsock_maybe_set_connected() have no callers yet.
The following patch in the series adds virtio_transport_netns_assign_allow()
and the calls into these helpers, so this is the usual core-first split
rather than dead code.
> @@ -622,6 +656,13 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
> goto err;
> }
>
> + if (new_transport && new_transport == transport_g2h &&
> + vsock_g2h_netns_assignable() &&
> + !vsock_g2h_net_reachable(sock_net(sk))) {
> + ret = -ENETUNREACH;
> + goto err;
> + }
> +
This gate is skipped by the preceding
if (vsk->transport && vsk->transport == new_transport) {
ret = 0;
goto err;
}
early return, but vsock_connect() still consults
transport->stream_allow() after vsock_assign_transport() returns, and the
follow-up patch makes virtio_transport_stream_allow() and
virtio_transport_seqpacket_allow() namespace aware, so a re-connect from an
unreachable namespace is still refused with -ENETUNREACH.
For SOCK_DGRAM, virtio registers only as VSOCK_TRANSPORT_F_G2H and
virtio_transport_dgram_allow() returns false unconditionally, so a datagram
socket never holds the assignable transport.
[ ... ]
> +bool vsock_maybe_set_connected(struct vsock_sock *vsk)
> +{
> + struct list_head *list = vsock_connected_sockets(&vsk->remote_addr,
> + &vsk->local_addr);
> + bool reachable;
> +
> + 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;
> +}
[ ... ]
> +static int vsock_g2h_net_assign(struct net *net)
> +{
[ ... ]
> + rcu_assign_pointer(vsock_g2h_net, net);
> + vsock_for_each_connected_socket(transport_g2h,
> + vsock_reset_unreachable_sock);
> + }
Sockets still in TCP_SYN_SENT, and pending children, are not in the
connected table and so are not visible to this sweep. The ordering here
covers that case:
vsock_g2h_net_assign()
rcu_assign_pointer(vsock_g2h_net, net); /* store first */
vsock_for_each_connected_socket() /* under vsock_table_lock */
vsock_maybe_set_connected()
spin_lock_bh(&vsock_table_lock);
reachable = vsock_g2h_reachable_sk(vsk); /* re-check under same lock */
Either the insert wins the lock and the later walk sees and resets the
socket, or the walk wins and the insert observes the new namespace and
refuses, so a socket cannot slip into TCP_ESTABLISHED in a namespace that
can no longer reach the device once the transport side lands.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260902-vsock-guest-ns-v1-0-9995383e9a8b%40meta.com
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH net-next 3/6] vsock/virtio: support guest device network namespace
2026-09-02 23:00 ` [PATCH net-next 3/6] vsock/virtio: support guest device network namespace Bobby Eshleman
@ 2026-09-06 17:04 ` netdev-bot+sashiko
0 siblings, 0 replies; 16+ messages in thread
From: netdev-bot+sashiko @ 2026-09-06 17:04 UTC (permalink / raw)
To: bobbyeshleman
Cc: sgarzare, davem, edumazet, kuba, pabeni, horms, corbet, skhan,
stefanha, mst, jasowangio, xuanzhuo, eperezma, shuah, rdunlap,
virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, bobbyeshleman
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 3 · Low: 1
- [Medium] After IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS becomes functional
(this patch adds the only .netns_assign_allow implementation in the…
- [Medium] vsock_maybe_set_connected() makes only the table insertion
atomic with respect to namespace assignment; the connection-setup…
- [Medium] virtio_transport_rx_work() samples the g2h namespace once (net
= vsock_g2h_net_get(), before mutex_lock(&vsock->rx_lock)) and reuses…
- [Low] In the new `!net` branch of virtio_transport_rx_work()
(net/vmw_vsock/virtio_transport.c:~694) the code passes `&init_net` to…
--
vsock/virtio: support guest device network namespace
This makes the virtio-vsock guest device assignable to a network namespace,
adds a .netns_assign_allow callback, and switches the RX path to look up
sockets in the device's namespace. It also exports
virtio_transport_reset_no_sock() so the RX path can reset packets that
arrive while the device's namespace is going away.
A few questions about the new namespace assignment behaviour below.
> diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
> index 4f9aa9c4c3aa..a453a4f828dc 100644
> --- a/net/vmw_vsock/virtio_transport.c
> +++ b/net/vmw_vsock/virtio_transport.c
> @@ -540,9 +540,14 @@ static bool virtio_transport_msgzerocopy_allow(void)
> return true;
> }
>
> +static bool virtio_transport_netns_assign_allow(void)
> +{
> + return true;
> +}
> +
[Severity: Medium]
This is the only .netns_assign_allow implementation in the tree, so this is
the patch that turns vsock_g2h_net_assign() and its reset sweep from
-EOPNOTSUPP into live behaviour.
The commit message says "every already established vsock socket that is no
longer able to reach the device is forcibly reset". Is the host peer ever
told about that reset? vsock_g2h_net_assign() only runs:
net/vmw_vsock/af_vsock.c:vsock_reset_unreachable_sock() {
if (vsock_g2h_net_reachable(sock_net(sk)))
return;
sk->sk_state = TCP_CLOSE;
sk->sk_err = ECONNRESET;
sk_error_report(sk);
}
There is no virtio_transport_reset() and no VIRTIO_VSOCK_OP_RST here, and
SOCK_DONE is not set nor is the socket removed from the connected table.
When the guest application later closes the socket, does anything get sent
either? virtio_transport_close() returns early for any state other than
TCP_ESTABLISHED/TCP_CLOSING:
if (!(sk->sk_state == TCP_ESTABLISHED ||
sk->sk_state == TCP_CLOSING))
return true;
so a swept socket in TCP_CLOSE emits neither SHUTDOWN nor RST, and
virtio_transport_release() just sets SOCK_DONE and calls
virtio_transport_remove_sock().
Does this leave the host side of each connection in ESTABLISHED until it
happens to send something (at which point recv_pkt's lookup misses in the
new namespace and replies RST)? A host peer blocked in recv() would then
wait for its own timeout, which is the same failure mode the commit message
rules out for the dropped-packet case.
> 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 +592,7 @@ static struct virtio_transport virtio_transport = {
> .seqpacket_has_data = virtio_transport_seqpacket_has_data,
>
> .msgzerocopy_allow = virtio_transport_msgzerocopy_allow,
> + .netns_assign_allow = virtio_transport_netns_assign_allow,
>
[ ... ]
> @@ -634,6 +640,9 @@ static void virtio_transport_rx_work(struct work_struct *work)
> struct virtio_vsock *vsock =
> container_of(work, struct virtio_vsock, rx_work);
> struct virtqueue *vq;
> + struct net *net;
> +
> + net = vsock_g2h_net_get();
>
> mutex_lock(&vsock->rx_lock);
[Severity: Medium]
The namespace is sampled once here and then reused for the entire batch,
i.e. for the whole inner for(;;) drain and the outer
do{}while(!virtqueue_enable_cb(vq)) retry. vsock_g2h_net is re-pointed by
IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS under vsock_register_mutex/RCU.
Can this snapshot go stale mid-batch, so that after the ioctl has already
returned success the remaining packets are still resolved against the old
namespace via vsock_find_connected_socket_net()/
vsock_find_bound_socket_net()?
For already established sockets the only fence is the assignment sweep, and
that sweep does not take the socket lock:
CPU0 (rx_work) CPU1 (assign ioctl)
virtio_transport_recv_pkt()
lock_sock(sk)
/* re-checks only SOCK_DONE and
vsk->transport, not the ns */
vsock_g2h_net_assign()
rcu_assign_pointer(vsock_g2h_net, net)
vsock_reset_unreachable_sock(sk)
sk->sk_state = TCP_CLOSE
switch (sk->sk_state) /* read TCP_ESTABLISHED earlier */
virtio_transport_recv_connected(sk, skb)
Does that let host payload be queued into a socket in the namespace the
device was just moved away from? Would re-validating reachability after
lock_sock() (e.g. vsock_g2h_reachable_sk()) close this window?
> @@ -682,10 +691,14 @@ 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.
> - */
> - virtio_transport_recv_pkt(&virtio_transport, skb, NULL);
> + if (unlikely(!net)) {
> + virtio_transport_reset_no_sock(
> + &virtio_transport, skb, &init_net);
> + kfree_skb(skb);
> + continue;
> + }
[Severity: Low]
This isn't a functional bug today, but is &init_net the right value to pass
here? vsock_g2h_net_get() returns NULL only after it has already rejected
init_net as a stand-in:
net/vmw_vsock/af_vsock.c:vsock_g2h_net_get() {
net = maybe_get_net(assigned);
...
if (!net && vsock_net_check_mode(&init_net, assigned))
net = get_net(&init_net);
}
So reaching this branch means vsock_net_check_mode(&init_net, assigned) was
false, yet &init_net is then handed to virtio_transport_reset_no_sock() as
"the namespace of whoever sent the invalid message". The pre-patch code
passed NULL, and virtio_transport_recv_pkt() below is given the real device
namespace.
virtio_transport_send_pkt() ignores its net argument, so nothing breaks in
this transport, but the function is now exported and declared in
include/linux/virtio_vsock.h, where the vhost and loopback send_pkt()
implementations do use net. Would NULL (as before) or a note that virtio
ignores net keep the two call sites consistent?
> +
> + virtio_transport_recv_pkt(&virtio_transport, 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 88df82364f77..313ef263fd2d 100644
> --- a/net/vmw_vsock/virtio_transport_common.c
> +++ b/net/vmw_vsock/virtio_transport_common.c
> @@ -1315,8 +1315,8 @@ static int virtio_transport_reset(struct vsock_sock *vsk,
> * loopback, this is the namespace of the socket. For vhost, this is the
> * namespace of the VM (i.e., vhost_vsock).
> */
> -static int virtio_transport_reset_no_sock(const struct virtio_transport *t,
> - struct sk_buff *skb, struct net *net)
> +int virtio_transport_reset_no_sock(const struct virtio_transport *t,
> + struct sk_buff *skb, struct net *net)
> {
[ ... ]
> @@ -1478,9 +1479,14 @@ virtio_transport_recv_connecting(struct sock *sk,
>
> switch (le16_to_cpu(hdr->op)) {
> case VIRTIO_VSOCK_OP_RESPONSE:
> - sk->sk_state = TCP_ESTABLISHED;
> + /* An assign cannot see a socket that is not connected yet. */
> + if (!vsock_maybe_set_connected(vsk)) {
> + skerr = ECONNRESET;
> + err = -ENETUNREACH;
> + goto destroy;
> + }
> +
> sk->sk_socket->state = SS_CONNECTED;
> - vsock_insert_connected(vsk);
> sk->sk_state_change(sk);
> break;
[ ... ]
> @@ -1758,7 +1762,17 @@ 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);
> + /* An assign cannot see a socket that is not connected yet, and the
> + * check in vsock_assign_transport() above has since dropped
> + * vsock_register_mutex.
> + */
> + if (!vsock_maybe_set_connected(vchild)) {
> + release_sock(child);
> + virtio_transport_reset_no_sock(t, skb, sock_net(sk));
> + sock_put(child);
> + return -ENETUNREACH;
> + }
> +
> vsock_enqueue_accept(sk, child);
> virtio_transport_send_response(vchild, skb);
>
[Severity: Medium]
vsock_maybe_set_connected() makes only the table insertion atomic against
assignment, and it drops vsock_table_lock before returning. Does anything
re-check the socket afterwards?
The commit message says "If it wins the race to the table and does NOT pass
the reachability tests, then the reset sweep will correctly catch it", but
the winner here keeps going past the insertion point:
CPU0 (virtio_transport_recv_listen) CPU1 (assign ioctl)
lock_sock_nested(child)
vsock_maybe_set_connected(vchild)
spin_lock_bh(&vsock_table_lock)
sk_state = TCP_ESTABLISHED
__vsock_insert_connected()
spin_unlock_bh(&vsock_table_lock)
vsock_g2h_net_assign()
vsock_for_each_connected_socket()
vsock_reset_unreachable_sock(child)
sk_state = TCP_CLOSE
sk_err = ECONNRESET
vsock_enqueue_accept(sk, child)
virtio_transport_send_response(vchild, skb)
The lock sets are disjoint (lock_sock(child) versus vsock_table_lock), so
does the guest end up sending VIRTIO_VSOCK_OP_RESPONSE for a child it has
already reset, and queueing that child on the accept queue? vsock_accept()
inspects neither sk_err nor sk_state before doing:
newsock->state = SS_CONNECTED;
sock_graft(connected, newsock);
The same gap seems to exist in virtio_transport_recv_connecting() above,
where sk->sk_socket->state = SS_CONNECTED and sk_state_change() run after
vsock_maybe_set_connected() has dropped the table lock, even if the sweep
has already forced sk_state to TCP_CLOSE.
Since vsock_reset_unreachable_sock() neither sets SOCK_DONE nor removes the
socket from the table, and virtio_transport_close() short-circuits for
TCP_CLOSE, is any RST sent to the host in this case?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260902-vsock-guest-ns-v1-0-9995383e9a8b%40meta.com
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH net-next 4/6] selftests/vsock: add a helper to assign the g2h device to a netns
2026-09-02 23:00 ` [PATCH net-next 4/6] selftests/vsock: add a helper to assign the g2h device to a netns Bobby Eshleman
@ 2026-09-06 17:04 ` netdev-bot+sashiko
0 siblings, 0 replies; 16+ messages in thread
From: netdev-bot+sashiko @ 2026-09-06 17:04 UTC (permalink / raw)
To: bobbyeshleman
Cc: sgarzare, davem, edumazet, kuba, pabeni, horms, corbet, skhan,
stefanha, mst, jasowangio, xuanzhuo, eperezma, shuah, rdunlap,
virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, bobbyeshleman
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 0 · Low: 2
- [Low] Concern (validated as a FALSE POSITIVE): the new helper binary
vsock_assign_g2h_netns was claimed to be built and installed with no…
- [Low] Documentation/admin-guide/sysctl/net.rst (lines 518-534, added by
"vsock: add IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS") describes G2H…
--
selftests/vsock: add a helper to assign the g2h device to a netns
This adds a small userspace helper, vsock_assign_g2h_netns, that opens
/dev/vsock and issues IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS so shell based
tests can move the guest vsock device into the caller's network
namespace. The helper exits with the ioctl errno and is added to
TEST_GEN_FILES and .gitignore.
> diff --git a/tools/testing/selftests/vsock/vsock_assign_g2h_netns.c b/tools/testing/selftests/vsock/vsock_assign_g2h_netns.c
> new file mode 100644
> index 000000000000..6f15629af607
> --- /dev/null
> +++ b/tools/testing/selftests/vsock/vsock_assign_g2h_netns.c
> @@ -0,0 +1,45 @@
[ ... ]
> + ret = ioctl(fd, IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS);
> + if (ret < 0) {
> + ret = errno;
> + fprintf(stderr,
> + "IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS: %s (errno %d)\n",
> + strerror(errno), errno);
> + }
[Severity: Low]
While looking at what errnos a caller of this helper can expect, the
documentation added earlier in the series for this ioctl reads as if the
move always happens. From the vsock section of
Documentation/admin-guide/sysctl/net.rst:
In a guest, the vsock device owned by the guest-to-host (G2H) transport
belongs to one network namespace at a time. The
IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS ioctl on /dev/vsock moves it to the
namespace of the calling process, which requires CAP_NET_ADMIN in the
initial user namespace.
The uapi comment above the define in include/uapi/linux/vm_sockets.h is
similarly unqualified:
/* Assign the guest's vsock device to the network namespace of the calling
* process. Requires CAP_NET_ADMIN in the initial user namespace. To undo an
* assignment, assign the device to the initial network namespace.
*/
The implementation only allows the move for transports that opt in. In
net/vmw_vsock/af_vsock.c, vsock_g2h_net_assign() refuses otherwise:
} else if (!vsock_g2h_netns_assignable()) {
ret = -EOPNOTSUPP;
and vsock_g2h_netns_assignable() requires the callback:
return transport_g2h && transport_g2h->netns_assign_allow &&
transport_g2h->netns_assign_allow();
Grepping net/vmw_vsock/ shows netns_assign_allow is only provided by
virtio_transport.c (virtio_transport_netns_assign_allow()).
hyperv_transport.c registers with vsock_core_register(&hvs_transport,
VSOCK_TRANSPORT_F_G2H) and vmci_transport.c sets features =
VSOCK_TRANSPORT_F_G2H, and neither defines the callback.
So on a Hyper-V or VMCI guest, does this ioctl always fail with
-EOPNOTSUPP even for a CAP_NET_ADMIN caller, contrary to what net.rst and
the uapi comment describe? Would it be worth saying in both places that
assignment is transport dependent and that transports which do not opt in
return -EOPNOTSUPP?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260902-vsock-guest-ns-v1-0-9995383e9a8b%40meta.com
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH net-next 5/6] selftests/vsock: test the guest vsock device network namespace
2026-09-02 23:00 ` [PATCH net-next 5/6] selftests/vsock: test the guest vsock device network namespace Bobby Eshleman
@ 2026-09-06 17:04 ` netdev-bot+sashiko
0 siblings, 0 replies; 16+ messages in thread
From: netdev-bot+sashiko @ 2026-09-06 17:04 UTC (permalink / raw)
To: bobbyeshleman
Cc: sgarzare, davem, edumazet, kuba, pabeni, horms, corbet, skhan,
stefanha, mst, jasowangio, xuanzhuo, eperezma, shuah, rdunlap,
virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, bobbyeshleman
Thank you for your contribution! Sashiko AI review found 9 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 4 · Low: 5
- [Medium] Vacuous PASS in the new negative tests: vm_ns_start() always
emits a non-empty PID (even when `unshare -n` failed) and…
- [Medium] check_deps() makes the new vsock_assign_g2h_netns helper a
hard prerequisite for the entire suite (a missing helper now skips…
- [Medium] guest_send_to_host()/host_send_to_guest() tear down their
socat helper with terminate_pids() (bare SIGTERM, never `wait`), and…
- [Medium] test_ns_guest_assign_g2h_netns_old_conn_send_fails() orders
three concurrent actors (the backgrounded guest sender, the host…
- [Low] The header comment of vsock_g2h_net_get()
(net/vmw_vsock/af_vsock.c:754-756) states it returns the g2h device's…
- [Low] In test_ns_guest_assign_g2h_netns_old_conn_send_fails() the long-
running sender is backgrounded (`vm_ssh ... &`) but its PID is never…
- [Low] host_send_to_guest() captures `$!` of the local backgrounded
`vm_ns_exec ...` (a subshell running the ssh client), not of the…
- [Low] vm_ns_start() publishes the backgrounded `unshare -n sleep
infinity` PID with no barrier proving that unshare(2) has already…
- [Low] Five of the six new tests return immediately after
vm_ns_stop()+vm_reset_g2h() while the guest's netns dismantling is…
--
selftests/vsock: test the guest vsock device network namespace
This adds six vmtest.sh cases around the guest vsock device namespace
assignment: guest to host and host to guest transfers, namespace deletion,
and moving the device between namespaces while a socket is live. The
namespaces are created inside the guest with unshare -n and held open by a
sleeping process.
A few questions about the new helpers and the pass/fail criteria below.
> diff --git a/tools/testing/selftests/vsock/vmtest.sh b/tools/testing/selftests/vsock/vmtest.sh
> index 310dfc2a39ad..53591fa07f1a 100755
> --- a/tools/testing/selftests/vsock/vmtest.sh
> +++ b/tools/testing/selftests/vsock/vmtest.sh
> @@ -17,6 +17,7 @@ readonly KERNEL_CHECKOUT=$(realpath "${SCRIPT_DIR}"/../../../../)
> source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh
>
> readonly VSOCK_TEST="${SCRIPT_DIR}"/vsock_test
> +readonly VSOCK_ASSIGN_G2H_NETNS="${SCRIPT_DIR}"/vsock_assign_g2h_netns
> readonly TEST_GUEST_PORT=51000
> readonly TEST_HOST_PORT=50000
> readonly TEST_HOST_PORT_LISTENER=50001
[ ... ]
> @@ -302,18 +333,20 @@ check_args() {
> }
>
> check_deps() {
> - for dep in vng ${QEMU} busybox pkill ssh ss socat nsenter; do
> + for dep in vng ${QEMU} busybox pkill ssh ss socat nsenter unshare; do
> if [[ ! -x $(command -v "${dep}") ]]; then
> echo -e "skip: dependency ${dep} not found!\n"
> exit "${KSFT_SKIP}"
> fi
> done
>
> - if [[ ! -x $(command -v "${VSOCK_TEST}") ]]; then
> - printf "skip: %s not found!" "${VSOCK_TEST}"
> - printf " Please build the kselftest vsock target.\n"
> - exit "${KSFT_SKIP}"
> - fi
> + for prog in "${VSOCK_TEST}" "${VSOCK_ASSIGN_G2H_NETNS}"; do
> + if [[ ! -x $(command -v "${prog}") ]]; then
> + printf "skip: %s not found!" "${prog}"
> + printf " Please build the kselftest vsock target.\n"
> + exit "${KSFT_SKIP}"
> + fi
> + done
> }
[Severity: Medium]
Should a missing vsock_assign_g2h_netns skip the whole suite? check_deps()
runs before any test selection, so with this loop "vmtest.sh vm_loopback" in
an environment that only installed vsock_test now exits KSFT_SKIP and the
pre-existing tests lose coverage too.
Related question on the other side: is there a gate for a kernel that does
not implement the assign ioctl? vm_ns_assign_g2h() just propagates the
helper's exit status:
vm_ns_assign_g2h() {
local ns=$1
local nspid=$2
vm_ns_exec "${ns}" "${nspid}" ./vsock_assign_g2h_netns
}
and all five callers turn a non-zero status into KSFT_FAIL:
if ! vm_ns_assign_g2h "init_ns" "${nspid}"; then
log_host "failed to assign the vsock device to the guest ns"
...
return "${KSFT_FAIL}"
fi
On a kernel without the feature the ioctl fails and the tests report FAIL
rather than SKIP. check_netns() already has the precedent of skipping only
the ns_* tests when the capability is absent:
if [[ "${tname}" =~ ^ns_ ]] &&
[[ ! -e /proc/self/ns ]]; then
log_host "No NS support detected for test ${tname}"
return 1
fi
Would a similar per-feature gate fit here?
[ ... ]
> @@ -528,6 +562,58 @@ 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
> +
> + vm_ssh "${ns}" -- \
> + "echo local > /proc/sys/net/vsock/child_ns_mode" &>/dev/null
> +
> + vm_ssh "${ns}" -- "unshare -n sleep infinity" \
> + '>/dev/null 2>&1 & echo $!'
> +}
[Severity: Medium]
Can the two negative tests pass without ever running anything in the guest?
The remote shell prints $! as soon as the job is forked, so vm_ns_start()
emits a non-empty PID and returns 0 even when unshare immediately fails (no
CONFIG_NET_NS in the guest, EPERM, exec failure). The write to
child_ns_mode above it is also &>/dev/null with its status ignored.
The only setup validation in each new test is:
nspid=$(vm_ns_start "init_ns")
if [[ -z "${nspid}" ]]; then
log_host "failed to create a namespace inside the guest"
return "${KSFT_FAIL}"
fi
which cannot fire in that case. With a stale PID, vm_ns_exec() runs
"nsenter -t <dead pid> -n sh -c ..." which exits non-zero without executing
socat, and guest_send_to_host() discards that status:
vm_ns_exec "${ns}" "${nspid}" "${cmd}" 2>/dev/null
The poll then just times out, and both
test_ns_guest_local_connect_to_host_fails() and
test_ns_guest_assign_g2h_netns_init_ns_connect_fails() evaluate an empty
file against:
if [[ "${result}" == TEST ]]; then
return "${KSFT_FAIL}"
fi
return "${KSFT_PASS}"
so "the command never ran" is indistinguishable from "the kernel blocked the
connection". Would checking the exit status of the guest command, or adding
a positive control to these two tests, make the difference visible?
[Severity: Low]
A second question on the same helper: is there a barrier proving unshare(2)
has already run before the PID is used? unshare execs, calls
unshare(CLONE_NEWNET), then execs sleep, while callers immediately use the
published PID as an nsenter target:
vm_ssh "${ns}" -- nsenter -t "${nspid}" -n sh -c "'${cmd}'"
If /proc/<pid>/ns/net is still the initial netns at that moment, the command
runs in the guest's initial namespace instead. In practice every consumer
first pays a fresh ssh round trip, so the window is small, but a poll until
readlink /proc/${nspid}/ns/net differs from the initial namespace would
close it.
> +
> +# 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}" ./vsock_assign_g2h_netns
> +}
> +
> +vm_reset_g2h() {
> + vm_ns_assign_g2h "init_ns" "" &>/dev/null
> +}
> +
[ ... ]
> @@ -1421,6 +1521,290 @@ 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'
> +
> + terminate_pids "${pid}"
> +}
[Severity: Medium]
Can consecutive tests collide on host vsock port 12345? The listener here
is torn down with terminate_pids(), which sends SIGTERM and never waits:
for pid in "$@"; do
kill -SIGTERM "${pid}" &>/dev/null || :
done
so the socket may still be bound when guest_send_to_host() returns.
test_ns_guest_local_connect_to_host_fails,
test_ns_guest_assign_g2h_netns_connect_to_host_ok,
test_ns_guest_assign_g2h_netns_init_ns_connect_fails and
test_ns_guest_assign_g2h_netns_host_connect_ok all declare "local
port=12345" and run back to back in the same host namespace.
If the previous socat is still bound, either the new bind fails with
EADDRINUSE, or ss() sees the old listener whose stdout points at the
previous, already-deleted outfile. Either way the new outfile stays empty,
and the failure is silent because wait_for_listener() breaks out of its loop
on timeout and returns 0 with no caller checking it.
test_ns_guest_assign_g2h_netns_reset_on_ns_delete_ok already uses
"$(( port + i ))" per retry. Would a per-test port, or a wait after the
kill, help here as well?
> +
> +# 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'
> +
> + terminate_pids "${pid}"
> +}
[Severity: Low]
Does terminate_pids() reach the guest-side listener here? The captured $!
is the local subshell running the ssh client, not the nsenter/socat inside
the guest namespace. ssh is invoked without a TTY, so killing the local
side does not signal the remote process group, and a socat blocked in
accept() does not notice the closed channel.
vm_ns_stop() then waits only for the sleep infinity holder, so its documented
postcondition:
# Returns once the holder is gone, so that the namespace is unreferenced and
# the kernel can start tearing it down.
does not hold while that leftover socat still references the namespace. In
the passing path the host does connect and send, so the guest socat sees EOF
and exits on its own; the leftover appears when the transfer fails.
> +
> +test_ns_guest_assign_g2h_netns_old_conn_send_fails() {
> + local gap=$(( WAIT_PERIOD * 3 ))
> + local port=12346
> + local outfile
> + local result
> + 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.
> + vm_ssh "init_ns" -- \
> + "(echo FIRST; sleep ${gap}; echo SECOND) |" \
> + "socat -u STDIN VSOCK-CONNECT:2:${port}" &>/dev/null &
> +
> + sleep "${WAIT_PERIOD}"
[Severity: Medium]
Can a correct kernel hit either FAIL verdict in this test? Three actors are
ordered with fixed sleeps only.
If the assign lands too early: the guest sender is backgrounded and the
script waits just "sleep ${WAIT_PERIOD}" (3 s). Bringing up a fresh ssh
session into the 9p-rootfs VM, starting socat and connecting can exceed that
on a loaded host, in which case the device moves before the connection
exists, FIRST never lands, and the test reports:
if [[ "${result}" != *FIRST* ]]; then
log_host "connection did not work before the assign: [${result}]"
return "${KSFT_FAIL}"
fi
If the assign lands too late: the assign is itself a new ssh session plus
nsenter plus the ioctl, while the second write happens at gap =
WAIT_PERIOD * 3 (9 s) after the sender started and the assign is issued at
about 3 s. If the assign round trip exceeds the remaining 6 s, SECOND is
delivered legitimately and the test reports "old connection still delivered
after the assign".
Would polling the outfile for FIRST before doing the assign, using the same
"timeout ... while [[ ! -s file ]]" idiom already used in
guest_send_to_host(), make this deterministic?
[Severity: Low]
Separately, is the backgrounded sender ever reaped? No $! is captured here,
so it is not passed to terminate_pids() and not registered with the PIDFILES
map used by the EXIT trap:
cleanup() {
terminate_pidfiles "${!PIDFILES[@]}"
del_namespaces
rm -rf "${TEST_HOME}"
}
On the assign-failure early return the host ssh client and the guest-side
subshell still have "sleep ${gap}" (9 s) to run, and on script interruption
they outlive the script. Every other background job in this script is
tracked either with pid=$! plus terminate_pids or via create_pidfile().
> +
> + if ! vm_ns_assign_g2h "init_ns" "${nspid}"; then
> + log_host "failed to assign the vsock device to the guest ns"
> + terminate_pids "${pid}"
> + rm -f "${outfile}"
> + vm_ns_stop "init_ns" "${nspid}"
> + vm_reset_g2h
> + return "${KSFT_FAIL}"
> + fi
> +
> + # Let the second write happen and land, if it is going to.
> + sleep $(( gap + WAIT_PERIOD ))
> +
> + terminate_pids "${pid}"
> + result=$(cat "${outfile}")
> + rm -f "${outfile}"
> +
> + vm_ns_stop "init_ns" "${nspid}"
> + vm_reset_g2h
> +
> + if [[ "${result}" != *FIRST* ]]; then
> + log_host "connection did not work before the assign: [${result}]"
> + return "${KSFT_FAIL}"
> + fi
> +
> + if [[ "${result}" == *SECOND* ]]; then
> + log_host "old connection still delivered after the assign"
> + return "${KSFT_FAIL}"
> + fi
> +
> + return "${KSFT_PASS}"
> +}
[ ... ]
> +test_ns_guest_assign_g2h_netns_reset_on_ns_delete_ok() {
[ ... ]
> + vm_ns_stop "init_ns" "${nspid}"
> +
> + # The holder is gone, but the namespace itself is dismantled from a
> + # workqueue, so the device does not come back the same instant. Retry
> + # until it does, rather than expecting the first send to succeed.
[Severity: Low]
This comment documents an asynchronous teardown that only this test waits
for. The other five return right after vm_ns_stop() plus vm_reset_g2h()
while the guest namespace is still being dismantled, and
run_shared_vm_test() snapshots the dmesg warn count per test:
vm_warn_cnt_before=$(vm_dmesg_warn_count "init_ns")
name=$(echo "${1}" | awk '{ print $1 }')
eval test_"${name}"
rc=$?
so a warning emitted by test N's in-flight teardown is charged to test N+1.
Would a short wait for the teardown in the other tests avoid that
misattribution?
The device state itself does look well defined: vsock_g2h_net_reset() in
net/vmw_vsock/af_vsock.c returns early unless the dying net is the current
one:
if (likely(rcu_access_pointer(vsock_g2h_net) != net))
return;
so a late teardown cannot pull the device out of a later test's namespace.
[ ... ]
One more note, on the kernel side of this series rather than on this patch:
[Severity: Low]
The header comment of vsock_g2h_net_get() in net/vmw_vsock/af_vsock.c says:
/* Return the g2h devices' namespace with a reference held, or NULL if that
* namespace is being destroyed.
*/
but in exactly that case the function can return a reference to a different
namespace instead of NULL:
if (!net && vsock_net_check_mode(&init_net, assigned))
net = get_net(&init_net);
The in-function comment just below explains the init_net fallback, so only
the header sentence looks out of date for an exported symbol that callers
such as virtio_transport_rx_work() rely on. Could the header comment
mention the init_net fallback?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260902-vsock-guest-ns-v1-0-9995383e9a8b%40meta.com
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH net-next 6/6] selftests/vsock: test the assign ioctl privilege checks
2026-09-02 23:00 ` [PATCH net-next 6/6] selftests/vsock: test the assign ioctl privilege checks Bobby Eshleman
@ 2026-09-06 17:04 ` netdev-bot+sashiko
0 siblings, 0 replies; 16+ messages in thread
From: netdev-bot+sashiko @ 2026-09-06 17:04 UTC (permalink / raw)
To: bobbyeshleman
Cc: sgarzare, davem, edumazet, kuba, pabeni, horms, corbet, skhan,
stefanha, mst, jasowangio, xuanzhuo, eperezma, shuah, rdunlap,
virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, bobbyeshleman
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 2 · Low: 1
- [Medium] test_ns_guest_assign_g2h_netns_unpriv_user_ns_fails()
(tools/testing/selftests/vsock/vmtest.sh:1835-1863) runs the helper as…
- [Medium] Both new tests equate a remote command exit status of 1 with
'the kernel returned -EPERM from IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS'.
- [Low] Both new shared-VM tests (vmtest.sh:1820-1833 and 1835-1863) are
added to USE_SHARED_VM but omit the `vm_reset_g2h` teardown that every…
--
selftests/vsock: test the assign ioctl privilege checks
This adds two vmtest.sh cases around IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS on
/dev/vsock: one drops CAP_NET_ADMIN via setpriv, the other runs the helper
as an unprivileged uid inside a new user plus net namespace. Both expect
the helper to exit with the ioctl's errno, and CONFIG_USER_NS is added to
the config.
No Link: tags are present in the commit header.
> diff --git a/tools/testing/selftests/vsock/vmtest.sh b/tools/testing/selftests/vsock/vmtest.sh
> index 53591fa07f1a..efb94d17d997 100755
> --- a/tools/testing/selftests/vsock/vmtest.sh
> +++ b/tools/testing/selftests/vsock/vmtest.sh
> @@ -28,6 +28,7 @@ readonly WAIT_PERIOD=3
> readonly WAIT_PERIOD_MAX=60
> readonly WAIT_QEMU=5
> readonly PIDFILE_TEMPLATE=/tmp/vsock_vmtest_XXXX.pid
> +readonly EPERM=1
> declare -A PIDFILES
[ ... ]
> @@ -333,7 +344,8 @@ check_args() {
> }
>
> check_deps() {
> - for dep in vng ${QEMU} busybox pkill ssh ss socat nsenter unshare; do
> + for dep in vng ${QEMU} busybox pkill ssh ss socat nsenter unshare \
> + setpriv; do
> if [[ ! -x $(command -v "${dep}") ]]; then
> echo -e "skip: dependency ${dep} not found!\n"
> exit "${KSFT_SKIP}"
[ ... ]
> @@ -1805,6 +1817,51 @@ test_ns_guest_assign_g2h_netns_reset_on_ns_delete_ok() {
> return "${KSFT_PASS}"
> }
>
> +test_ns_guest_assign_g2h_netns_no_cap_net_admin_fails() {
> + local cmd="unshare -n setpriv --bounding-set=-net_admin"
> + local rc
> +
> + vm_ssh "init_ns" -- "${cmd}" ./vsock_assign_g2h_netns &>/dev/null
> + rc=$?
> +
> + if [[ "${rc}" -ne "${EPERM}" ]]; then
> + log_host "expected EPERM (${EPERM}) without CAP_NET_ADMIN, got ${rc}"
> + return "${KSFT_FAIL}"
> + fi
> +
> + return "${KSFT_PASS}"
> +}
[Severity: Medium]
Can this test pass without ever issuing the ioctl?
The helper turns errno into its own exit status:
tools/testing/selftests/vsock/vsock_assign_g2h_netns.c:main() {
ret = ioctl(fd, IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS);
if (ret < 0) {
ret = errno;
...
return ret;
}
so "the ioctl returned -EPERM" is encoded as exit status 1, which is also
the generic EXIT_FAILURE of the two wrappers that are prepended here. If
util-linux unshare cannot create the network namespace, or setpriv was
built without libcap-ng and cannot handle --bounding-set=-net_admin, the
wrapper exits 1 before exec'ing the helper, rc is 1, and the test returns
KSFT_PASS with vsock_dev_do_ioctl() never reached.
The &>/dev/null redirection discards the wrapper's own error message, so
that outcome is silent.
check_deps() only verifies that the setpriv binary exists, not that
--bounding-set is supported, so an unsupported setpriv becomes a pass
rather than a skip.
The same conflation applies to the second test below: the pre-flight probe
runs only "unshare -U true", while the command actually executed is
"${unpriv} unshare -Urn ${helper}". A failure of the -r or -n part, or of
setpriv --reuid/--regid/--clear-groups, also exits 1 and is read as the
ioctl's EPERM.
Would it be better for the helper to report a distinct sentinel exit code
(or print a machine-checkable marker) for the ioctl result, so that the
kernel's capable(CAP_NET_ADMIN) check in vsock_dev_do_ioctl() cannot be
weakened without these tests noticing?
> +
> +test_ns_guest_assign_g2h_netns_unpriv_user_ns_fails() {
> + local helper=/tmp/vsock_assign_g2h_netns
> + local unpriv_uid=65534
> + local unpriv
> + local rc
> +
> + unpriv="setpriv --reuid=${unpriv_uid} --regid=${unpriv_uid}"
> + unpriv="${unpriv} --clear-groups"
> +
> + if ! vm_ssh "init_ns" -- "${unpriv} unshare -U true"; then
> + log_host "unprivileged user namespaces unavailable, skipping"
> + return "${KSFT_SKIP}"
> + fi
> +
> + # The home shared with the guest is root-only, so place the helper where
> + # an unprivileged user can execute it.
> + vm_ssh "init_ns" -- \
> + "cp ./vsock_assign_g2h_netns ${helper} && chmod 755 ${helper}"
> +
> + vm_ssh "init_ns" -- "${unpriv} unshare -Urn ${helper}" &>/dev/null
> + rc=$?
> +
> + if [[ "${rc}" -ne "${EPERM}" ]]; then
> + log_host "expected EPERM (${EPERM}) for an unprivileged user, got ${rc}"
> + return "${KSFT_FAIL}"
> + fi
[Severity: Medium]
Can uid 65534 open /dev/vsock in this guest at all?
The node is created from a miscdevice with no .mode:
net/vmw_vsock/af_vsock.c
static struct miscdevice vsock_device = {
.name = "vsock",
.fops = &vsock_device_ops,
};
misc_devnode() only overrides the mode when c->mode is non-zero, and
devtmpfs_create_node() then applies mode 0600 with uid/gid
GLOBAL_ROOT_UID/GLOBAL_ROOT_GID. In the bare virtme-ng guest this script
boots there is no udev rule loosening it, so /dev/vsock is 0600 root:root.
With unshare -Urn only kuid 65534 is mapped, so the inode's kuid 0 is
unmapped in the new user namespace and capable_wrt_inode_uidgid() cannot
grant CAP_DAC_OVERRIDE. open("/dev/vsock", O_RDONLY) then fails with
EACCES and the helper does:
tools/testing/selftests/vsock/vsock_assign_g2h_netns.c:main() {
fd = open("/dev/vsock", O_RDONLY);
if (fd < 0) {
fprintf(stderr, "open /dev/vsock: %s\n", strerror(errno));
return -1;
}
which is exit status 255, so this rc check reports "expected EPERM (1) for
an unprivileged user, got 255" and returns KSFT_FAIL even though the
kernel behaved as intended. The stderr message explaining why is dropped
by &>/dev/null.
Would it make sense to test whether the unprivileged uid can open
/dev/vsock first and return KSFT_SKIP if it cannot, so that an
inaccessible device node is distinguished from a refusal by the
CAP_NET_ADMIN check?
> +
> + return "${KSFT_PASS}"
> +}
> +
[Severity: Low]
Both new tests are listed in USE_SHARED_VM but neither calls vm_reset_g2h,
while every other g2h-assign test does so on both the success and failure
paths, for example test_ns_guest_assign_g2h_netns_connect_to_host_ok(),
..._init_ns_connect_fails(), ..._host_connect_ok() and
..._reset_on_ns_delete_ok().
If the capable(CAP_NET_ADMIN) gate in vsock_dev_do_ioctl() is ever
weakened, the assign succeeds inside the throwaway unshare -n or
unshare -Urn namespace and these tests return KSFT_FAIL with the guest's
vsock device still owned by a dying namespace.
The neighbouring test already documents that the hand-back is not
immediate:
# The holder is gone, but the namespace itself is dismantled from a
# workqueue, so the device does not come back the same instant. Retry
# until it does, rather than expecting the first send to succeed.
Could the following shared-VM tests then run while the device is still
unavailable, turning one real failure into a series of spurious ones?
Should these two tests call vm_reset_g2h before returning, like the others?
> shared_vm_test() {
> local tname
>
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260902-vsock-guest-ns-v1-0-9995383e9a8b%40meta.com
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-09-06 17:04 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-02 23:00 [PATCH net-next 0/6] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
2026-09-02 23:00 ` [PATCH net-next 1/6] vsock: constify the transport in vsock_for_each_connected_socket() Bobby Eshleman
2026-09-02 23:00 ` [PATCH net-next 2/6] vsock: add IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS Bobby Eshleman
2026-09-02 23:35 ` Randy Dunlap
2026-09-02 23:58 ` Bobby Eshleman
2026-09-06 17:03 ` netdev-bot+sashiko
2026-09-02 23:00 ` [PATCH net-next 3/6] vsock/virtio: support guest device network namespace Bobby Eshleman
2026-09-06 17:04 ` netdev-bot+sashiko
2026-09-02 23:00 ` [PATCH net-next 4/6] selftests/vsock: add a helper to assign the g2h device to a netns Bobby Eshleman
2026-09-06 17:04 ` netdev-bot+sashiko
2026-09-02 23:00 ` [PATCH net-next 5/6] selftests/vsock: test the guest vsock device network namespace Bobby Eshleman
2026-09-06 17:04 ` netdev-bot+sashiko
2026-09-02 23:00 ` [PATCH net-next 6/6] selftests/vsock: test the assign ioctl privilege checks Bobby Eshleman
2026-09-06 17:04 ` netdev-bot+sashiko
2026-09-04 8:55 ` [PATCH net-next 0/6] vsock: assign the guest vsock device to a network namespace Stefano Garzarella
2026-09-04 17:30 ` Bobby Eshleman
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox