From: Bobby Eshleman <[email protected]> Namespaces let a host isolate a VM's vsock traffic to a specific namespace, but in a guest vsock traffic cannot be isolated to a namespace. The vsock device is hardcoded to global mode and can't be moved into a local-mode namespace.
Introduce a vsock generic netlink family whose one command, VSOCK_CMD_DEV_NETNS_SET, moves the device to the namespace the request was sent from. VSOCK_CMD_DEV_NETNS_GET reads the assignment back, reporting the namespace as an nsid relative to the caller. The command requires CAP_NET_ADMIN in the initial user namespace. A privileged user wishing to "unassign" the device can move it to the init_net, which is hardcoded to global mode (so no unassign call is necessary). Add a transport flag 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 command 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. The device's CID must not be observable from such a namespace either. It is reached through three paths: IOCTL_VM_SOCKETS_GET_LOCAL_CID reports it, bind() accepts it because vsock_find_cid() matches it, and connect() to it selects the loopback transport because vsock_use_local_transport() compares against it. All three read it through vsock_registered_transport_cid(), which now takes the namespace asking and reports VMADDR_CID_ANY for the g2h slot when that namespace cannot reach the device. 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_net ever supports local-mode in the future, this logic will have to be changed. Suggested-by: Stefano Garzarella <[email protected]> Link: https://lore.kernel.org/all/20200427142518.uwssa6dtasrp3bfc@steredhat/ Signed-off-by: Bobby Eshleman <[email protected]> --- Changes in v2: - New patch, replaces the ioctl with a genl family and VSOCK_CMD_DEV_NETNS_SET (Stefano) - Make netns_assign_allow a bool, not a callback (Stefano) - One vsock_netns_assignable(t) helper for both call sites (Stefano) - Make vsock_g2h_reachable_sk() static, drop the export (Stefano) - RST the peer when socket loses access to the device's net (Stefano) - Move the netns reset out of the sysctl exit hook, into the renamed vsock_pernet_ops (Stefano). - Remove the synchronize_rcu() from the pernet exit path. The net_namespace documentation states that synchronize_rcu() should be avoided in the pernet exit path. - Run the socket reset in the pernet pre_exit() hook, as the net_namespace management code (and documentation) guarantees synchronize_rcu() between pre_exit() and exit() - Drop the conditional synchronize_rcu() in vsock_core_unregister() (Stefano) - Specifically state "G2H transport" in the netns_assign_allow comment (Stefano) - Avoid leaking the g2h CID to a namespace that cannot reach it; GET_LOCAL_CID, bind() and loopback transport selection all read it through vsock_registered_transport_cid(), which now takes a netns - Add VSOCK_CMD_DEV_NETNS_GET --- Documentation/admin-guide/sysctl/net.rst | 23 ++ Documentation/netlink/specs/vsock.yaml | 68 ++++++ MAINTAINERS | 2 + drivers/vhost/vsock.c | 6 +- include/net/af_vsock.h | 17 +- include/uapi/linux/vsock.h | 28 +++ net/vmw_vsock/Makefile | 2 +- net/vmw_vsock/af_vsock.c | 358 +++++++++++++++++++++++++++++-- net/vmw_vsock/vsock_nl_gen.c | 36 ++++ net/vmw_vsock/vsock_nl_gen.h | 20 ++ tools/net/ynl/Makefile.deps | 1 + 11 files changed, 543 insertions(+), 18 deletions(-) diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst index fe43e8595958..f2d8e4e84f89 100644 --- a/Documentation/admin-guide/sysctl/net.rst +++ b/Documentation/admin-guide/sysctl/net.rst @@ -529,6 +529,29 @@ their hosts. The behavior of VSOCK sockets in a network namespace is determined by the namespace's mode (``global`` or ``local``), which controls how CIDs (Context IDs) are allocated and how sockets interact across namespaces. +In a guest, the vsock device owned by the guest-to-host (G2H) transport belongs +to one network namespace at a time. The ``VSOCK_CMD_DEV_NETNS_SET`` command of +the ``vsock`` netlink family, described in +Documentation/netlink/specs/vsock.yaml, moves it to the namespace the request +was sent from, which requires ``CAP_NET_ADMIN`` in the initial user namespace. +The namespace's mode decides who may then use the device: + +- ``global`` - every ``global`` mode namespace may use it. +- ``local`` - only that namespace may use it, which reserves the connection to + the host for it alone. + +The device starts out in the initial namespace, so until the command is issued +nothing has moved and no mode has changed. + +Support is transport dependent. A G2H transport that does not implement the +move refuses the command with ``EOPNOTSUPP``; of the in-tree guest transports +only virtio-vsock implements it. + +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/Documentation/netlink/specs/vsock.yaml b/Documentation/netlink/specs/vsock.yaml new file mode 100644 index 000000000000..a33e02cb6f34 --- /dev/null +++ b/Documentation/netlink/specs/vsock.yaml @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +# +# Copyright (c) 2026 Meta Platforms, Inc. and affiliates +# +--- +name: vsock + +doc: | + Control interface for the vsock (AF_VSOCK) core. + +protocol: genetlink + +uapi-header: linux/vsock.h + +attribute-sets: + - + name: vsock + attributes: + - + name: netns-id + type: s32 + doc: | + Network namespace the guest's vsock device is assigned to, as an + nsid relative to the caller. + + Absent when the device is in the caller's own namespace, and + NETNSA_NSID_NOT_ASSIGNED when the caller's namespace cannot reach + the device under the mode rules, or when no nsid could be + allocated for it. + +operations: + list: + - + name: dev-netns-set + doc: | + Set the network namespace of the guest's vsock device, the one owned + by the guest-to-host transport. + + The device is moved to the namespace the request was sent from. It + starts out in the initial namespace, and moving it back there is how + an assignment is undone. + + Support is transport dependent: a guest-to-host transport that does + not implement the move refuses the request with EOPNOTSUPP. + + The namespace's mode decides who may then use the device: a global + mode namespace shares it with every other global mode namespace, + while a local mode namespace reserves it for itself. + + Connections made before the assignment, from a namespace that can no + longer reach the device, are reset. + attribute-set: vsock + flags: [admin-perm] + do: {} + - + name: dev-netns-get + doc: | + Get the network namespace the guest's vsock device is assigned to. + + The namespace is reported as an nsid relative to the caller, so the + attribute is absent when the device is already in the caller's own + namespace. A caller whose namespace cannot reach the device is told + NETNSA_NSID_NOT_ASSIGNED rather than where the device went. + attribute-set: vsock + do: + reply: + attributes: + - netns-id diff --git a/MAINTAINERS b/MAINTAINERS index df8ab9b82402..e30e6f8d71e2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29143,10 +29143,12 @@ M: Stefano Garzarella <[email protected]> L: [email protected] L: [email protected] S: Maintained +F: Documentation/netlink/specs/vsock.yaml F: drivers/net/vsockmon.c F: include/net/af_vsock.h F: include/uapi/linux/vm_sockets.h F: include/uapi/linux/vm_sockets_diag.h +F: include/uapi/linux/vsock.h F: include/uapi/linux/vsockmon.h F: net/vmw_vsock/ F: tools/testing/selftests/vsock/ diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c index abed1fbcf66c..badc064964b3 100644 --- a/drivers/vhost/vsock.c +++ b/drivers/vhost/vsock.c @@ -828,9 +828,11 @@ static int vhost_vsock_set_cid(struct vhost_vsock *vsock, u64 guest_cid) return -EINVAL; /* Refuse if CID is assigned to the guest->host transport (i.e. nested - * VM), to make the loopback work. + * VM), to make the loopback work. Only when that device is reachable + * from this VM's namespace, which is the same test the guest CID + * collision check below applies. */ - if (vsock_find_cid(guest_cid)) + if (vsock_find_cid(vsock->net, guest_cid)) return -EADDRINUSE; /* Refuse if CID is already in use */ diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h index 370fcd3ddabc..dbf1a6aa367f 100644 --- a/include/net/af_vsock.h +++ b/include/net/af_vsock.h @@ -35,6 +35,8 @@ struct vsock_sock { /* Links for the global tables of bound and connected sockets. */ struct list_head bound_table; struct list_head connected_table; + /* Protected by vsock_register_mutex. */ + struct list_head pending_reset; /* Accessed without the socket lock held. This means it can never be * modified outsided of socket create or destruct. */ @@ -190,6 +192,16 @@ struct vsock_transport { /* Zero-copy. */ bool (*msgzerocopy_allow)(void); + + /* True if the G2H transport honours VSOCK_CMD_DEV_NETNS_SET. A + * transport that sets this must also implement reset. + */ + bool netns_assign_allow; + + /* Send a reset to @vsk's peer. @skb is the packet being replied to, or + * NULL when the reset is not a reply. May sleep. + */ + int (*reset)(struct vsock_sock *vsk, struct sk_buff *skb); }; /**** CORE ****/ @@ -236,8 +248,11 @@ void vsock_remove_sock(struct vsock_sock *vsk); void vsock_for_each_connected_socket(const struct vsock_transport *transport, void (*fn)(struct sock *sk)); int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk); -bool vsock_find_cid(unsigned int cid); +bool vsock_find_cid(struct net *net, unsigned int cid); void vsock_linger(struct sock *sk); +struct net *vsock_g2h_net_get(void); +bool vsock_g2h_net_reachable(struct net *net); +bool vsock_maybe_set_connected(struct vsock_sock *vsk); /**** TAP ****/ diff --git a/include/uapi/linux/vsock.h b/include/uapi/linux/vsock.h new file mode 100644 index 000000000000..803b9aa1f0b3 --- /dev/null +++ b/include/uapi/linux/vsock.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/vsock.yaml */ +/* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ + +#ifndef _UAPI_LINUX_VSOCK_H +#define _UAPI_LINUX_VSOCK_H + +#define VSOCK_FAMILY_NAME "vsock" +#define VSOCK_FAMILY_VERSION 1 + +enum { + VSOCK_A_NETNS_ID = 1, + + __VSOCK_A_MAX, + VSOCK_A_MAX = (__VSOCK_A_MAX - 1) +}; + +enum { + VSOCK_CMD_DEV_NETNS_SET = 1, + VSOCK_CMD_DEV_NETNS_GET, + + __VSOCK_CMD_MAX, + VSOCK_CMD_MAX = (__VSOCK_CMD_MAX - 1) +}; + +#endif /* _UAPI_LINUX_VSOCK_H */ diff --git a/net/vmw_vsock/Makefile b/net/vmw_vsock/Makefile index 5da74c4a9f1d..97e2a559e2bf 100644 --- a/net/vmw_vsock/Makefile +++ b/net/vmw_vsock/Makefile @@ -7,7 +7,7 @@ obj-$(CONFIG_VIRTIO_VSOCKETS_COMMON) += vmw_vsock_virtio_transport_common.o obj-$(CONFIG_HYPERV_VSOCKETS) += hv_sock.o obj-$(CONFIG_VSOCKETS_LOOPBACK) += vsock_loopback.o -vsock-y += af_vsock.o af_vsock_tap.o vsock_addr.o +vsock-y += af_vsock.o af_vsock_tap.o vsock_addr.o vsock_nl_gen.o vsock-$(CONFIG_BPF_SYSCALL) += vsock_bpf.o vsock_diag-y += diag.o diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index 95a435aef512..9938dd501019 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. + * The VSOCK_CMD_DEV_NETNS_SET netlink command moves it to the namespace the + * request was sent from. It starts out in init_net. The mode rules then + * decide who may use it, and which namespace packets from the host are + * delivered to: + * + * - assigned to a global mode namespace - every global mode namespace may + * use it. Until the command is issued nothing has moved and no mode has + * changed, so the default is the behaviour that predates it. + * - assigned to a local mode namespace - only that namespace may use it. + * This is how a nested VM is isolated from the rest of the guest. + * + * Connections made before an assignment, from a namespace that can no + * longer reach the device, are 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> @@ -161,10 +179,14 @@ #include <linux/workqueue.h> #include <net/sock.h> #include <net/af_vsock.h> +#include <linux/net_namespace.h> +#include <net/genetlink.h> #include <net/netns/vsock.h> #include <uapi/linux/vm_sockets.h> #include <uapi/asm-generic/ioctls.h> +#include "vsock_nl_gen.h" + #define VSOCK_NET_MODE_STR_GLOBAL "global" #define VSOCK_NET_MODE_STR_LOCAL "local" @@ -208,6 +230,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 @@ -553,7 +580,37 @@ void vsock_enqueue_accept(struct sock *listener, struct sock *connected) } EXPORT_SYMBOL_GPL(vsock_enqueue_accept); -static bool vsock_use_local_transport(unsigned int remote_cid) +/* Return true if @t honours namespace assignment. One that does not keeps the + * reachability rules it had before VSOCK_CMD_DEV_NETNS_SET existed. + */ +static bool vsock_netns_assignable(const struct vsock_transport *t) +{ + return t && t->netns_assign_allow && t->reset; +} + +/* Return the CID of the transport in @transport, as seen from @net. + * + * The g2h device is the one that can move between namespaces, so a @net that + * cannot reach it is told VMADDR_CID_ANY: the same answer it would get if no + * g2h transport were registered at all. + */ +static u32 +__vsock_registered_transport_cid(const struct vsock_transport **transport, + struct net *net) +{ + lockdep_assert_held(&vsock_register_mutex); + + if (!*transport) + return VMADDR_CID_ANY; + + if (transport == &transport_g2h && vsock_netns_assignable(*transport) && + !vsock_g2h_net_reachable(net)) + return VMADDR_CID_ANY; + + return (*transport)->get_local_cid(); +} + +static bool vsock_use_local_transport(struct net *net, unsigned int remote_cid) { lockdep_assert_held(&vsock_register_mutex); @@ -564,7 +621,12 @@ static bool vsock_use_local_transport(unsigned int remote_cid) return true; if (transport_g2h) { - return remote_cid == transport_g2h->get_local_cid(); + u32 cid = __vsock_registered_transport_cid(&transport_g2h, net); + + /* The device may be unreachable from @net, in which case + * @remote_cid is not the local CID. + */ + return cid != VMADDR_CID_ANY && remote_cid == cid; } else { return remote_cid == VMADDR_CID_HOST; } @@ -626,7 +688,7 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk) break; case SOCK_STREAM: case SOCK_SEQPACKET: - if (vsock_use_local_transport(remote_cid)) + if (vsock_use_local_transport(sock_net(sk), remote_cid)) new_transport = transport_local; else if (remote_cid <= VMADDR_CID_HOST || (remote_flags & VMADDR_FLAG_TO_HOST)) @@ -654,6 +716,13 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk) goto err; } + if (new_transport && new_transport == transport_g2h && + vsock_netns_assignable(new_transport) && + !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. */ @@ -715,21 +784,22 @@ EXPORT_SYMBOL_GPL(vsock_assign_transport); * Provide safe access to static transport_{h2g,g2h,dgram,local} callbacks. * Otherwise we may race with module removal. Do not use on `vsk->transport`. */ -static u32 vsock_registered_transport_cid(const struct vsock_transport **transport) +static u32 +vsock_registered_transport_cid(const struct vsock_transport **transport, + struct net *net) { - u32 cid = VMADDR_CID_ANY; + u32 cid; mutex_lock(&vsock_register_mutex); - if (*transport) - cid = (*transport)->get_local_cid(); + cid = __vsock_registered_transport_cid(transport, net); mutex_unlock(&vsock_register_mutex); return cid; } -bool vsock_find_cid(unsigned int cid) +bool vsock_find_cid(struct net *net, unsigned int cid) { - if (cid == vsock_registered_transport_cid(&transport_g2h)) + if (cid == vsock_registered_transport_cid(&transport_g2h, net)) return true; if (transport_h2g && cid == VMADDR_CID_HOST) @@ -742,6 +812,173 @@ bool vsock_find_cid(unsigned int cid) } EXPORT_SYMBOL_GPL(vsock_find_cid); +/* Return the namespace the g2h device is assigned to, with a reference held, + * or NULL when that namespace is going away and the init_net cannot stand in + * for it. + */ +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. + * + * vsock_net_check_mode() is safe here because 'assigned' is pointing + * to a net that won't be freed until the following rcu grace period. + */ + 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); + +static bool vsock_g2h_reachable_sk(struct vsock_sock *vsk) +{ + if (!vsock_netns_assignable(vsk->transport)) + return true; + + return vsock_g2h_net_reachable(sock_net(sk_vsock(vsk))); +} + +/* Move @vsk to TCP_ESTABLISHED and into the connected table, unless the device + * has moved to a namespace @vsk cannot reach. Returns false without doing + * either in that case. + * + * The reset sweep walks the same table under the same lock, so an assign + * cannot land between the check and the insert: either the sweep finds @vsk + * and resets it, or @vsk is never added. + */ +bool vsock_maybe_set_connected(struct vsock_sock *vsk) +{ + struct list_head *list; + bool reachable; + + list = vsock_connected_sockets(&vsk->remote_addr, &vsk->local_addr); + + spin_lock_bh(&vsock_table_lock); + reachable = vsock_g2h_reachable_sk(vsk); + if (reachable) { + sk_vsock(vsk)->sk_state = TCP_ESTABLISHED; + __vsock_insert_connected(list, vsk); + } + spin_unlock_bh(&vsock_table_lock); + + return reachable; +} +EXPORT_SYMBOL_GPL(vsock_maybe_set_connected); + +/* Reset every connected socket of @t that can no longer reach the g2h device, + * and let the transport tell each peer. + */ +static void vsock_g2h_reset_unreachable(const struct vsock_transport *t) +{ + struct vsock_sock *vsk, *tmp; + LIST_HEAD(reset_list); + struct sock *sk; + int i; + + /* The calling context must hold vsock_register_mutex, which serializes + * concurrent netns assignments' use of vsk->pending_reset. + */ + lockdep_assert_held(&vsock_register_mutex); + + spin_lock_bh(&vsock_table_lock); + + for (i = 0; i < ARRAY_SIZE(vsock_connected_table); i++) { + list_for_each_entry(vsk, &vsock_connected_table[i], + connected_table) { + sk = sk_vsock(vsk); + + if (vsk->transport != t || + sk->sk_state == TCP_CLOSE || + vsock_g2h_reachable_sk(vsk)) + continue; + + sk->sk_state = TCP_CLOSE; + sk->sk_err = ECONNRESET; + sk_error_report(sk); + + sock_hold(sk); + list_add_tail(&vsk->pending_reset, &reset_list); + } + } + + spin_unlock_bh(&vsock_table_lock); + + /* Reset outside of spinlock because the transport may sleep + * (e.g., GFP_KERNEL alloc). + */ + list_for_each_entry_safe(vsk, tmp, &reset_list, pending_reset) { + list_del_init(&vsk->pending_reset); + t->reset(vsk, NULL); + sock_put(sk_vsock(vsk)); + } +} + +/* 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_netns_assignable(transport_g2h)) { + ret = -EOPNOTSUPP; + } else { + /* See vsock_maybe_set_connected() comment about synchronizing + * with connecting sockets. + */ + rcu_assign_pointer(vsock_g2h_net, net); + vsock_g2h_reset_unreachable(transport_g2h); + } + 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. + */ +/* Runs as .pre_exit: pernet_operations guarantees a synchronize_rcu() + * between pre_exit() and exit(), which drains the readers this drops. + */ +static void __net_exit vsock_g2h_net_reset(struct net *net) +{ + /* 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); + mutex_unlock(&vsock_register_mutex); +} + static struct sock *vsock_dequeue_accept(struct sock *listener) { struct vsock_sock *vlistener; @@ -908,7 +1145,8 @@ static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr) * like AF_INET prevents binding to a non-local IP address (in most * cases), we only allow binding to a local CID. */ - if (addr->svm_cid != VMADDR_CID_ANY && !vsock_find_cid(addr->svm_cid)) + if (addr->svm_cid != VMADDR_CID_ANY && + !vsock_find_cid(sock_net(sk_vsock(vsk)), addr->svm_cid)) return -EADDRNOTAVAIL; switch (sk->sk_socket->type) { @@ -967,6 +1205,7 @@ static struct sock *__vsock_create(struct net *net, INIT_LIST_HEAD(&vsk->bound_table); INIT_LIST_HEAD(&vsk->connected_table); + INIT_LIST_HEAD(&vsk->pending_reset); vsk->listener = NULL; INIT_LIST_HEAD(&vsk->pending_links); INIT_LIST_HEAD(&vsk->accept_queue); @@ -2760,6 +2999,7 @@ static long vsock_dev_do_ioctl(struct file *filp, { u32 __user *p = ptr; int retval = 0; + struct net *net; u32 cid; switch (cmd) { @@ -2767,11 +3007,14 @@ static long vsock_dev_do_ioctl(struct file *filp, /* To be compatible with the VMCI behavior, we prioritize the * guest CID instead of well-know host CID (VMADDR_CID_HOST). */ - cid = vsock_registered_transport_cid(&transport_g2h); + net = current->nsproxy->net_ns; + cid = vsock_registered_transport_cid(&transport_g2h, net); if (cid == VMADDR_CID_ANY) - cid = vsock_registered_transport_cid(&transport_h2g); + cid = vsock_registered_transport_cid(&transport_h2g, + net); if (cid == VMADDR_CID_ANY) - cid = vsock_registered_transport_cid(&transport_local); + cid = vsock_registered_transport_cid(&transport_local, + net); if (put_user(cid, p) != 0) retval = -EFAULT; @@ -2812,6 +3055,81 @@ static struct miscdevice vsock_device = { .fops = &vsock_device_ops, }; +int vsock_nl_dev_netns_get_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct net *net = genl_info_net(info); + struct net *assigned; + struct sk_buff *msg; + bool report; + void *hdr; + s32 id; + int err; + + mutex_lock(&vsock_register_mutex); + if (!transport_g2h) { + mutex_unlock(&vsock_register_mutex); + NL_SET_ERR_MSG(info->extack, + "no guest-to-host transport is loaded"); + return -ENODEV; + } + + rcu_read_lock(); + assigned = rcu_dereference(vsock_g2h_net); + report = !net_eq(net, assigned); + if (report) { + /* Hide the assignment on the same terms the CID is hidden: + * only a transport that honours namespace assignment keeps it + * from a namespace that cannot reach the device. + */ + if (vsock_netns_assignable(transport_g2h) && + !vsock_net_check_mode(net, assigned)) + id = NETNSA_NSID_NOT_ASSIGNED; + else + id = peernet2id_alloc(net, assigned, GFP_ATOMIC); + } + rcu_read_unlock(); + mutex_unlock(&vsock_register_mutex); + + msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_iput(msg, info); + if (!hdr) { + err = -EMSGSIZE; + goto err_free; + } + + if (report && nla_put_s32(msg, VSOCK_A_NETNS_ID, id)) { + err = -EMSGSIZE; + goto err_cancel; + } + + genlmsg_end(msg, hdr); + + return genlmsg_reply(msg, info); + +err_cancel: + genlmsg_cancel(msg, hdr); +err_free: + nlmsg_free(msg); + return err; +} + +int vsock_nl_dev_netns_set_doit(struct sk_buff *skb, struct genl_info *info) +{ + int err = vsock_g2h_net_assign(genl_info_net(info)); + + if (err == -ENODEV) + NL_SET_ERR_MSG(info->extack, + "no guest-to-host transport is loaded"); + else if (err == -EOPNOTSUPP) + NL_SET_ERR_MSG(info->extack, + "the loaded guest-to-host transport does not support namespace assignment"); + + return err; +} + static int __vsock_net_mode_string(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos, enum vsock_net_mode mode, @@ -3015,6 +3333,7 @@ static __net_exit void vsock_pernet_exit(struct net *net) static struct pernet_operations vsock_pernet_ops = { .init = vsock_pernet_init, + .pre_exit = vsock_g2h_net_reset, .exit = vsock_pernet_exit, }; @@ -3050,10 +3369,18 @@ static int __init vsock_init(void) goto err_unregister_sock; } + err = genl_register_family(&vsock_nl_family); + if (err) { + pr_err("Cannot register vsock netlink family: %d\n", err); + goto err_unregister_pernet; + } + vsock_bpf_build_proto(); return 0; +err_unregister_pernet: + unregister_pernet_subsys(&vsock_pernet_ops); err_unregister_sock: sock_unregister(AF_VSOCK); err_unregister_proto: @@ -3066,6 +3393,7 @@ static int __init vsock_init(void) static void __exit vsock_exit(void) { + genl_unregister_family(&vsock_nl_family); misc_deregister(&vsock_device); sock_unregister(AF_VSOCK); proto_unregister(&vsock_proto); @@ -3141,8 +3469,10 @@ void vsock_core_unregister(const struct vsock_transport *t) if (transport_h2g == t) transport_h2g = NULL; - if (transport_g2h == t) + if (transport_g2h == t) { transport_g2h = NULL; + rcu_assign_pointer(vsock_g2h_net, &init_net); + } if (transport_dgram == t) transport_dgram = NULL; diff --git a/net/vmw_vsock/vsock_nl_gen.c b/net/vmw_vsock/vsock_nl_gen.c new file mode 100644 index 000000000000..81db4c61d7bb --- /dev/null +++ b/net/vmw_vsock/vsock_nl_gen.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/vsock.yaml */ +/* YNL-GEN kernel source */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ + +#include <net/netlink.h> +#include <net/genetlink.h> + +#include "vsock_nl_gen.h" + +#include <uapi/linux/vsock.h> + +/* Ops table for vsock */ +static const struct genl_split_ops vsock_nl_ops[] = { + { + .cmd = VSOCK_CMD_DEV_NETNS_SET, + .doit = vsock_nl_dev_netns_set_doit, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = VSOCK_CMD_DEV_NETNS_GET, + .doit = vsock_nl_dev_netns_get_doit, + .flags = GENL_CMD_CAP_DO, + }, +}; + +struct genl_family vsock_nl_family __ro_after_init = { + .name = VSOCK_FAMILY_NAME, + .version = VSOCK_FAMILY_VERSION, + .netnsok = true, + .parallel_ops = true, + .module = THIS_MODULE, + .split_ops = vsock_nl_ops, + .n_split_ops = ARRAY_SIZE(vsock_nl_ops), +}; diff --git a/net/vmw_vsock/vsock_nl_gen.h b/net/vmw_vsock/vsock_nl_gen.h new file mode 100644 index 000000000000..c041195584a0 --- /dev/null +++ b/net/vmw_vsock/vsock_nl_gen.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/vsock.yaml */ +/* YNL-GEN kernel header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ + +#ifndef _LINUX_VSOCK_GEN_H +#define _LINUX_VSOCK_GEN_H + +#include <net/netlink.h> +#include <net/genetlink.h> + +#include <uapi/linux/vsock.h> + +int vsock_nl_dev_netns_set_doit(struct sk_buff *skb, struct genl_info *info); +int vsock_nl_dev_netns_get_doit(struct sk_buff *skb, struct genl_info *info); + +extern struct genl_family vsock_nl_family; + +#endif /* _LINUX_VSOCK_GEN_H */ diff --git a/tools/net/ynl/Makefile.deps b/tools/net/ynl/Makefile.deps index 1e746e25e2bc..fa51108c43d4 100644 --- a/tools/net/ynl/Makefile.deps +++ b/tools/net/ynl/Makefile.deps @@ -55,4 +55,5 @@ CFLAGS_tc:= $(call get_hdr_inc,__LINUX_RTNETLINK_H,rtnetlink.h) \ $(call get_hdr_inc,_TC_SKBEDIT_H,tc_act/tc_skbedit.h) \ $(call get_hdr_inc,_TC_TUNNEL_KEY_H,tc_act/tc_tunnel_key.h) CFLAGS_tcp_metrics:=$(call get_hdr_inc,_LINUX_TCP_METRICS_H,tcp_metrics.h) +CFLAGS_vsock:=$(call get_hdr_inc,_LINUX_VSOCK_H,vsock.h) CFLAGS_wireguard:=$(call get_hdr_inc2,_LINUX_WIREGUARD_H,_WG_UAPI_WIREGUARD_H,wireguard.h) -- 2.53.0-Meta

