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