From: Lennart Poettering Date: Mon, 10 Mar 2025 11:39:35 +0000 (+0100) Subject: nsresourced: explicitly remove network interfaces when their userns goes away X-Git-Tag: v258-rc1~1062^2~6 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7d2e2900f18ccfc7284e1a4026046f95c528256d;p=thirdparty%2Fsystemd.git nsresourced: explicitly remove network interfaces when their userns goes away Let's tighten the screws a bit on the network interfaces we delegate, and explicitly destroy them, just like we destroy delegated cgroups. Ideally, this should happen automatically because the userns goes away that pins the veth, or because the client holding an fd for a tap device goes away as the userns goes away. But you never know who keeps a reference, hence let's explicitly destroy this too. --- diff --git a/src/nsresourced/nsresourced-manager.c b/src/nsresourced/nsresourced-manager.c index 612b3c86854..99a6e1fedec 100644 --- a/src/nsresourced/nsresourced-manager.c +++ b/src/nsresourced/nsresourced-manager.c @@ -362,7 +362,12 @@ static void manager_release_userns_by_inode(Manager *m, uint64_t inode) { /* Remove the cgroups of this userns */ r = userns_info_remove_cgroups(userns_info); if (r < 0) - log_warning_errno(r, "Failed to remove cgroups of user namespace: %m"); + log_warning_errno(r, "Failed to remove cgroups of user namespace, ignoring: %m"); + + /* Remove the netifs of this userns */ + r = userns_info_remove_netifs(userns_info); + if (r < 0) + log_warning_errno(r, "Failed to remove netifs of user namespace, ignoring: %m"); r = userns_registry_remove(m->registry_fd, userns_info); if (r < 0) diff --git a/src/nsresourced/nsresourcework.c b/src/nsresourced/nsresourcework.c index abbd09e8e29..eda2d143528 100644 --- a/src/nsresourced/nsresourcework.c +++ b/src/nsresourced/nsresourcework.c @@ -1760,6 +1760,17 @@ static int vl_method_add_netif_to_user_namespace(sd_varlink *link, sd_json_varia return -ENOMEM; strshorten(ifname_host, IFNAMSIZ-1); + /* Register the interface in the userns store first, so that we can be sure it's properly 'owned' at + * any time, in case setup fails for some reason. Given we the interface name is hashed accidental + * collisions should be unlikely. */ + r = userns_info_add_netif(userns_info, ifname_host); + if (r < 0) + return r; + + r = userns_registry_store(registry_dir_fd, userns_info); + if (r < 0) + return r; + if (p.ifname) r = asprintf(&altifname_host, "ns-" UID_FMT "-%s-%s", userns_info->owner, userns_info->name, p.ifname); else diff --git a/src/nsresourced/userns-registry.c b/src/nsresourced/userns-registry.c index 2fbf9216d80..34205e71073 100644 --- a/src/nsresourced/userns-registry.c +++ b/src/nsresourced/userns-registry.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "sd-json.h" +#include "sd-netlink.h" #include "chase.h" #include "fd-util.h" @@ -71,6 +72,8 @@ UserNamespaceInfo *userns_info_free(UserNamespaceInfo *userns) { free(userns->cgroups); free(userns->name); + strv_free(userns->netifs); + return mfree(userns); } @@ -130,6 +133,7 @@ static int userns_registry_load(int dir_fd, const char *fn, UserNamespaceInfo ** { "startGid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, start_gid), 0 }, { "targetGid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, target_gid), 0 }, { "cgroups", SD_JSON_VARIANT_ARRAY, dispatch_cgroups_array, 0, 0 }, + { "netifs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserNamespaceInfo, netifs), 0 }, {} }; @@ -443,7 +447,8 @@ int userns_registry_store(int dir_fd, UserNamespaceInfo *info) { SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->target_uid), "target", SD_JSON_BUILD_UNSIGNED(info->target_uid)), SD_JSON_BUILD_PAIR_CONDITION(gid_is_valid(info->start_gid), "startGid", SD_JSON_BUILD_UNSIGNED(info->start_gid)), SD_JSON_BUILD_PAIR_CONDITION(gid_is_valid(info->target_gid), "targetGid", SD_JSON_BUILD_UNSIGNED(info->target_gid)), - SD_JSON_BUILD_PAIR_CONDITION(!!cgroup_array, "cgroups", SD_JSON_BUILD_VARIANT(cgroup_array))); + SD_JSON_BUILD_PAIR_CONDITION(!!cgroup_array, "cgroups", SD_JSON_BUILD_VARIANT(cgroup_array)), + JSON_BUILD_PAIR_STRV_NON_EMPTY("netifs", info->netifs)); if (r < 0) return r; @@ -611,6 +616,7 @@ bool userns_info_has_cgroup(UserNamespaceInfo *userns, uint64_t cgroup_id) { } int userns_info_add_cgroup(UserNamespaceInfo *userns, uint64_t cgroup_id) { + assert(userns); if (userns_info_has_cgroup(userns, cgroup_id)) return 0; @@ -686,6 +692,67 @@ int userns_info_remove_cgroups(UserNamespaceInfo *userns) { return ret; } +int userns_info_add_netif(UserNamespaceInfo *userns, const char *netif) { + int r; + + assert(userns); + assert(netif); + + if (strv_contains(userns->netifs, netif)) + return 0; + + r = strv_extend(&userns->netifs, netif); + if (r < 0) + return r; + + return 1; +} + +static int userns_destroy_netif(sd_netlink **rtnl, const char *name) { + int r; + + assert(rtnl); + assert(name); + + log_debug("Removing delegated network interface '%s'", name); + + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return log_debug_errno(r, "Failed to connect to netlink: %m"); + } + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + r = sd_rtnl_message_new_link(*rtnl, &m, RTM_DELLINK, /* ifindex= */ 0); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, name); + if (r < 0) + return r; + + r = sd_netlink_call(*rtnl, m, /* timeout_usec= */ 0, /* ret_reply= */ NULL); + if (ERRNO_IS_NEG_DEVICE_ABSENT(r)) /* Already gone? */ + return 0; + if (r < 0) + return log_debug_errno(r, "Failed to remove interface %s: %m", name); + + return 1; +} + +int userns_info_remove_netifs(UserNamespaceInfo *userns) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + int ret = 0; + + assert(userns); + + STRV_FOREACH(c, userns->netifs) + RET_GATHER(ret, userns_destroy_netif(&rtnl, *c)); + + userns->netifs = strv_free(userns->netifs); + return ret; +} + bool userns_name_is_valid(const char *name) { /* Checks if the specified string is suitable as user namespace name. */ diff --git a/src/nsresourced/userns-registry.h b/src/nsresourced/userns-registry.h index c3e0779f4bf..0a6f1c2c6eb 100644 --- a/src/nsresourced/userns-registry.h +++ b/src/nsresourced/userns-registry.h @@ -16,6 +16,7 @@ typedef struct UserNamespaceInfo { gid_t target_gid; uint64_t *cgroups; size_t n_cgroups; + char **netifs; } UserNamespaceInfo; UserNamespaceInfo* userns_info_new(void); @@ -27,6 +28,9 @@ bool userns_info_has_cgroup(UserNamespaceInfo *userns, uint64_t cgroup_id); int userns_info_add_cgroup(UserNamespaceInfo *userns, uint64_t cgroup_id); int userns_info_remove_cgroups(UserNamespaceInfo *userns); +int userns_info_add_netif(UserNamespaceInfo *userns, const char *netif); +int userns_info_remove_netifs(UserNamespaceInfo *userns); + bool userns_name_is_valid(const char *name); int userns_registry_open_fd(void);