diff --git a/src/nsresourced/nsresourced-manager.c b/src/nsresourced/nsresourced-manager.c index 612b3c8685..99a6e1fede 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 abbd09e8e2..eda2d14352 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 2fbf9216d8..34205e7107 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 c3e0779f4b..0a6f1c2c6e 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);