]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
nsresourced: explicitly remove network interfaces when their userns goes away
authorLennart Poettering <lennart@poettering.net>
Mon, 10 Mar 2025 11:39:35 +0000 (12:39 +0100)
committerLennart Poettering <lennart@poettering.net>
Mon, 17 Mar 2025 15:03:18 +0000 (16:03 +0100)
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.

src/nsresourced/nsresourced-manager.c
src/nsresourced/nsresourcework.c
src/nsresourced/userns-registry.c
src/nsresourced/userns-registry.h

index 612b3c86854ee7c7f9d9d68adeeff5712aa61a10..99a6e1fedeccd216682ac1ed6d7dce4eb2bece0d 100644 (file)
@@ -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)
index abbd09e8e296e6e0d3b017cf6156a34c7cd508c0..eda2d143528429963f2921253c56e440e3a70b4d 100644 (file)
@@ -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
index 2fbf9216d8085a80357f84d6062da92b6e2cbad4..34205e71073b48f210dda404cf0ed690c7d21d79 100644 (file)
@@ -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. */
index c3e0779f4bf82cfc4cd94162f3ce9064e69bb4f7..0a6f1c2c6eb629854d57072c6ca96280509890c9 100644 (file)
@@ -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);