mirror of
https://github.com/systemd/systemd-stable.git
synced 2025-02-03 13:47:04 +03:00
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 <mauricio@kinvolk.io>
This commit is contained in:
parent
dc83b840d3
commit
6f50d4f7d6
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
205
src/core/restrict-ifaces.c
Normal file
205
src/core/restrict-ifaces.c
Normal file
@ -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
|
16
src/core/restrict-ifaces.h
Normal file
16
src/core/restrict-ifaces.h
Normal file
@ -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);
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user