From: Mauricio Vásquez Date: Thu, 21 Jan 2021 16:08:19 +0000 (-0500) Subject: core: implement RestrictNetworkInterfaces= X-Git-Tag: v250-rc1~800^2~8 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=6f50d4f7d6406648232c8cc121ec3f9ea969de1c;p=thirdparty%2Fsystemd.git core: implement RestrictNetworkInterfaces= 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 --- diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index 5bf7c03c00e..95891f68aa9 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -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); diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h index b2dd9b56a54..90ccd2c0326 100644 --- a/src/basic/cgroup-util.h +++ b/src/basic/cgroup-util.h @@ -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; diff --git a/src/core/cgroup.c b/src/core/cgroup.c index bbeb5b2f50b..5b612ee22b0 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -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; } diff --git a/src/core/cgroup.h b/src/core/cgroup.h index 3f8cad899de..99bf7e22d8f 100644 --- a/src/core/cgroup.h +++ b/src/core/cgroup.h @@ -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; diff --git a/src/core/meson.build b/src/core/meson.build index df773b6675f..489a4d869fd 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -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 index 00000000000..709f74929b2 --- /dev/null +++ b/src/core/restrict-ifaces.c @@ -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 index 00000000000..be9e5224c39 --- /dev/null +++ b/src/core/restrict-ifaces.h @@ -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); diff --git a/src/core/unit-serialize.c b/src/core/unit-serialize.c index ab0f03a0ca5..9e1664ff53a 100644 --- a/src/core/unit-serialize.c +++ b/src/core/unit-serialize.c @@ -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; diff --git a/src/core/unit.c b/src/core/unit.c index 2f7f022ef35..7d72dfa864b 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -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); diff --git a/src/core/unit.h b/src/core/unit.h index 48074d8ca5b..b3e9c2106fd 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -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;