blob: 0be250af5cca8250639e4f7cc2b6a653cc60f90e [file] [log] [blame]
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <errno.h>
#include <fcntl.h>
#include <linux/types.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include "alloc-util.h"
#include "bpf-lsm.h"
#include "cgroup-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "filesystems.h"
#include "log.h"
#include "manager.h"
#include "mkdir.h"
#include "nulstr-util.h"
#include "stat-util.h"
#include "strv.h"
#if BPF_FRAMEWORK
/* libbpf, clang and llc compile time dependencies are satisfied */
#include "bpf-dlopen.h"
#include "bpf-link.h"
#include "bpf-util.h"
#include "bpf/restrict_fs/restrict-fs-skel.h"
#define CGROUP_HASH_SIZE_MAX 2048
static struct restrict_fs_bpf *restrict_fs_bpf_free(struct restrict_fs_bpf *obj) {
/* restrict_fs_bpf__destroy handles object == NULL case */
(void) restrict_fs_bpf__destroy(obj);
return NULL;
}
DEFINE_TRIVIAL_CLEANUP_FUNC(struct restrict_fs_bpf *, restrict_fs_bpf_free);
static bool bpf_can_link_lsm_program(struct bpf_program *prog) {
_cleanup_(bpf_link_freep) struct bpf_link *link = NULL;
assert(prog);
link = sym_bpf_program__attach_lsm(prog);
/* If bpf_program__attach_lsm fails the resulting value stores libbpf error code instead of memory
* pointer. That is the case when the helper is called on architectures where BPF trampoline (hence
* BPF_LSM_MAC attach type) is not supported. */
return sym_libbpf_get_error(link) == 0;
}
static int prepare_restrict_fs_bpf(struct restrict_fs_bpf **ret_obj) {
_cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL;
_cleanup_close_ int inner_map_fd = -EBADF;
int r;
assert(ret_obj);
obj = restrict_fs_bpf__open();
if (!obj)
return log_error_errno(errno, "bpf-lsm: Failed to open BPF object: %m");
/* TODO Maybe choose a number based on runtime information? */
r = sym_bpf_map__set_max_entries(obj->maps.cgroup_hash, CGROUP_HASH_SIZE_MAX);
assert(r <= 0);
if (r < 0)
return log_error_errno(r, "bpf-lsm: Failed to resize BPF map '%s': %m",
sym_bpf_map__name(obj->maps.cgroup_hash));
/* Dummy map to satisfy the verifier */
inner_map_fd = compat_bpf_map_create(BPF_MAP_TYPE_HASH, NULL, sizeof(uint32_t), sizeof(uint32_t), 128U, NULL);
if (inner_map_fd < 0)
return log_error_errno(errno, "bpf-lsm: Failed to create BPF map: %m");
r = sym_bpf_map__set_inner_map_fd(obj->maps.cgroup_hash, inner_map_fd);
assert(r <= 0);
if (r < 0)
return log_error_errno(r, "bpf-lsm: Failed to set inner map fd: %m");
r = restrict_fs_bpf__load(obj);
assert(r <= 0);
if (r < 0)
return log_error_errno(r, "bpf-lsm: Failed to load BPF object: %m");
*ret_obj = TAKE_PTR(obj);
return 0;
}
static int mac_bpf_use(void) {
_cleanup_free_ char *lsm_list = NULL;
static int cached_use = -1;
int r;
if (cached_use >= 0)
return cached_use;
cached_use = 0;
r = read_one_line_file("/sys/kernel/security/lsm", &lsm_list);
if (r < 0) {
if (r != -ENOENT)
log_notice_errno(r, "bpf-lsm: Failed to read /sys/kernel/security/lsm, assuming bpf is unavailable: %m");
return 0;
}
for (const char *p = lsm_list;;) {
_cleanup_free_ char *word = NULL;
r = extract_first_word(&p, &word, ",", 0);
if (r == 0)
return 0;
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_notice_errno(r, "bpf-lsm: Failed to parse /sys/kernel/security/lsm, assuming bpf is unavailable: %m");
return 0;
}
if (streq(word, "bpf"))
return cached_use = 1;
}
}
bool lsm_bpf_supported(bool initialize) {
_cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL;
static int supported = -1;
int r;
if (supported >= 0)
return supported;
if (!initialize)
return false;
if (!cgroup_bpf_supported())
return (supported = false);
r = mac_bpf_use();
if (r < 0) {
log_warning_errno(r, "bpf-lsm: Can't determine whether the BPF LSM module is used: %m");
return (supported = false);
}
if (r == 0) {
log_info_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"bpf-lsm: BPF LSM hook not enabled in the kernel, BPF LSM not supported");
return (supported = false);
}
r = prepare_restrict_fs_bpf(&obj);
if (r < 0)
return (supported = false);
if (!bpf_can_link_lsm_program(obj->progs.restrict_filesystems)) {
log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"bpf-lsm: Failed to link program; assuming BPF LSM is not available");
return (supported = false);
}
return (supported = true);
}
int lsm_bpf_setup(Manager *m) {
_cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL;
_cleanup_(bpf_link_freep) struct bpf_link *link = NULL;
int r;
assert(m);
r = prepare_restrict_fs_bpf(&obj);
if (r < 0)
return r;
link = sym_bpf_program__attach_lsm(obj->progs.restrict_filesystems);
r = sym_libbpf_get_error(link);
if (r != 0)
return log_error_errno(r, "bpf-lsm: Failed to link '%s' LSM BPF program: %m",
sym_bpf_program__name(obj->progs.restrict_filesystems));
log_info("bpf-lsm: LSM BPF program attached");
obj->links.restrict_filesystems = TAKE_PTR(link);
m->restrict_fs = TAKE_PTR(obj);
return 0;
}
int lsm_bpf_unit_restrict_filesystems(Unit *u, const Set *filesystems, bool allow_list) {
uint32_t dummy_value = 1, zero = 0;
const char *fs;
const statfs_f_type_t *magic;
int r;
assert(filesystems);
assert(u);
if (!u->manager->restrict_fs)
return log_unit_error_errno(u, SYNTHETIC_ERRNO(EINVAL),
"bpf-lsm: BPF LSM object is not installed, has setup failed?");
int inner_map_fd = compat_bpf_map_create(
BPF_MAP_TYPE_HASH,
NULL,
sizeof(uint32_t),
sizeof(uint32_t),
128U, /* Should be enough for all filesystem types */
NULL);
if (inner_map_fd < 0)
return log_unit_error_errno(u, errno, "bpf-lsm: Failed to create inner BPF map: %m");
int outer_map_fd = sym_bpf_map__fd(u->manager->restrict_fs->maps.cgroup_hash);
if (outer_map_fd < 0)
return log_unit_error_errno(u, errno, "bpf-lsm: Failed to get BPF map fd: %m");
if (sym_bpf_map_update_elem(outer_map_fd, &u->cgroup_id, &inner_map_fd, BPF_ANY) != 0)
return log_unit_error_errno(u, errno, "bpf-lsm: Error populating BPF map: %m");
uint32_t allow = allow_list;
/* Use key 0 to store whether this is an allow list or a deny list */
if (sym_bpf_map_update_elem(inner_map_fd, &zero, &allow, BPF_ANY) != 0)
return log_unit_error_errno(u, errno, "bpf-lsm: Error initializing map: %m");
SET_FOREACH(fs, filesystems) {
r = fs_type_from_string(fs, &magic);
if (r < 0) {
log_unit_warning(u, "bpf-lsm: Invalid filesystem name '%s', ignoring.", fs);
continue;
}
log_unit_debug(u, "bpf-lsm: Restricting filesystem access to '%s'", fs);
for (int i = 0; i < FILESYSTEM_MAGIC_MAX; i++) {
if (magic[i] == 0)
break;
if (sym_bpf_map_update_elem(inner_map_fd, &magic[i], &dummy_value, BPF_ANY) != 0) {
r = log_unit_error_errno(u, errno, "bpf-lsm: Failed to update BPF map: %m");
if (sym_bpf_map_delete_elem(outer_map_fd, &u->cgroup_id) != 0)
log_unit_debug_errno(u, errno, "bpf-lsm: Failed to delete cgroup entry from BPF map: %m");
return r;
}
}
}
return 0;
}
int lsm_bpf_cleanup(const Unit *u) {
assert(u);
assert(u->manager);
/* If we never successfully detected support, there is nothing to clean up. */
if (!lsm_bpf_supported(/* initialize = */ false))
return 0;
if (!u->manager->restrict_fs)
return 0;
int fd = sym_bpf_map__fd(u->manager->restrict_fs->maps.cgroup_hash);
if (fd < 0)
return log_unit_error_errno(u, errno, "bpf-lsm: Failed to get BPF map fd: %m");
if (sym_bpf_map_delete_elem(fd, &u->cgroup_id) != 0)
return log_unit_debug_errno(u, errno, "bpf-lsm: Failed to delete cgroup entry from LSM BPF map: %m");
return 0;
}
int lsm_bpf_map_restrict_fs_fd(Unit *unit) {
assert(unit);
assert(unit->manager);
if (!unit->manager->restrict_fs)
return -ENOMEDIUM;
return sym_bpf_map__fd(unit->manager->restrict_fs->maps.cgroup_hash);
}
void lsm_bpf_destroy(struct restrict_fs_bpf *prog) {
restrict_fs_bpf__destroy(prog);
}
#else /* ! BPF_FRAMEWORK */
bool lsm_bpf_supported(bool initialize) {
return false;
}
int lsm_bpf_setup(Manager *m) {
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "bpf-lsm: Failed to set up LSM BPF: %m");
}
int lsm_bpf_unit_restrict_filesystems(Unit *u, const Set *filesystems, const bool allow_list) {
return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EOPNOTSUPP), "bpf-lsm: Failed to restrict filesystems using LSM BPF: %m");
}
int lsm_bpf_cleanup(const Unit *u) {
return 0;
}
int lsm_bpf_map_restrict_fs_fd(Unit *unit) {
return -ENOMEDIUM;
}
void lsm_bpf_destroy(struct restrict_fs_bpf *prog) {
return;
}
#endif
int lsm_bpf_parse_filesystem(
const char *name,
Set **filesystems,
FilesystemParseFlags flags,
const char *unit,
const char *filename,
unsigned line) {
int r;
assert(name);
assert(filesystems);
if (name[0] == '@') {
const FilesystemSet *set;
set = filesystem_set_find(name);
if (!set) {
log_syntax(unit, flags & FILESYSTEM_PARSE_LOG ? LOG_WARNING : LOG_DEBUG, filename, line, 0,
"bpf-lsm: Unknown filesystem group, ignoring: %s", name);
return 0;
}
NULSTR_FOREACH(i, set->value) {
/* Call ourselves again, for the group to parse. Note that we downgrade logging here
* (i.e. take away the FILESYSTEM_PARSE_LOG flag) since any issues in the group table
* are our own problem, not a problem in user configuration data and we shouldn't
* pretend otherwise by complaining about them. */
r = lsm_bpf_parse_filesystem(i, filesystems, flags &~ FILESYSTEM_PARSE_LOG, unit, filename, line);
if (r < 0)
return r;
}
} else {
/* If we previously wanted to forbid access to a filesystem and now
* we want to allow it, then remove it from the list. */
if (!(flags & FILESYSTEM_PARSE_INVERT) == !!(flags & FILESYSTEM_PARSE_ALLOW_LIST)) {
r = set_put_strdup(filesystems, name);
if (r == -ENOMEM)
return flags & FILESYSTEM_PARSE_LOG ? log_oom() : -ENOMEM;
if (r < 0 && r != -EEXIST) /* When already in set, ignore */
return r;
} else
free(set_remove(*filesystems, name));
}
return 0;
}