]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: implement RestrictNetworkInterfaces=
authorMauricio Vásquez <mauricio@kinvolk.io>
Thu, 21 Jan 2021 16:08:19 +0000 (11:08 -0500)
committerMauricio Vásquez <mauricio@kinvolk.io>
Wed, 18 Aug 2021 20:55:53 +0000 (15:55 -0500)
This commit introduces all the logic to load and attach the BPF
programs to restrict network interfaces when a unit specifying it is
loaded.

Signed-off-by: Mauricio Vásquez <mauricio@kinvolk.io>
src/basic/cgroup-util.c
src/basic/cgroup-util.h
src/core/cgroup.c
src/core/cgroup.h
src/core/meson.build
src/core/restrict-ifaces.c [new file with mode: 0644]
src/core/restrict-ifaces.h [new file with mode: 0644]
src/core/unit-serialize.c
src/core/unit.c
src/core/unit.h

index 5bf7c03c00ec8e7cc957d331a4d41236ea7b55c7..95891f68aa9fede1f250a5671edee79cd9f28825 100644 (file)
@@ -2214,6 +2214,7 @@ static const char *const cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = {
         [CGROUP_CONTROLLER_BPF_DEVICES] = "bpf-devices",
         [CGROUP_CONTROLLER_BPF_FOREIGN] = "bpf-foreign",
         [CGROUP_CONTROLLER_BPF_SOCKET_BIND] = "bpf-socket-bind",
+        [CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES] = "bpf-restrict-network-interfaces",
 };
 
 DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController);
index b2dd9b56a54662883aef87c33ff0f67ba320d0f4..90ccd2c032652b1cde79b9f8a9001f36880cb06f 100644 (file)
@@ -32,6 +32,7 @@ typedef enum CGroupController {
         CGROUP_CONTROLLER_BPF_DEVICES,
         CGROUP_CONTROLLER_BPF_FOREIGN,
         CGROUP_CONTROLLER_BPF_SOCKET_BIND,
+        CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES,
 
         _CGROUP_CONTROLLER_MAX,
         _CGROUP_CONTROLLER_INVALID = -EINVAL,
@@ -53,6 +54,7 @@ typedef enum CGroupMask {
         CGROUP_MASK_BPF_DEVICES = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_DEVICES),
         CGROUP_MASK_BPF_FOREIGN = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_FOREIGN),
         CGROUP_MASK_BPF_SOCKET_BIND = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_SOCKET_BIND),
+        CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES),
 
         /* All real cgroup v1 controllers */
         CGROUP_MASK_V1 = CGROUP_MASK_CPU|CGROUP_MASK_CPUACCT|CGROUP_MASK_BLKIO|CGROUP_MASK_MEMORY|CGROUP_MASK_DEVICES|CGROUP_MASK_PIDS,
@@ -61,7 +63,7 @@ typedef enum CGroupMask {
         CGROUP_MASK_V2 = CGROUP_MASK_CPU|CGROUP_MASK_CPUSET|CGROUP_MASK_IO|CGROUP_MASK_MEMORY|CGROUP_MASK_PIDS,
 
         /* All cgroup v2 BPF pseudo-controllers */
-        CGROUP_MASK_BPF = CGROUP_MASK_BPF_FIREWALL|CGROUP_MASK_BPF_DEVICES|CGROUP_MASK_BPF_FOREIGN|CGROUP_MASK_BPF_SOCKET_BIND,
+        CGROUP_MASK_BPF = CGROUP_MASK_BPF_FIREWALL|CGROUP_MASK_BPF_DEVICES|CGROUP_MASK_BPF_FOREIGN|CGROUP_MASK_BPF_SOCKET_BIND|CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES,
 
         _CGROUP_MASK_ALL = CGROUP_CONTROLLER_TO_MASK(_CGROUP_CONTROLLER_MAX) - 1
 } CGroupMask;
index bbeb5b2f50b98bc4899296f0038547a11d333aeb..5b612ee22b075cbd877d35f93e308c099be4a4dc 100644 (file)
@@ -28,6 +28,7 @@
 #include "percent-util.h"
 #include "process-util.h"
 #include "procfs-util.h"
+#include "restrict-ifaces.h"
 #include "special.h"
 #include "stat-util.h"
 #include "stdio-util.h"
@@ -246,6 +247,8 @@ void cgroup_context_done(CGroupContext *c) {
         while (c->bpf_foreign_programs)
                 cgroup_context_remove_bpf_foreign_program(c, c->bpf_foreign_programs);
 
+        c->restrict_network_interfaces = set_free(c->restrict_network_interfaces);
+
         cpu_set_reset(&c->cpuset_cpus);
         cpu_set_reset(&c->cpuset_mems);
 }
@@ -583,6 +586,12 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) {
                         cgroup_context_dump_socket_bind_item(bi, f);
                 fputc('\n', f);
         }
+
+        if (c->restrict_network_interfaces) {
+                char *iface;
+                SET_FOREACH(iface, c->restrict_network_interfaces)
+                        fprintf(f, "%sRestrictNetworkInterfaces: %s\n", prefix, iface);
+        }
 }
 
 void cgroup_context_dump_socket_bind_item(const CGroupSocketBindItem *item, FILE *f) {
@@ -1097,6 +1106,12 @@ static void cgroup_apply_socket_bind(Unit *u) {
         (void) bpf_socket_bind_install(u);
 }
 
+static void cgroup_apply_restrict_network_interfaces(Unit *u) {
+        assert(u);
+
+        (void) restrict_network_interfaces_install(u);
+}
+
 static int cgroup_apply_devices(Unit *u) {
         _cleanup_(bpf_program_unrefp) BPFProgram *prog = NULL;
         const char *path;
@@ -1527,6 +1542,9 @@ static void cgroup_context_apply(
 
         if (apply_mask & CGROUP_MASK_BPF_SOCKET_BIND)
                 cgroup_apply_socket_bind(u);
+
+        if (apply_mask & CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES)
+                cgroup_apply_restrict_network_interfaces(u);
 }
 
 static bool unit_get_needs_bpf_firewall(Unit *u) {
@@ -1580,6 +1598,17 @@ static bool unit_get_needs_socket_bind(Unit *u) {
         return c->socket_bind_allow || c->socket_bind_deny;
 }
 
+static bool unit_get_needs_restrict_network_interfaces(Unit *u) {
+        CGroupContext *c;
+        assert(u);
+
+        c = unit_get_cgroup_context(u);
+        if (!c)
+                return false;
+
+        return !set_isempty(c->restrict_network_interfaces);
+}
+
 static CGroupMask unit_get_cgroup_mask(Unit *u) {
         CGroupMask mask = 0;
         CGroupContext *c;
@@ -1635,6 +1664,9 @@ static CGroupMask unit_get_bpf_mask(Unit *u) {
         if (unit_get_needs_socket_bind(u))
                 mask |= CGROUP_MASK_BPF_SOCKET_BIND;
 
+        if (unit_get_needs_restrict_network_interfaces(u))
+                mask |= CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES;
+
         return mask;
 }
 
@@ -3132,6 +3164,11 @@ static int cg_bpf_mask_supported(CGroupMask *ret) {
         if (r > 0)
                 mask |= CGROUP_MASK_BPF_SOCKET_BIND;
 
+        /* BPF-based cgroup_skb/{egress|ingress} hooks */
+        r = restrict_network_interfaces_supported();
+        if (r > 0)
+                mask |= CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES;
+
         *ret = mask;
         return 0;
 }
index 3f8cad899de56ff398a2684c4e3952a21778793d..99bf7e22d8f18fc3b08ba6bca4da8eea69c425f6 100644 (file)
@@ -160,6 +160,9 @@ struct CGroupContext {
         char **ip_filters_egress;
         LIST_HEAD(CGroupBPFForeignProgram, bpf_foreign_programs);
 
+        Set *restrict_network_interfaces;
+        bool restrict_network_interfaces_is_allow_list;
+
         /* For legacy hierarchies */
         uint64_t cpu_shares;
         uint64_t startup_cpu_shares;
index df773b6675f883f98b579fa2ac75297b46cfe8b3..489a4d869fd9b3712b58bc7a8f76fb0250d0a60c 100644 (file)
@@ -97,6 +97,8 @@ libcore_sources = '''
         namespace.h
         path.c
         path.h
+        restrict-ifaces.c
+        restrict-ifaces.h
         scope.c
         scope.h
         selinux-access.c
diff --git a/src/core/restrict-ifaces.c b/src/core/restrict-ifaces.c
new file mode 100644 (file)
index 0000000..709f749
--- /dev/null
@@ -0,0 +1,205 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "fd-util.h"
+#include "restrict-ifaces.h"
+#include "netlink-util.h"
+
+#if BPF_FRAMEWORK
+/* libbpf, clang and llc compile time dependencies are satisfied */
+
+#include "bpf-dlopen.h"
+#include "bpf-link.h"
+
+#include "bpf/restrict_ifaces/restrict-ifaces.skel.h"
+
+static struct restrict_ifaces_bpf *restrict_ifaces_bpf_free(struct restrict_ifaces_bpf *obj) {
+        restrict_ifaces_bpf__destroy(obj);
+        return NULL;
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct restrict_ifaces_bpf *, restrict_ifaces_bpf_free);
+
+static int prepare_restrict_ifaces_bpf(Unit* u, bool is_allow_list,
+                const Set *restrict_network_interfaces,
+                struct restrict_ifaces_bpf **ret_object) {
+        _cleanup_(restrict_ifaces_bpf_freep) struct restrict_ifaces_bpf *obj = NULL;
+        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+        char *iface;
+        int r, map_fd;
+
+        assert(ret_object);
+
+        obj = restrict_ifaces_bpf__open();
+        if (!obj)
+                return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOMEM), "Failed to open BPF object");
+
+        r = sym_bpf_map__resize(obj->maps.sd_restrictif, MAX(set_size(restrict_network_interfaces), 1u));
+        if (r != 0)
+                return log_unit_error_errno(u, r,
+                                "Failed to resize BPF map '%s': %m",
+                                sym_bpf_map__name(obj->maps.sd_restrictif));
+
+        obj->rodata->is_allow_list = is_allow_list;
+
+        r = restrict_ifaces_bpf__load(obj);
+        if (r != 0)
+                return log_unit_error_errno(u, r, "Failed to load BPF object: %m");
+
+        map_fd = sym_bpf_map__fd(obj->maps.sd_restrictif);
+
+        SET_FOREACH(iface, restrict_network_interfaces) {
+                uint8_t dummy = 0;
+                int ifindex;
+                ifindex = rtnl_resolve_interface(&rtnl, iface);
+                if (ifindex < 0) {
+                        log_unit_warning_errno(u, ifindex, "Couldn't find index of network interface: %m. Ignoring '%s'", iface);
+                        continue;
+                }
+
+                if (sym_bpf_map_update_elem(map_fd, &ifindex, &dummy, BPF_ANY))
+                        return log_unit_error_errno(u, errno, "Failed to update BPF map '%s' fd: %m", sym_bpf_map__name(obj->maps.sd_restrictif));
+        }
+
+        *ret_object = TAKE_PTR(obj);
+        return 0;
+}
+
+int restrict_network_interfaces_supported(void) {
+        _cleanup_(restrict_ifaces_bpf_freep) struct restrict_ifaces_bpf *obj = NULL;
+        int r;
+        static int supported = -1;
+
+        if (supported >= 0)
+                return supported;
+
+        r = cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER);
+        if (r < 0) {
+                log_warning_errno(r, "Can't determine whether the unified hierarchy is used: %m");
+                supported = 0;
+                return supported;
+        }
+        if (r == 0) {
+                log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                "Not running with unified cgroup hierarchy, BPF is not supported");
+                supported = 0;
+                return supported;
+        }
+
+        if (dlopen_bpf() < 0)
+                return false;
+
+        if (!sym_bpf_probe_prog_type(BPF_PROG_TYPE_CGROUP_SKB, /*ifindex=*/0)) {
+                log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                "BPF program type cgroup_skb is not supported");
+                supported = 0;
+                return supported;
+        }
+
+        r = prepare_restrict_ifaces_bpf(NULL, true, NULL, &obj);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to load BPF object: %m");
+
+        supported = bpf_can_link_program(obj->progs.sd_restrictif_i);
+        return supported;
+}
+
+static int restrict_network_interfaces_install_impl(Unit *u) {
+        _cleanup_(bpf_link_freep) struct bpf_link *egress_link = NULL, *ingress_link = NULL;
+        _cleanup_(restrict_ifaces_bpf_freep) struct restrict_ifaces_bpf *obj = NULL;
+        _cleanup_free_ char *cgroup_path = NULL;
+        _cleanup_close_ int cgroup_fd = -1;
+        CGroupContext *cc;
+        int r;
+
+        cc = unit_get_cgroup_context(u);
+        if (!cc)
+                return 0;
+
+        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, NULL, &cgroup_path);
+        if (r < 0)
+                return log_unit_error_errno(u, r, "Failed to get cgroup path: %m");
+
+        if (!cc->restrict_network_interfaces)
+                return 0;
+
+        r = prepare_restrict_ifaces_bpf(u,
+                cc->restrict_network_interfaces_is_allow_list,
+                cc->restrict_network_interfaces,
+                &obj);
+        if (r < 0)
+                return r;
+
+        cgroup_fd = open(cgroup_path, O_RDONLY | O_CLOEXEC | O_DIRECTORY, 0);
+        if (cgroup_fd < 0)
+                return -errno;
+
+        ingress_link = sym_bpf_program__attach_cgroup(obj->progs.sd_restrictif_i, cgroup_fd);
+        r = sym_libbpf_get_error(ingress_link);
+        if (r != 0)
+                return log_unit_error_errno(u, r, "Failed to create ingress cgroup link: %m");
+
+        egress_link = sym_bpf_program__attach_cgroup(obj->progs.sd_restrictif_e, cgroup_fd);
+        r = sym_libbpf_get_error(egress_link);
+        if (r != 0)
+                return log_unit_error_errno(u, r, "Failed to create egress cgroup link: %m");
+
+        u->restrict_ifaces_ingress_bpf_link = TAKE_PTR(ingress_link);
+        u->restrict_ifaces_egress_bpf_link = TAKE_PTR(egress_link);
+
+        return 0;
+}
+
+int restrict_network_interfaces_install(Unit *u) {
+        int r = restrict_network_interfaces_install_impl(u);
+        fdset_close(u->initial_restric_ifaces_link_fds);
+        return r;
+}
+
+int serialize_restrict_network_interfaces(Unit *u, FILE *f, FDSet *fds) {
+        int r;
+
+        assert(u);
+
+        r = bpf_serialize_link(f, fds, "restrict-ifaces-bpf-fd", u->restrict_ifaces_ingress_bpf_link);
+        if (r < 0)
+                return r;
+
+        return bpf_serialize_link(f, fds, "restrict-ifaces-bpf-fd", u->restrict_ifaces_egress_bpf_link);
+}
+
+int restrict_network_interfaces_add_initial_link_fd(Unit *u, int fd) {
+        int r;
+
+        assert(u);
+
+        if (!u->initial_restric_ifaces_link_fds) {
+                u->initial_restric_ifaces_link_fds = fdset_new();
+                if (!u->initial_restric_ifaces_link_fds)
+                        return log_oom();
+        }
+
+        r = fdset_put(u->initial_restric_ifaces_link_fds, fd);
+        if (r < 0)
+                return log_unit_error_errno(u, r, "Failed to put restrict-ifaces-bpf-fd %d to restored fdset: %m", fd);
+
+        return 0;
+}
+
+#else /* ! BPF_FRAMEWORK */
+int restrict_network_interfaces_supported(void) {
+        return 0;
+}
+
+int restrict_network_interfaces_install(Unit *u) {
+        return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EOPNOTSUPP),
+                        "Failed to install RestrictInterfaces: BPF programs built from source code are not supported: %m");
+}
+
+int serialize_restrict_network_interfaces(Unit *u, FILE *f, FDSet *fds) {
+        return 0;
+}
+
+int restrict_network_interfaces_add_initial_link_fd(Unit *u, int fd) {
+        return 0;
+}
+#endif
diff --git a/src/core/restrict-ifaces.h b/src/core/restrict-ifaces.h
new file mode 100644 (file)
index 0000000..be9e522
--- /dev/null
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "fdset.h"
+#include "unit.h"
+
+typedef struct Unit Unit;
+
+int restrict_network_interfaces_supported(void);
+int restrict_network_interfaces_install(Unit *u);
+
+int serialize_restrict_network_interfaces(Unit *u, FILE *f, FDSet *fds);
+
+/* Add BPF link fd created before daemon-reload or daemon-reexec.
+ * FDs will be closed at the end of restrict_network_interfaces_install. */
+int restrict_network_interfaces_add_initial_link_fd(Unit *u, int fd);
index ab0f03a0ca5309ba7865cc2e808966b71110fe3a..9e1664ff53af5215e5f52930313d1d44e73d5799 100644 (file)
@@ -7,6 +7,7 @@
 #include "fileio.h"
 #include "format-util.h"
 #include "parse-util.h"
+#include "restrict-ifaces.h"
 #include "serialize.h"
 #include "string-table.h"
 #include "unit-serialize.h"
@@ -173,6 +174,8 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool switching_root) {
         (void) bpf_program_serialize_attachment_set(f, fds, "ip-bpf-custom-ingress-installed", u->ip_bpf_custom_ingress_installed);
         (void) bpf_program_serialize_attachment_set(f, fds, "ip-bpf-custom-egress-installed", u->ip_bpf_custom_egress_installed);
 
+        (void) serialize_restrict_network_interfaces(u, f, fds);
+
         if (uid_is_valid(u->ref_uid))
                 (void) serialize_item_format(f, "ref-uid", UID_FMT, u->ref_uid);
         if (gid_is_valid(u->ref_gid))
@@ -413,6 +416,21 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
                          (void) bpf_program_deserialize_attachment_set(v, fds, &u->ip_bpf_custom_egress_installed);
                          continue;
 
+                } else if (streq(l, "restrict-ifaces-bpf-fd")) {
+                        int fd;
+
+                        if (safe_atoi(v, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) {
+                                log_unit_debug(u, "Failed to parse restrict-ifaces-bpf-fd value: %s", v);
+                                continue;
+                        }
+                        if (fdset_remove(fds, fd) < 0) {
+                                log_unit_debug(u, "Failed to remove restrict-ifaces-bpf-fd %d from fdset", fd);
+                                continue;
+                        }
+
+                        (void) restrict_network_interfaces_add_initial_link_fd(u, fd);
+                        continue;
+
                 } else if (streq(l, "ref-uid")) {
                         uid_t uid;
 
index 2f7f022ef35492cfb42e62b2f309d76126b5c8dc..7d72dfa864be73b118a4e7385e172ed2ce22ddd2 100644 (file)
@@ -766,6 +766,12 @@ Unit* unit_free(Unit *u) {
 
         bpf_program_unref(u->bpf_device_control_installed);
 
+#if BPF_FRAMEWORK
+        bpf_link_free(u->restrict_ifaces_ingress_bpf_link);
+        bpf_link_free(u->restrict_ifaces_egress_bpf_link);
+#endif
+        fdset_free(u->initial_restric_ifaces_link_fds);
+
         condition_free_list(u->conditions);
         condition_free_list(u->asserts);
 
index 48074d8ca5b680b6723d79c043a4c97bc068b459..b3e9c2106fd3498e97eed8bab28cc41b9e9743d0 100644 (file)
@@ -335,6 +335,12 @@ typedef struct Unit {
         struct bpf_link *ipv6_socket_bind_link;
 #endif
 
+        FDSet *initial_restric_ifaces_link_fds;
+#if BPF_FRAMEWORK
+        struct bpf_link *restrict_ifaces_ingress_bpf_link;
+        struct bpf_link *restrict_ifaces_egress_bpf_link;
+#endif
+
         /* Low-priority event source which is used to remove watched PIDs that have gone away, and subscribe to any new
          * ones which might have appeared. */
         sd_event_source *rewatch_pids_event_source;