diff --git a/README b/README
index 6a151a49e9..3811abfe06 100644
--- a/README
+++ b/README
@@ -35,7 +35,7 @@ REQUIREMENTS:
Linux kernel >= 4.17 for cgroup-bpf socket address hooks
Linux kernel >= 5.3 for bounded-loops in BPF program
Linux kernel >= 5.4 for signed Verity images support
- Linux kernel >= 5.7 for BPF links
+ Linux kernel >= 5.7 for BPF links and the BPF LSM hook
Kernel Config Options:
CONFIG_DEVTMPFS
@@ -119,6 +119,13 @@ REQUIREMENTS:
Required for signed Verity images support:
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG
+ Required for RestrictFileSystems= in service units:
+ CONFIG_BPF
+ CONFIG_BPF_SYSCALL
+ CONFIG_BPF_LSM
+ CONFIG_DEBUG_INFO_BTF
+ CONFIG_LSM="...,bpf" or kernel booted with lsm="...,bpf".
+
We recommend to turn off Real-Time group scheduling in the
kernel when using systemd. RT group scheduling effectively
makes RT scheduling unavailable for most userspace, since it
diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
index bede222670..0e1b5da333 100644
--- a/man/org.freedesktop.systemd1.xml
+++ b/man/org.freedesktop.systemd1.xml
@@ -2837,6 +2837,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly t RestrictNamespaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly (bas) RestrictFileSystems = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(ssbt) BindPaths = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(ssbt) BindReadOnlyPaths = [...];
@@ -3362,6 +3364,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
+
+
@@ -3966,6 +3970,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
+
+
@@ -4677,6 +4683,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly t RestrictNamespaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly (bas) RestrictFileSystems = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(ssbt) BindPaths = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(ssbt) BindReadOnlyPaths = [...];
@@ -5228,6 +5236,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
+
+
@@ -5828,6 +5838,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
+
+
@@ -6436,6 +6448,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly t RestrictNamespaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly (bas) RestrictFileSystems = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(ssbt) BindPaths = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(ssbt) BindReadOnlyPaths = [...];
@@ -6915,6 +6929,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
+
+
@@ -7433,6 +7449,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
+
+
@@ -8162,6 +8180,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly t RestrictNamespaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly (bas) RestrictFileSystems = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(ssbt) BindPaths = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(ssbt) BindReadOnlyPaths = [...];
@@ -8627,6 +8647,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
+
+
@@ -9131,6 +9153,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
+
+
diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml
index fb872c4990..87e1536986 100644
--- a/man/systemd-analyze.xml
+++ b/man/systemd-analyze.xml
@@ -88,6 +88,12 @@
syscall-filter
SET…
+
+ systemd-analyze
+ OPTIONS
+ filesystems
+ SET…
+
systemd-analyze
OPTIONS
@@ -410,6 +416,14 @@ Conditions succeeded.
SET must include the @ prefix.
+
+ systemd-analyze filesystems SET...
+
+ This command will list filesystems in the specified filesystem set
+ SET, or all known sets if no sets are specified. Argument
+ SET must include the @ prefix.
+
+
systemd-analyze calendar EXPRESSION...
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index 3cef36d3c3..2b7129eb40 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -1842,6 +1842,100 @@ BindReadOnlyPaths=/var/lib/systemd
logging.
+
+ RestrictFileSystems=
+
+ Restricts the set of filesystems processes of this unit can open files on. Takes a space-separated
+ list of filesystem names. Any filesystem listed is made accessible to the unit's processes, access to filesystem
+ types not listed is prohibited (allow-listing). If the first character of the list is ~, the
+ effect is inverted: access to the filesystems listed is prohibited (deny-listing). If the empty string is assigned,
+ access to filesystems is not restricted.
+
+ If you specify both types of this option (i.e. allow-listing and deny-listing), the first encountered will take
+ precedence and will dictate the default action (allow access to the filesystem or deny it). Then the next occurrences
+ of this option will add or delete the listed filesystems from the set of the restricted filesystems, depending on its
+ type and the default action.
+
+ Example: if a unit has the following,
+ RestrictFileSystems=ext4 tmpfs
+RestrictFileSystems=ext2 ext4
+ then access to ext4, tmpfs, and ext2 is allowed
+ and access to other filesystems is denied.
+
+ Example: if a unit has the following,
+ RestrictFileSystems=ext4 tmpfs
+RestrictFileSystems=~ext4
+ then only access tmpfs is allowed.
+
+ Example: if a unit has the following,
+ RestrictFileSystems=~ext4 tmpfs
+RestrictFileSystems=ext4
+ then only access to tmpfs is denied.
+
+ As the number of possible filesystems is large, predefined sets of filesystems are provided. A set
+ starts with @ character, followed by name of the set.
+
+
+ Currently predefined filesystem sets
+
+
+
+
+
+
+ Set
+ Description
+
+
+
+
+ @basic-api
+ Basic filesystem API.
+
+
+ @auxiliary-api
+ Auxiliary filesystem API.
+
+
+ @common-block
+ Common block device filesystems.
+
+
+ @historical-block
+ Historical block device filesystems.
+
+
+ @network
+ Well-known network filesystems.
+
+
+ @privileged-api
+ Privileged filesystem API.
+
+
+ @temporary
+ Temporary filesystems: tmpfs, ramfs.
+
+
+ @known
+ All known filesystems defined by the kernel. This list is defined statically in systemd based on a kernel
+ version that was available when this systemd version was released. It will become progressively more
+ out-of-date as the kernel is updated.
+
+
+
+
+
+ Use
+ systemd-analyze1's
+ filesystems command to retrieve a list of filesystems defined on the local
+ system.
+
+ Note that this setting might not be supported on some systems (for example if the LSM eBPF hook is
+ not enabled in the underlying kernel or if not using the unified control group hierarchy). In that case this setting
+ has no effect.
+
+
RestrictNamespaces=
@@ -3761,6 +3855,11 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
EXIT_CREDENTIALS
Failed to set up unit's credentials. See LoadCredential= and SetCredential= above.
+
+ 245
+ EXIT_BPF
+ Failed to apply BPF restrictions. See RestrictFileSystems= above.
+
diff --git a/mkosi.default.d/arch/mkosi.arch b/mkosi.default.d/arch/mkosi.arch
index f584d2d126..19bc45a8d7 100644
--- a/mkosi.default.d/arch/mkosi.arch
+++ b/mkosi.default.d/arch/mkosi.arch
@@ -12,6 +12,7 @@ Distribution=arch
BuildPackages=
acl
bzip2
+ clang
cryptsetup
curl
dbus
@@ -26,6 +27,7 @@ BuildPackages=
inetutils
iptables
kmod
+ libbpf
libcap
libgcrypt
libidn2
@@ -34,6 +36,7 @@ BuildPackages=
libutil-linux
libxkbcommon
libxslt
+ llvm
lz4
meson
pam
@@ -48,6 +51,7 @@ BuildPackages=
Packages=
gdb
+ libbpf
libidn2
nano
qrencode
diff --git a/mkosi.default.d/debian/mkosi.debian b/mkosi.default.d/debian/mkosi.debian
index 9bd504bf57..2be71f3c7f 100644
--- a/mkosi.default.d/debian/mkosi.debian
+++ b/mkosi.default.d/debian/mkosi.debian
@@ -10,9 +10,11 @@ Release=unstable
[Packages]
BuildPackages=
acl
+ clang
docbook-xml
docbook-xsl
gcc
+ g++
gettext
git
gnu-efi
@@ -20,6 +22,7 @@ BuildPackages=
libacl1-dev
libaudit-dev
libblkid-dev
+ libbpf-dev
libbz2-dev
libcap-dev
libcryptsetup-dev
@@ -46,6 +49,7 @@ BuildPackages=
libtss2-dev
libxkbcommon-dev
libzstd-dev
+ llvm
meson
pkg-config
python3
@@ -59,6 +63,7 @@ BuildPackages=
Packages=
gdb
+ libbpf0
libfdisk1
libfido2-1
libidn2-0
diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c
index 1410880be1..05b4afda54 100644
--- a/src/analyze/analyze.c
+++ b/src/analyze/analyze.c
@@ -28,6 +28,7 @@
#include "exit-status.h"
#include "fd-util.h"
#include "fileio.h"
+#include "filesystems.h"
#include "format-table.h"
#include "glob-util.h"
#include "hashmap.h"
@@ -46,6 +47,7 @@
#endif
#include "sort-util.h"
#include "special.h"
+#include "stat-util.h"
#include "string-table.h"
#include "strv.h"
#include "strxcpyx.h"
@@ -1622,6 +1624,9 @@ static int load_kernel_syscalls(Set **ret) {
static void syscall_set_remove(Set *s, const SyscallFilterSet *set) {
const char *syscall;
+ if (!set)
+ return;
+
NULSTR_FOREACH(syscall, set->value) {
if (syscall[0] == '@')
continue;
@@ -1743,6 +1748,172 @@ static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
}
#endif
+static int load_available_kernel_filesystems(Set **ret) {
+ _cleanup_set_free_ Set *filesystems = NULL;
+ int r;
+ char *t;
+
+ assert(ret);
+
+ /* Let's read the available filesystems */
+
+ r = read_virtual_file("/proc/filesystems", SIZE_MAX, &t, NULL);
+ if (r < 0)
+ return r;
+
+ for (int i = 0;;) {
+ _cleanup_free_ char *line = NULL;
+ const char *p;
+
+ r = string_extract_line(t, i++, &line);
+ if (r < 0)
+ return log_oom();
+ if (r == 0)
+ break;
+
+ if (!line)
+ line = t;
+
+ p = strchr(line, '\t');
+ if (!p)
+ continue;
+
+ p += strspn(p, WHITESPACE);
+
+ r = set_put_strdup(&filesystems, p);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add filesystem to list: %m");
+ }
+
+ *ret = TAKE_PTR(filesystems);
+ return 0;
+}
+
+static void filesystem_set_remove(Set *s, const FilesystemSet *set) {
+ const char *filesystem;
+
+ NULSTR_FOREACH(filesystem, set->value) {
+ if (filesystem[0] == '@')
+ continue;
+
+ free(set_remove(s, filesystem));
+ }
+}
+
+static void dump_filesystem(const FilesystemSet *set) {
+ const char *filesystem;
+
+ if (!set)
+ return;
+
+ printf("%s%s%s\n"
+ " # %s\n",
+ ansi_highlight(),
+ set->name,
+ ansi_normal(),
+ set->help);
+
+ NULSTR_FOREACH(filesystem, set->value)
+ printf(" %s%s%s\n", filesystem[0] == '@' ? ansi_underline() : "", filesystem, ansi_normal());
+}
+
+static int dump_filesystems(int argc, char *argv[], void *userdata) {
+ bool first = true;
+
+#if ! HAVE_LIBBPF
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not compiled with libbpf support, sorry.");
+#endif
+
+ (void) pager_open(arg_pager_flags);
+
+ if (strv_isempty(strv_skip(argv, 1))) {
+ _cleanup_set_free_ Set *kernel = NULL, *known = NULL;
+ const char *fs;
+ int k;
+
+ NULSTR_FOREACH(fs, filesystem_sets[FILESYSTEM_SET_KNOWN].value)
+ if (set_put_strdup(&known, fs) < 0)
+ return log_oom();
+
+ k = load_available_kernel_filesystems(&kernel);
+
+ for (FilesystemGroups i = 0; i < _FILESYSTEM_SET_MAX; i++) {
+ const FilesystemSet *set = filesystem_sets + i;
+ if (!first)
+ puts("");
+
+ dump_filesystem(set);
+ filesystem_set_remove(kernel, set);
+ if (i != FILESYSTEM_SET_KNOWN)
+ filesystem_set_remove(known, set);
+ first = false;
+ }
+
+ if (!set_isempty(known)) {
+ _cleanup_free_ char **l = NULL;
+ char **filesystem;
+
+ printf("\n"
+ "# %sUngrouped filesystems%s (known but not included in any of the groups except @known):\n",
+ ansi_highlight(), ansi_normal());
+
+ l = set_get_strv(known);
+ if (!l)
+ return log_oom();
+
+ strv_sort(l);
+
+ STRV_FOREACH(filesystem, l)
+ printf("# %s\n", *filesystem);
+ }
+
+ if (k < 0) {
+ fputc('\n', stdout);
+ fflush(stdout);
+ log_notice_errno(k, "# Not showing unlisted filesystems, couldn't retrieve kernel filesystem list: %m");
+ } else if (!set_isempty(kernel)) {
+ _cleanup_free_ char **l = NULL;
+ char **filesystem;
+
+ printf("\n"
+ "# %sUnlisted filesystems%s (available to the local kernel, but not included in any of the groups listed above):\n",
+ ansi_highlight(), ansi_normal());
+
+ l = set_get_strv(kernel);
+ if (!l)
+ return log_oom();
+
+ strv_sort(l);
+
+ STRV_FOREACH(filesystem, l)
+ printf("# %s\n", *filesystem);
+ }
+ } else {
+ char **name;
+
+ STRV_FOREACH(name, strv_skip(argv, 1)) {
+ const FilesystemSet *set;
+
+ if (!first)
+ puts("");
+
+ set = filesystem_set_find(*name);
+ if (!set) {
+ /* make sure the error appears below normal output */
+ fflush(stdout);
+
+ return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
+ "Filesystem set \"%s\" not found.", *name);
+ }
+
+ dump_filesystem(set);
+ first = false;
+ }
+ }
+
+ return 0;
+}
+
static void parsing_hint(const char *p, bool calendar, bool timestamp, bool timespan) {
if (calendar && calendar_spec_from_string(p, NULL) >= 0)
log_notice("Hint: this expression is a valid calendar specification. "
@@ -2169,6 +2340,7 @@ static int help(int argc, char *argv[], void *userdata) {
" capability [CAP...] List capability definitions\n"
" syscall-filter [NAME...] Print list of syscalls in seccomp\n"
" filter\n"
+ " filesystems [NAME...] Print list of filesystems\n"
" condition CONDITION... Evaluate conditions and asserts\n"
" verify FILE... Check unit files for correctness\n"
" calendar SPEC... Validate repetitive calendar time\n"
@@ -2503,6 +2675,7 @@ static int run(int argc, char *argv[]) {
{ "exit-status", VERB_ANY, VERB_ANY, 0, dump_exit_status },
{ "syscall-filter", VERB_ANY, VERB_ANY, 0, dump_syscall_filters },
{ "capability", VERB_ANY, VERB_ANY, 0, dump_capabilities },
+ { "filesystems", VERB_ANY, VERB_ANY, 0, dump_filesystems },
{ "condition", VERB_ANY, VERB_ANY, 0, do_condition },
{ "verify", 2, VERB_ANY, 0, do_verify },
{ "calendar", 2, VERB_ANY, 0, test_calendar },
diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c
index 9ba9f375b0..221157d57a 100644
--- a/src/basic/cgroup-util.c
+++ b/src/basic/cgroup-util.c
@@ -1367,6 +1367,29 @@ int cg_pid_get_machine_name(pid_t pid, char **machine) {
return cg_path_get_machine_name(cgroup, machine);
}
+int cg_path_get_cgroupid(const char *path, uint64_t *ret) {
+ int mnt_id = -1;
+
+ assert(path);
+ assert(ret);
+
+ union {
+ struct file_handle f_handle;
+ uint8_t space[offsetof(struct file_handle, f_handle) + sizeof(uint64_t)];
+ } buf = {
+ .f_handle.handle_bytes = sizeof(uint64_t),
+ };
+
+ /* This is cgroupfs so we know the size of the handle, thus no need to loop around like
+ * name_to_handle_at_loop() does in mountpoint-util.c */
+ if (name_to_handle_at(AT_FDCWD, path, &buf.f_handle, &mnt_id, 0) < 0)
+ return -errno;
+
+ *ret = *(uint64_t *) buf.f_handle.f_handle;
+
+ return 0;
+}
+
int cg_path_get_session(const char *path, char **session) {
_cleanup_free_ char *unit = NULL;
char *start, *end;
diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h
index 90ccd2c032..43801ee0f4 100644
--- a/src/basic/cgroup-util.h
+++ b/src/basic/cgroup-util.h
@@ -33,6 +33,9 @@ typedef enum CGroupController {
CGROUP_CONTROLLER_BPF_FOREIGN,
CGROUP_CONTROLLER_BPF_SOCKET_BIND,
CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES,
+ /* The BPF hook implementing RestrictFileSystems= is not defined here.
+ * It's applied as late as possible in exec_child() so we don't block
+ * our own unit setup code. */
_CGROUP_CONTROLLER_MAX,
_CGROUP_CONTROLLER_INVALID = -EINVAL,
@@ -236,6 +239,7 @@ int cg_is_empty_recursive(const char *controller, const char *path);
int cg_get_root_path(char **path);
+int cg_path_get_cgroupid(const char *path, uint64_t *ret);
int cg_path_get_session(const char *path, char **session);
int cg_path_get_owner_uid(const char *path, uid_t *uid);
int cg_path_get_unit(const char *path, char **unit);
diff --git a/src/basic/check-filesystems.sh b/src/basic/check-filesystems.sh
new file mode 100755
index 0000000000..026574652e
--- /dev/null
+++ b/src/basic/check-filesystems.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eu
+set -o pipefail
+
+cpp="$1"
+filesystems_gperf="$2"
+shift 2
+
+includes=""
+for i in "$@"; do
+ includes="$includes -include $i"
+done
+
+error=false
+
+# shellcheck disable=SC2086
+for fs in $($cpp -dM $includes - /dev/null; then
+ # STACK_END_MAGIC doesn't refer to a filesystem
+ # mtd_inode was removed in 2015
+ # futexfs was removed in 2018
+ if [[ "$fs" =~ ^(STACK_END_MAGIC|MTD_INODE_FS_MAGIC|FUTEXFS_SUPER_MAGIC)$ ]]; then
+ continue
+ fi
+ echo "Filesystem found in kernel header but not in $(basename "$filesystems_gperf"): $fs";
+ error=true
+ fi
+done
+
+if $error; then
+ exit 1
+fi
diff --git a/src/basic/filesystems-gperf.gperf b/src/basic/filesystems-gperf.gperf
new file mode 100644
index 0000000000..f31c9de192
--- /dev/null
+++ b/src/basic/filesystems-gperf.gperf
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+%{
+#include
+
+#include "filesystems.h"
+#include "missing_magic.h"
+#include "stat-util.h"
+
+struct FilesystemMagic {
+ const char *name;
+ statfs_f_type_t magic[FILESYSTEM_MAGIC_MAX];
+};
+%}
+struct FilesystemMagic;
+%language=ANSI-C
+%define hash-function-name filesystems_gperf_hash
+%define lookup-function-name filesystems_gperf_lookup
+%define slot-name name
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+apparmorfs, {AAFS_MAGIC}
+adfs, {ADFS_SUPER_MAGIC}
+affs, {AFFS_SUPER_MAGIC}
+afs, {AFS_FS_MAGIC, AFS_SUPER_MAGIC}
+anon_inodefs, {ANON_INODE_FS_MAGIC}
+autofs, {AUTOFS_SUPER_MAGIC}
+balloon-kvm, {BALLOON_KVM_MAGIC}
+bdev, {BDEVFS_MAGIC}
+binder, {BINDERFS_SUPER_MAGIC}
+binfmt_misc, {BINFMTFS_MAGIC}
+bpf, {BPF_FS_MAGIC}
+btrfs, {BTRFS_SUPER_MAGIC}
+btrfs_test_fs, {BTRFS_TEST_MAGIC}
+ceph, {CEPH_SUPER_MAGIC}
+cgroup2, {CGROUP2_SUPER_MAGIC}
+cgroup, {CGROUP_SUPER_MAGIC}
+cifs, {CIFS_MAGIC_NUMBER}
+coda, {CODA_SUPER_MAGIC}
+configfs, {CONFIGFS_MAGIC}
+cramfs, {CRAMFS_MAGIC}
+dax, {DAXFS_MAGIC}
+debugfs, {DEBUGFS_MAGIC}
+devmem, {DEVMEM_MAGIC}
+devpts, {DEVPTS_SUPER_MAGIC}
+dmabuf, {DMA_BUF_MAGIC}
+ecryptfs, {ECRYPTFS_SUPER_MAGIC}
+efivarfs, {EFIVARFS_MAGIC}
+efs, {EFS_SUPER_MAGIC}
+erofs, {EROFS_SUPER_MAGIC_V1}
+ext2, {EXT2_SUPER_MAGIC}
+ext3, {EXT3_SUPER_MAGIC}
+ext4, {EXT4_SUPER_MAGIC}
+exfat, {EXFAT_SUPER_MAGIC}
+f2fs, {F2FS_SUPER_MAGIC}
+fuseblk, {FUSE_SUPER_MAGIC}
+fuse, {FUSE_SUPER_MAGIC}
+fusectl, {FUSE_CTL_SUPER_MAGIC}
+gfs, {GFS2_MAGIC}
+gfs2, {GFS2_MAGIC}
+hostfs, {HOSTFS_SUPER_MAGIC}
+hpfs, {HPFS_SUPER_MAGIC}
+hugetlbfs, {HUGETLBFS_MAGIC}
+iso9660, {ISOFS_SUPER_MAGIC}
+jffs2, {JFFS2_SUPER_MAGIC}
+minix, {MINIX_SUPER_MAGIC, MINIX_SUPER_MAGIC2, MINIX2_SUPER_MAGIC, MINIX2_SUPER_MAGIC2, MINIX3_SUPER_MAGIC}
+mqueue, {MQUEUE_MAGIC}
+msdos, {MSDOS_SUPER_MAGIC}
+ncp, {NCP_SUPER_MAGIC}
+ncpfs, {NCP_SUPER_MAGIC}
+nfs, {NFS_SUPER_MAGIC}
+nfs4, {NFS_SUPER_MAGIC}
+nilfs2, {NILFS_SUPER_MAGIC}
+nsfs, {NSFS_MAGIC}
+ocfs2, {OCFS2_SUPER_MAGIC}
+openpromfs, {OPENPROM_SUPER_MAGIC}
+orangefs, {ORANGEFS_DEVREQ_MAGIC}
+overlay, {OVERLAYFS_SUPER_MAGIC}
+pipefs, {PIPEFS_MAGIC}
+ppc-cmm, {PPC_CMM_MAGIC}
+proc, {PROC_SUPER_MAGIC}
+pstore, {PSTOREFS_MAGIC}
+pvfs2, {ORANGEFS_DEVREQ_MAGIC}
+qnx4, {QNX4_SUPER_MAGIC}
+qnx6, {QNX6_SUPER_MAGIC}
+ramfs, {RAMFS_MAGIC}
+resctrl, {RDTGROUP_SUPER_MAGIC}
+reiserfs, {REISERFS_SUPER_MAGIC}
+secretmem, {SECRETMEM_MAGIC}
+securityfs, {SECURITYFS_MAGIC}
+selinuxfs, {SELINUX_MAGIC}
+shiftfs, {SHIFTFS_MAGIC}
+smackfs, {SMACK_MAGIC}
+smb3, {SMB_SUPER_MAGIC}
+smbfs, {SMB_SUPER_MAGIC}
+sockfs, {SOCKFS_MAGIC}
+squashfs, {SQUASHFS_MAGIC}
+sysfs, {SYSFS_MAGIC}
+tmpfs, {TMPFS_MAGIC}
+tracefs, {TRACEFS_MAGIC}
+udf, {UDF_SUPER_MAGIC}
+usbdevfs, {USBDEVICE_SUPER_MAGIC}
+vboxsf, {VBOXSF_SUPER_MAGIC}
+vfat, {MSDOS_SUPER_MAGIC}
+v9fs, {V9FS_MAGIC}
+xenfs, {XENFS_SUPER_MAGIC}
+xfs, {XFS_SUPER_MAGIC}
+z3fold, {Z3FOLD_MAGIC}
+zonefs, {ZONEFS_MAGIC}
+zsmalloc, {ZSMALLOC_MAGIC}
diff --git a/src/basic/filesystems.c b/src/basic/filesystems.c
new file mode 100644
index 0000000000..9fccea2018
--- /dev/null
+++ b/src/basic/filesystems.c
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "filesystems-gperf.h"
+
+int fs_type_from_string(const char *name, const statfs_f_type_t **ret) {
+ const struct FilesystemMagic *fs_magic;
+
+ assert(name);
+ assert(ret);
+
+ fs_magic = filesystems_gperf_lookup(name, strlen(name));
+ if (!fs_magic)
+ return -EINVAL;
+
+ *ret = fs_magic->magic;
+
+ return 0;
+}
+
+int fs_in_group(const struct statfs *s, FilesystemGroups fs_group) {
+ const char *fs;
+ int r;
+
+ NULSTR_FOREACH(fs, filesystem_sets[fs_group].value) {
+ const statfs_f_type_t *magic;
+
+ r = fs_type_from_string(fs, &magic);
+ if (r == 0) {
+ for (size_t i = 0; i < FILESYSTEM_MAGIC_MAX; i++) {
+ if (magic[i] == 0)
+ break;
+
+ if (is_fs_type(s, magic[i]))
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+const FilesystemSet filesystem_sets[_FILESYSTEM_SET_MAX] = {
+ [FILESYSTEM_SET_BASIC_API] = {
+ .name = "@basic-api",
+ .help = "Basic filesystem API",
+ .value =
+ "cgroup\0"
+ "cgroup2\0"
+ "devpts\0"
+ "mqueue\0"
+ "proc\0"
+ "sysfs\0"
+ },
+ [FILESYSTEM_SET_AUXILIARY_API] = {
+ .name = "@auxiliary-api",
+ .help = "Auxiliary filesystem API",
+ .value =
+ "configfs\0"
+ "efivarfs\0"
+ "fusectl\0"
+ "hugetlbfs\0"
+ "securityfs\0"
+ },
+ [FILESYSTEM_SET_COMMON_BLOCK] = {
+ .name = "@common-block",
+ .help = "Common block device filesystems",
+ .value =
+ "btrfs\0"
+ "ext4\0"
+ "vfat\0"
+ "xfs\0"
+ },
+ [FILESYSTEM_SET_HISTORICAL_BLOCK] = {
+ .name = "@historical-block",
+ .help = "Historical block device filesystems",
+ .value =
+ "ext2\0"
+ "ext3\0"
+ "minix\0"
+ },
+ [FILESYSTEM_SET_NETWORK] = {
+ .name = "@network",
+ .help = "Well-known network filesystems",
+ .value =
+ "afs\0"
+ "cifs\0"
+ "gfs\0"
+ "gfs2\0"
+ "ncpfs\0"
+ "ncp\0"
+ "nfs\0"
+ "nfs4\0"
+ "ocfs2\0"
+ "pvfs2\0"
+ "smb3\0"
+ "smbfs\0"
+ },
+ [FILESYSTEM_SET_PRIVILEGED_API] = {
+ .name = "@privileged-api",
+ .help = "Privileged filesystem API",
+ .value =
+ "bpf\0"
+ "debugfs\0"
+ "pstore\0"
+ "tracefs\0"
+ },
+ [FILESYSTEM_SET_TEMPORARY] = {
+ .name = "@temporary",
+ .help = "Temporary filesystems",
+ .value =
+ "ramfs\0"
+ "tmpfs\0"
+ },
+ [FILESYSTEM_SET_KNOWN] = {
+ .name = "@known",
+ .help = "All known filesystems declared in the kernel",
+ .value =
+#include "filesystem-list.h"
+ },
+};
+
+const FilesystemSet *filesystem_set_find(const char *name) {
+ if (isempty(name) || name[0] != '@')
+ return NULL;
+
+ for (FilesystemGroups i = 0; i < _FILESYSTEM_SET_MAX; i++)
+ if (streq(filesystem_sets[i].name, name))
+ return filesystem_sets + i;
+
+ return NULL;
+}
diff --git a/src/basic/filesystems.h b/src/basic/filesystems.h
new file mode 100644
index 0000000000..a0964476af
--- /dev/null
+++ b/src/basic/filesystems.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "nulstr-util.h"
+#include "stat-util.h"
+#include "string-util.h"
+
+#define FILESYSTEM_MAGIC_MAX 10
+
+typedef enum FilesystemGroups {
+ /* Please leave BASIC_API first and KNOWN last, but sort the rest alphabetically */
+ FILESYSTEM_SET_BASIC_API,
+ FILESYSTEM_SET_AUXILIARY_API,
+ FILESYSTEM_SET_COMMON_BLOCK,
+ FILESYSTEM_SET_HISTORICAL_BLOCK,
+ FILESYSTEM_SET_NETWORK,
+ FILESYSTEM_SET_PRIVILEGED_API,
+ FILESYSTEM_SET_TEMPORARY,
+ FILESYSTEM_SET_KNOWN,
+ _FILESYSTEM_SET_MAX,
+ _FILESYSTEM_SET_INVALID = -EINVAL,
+} FilesystemGroups;
+
+typedef struct FilesystemSet {
+ const char *name;
+ const char *help;
+ const char *value;
+} FilesystemSet;
+
+extern const FilesystemSet filesystem_sets[];
+
+const FilesystemSet *filesystem_set_find(const char *name);
+
+int fs_type_from_string(const char *name, const statfs_f_type_t **ret);
+int fs_in_group(const struct statfs *s, enum FilesystemGroups fs_group);
+
+/* gperf prototypes */
+const struct FilesystemMagic* filesystems_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
diff --git a/src/basic/generate-filesystem-list.py b/src/basic/generate-filesystem-list.py
new file mode 100755
index 0000000000..8271b3fbeb
--- /dev/null
+++ b/src/basic/generate-filesystem-list.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+import sys
+
+keywords_section = False
+
+for line in open(sys.argv[1]):
+ if keywords_section:
+ print('"{}\\0"'.format(line.split(',')[0].strip()))
+ elif line.startswith('%%'):
+ keywords_section = True
diff --git a/src/basic/meson.build b/src/basic/meson.build
index ade45bdfc2..3bc5196fa0 100644
--- a/src/basic/meson.build
+++ b/src/basic/meson.build
@@ -54,6 +54,8 @@ basic_sources = files('''
fd-util.h
fileio.c
fileio.h
+ filesystems.c
+ filesystems.h
format-util.c
format-util.h
fs-util.c
@@ -373,6 +375,34 @@ run_target(
############################################################
+filesystem_includes = ['linux/magic.h',
+ 'linux/gfs2_ondisk.h']
+
+check_filesystems = find_program('check-filesystems.sh')
+r = run_command([check_filesystems, cpp, 'filesystems-gperf.gperf'] + filesystem_includes)
+if r.returncode() != 0
+ error('found unknown filesystem(s) defined in kernel headers:\n\n' + r.stdout())
+ r.stdout()
+endif
+
+filesystems_gperf_h = custom_target(
+ 'filesystems-gperf.h',
+ input : 'filesystems-gperf.gperf',
+ output : 'filesystems-gperf.h',
+ command : [gperf, '@INPUT@', '--output-file', '@OUTPUT@'])
+
+generate_filesystem_list = find_program('generate-filesystem-list.py')
+fname = 'filesystem-list.h'
+filesystem_list_h = custom_target(
+ fname,
+ input : 'filesystems-gperf.gperf',
+ output : fname,
+ command : [generate_filesystem_list,
+ '@INPUT@'],
+ capture : true)
+
+basic_sources += [filesystem_list_h, filesystems_gperf_h]
+
libbasic = static_library(
'basic',
basic_sources,
diff --git a/src/basic/missing_fs.h b/src/basic/missing_fs.h
index 79c1620e83..cc43d7c277 100644
--- a/src/basic/missing_fs.h
+++ b/src/basic/missing_fs.h
@@ -52,11 +52,6 @@
#define EXT4_IOC_RESIZE_FS _IOW('f', 16, __u64)
#endif
-/* Not exposed yet. Defined at fs/cifs/cifsglob.h */
-#ifndef CIFS_MAGIC_NUMBER
-#define CIFS_MAGIC_NUMBER 0xFF534D42
-#endif
-
/* linux/nsfs.h */
#ifndef NS_GET_NSTYPE /* d95fa3c76a66b6d76b1e109ea505c55e66360f3c (4.11) */
#define NS_GET_NSTYPE _IO(0xb7, 0x3)
diff --git a/src/basic/missing_magic.h b/src/basic/missing_magic.h
index e7466cb6bc..1f50e565a2 100644
--- a/src/basic/missing_magic.h
+++ b/src/basic/missing_magic.h
@@ -37,3 +37,138 @@
#ifndef XFS_SB_MAGIC
#define XFS_SB_MAGIC 0x58465342
#endif
+
+/* Not exposed yet. Defined at fs/cifs/cifsglob.h */
+#ifndef CIFS_MAGIC_NUMBER
+#define CIFS_MAGIC_NUMBER 0xFF534D42
+#endif
+
+/* 257f871993474e2bde6c497b54022c362cf398e1 (4.5) */
+#ifndef OVERLAYFS_SUPER_MAGIC
+#define OVERLAYFS_SUPER_MAGIC 0x794c7630
+#endif
+
+/* 2a28900be20640fcd1e548b1e3bad79e8221fcf9 (4.7) */
+#ifndef UDF_SUPER_MAGIC
+#define UDF_SUPER_MAGIC 0x15013346
+#endif
+
+/* b1123ea6d3b3da25af5c8a9d843bd07ab63213f4 (4.8)*/
+#ifndef BALLOON_KVM_MAGIC
+#define BALLOON_KVM_MAGIC 0x13661366
+#endif
+
+/* 48b4800a1c6af2cdda344ea4e2c843dcc1f6afc9 (4.8) */
+#ifndef ZSMALLOC_MAGIC
+#define ZSMALLOC_MAGIC 0x58295829
+#endif
+
+/* 3bc52c45bac26bf7ed1dc8d287ad1aeaed1250b6 (4.9) */
+#ifndef DAXFS_MAGIC
+#define DAXFS_MAGIC 0x64646178
+#endif
+
+/* 5ff193fbde20df5d80fec367cea3e7856c057320 (4.10) */
+#ifndef RDTGROUP_SUPER_MAGIC
+#define RDTGROUP_SUPER_MAGIC 0x7655821
+#endif
+
+/* a481f4d917835cad86701fc0d1e620c74bb5cd5f (4.13) */
+#ifndef AAFS_MAGIC
+#define AAFS_MAGIC 0x5a3c69f0
+#endif
+
+/* f044c8847bb61eff5e1e95b6f6bb950e7f4a73a4 (4.15) */
+#ifndef AFS_FS_MAGIC
+#define AFS_FS_MAGIC 0x6b414653
+#endif
+
+/* dddde68b8f06dd83486124b8d245e7bfb15c185d (4.20) */
+#ifndef XFS_SUPER_MAGIC
+#define XFS_SUPER_MAGIC 0x58465342
+#endif
+
+/* 3ad20fe393b31025bebfc2d76964561f65df48aa (5.0) */
+#ifndef BINDERFS_SUPER_MAGIC
+#define BINDERFS_SUPER_MAGIC 0x6c6f6f70
+#endif
+
+/* ed63bb1d1f8469586006a9ca63c42344401aa2ab (5.3) */
+#ifndef DMA_BUF_MAGIC
+#define DMA_BUF_MAGIC 0x444d4142
+#endif
+
+/* ea8157ab2ae5e914dd427e5cfab533b6da3819cd (5.3) */
+#ifndef Z3FOLD_MAGIC
+#define Z3FOLD_MAGIC 0x33
+#endif
+
+/* 47e4937a4a7ca4184fd282791dfee76c6799966a (5.4) */
+#ifndef EROFS_SUPER_MAGIC_V1
+#define EROFS_SUPER_MAGIC_V1 0xe0f5e1e2
+#endif
+
+/* fe030c9b85e6783bc52fe86449c0a4b8aa16c753 (5.5) */
+#ifndef PPC_CMM_MAGIC
+#define PPC_CMM_MAGIC 0xc7571590
+#endif
+
+/* 8dcc1a9d90c10fa4143e5c17821082e5e60e46a1 (5.6) */
+#ifndef ZONEFS_MAGIC
+#define ZONEFS_MAGIC 0x5a4f4653
+#endif
+
+/* 3234ac664a870e6ea69ae3a57d824cd7edbeacc5 (5.8) */
+#ifndef DEVMEM_MAGIC
+#define DEVMEM_MAGIC 0x454d444d
+#endif
+
+/* Not in mainline but included in Ubuntu */
+#ifndef SHIFTFS_MAGIC
+#define SHIFTFS_MAGIC 0x6a656a62
+#endif
+
+/* 1507f51255c9ff07d75909a84e7c0d7f3c4b2f49 (5.14) */
+#ifndef SECRETMEM_MAGIC
+#define SECRETMEM_MAGIC 0x5345434d
+#endif
+
+/* Not exposed yet. Defined at fs/fuse/inode.c */
+#ifndef FUSE_SUPER_MAGIC
+#define FUSE_SUPER_MAGIC 0x65735546
+#endif
+
+/* Not exposed yet. Defined at fs/fuse/control.c */
+#ifndef FUSE_CTL_SUPER_MAGIC
+#define FUSE_CTL_SUPER_MAGIC 0x65735543
+#endif
+
+/* Not exposed yet. Defined at fs/ceph/super.h */
+#ifndef CEPH_SUPER_MAGIC
+#define CEPH_SUPER_MAGIC 0x00c36400
+#endif
+
+/* Not exposed yet. Defined at fs/orangefs/orangefs-kernel.h */
+#ifndef ORANGEFS_DEVREQ_MAGIC
+#define ORANGEFS_DEVREQ_MAGIC 0x20030529
+#endif
+
+/* linux/gfs2_ondisk.h */
+#ifndef GFS2_MAGIC
+#define GFS2_MAGIC 0x01161970
+#endif
+
+/* Not exposed yet. Defined at fs/configfs/mount.c */
+#ifndef CONFIGFS_MAGIC
+#define CONFIGFS_MAGIC 0x62656570
+#endif
+
+/* Not exposed yet. Defined at fs/vboxsf/super.c */
+#ifndef VBOXSF_SUPER_MAGIC
+#define VBOXSF_SUPER_MAGIC 0x786f4256
+#endif
+
+/* Not exposed yet. Defined at fs/exfat/exfat_fs.h */
+#ifndef EXFAT_SUPER_MAGIC
+#define EXFAT_SUPER_MAGIC 0x2011BAB0UL
+#endif
diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c
index f07534267f..783f94b2f7 100644
--- a/src/basic/mountpoint-util.c
+++ b/src/basic/mountpoint-util.c
@@ -8,11 +8,13 @@
#include "chase-symlinks.h"
#include "fd-util.h"
#include "fileio.h"
+#include "filesystems.h"
#include "fs-util.h"
#include "missing_stat.h"
#include "missing_syscall.h"
#include "mkdir.h"
#include "mountpoint-util.h"
+#include "nulstr-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "stat-util.h"
@@ -366,48 +368,34 @@ bool fstype_is_network(const char *fstype) {
if (x)
fstype = x;
+ if (nulstr_contains(filesystem_sets[FILESYSTEM_SET_NETWORK].value, fstype))
+ return true;
+
+ /* Filesystems not present in the internal database */
return STR_IN_SET(fstype,
- "afs",
"ceph",
- "cifs",
- "smb3",
- "smbfs",
- "sshfs",
- "ncpfs",
- "ncp",
- "nfs",
- "nfs4",
- "gfs",
- "gfs2",
+ "davfs",
"glusterfs",
- "pvfs2", /* OrangeFS */
- "ocfs2",
"lustre",
- "davfs");
+ "sshfs");
}
bool fstype_is_api_vfs(const char *fstype) {
+ const FilesystemSet *fs;
+
+ FOREACH_POINTER(fs,
+ filesystem_sets + FILESYSTEM_SET_BASIC_API,
+ filesystem_sets + FILESYSTEM_SET_AUXILIARY_API,
+ filesystem_sets + FILESYSTEM_SET_PRIVILEGED_API,
+ filesystem_sets + FILESYSTEM_SET_TEMPORARY)
+ if (nulstr_contains(fs->value, fstype))
+ return true;
+
+ /* Filesystems not present in the internal database */
return STR_IN_SET(fstype,
"autofs",
- "bpf",
- "cgroup",
- "cgroup2",
- "configfs",
"cpuset",
- "debugfs",
- "devpts",
- "devtmpfs",
- "efivarfs",
- "fusectl",
- "hugetlbfs",
- "mqueue",
- "proc",
- "pstore",
- "ramfs",
- "securityfs",
- "sysfs",
- "tmpfs",
- "tracefs");
+ "devtmpfs");
}
bool fstype_is_blockdev_backed(const char *fstype) {
diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c
index 45864e9e62..0004255bdb 100644
--- a/src/basic/stat-util.c
+++ b/src/basic/stat-util.c
@@ -13,10 +13,13 @@
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
+#include "filesystems.h"
+#include "fs-util.h"
#include "macro.h"
#include "missing_fs.h"
#include "missing_magic.h"
#include "missing_syscall.h"
+#include "nulstr-util.h"
#include "parse-util.h"
#include "stat-util.h"
#include "string-util.h"
@@ -198,19 +201,11 @@ int path_is_fs_type(const char *path, statfs_f_type_t magic_value) {
}
bool is_temporary_fs(const struct statfs *s) {
- return is_fs_type(s, TMPFS_MAGIC) ||
- is_fs_type(s, RAMFS_MAGIC);
+ return fs_in_group(s, FILESYSTEM_SET_TEMPORARY);
}
bool is_network_fs(const struct statfs *s) {
- return is_fs_type(s, CIFS_MAGIC_NUMBER) ||
- is_fs_type(s, CODA_SUPER_MAGIC) ||
- is_fs_type(s, NCP_SUPER_MAGIC) ||
- is_fs_type(s, NFS_SUPER_MAGIC) ||
- is_fs_type(s, SMB_SUPER_MAGIC) ||
- is_fs_type(s, V9FS_MAGIC) ||
- is_fs_type(s, AFS_SUPER_MAGIC) ||
- is_fs_type(s, OCFS2_SUPER_MAGIC);
+ return fs_in_group(s, FILESYSTEM_SET_NETWORK);
}
int fd_is_temporary_fs(int fd) {
diff --git a/src/core/bpf-lsm.c b/src/core/bpf-lsm.c
new file mode 100644
index 0000000000..46c2446849
--- /dev/null
+++ b/src/core/bpf-lsm.c
@@ -0,0 +1,381 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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/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 (!link)
+ return -ENOMEM;
+
+ return 1;
+}
+
+static int prepare_restrict_fs_bpf(struct restrict_fs_bpf **ret_obj) {
+ struct restrict_fs_bpf *obj = 0;
+ _cleanup_close_ int inner_map_fd = -1;
+ int r;
+
+ assert(ret_obj);
+
+ obj = restrict_fs_bpf__open();
+ if (!obj)
+ return log_error_errno(errno, "Failed to open BPF object: %m");
+
+ /* TODO Maybe choose a number based on runtime information? */
+ r = sym_bpf_map__resize(obj->maps.cgroup_hash, CGROUP_HASH_SIZE_MAX);
+ if (r != 0)
+ return log_error_errno(r,
+ "Failed to resize BPF map '%s': %m",
+ sym_bpf_map__name(obj->maps.cgroup_hash));
+
+ /* Dummy map to satisfy the verifier */
+ inner_map_fd = sym_bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint32_t), 128, 0);
+ if (inner_map_fd < 0)
+ return log_error_errno(errno, "Failed to create BPF map: %m");
+
+ r = sym_bpf_map__set_inner_map_fd(obj->maps.cgroup_hash, inner_map_fd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set inner map fd: %m");
+
+ r = restrict_fs_bpf__load(obj);
+ if (r)
+ return log_error_errno(r, "Failed to load BPF object");
+
+ *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 (errno != ENOENT)
+ log_debug_errno(r, "Failed to read /sys/kernel/security/lsm, ignoring: %m");
+
+ return 0;
+ }
+
+ const char *p = lsm_list;
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+
+ r = extract_first_word(&p, &word, ",", 0);
+ if (r == 0)
+ break;
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse /sys/kernel/security/lsm, ignoring: %m");
+ return 0;
+ }
+
+ if (streq(word, "bpf")) {
+ cached_use = 1;
+ break;
+ }
+ }
+
+ return cached_use;
+}
+
+int lsm_bpf_supported(void) {
+ _cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL;
+ static int supported = -1;
+ int r;
+
+ if (supported >= 0)
+ return supported;
+
+ r = dlopen_bpf();
+ if (r < 0) {
+ log_info_errno(r, "Failed to open libbpf, LSM BPF is not supported: %m");
+ return supported = 0;
+ }
+
+ r = cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER);
+ if (r < 0) {
+ log_warning_errno(r, "Can't determine whether the unified hierarchy is used: %m");
+ return supported = 0;
+ }
+
+ if (r == 0) {
+ log_info_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Not running with unified cgroup hierarchy, LSM BPF is not supported");
+ return supported = 0;
+ }
+
+ r = mac_bpf_use();
+ if (r < 0) {
+ log_warning_errno(r, "Can't determine whether the BPF LSM module is used: %m");
+ return supported = 0;
+ }
+
+ if (r == 0) {
+ log_info_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "BPF LSM hook not enabled in the kernel, LSM BPF not supported");
+ return supported = 0;
+ }
+
+ r = prepare_restrict_fs_bpf(&obj);
+ if (r < 0)
+ return supported = 0;
+
+ r = bpf_can_link_lsm_program(obj->progs.restrict_filesystems);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to link BPF program. Assuming BPF is not available: %m");
+ return supported = 0;
+ }
+
+ return supported = 1;
+}
+
+int lsm_bpf_setup(Manager *m) {
+ 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;
+
+ m->restrict_fs = obj;
+
+ link = sym_bpf_program__attach_lsm(m->restrict_fs->progs.restrict_filesystems);
+ r = sym_libbpf_get_error(link);
+ if (r != 0)
+ return log_error_errno(r, "Failed to link '%s' LSM BPF program: %m",
+ sym_bpf_program__name(m->restrict_fs->progs.restrict_filesystems));
+
+ log_info("LSM BPF program attached");
+
+ m->restrict_fs->links.restrict_filesystems = TAKE_PTR(link);
+
+ return 0;
+}
+
+int lsm_bpf_unit_restrict_filesystems(Unit *u, const Set *filesystems, bool allow_list) {
+ int inner_map_fd = -1, outer_map_fd = -1;
+ uint32_t dummy_value = 1, zero = 0;
+ const char *fs;
+ const statfs_f_type_t *magic;
+ int r;
+
+ assert(filesystems);
+ assert(u);
+
+ inner_map_fd = sym_bpf_create_map(
+ BPF_MAP_TYPE_HASH,
+ sizeof(uint32_t),
+ sizeof(uint32_t),
+ 128, /* Should be enough for all filesystem types */
+ 0);
+ if (inner_map_fd < 0)
+ return log_unit_error_errno(u, errno, "Failed to create inner LSM map: %m");
+
+ 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, "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, "Error populating LSM 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, "Error initializing BPF map: %m");
+
+ SET_FOREACH(fs, filesystems) {
+ r = fs_type_from_string(fs, &magic);
+ if (r < 0) {
+ log_unit_warning(u, "Invalid filesystem name '%s', ignoring.", fs);
+ continue;
+ }
+
+ log_unit_debug(u, "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, "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, "Failed to delete cgroup entry from LSM BPF map: %m");
+
+ return r;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int lsm_bpf_cleanup(const Unit *u) {
+ int fd = -1;
+
+ assert(u);
+ assert(u->manager);
+
+ if (!lsm_bpf_supported())
+ return 0;
+
+ if (!u->manager->restrict_fs)
+ return 0;
+
+ fd = sym_bpf_map__fd(u->manager->restrict_fs->maps.cgroup_hash);
+ if (fd < 0)
+ return log_unit_error_errno(u, errno, "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, "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 */
+int lsm_bpf_supported(void) {
+ return 0;
+}
+
+int lsm_bpf_setup(Manager *m) {
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "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), "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;
+ const char *i;
+
+ set = filesystem_set_find(name);
+ if (!set) {
+ log_syntax(unit, flags & FILESYSTEM_PARSE_LOG ? LOG_WARNING : LOG_DEBUG, filename, line, 0,
+ "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 < 0)
+ switch (r) {
+ case -ENOMEM:
+ return flags & FILESYSTEM_PARSE_LOG ? log_oom() : -ENOMEM;
+ case -EEXIST:
+ /* Alredy in set, ignore */
+ break;
+ default:
+ return r;
+ }
+ } else
+ free(set_remove(*filesystems, name));
+ }
+
+ return 0;
+}
diff --git a/src/core/bpf-lsm.h b/src/core/bpf-lsm.h
new file mode 100644
index 0000000000..8bd58a29e5
--- /dev/null
+++ b/src/core/bpf-lsm.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "hashmap.h"
+
+typedef enum FilesystemParseFlags {
+ FILESYSTEM_PARSE_INVERT = 1 << 0,
+ FILESYSTEM_PARSE_ALLOW_LIST = 1 << 1,
+ FILESYSTEM_PARSE_LOG = 1 << 2,
+} FilesystemParseFlags;
+
+typedef struct Unit Unit;
+typedef struct Manager Manager;
+
+typedef struct restrict_fs_bpf restrict_fs_bpf;
+
+int lsm_bpf_supported(void);
+int lsm_bpf_setup(Manager *m);
+int lsm_bpf_unit_restrict_filesystems(Unit *u, const Set *filesystems, bool allow_list);
+int lsm_bpf_cleanup(const Unit *u);
+int lsm_bpf_map_restrict_fs_fd(Unit *u);
+void lsm_bpf_destroy(struct restrict_fs_bpf *prog);
+int lsm_bpf_parse_filesystem(const char *name,
+ Set **filesystems,
+ FilesystemParseFlags flags,
+ const char *unit,
+ const char *filename,
+ unsigned line);
diff --git a/src/core/bpf/restrict_fs/meson.build b/src/core/bpf/restrict_fs/meson.build
new file mode 100644
index 0000000000..7a21f5276e
--- /dev/null
+++ b/src/core/bpf/restrict_fs/meson.build
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
+if conf.get('BPF_FRAMEWORK') == 1
+ restrict_fs_skel_h = custom_target(
+ 'restrict-fs-skel.h',
+ input : 'restrict-fs.bpf.c',
+ output : 'restrict-fs-skel.h',
+ command : [build_bpf_skel_py,
+ '--clang_exec', clang.path(),
+ '--llvm_strip_exec', llvm_strip.path(),
+ '--bpftool_exec', bpftool.path(),
+ '--arch', host_machine.cpu_family(),
+ '@INPUT@', '@OUTPUT@'])
+endif
diff --git a/src/core/bpf/restrict_fs/restrict-fs.bpf.c b/src/core/bpf/restrict_fs/restrict-fs.bpf.c
new file mode 100644
index 0000000000..cdc0613a01
--- /dev/null
+++ b/src/core/bpf/restrict_fs/restrict-fs.bpf.c
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/* The SPDX header above is actually correct in claiming this was
+ * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that
+ * compatible with GPL we will claim this to be GPL however, which should be
+ * fine given that LGPL-2.1-or-later downgrades to GPL if needed.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+struct super_block {
+ long unsigned int s_magic;
+} __attribute__((preserve_access_index));
+
+struct inode {
+ struct super_block *i_sb;
+} __attribute__((preserve_access_index));
+
+struct file {
+ struct inode *f_inode;
+} __attribute__((preserve_access_index));
+
+/*
+ * max_entries is set from user space with the bpf_map__resize helper.
+ * */
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH_OF_MAPS);
+ __type(key, uint64_t); /* cgroup ID */
+ __type(value, uint32_t); /* fs magic set */
+} cgroup_hash SEC(".maps");
+
+SEC("lsm/file_open")
+int BPF_PROG(restrict_filesystems, struct file *file, int ret)
+{
+ unsigned long magic_number;
+ uint64_t cgroup_id;
+ uint32_t *value, *magic_map, zero = 0, *is_allow;
+
+ /* ret is the return value from the previous BPF program or 0 if it's
+ * the first hook */
+ if (ret != 0)
+ return ret;
+
+ BPF_CORE_READ_INTO(&magic_number, file, f_inode, i_sb, s_magic);
+
+ cgroup_id = bpf_get_current_cgroup_id();
+
+ magic_map = bpf_map_lookup_elem(&cgroup_hash, &cgroup_id);
+ if (!magic_map)
+ return 0;
+
+ is_allow = bpf_map_lookup_elem(magic_map, &zero);
+ if (!is_allow)
+ /* Malformed map, it doesn't include whether it's an allow list
+ * or a deny list. Allow. */
+ return 0;
+
+ if (*is_allow) {
+ /* Allow-list: Allow access only if magic_number present in inner map */
+ if (!bpf_map_lookup_elem(magic_map, &magic_number))
+ return -EPERM;
+ } else {
+ /* Deny-list: Allow access only if magic_number is not present in inner map */
+ if (bpf_map_lookup_elem(magic_map, &magic_number))
+ return -EPERM;
+ }
+
+ return 0;
+}
+
+static const char _license[] SEC("license") = "GPL";
diff --git a/src/core/cgroup.c b/src/core/cgroup.c
index a5770c0332..2b15310191 100644
--- a/src/core/cgroup.c
+++ b/src/core/cgroup.c
@@ -37,6 +37,12 @@
#include "string-util.h"
#include "virt.h"
+#if BPF_FRAMEWORK
+#include "bpf-dlopen.h"
+#include "bpf-link.h"
+#include "bpf/restrict_fs/restrict-fs-skel.h"
+#endif
+
#define CGROUP_CPU_QUOTA_DEFAULT_PERIOD_USEC ((usec_t) 100 * USEC_PER_MSEC)
/* Returns the log level to use when cgroup attribute writes fail. When an attribute is missing or we have access
@@ -2102,6 +2108,8 @@ static int unit_update_cgroup(
bool created, is_root_slice;
CGroupMask migrate_mask = 0;
+ _cleanup_free_ char *cgroup_full_path = NULL;
+ uint64_t cgroup_id = 0;
int r;
assert(u);
@@ -2120,6 +2128,18 @@ static int unit_update_cgroup(
return log_unit_error_errno(u, r, "Failed to create cgroup %s: %m", empty_to_root(u->cgroup_path));
created = r;
+ if (cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER) > 0) {
+ r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, NULL, &cgroup_full_path);
+ if (r == 0) {
+ r = cg_path_get_cgroupid(cgroup_full_path, &cgroup_id);
+ if (r < 0)
+ log_unit_warning_errno(u, r, "Failed to get cgroup ID on cgroup %s, ignoring: %m", cgroup_full_path);
+ } else
+ log_unit_warning_errno(u, r, "Failed to get full cgroup path on cgroup %s, ignoring: %m", empty_to_root(u->cgroup_path));
+
+ u->cgroup_id = cgroup_id;
+ }
+
/* Start watching it */
(void) unit_watch_cgroup(u);
(void) unit_watch_cgroup_memory(u);
@@ -2722,6 +2742,10 @@ void unit_prune_cgroup(Unit *u) {
(void) unit_get_cpu_usage(u, NULL); /* Cache the last CPU usage value before we destroy the cgroup */
+#if BPF_FRAMEWORK
+ (void) lsm_bpf_cleanup(u); /* Remove cgroup from the global LSM BPF map */
+#endif
+
is_root_slice = unit_has_name(u, SPECIAL_ROOT_SLICE);
r = cg_trim_everywhere(u->manager->cgroup_supported, u->cgroup_path, !is_root_slice);
diff --git a/src/core/cgroup.h b/src/core/cgroup.h
index 8795f2724e..4413eeaaa0 100644
--- a/src/core/cgroup.h
+++ b/src/core/cgroup.h
@@ -3,6 +3,7 @@
#include
+#include "bpf-lsm.h"
#include "cgroup-util.h"
#include "cpu-set-util.h"
#include "list.h"
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index 488de1242a..87d6aefc84 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -37,6 +37,7 @@
#endif
#include "securebits-util.h"
#include "specifier.h"
+#include "stat-util.h"
#include "strv.h"
#include "syslog-util.h"
#include "unit-printf.h"
@@ -681,6 +682,46 @@ static int property_get_input_data(
return sd_bus_message_append_array(reply, 'y', c->stdin_data, c->stdin_data_size);
}
+static int property_get_restrict_filesystems(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ ExecContext *c = userdata;
+ _cleanup_free_ char **l = NULL;
+ int r;
+
+ assert(bus);
+ assert(reply);
+ assert(c);
+
+ r = sd_bus_message_open_container(reply, 'r', "bas");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "b", c->restrict_filesystems_allow_list);
+ if (r < 0)
+ return r;
+
+#if HAVE_LIBBPF
+ l = set_get_strv(c->restrict_filesystems);
+ if (!l)
+ return -ENOMEM;
+#endif
+
+ strv_sort(l);
+
+ r = sd_bus_message_append_strv(reply, l);
+ if (r < 0)
+ return r;
+
+ return sd_bus_message_close_container(reply);
+}
+
static int property_get_bind_paths(
sd_bus *bus,
const char *path,
@@ -1199,6 +1240,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("RestrictRealtime", "b", bus_property_get_bool, offsetof(ExecContext, restrict_realtime), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RestrictSUIDSGID", "b", bus_property_get_bool, offsetof(ExecContext, restrict_suid_sgid), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RestrictNamespaces", "t", bus_property_get_ulong, offsetof(ExecContext, restrict_namespaces), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RestrictFileSystems", "(bas)", property_get_restrict_filesystems, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("BindPaths", "a(ssbt)", property_get_bind_paths, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("BindReadOnlyPaths", "a(ssbt)", property_get_bind_paths, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TemporaryFileSystem", "a(ss)", property_get_temporary_filesystems, 0, SD_BUS_VTABLE_PROPERTY_CONST),
@@ -1875,6 +1917,64 @@ int bus_exec_context_set_transient_property(
if (streq(name, "RestrictNamespaces"))
return bus_set_transient_namespace_flag(u, name, &c->restrict_namespaces, message, flags, error);
+ if (streq(name, "RestrictFileSystems")) {
+ int allow_list;
+ _cleanup_strv_free_ char **l = NULL;
+
+ r = sd_bus_message_enter_container(message, 'r', "bas");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(message, "b", &allow_list);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read_strv(message, &l);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_exit_container(message);
+ if (r < 0)
+ return r;
+
+ if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+ _cleanup_free_ char *joined = NULL;
+ FilesystemParseFlags invert_flag = allow_list ? 0 : FILESYSTEM_PARSE_INVERT;
+ char **s;
+
+ if (strv_isempty(l)) {
+ c->restrict_filesystems_allow_list = false;
+ c->restrict_filesystems = set_free(c->restrict_filesystems);
+
+ unit_write_setting(u, flags, name, "RestrictFileSystems=");
+ return 1;
+ }
+
+ if (!c->restrict_filesystems)
+ c->restrict_filesystems_allow_list = allow_list;
+
+ STRV_FOREACH(s, l) {
+ r = lsm_bpf_parse_filesystem(
+ *s,
+ &c->restrict_filesystems,
+ FILESYSTEM_PARSE_LOG|
+ (invert_flag ? FILESYSTEM_PARSE_INVERT : 0)|
+ (c->restrict_filesystems_allow_list ? FILESYSTEM_PARSE_ALLOW_LIST : 0),
+ u->id, NULL, 0);
+ if (r < 0)
+ return r;
+ }
+
+ joined = strv_join(l, " ");
+ if (!joined)
+ return -ENOMEM;
+
+ unit_write_settingf(u, flags, name, "%s=%s%s", name, allow_list ? "" : "~", joined);
+ }
+
+ return 1;
+ }
+
if (streq(name, "MountFlags"))
return bus_set_transient_mount_flags(u, name, &c->mount_flags, message, flags, error);
diff --git a/src/core/execute.c b/src/core/execute.c
index d68e31eb7d..6397bab315 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -41,6 +41,7 @@
#endif
#include "async.h"
#include "barrier.h"
+#include "bpf-lsm.h"
#include "cap-list.h"
#include "capability-util.h"
#include "cgroup-setup.h"
@@ -1685,6 +1686,29 @@ static int apply_restrict_namespaces(const Unit *u, const ExecContext *c) {
return seccomp_restrict_namespaces(c->restrict_namespaces);
}
+#if HAVE_LIBBPF
+static bool skip_lsm_bpf_unsupported(const Unit* u, const char* msg) {
+ if (lsm_bpf_supported())
+ return false;
+
+ log_unit_debug(u, "LSM BPF not supported, skipping %s", msg);
+ return true;
+}
+
+static int apply_restrict_filesystems(Unit *u, const ExecContext *c) {
+ assert(u);
+ assert(c);
+
+ if (!exec_context_restrict_filesystems_set(c))
+ return 0;
+
+ if (skip_lsm_bpf_unsupported(u, "RestrictFileSystems="))
+ return 0;
+
+ return lsm_bpf_unit_restrict_filesystems(u, c->restrict_filesystems, c->restrict_filesystems_allow_list);
+}
+#endif
+
static int apply_lock_personality(const Unit* u, const ExecContext *c) {
unsigned long personality;
int r;
@@ -3813,7 +3837,7 @@ static int exec_child(
/* In case anything used libc syslog(), close this here, too */
closelog();
- int keep_fds[n_fds + 2];
+ int keep_fds[n_fds + 3];
memcpy_safe(keep_fds, fds, n_fds * sizeof(int));
n_keep_fds = n_fds;
@@ -3823,6 +3847,24 @@ static int exec_child(
return log_unit_error_errno(unit, r, "Failed to shift fd and set FD_CLOEXEC: %m");
}
+#if HAVE_LIBBPF
+ if (MANAGER_IS_SYSTEM(unit->manager) && lsm_bpf_supported()) {
+ int bpf_map_fd = -1;
+
+ bpf_map_fd = lsm_bpf_map_restrict_fs_fd(unit);
+ if (bpf_map_fd < 0) {
+ *exit_status = EXIT_FDS;
+ return log_unit_error_errno(unit, r, "Failed to get restrict filesystems BPF map fd: %m");
+ }
+
+ r = add_shifted_fd(keep_fds, ELEMENTSOF(keep_fds), &n_keep_fds, bpf_map_fd, &bpf_map_fd);
+ if (r < 0) {
+ *exit_status = EXIT_FDS;
+ return log_unit_error_errno(unit, r, "Failed to shift fd and set FD_CLOEXEC: %m");
+ }
+ }
+#endif
+
r = close_remaining_fds(params, runtime, dcreds, user_lookup_fd, socket_fd, keep_fds, n_keep_fds);
if (r < 0) {
*exit_status = EXIT_FDS;
@@ -4682,6 +4724,15 @@ static int exec_child(
return log_unit_error_errno(unit, r, "Failed to apply system call filters: %m");
}
#endif
+
+#if HAVE_LIBBPF
+ r = apply_restrict_filesystems(unit, context);
+ if (r < 0) {
+ *exit_status = EXIT_BPF;
+ return log_unit_error_errno(unit, r, "Failed to restrict filesystems: %m");
+ }
+#endif
+
}
if (!strv_isempty(context->unset_environment)) {
@@ -4967,6 +5018,8 @@ void exec_context_done(ExecContext *c) {
c->apparmor_profile = mfree(c->apparmor_profile);
c->smack_process_label = mfree(c->smack_process_label);
+ c->restrict_filesystems = set_free(c->restrict_filesystems);
+
c->syscall_filter = hashmap_free(c->syscall_filter);
c->syscall_archs = set_free(c->syscall_archs);
c->address_families = set_free(c->address_families);
@@ -5734,6 +5787,12 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
prefix, strna(s));
}
+#if HAVE_LIBBPF
+ if (exec_context_restrict_filesystems_set(c))
+ SET_FOREACH(e, c->restrict_filesystems)
+ fprintf(f, "%sRestrictFileSystems: %s\n", prefix, *e);
+#endif
+
if (c->network_namespace_path)
fprintf(f,
"%sNetworkNamespacePath: %s\n",
diff --git a/src/core/execute.h b/src/core/execute.h
index 64a38b2d26..560dcbcc5e 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -314,6 +314,9 @@ struct ExecContext {
unsigned long restrict_namespaces; /* The CLONE_NEWxyz flags permitted to the unit's processes */
+ Set *restrict_filesystems;
+ bool restrict_filesystems_allow_list:1;
+
Hashmap *syscall_filter;
Set *syscall_archs;
int syscall_errno;
@@ -342,6 +345,13 @@ static inline bool exec_context_restrict_namespaces_set(const ExecContext *c) {
return (c->restrict_namespaces & NAMESPACE_FLAGS_ALL) != NAMESPACE_FLAGS_ALL;
}
+static inline bool exec_context_restrict_filesystems_set(const ExecContext *c) {
+ assert(c);
+
+ return c->restrict_filesystems_allow_list ||
+ !set_isempty(c->restrict_filesystems);
+}
+
static inline bool exec_context_with_rootfs(const ExecContext *c) {
assert(c);
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
index 799a179430..480ec66bf9 100644
--- a/src/core/load-fragment-gperf.gperf.in
+++ b/src/core/load-fragment-gperf.gperf.in
@@ -81,6 +81,7 @@
{{type}}.RestrictAddressFamilies, config_parse_warn_compat, DISABLED_CONFIGURATION, 0
{{type}}.LockPersonality, config_parse_warn_compat, DISABLED_CONFIGURATION, 0
{% endif %}
+{{type}}.RestrictFileSystems, config_parse_restrict_filesystems, 0, offsetof({{type}}, exec_context)
{{type}}.LimitCPU, config_parse_rlimit, RLIMIT_CPU, offsetof({{type}}, exec_context.rlimit)
{{type}}.LimitFSIZE, config_parse_rlimit, RLIMIT_FSIZE, offsetof({{type}}, exec_context.rlimit)
{{type}}.LimitDATA, config_parse_rlimit, RLIMIT_DATA, offsetof({{type}}, exec_context.rlimit)
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index f971084c28..10fe579aeb 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -19,6 +19,7 @@
#include "all-units.h"
#include "alloc-util.h"
#include "bpf-firewall.h"
+#include "bpf-lsm.h"
#include "bpf-program.h"
#include "bpf-socket-bind.h"
#include "bus-error.h"
@@ -3597,6 +3598,76 @@ int config_parse_restrict_namespaces(
}
#endif
+int config_parse_restrict_filesystems(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ ExecContext *c = data;
+ bool invert = false;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (isempty(rvalue)) {
+ /* Empty assignment resets the list */
+ c->restrict_filesystems = set_free(c->restrict_filesystems);
+ c->restrict_filesystems_allow_list = false;
+ return 0;
+ }
+
+ if (rvalue[0] == '~') {
+ invert = true;
+ rvalue++;
+ }
+
+ if (!c->restrict_filesystems) {
+ if (invert)
+ /* Allow everything but the ones listed */
+ c->restrict_filesystems_allow_list = false;
+ else
+ /* Allow nothing but the ones listed */
+ c->restrict_filesystems_allow_list = true;
+ }
+
+ for (const char *p = rvalue;;) {
+ _cleanup_free_ char *word = NULL;
+
+ r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE);
+ if (r == 0)
+ break;
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Trailing garbage in %s, ignoring: %s", lvalue, rvalue);
+ break;
+ }
+
+ r = lsm_bpf_parse_filesystem(
+ word,
+ &c->restrict_filesystems,
+ FILESYSTEM_PARSE_LOG|
+ (invert ? FILESYSTEM_PARSE_INVERT : 0)|
+ (c->restrict_filesystems_allow_list ? FILESYSTEM_PARSE_ALLOW_LIST : 0),
+ unit, filename, line);
+
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
int config_parse_unit_slice(
const char *unit,
const char *filename,
@@ -6030,6 +6101,7 @@ void unit_dump_config_items(FILE *f) {
{ config_parse_address_families, "FAMILIES" },
{ config_parse_restrict_namespaces, "NAMESPACES" },
#endif
+ { config_parse_restrict_filesystems, "FILESYSTEMS" },
{ config_parse_cpu_shares, "SHARES" },
{ config_parse_cg_weight, "WEIGHT" },
{ config_parse_memory_limit, "LIMIT" },
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index e84f9ee391..9173f30add 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -113,6 +113,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_sec_fix_0);
CONFIG_PARSER_PROTOTYPE(config_parse_user_group_compat);
CONFIG_PARSER_PROTOTYPE(config_parse_user_group_strv_compat);
CONFIG_PARSER_PROTOTYPE(config_parse_restrict_namespaces);
+CONFIG_PARSER_PROTOTYPE(config_parse_restrict_filesystems);
CONFIG_PARSER_PROTOTYPE(config_parse_bind_paths);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_keyring_mode);
CONFIG_PARSER_PROTOTYPE(config_parse_protect_proc);
diff --git a/src/core/main.c b/src/core/main.c
index 059ba6dd49..62f39c7378 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -22,6 +22,9 @@
#include "alloc-util.h"
#include "apparmor-setup.h"
#include "architecture.h"
+#if HAVE_LIBBPF
+#include "bpf-lsm.h"
+#endif
#include "build.h"
#include "bus-error.h"
#include "bus-util.h"
diff --git a/src/core/manager.c b/src/core/manager.c
index 0b2e29ae14..6bcb6bd153 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -930,6 +930,14 @@ int manager_new(UnitFileScope scope, ManagerTestRunFlags test_run_flags, Manager
r = manager_setup_sigchld_event_source(m);
if (r < 0)
return r;
+
+#if HAVE_LIBBPF
+ if (MANAGER_IS_SYSTEM(m) && lsm_bpf_supported()) {
+ r = lsm_bpf_setup(m);
+ if (r < 0)
+ return r;
+ }
+#endif
}
if (test_run_flags == 0) {
@@ -1535,6 +1543,10 @@ Manager* manager_free(Manager *m) {
m->prefix[dt] = mfree(m->prefix[dt]);
free(m->received_credentials);
+#if BPF_FRAMEWORK
+ lsm_bpf_destroy(m->restrict_fs);
+#endif
+
return mfree(m);
}
diff --git a/src/core/manager.h b/src/core/manager.h
index 97c12ce48f..29ce812121 100644
--- a/src/core/manager.h
+++ b/src/core/manager.h
@@ -450,6 +450,9 @@ struct Manager {
* we're a user manager, this object manages the client connection from the user manager to
* systemd-oomd to report changes in ManagedOOM settings (systemd client - oomd server). */
Varlink *managed_oom_varlink;
+
+ /* Reference to RestrictFileSystems= BPF program */
+ struct restrict_fs_bpf *restrict_fs;
};
static inline usec_t manager_default_timeout_abort_usec(Manager *m) {
diff --git a/src/core/meson.build b/src/core/meson.build
index 4b53d7e43b..de7c2ae798 100644
--- a/src/core/meson.build
+++ b/src/core/meson.build
@@ -13,6 +13,8 @@ libcore_sources = '''
bpf-firewall.h
bpf-foreign.c
bpf-foreign.h
+ bpf-lsm.c
+ bpf-lsm.h
bpf-socket-bind.c
bpf-socket-bind.h
cgroup.c
@@ -134,6 +136,8 @@ libcore_sources = '''
subdir('bpf/socket_bind')
if conf.get('BPF_FRAMEWORK') == 1
libcore_sources += [socket_bind_skel_h]
+ subdir('bpf/restrict_fs')
+ libcore_sources += [restrict_fs_skel_h]
endif
subdir('bpf/restrict_ifaces')
diff --git a/src/core/unit.h b/src/core/unit.h
index c8e99acf70..0dd6a9591d 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -294,6 +294,7 @@ typedef struct Unit {
/* Counterparts in the cgroup filesystem */
char *cgroup_path;
+ uint64_t cgroup_id;
CGroupMask cgroup_realized_mask; /* In which hierarchies does this unit's cgroup exist? (only relevant on cgroup v1) */
CGroupMask cgroup_enabled_mask; /* Which controllers are enabled (or more correctly: enabled for the children) for this unit's cgroup? (only relevant on cgroup v2) */
CGroupMask cgroup_invalidated_mask; /* A mask specifying controllers which shall be considered invalidated, and require re-realization */
diff --git a/src/shared/bpf-dlopen.c b/src/shared/bpf-dlopen.c
index 45ab986ab2..6f82002ff8 100644
--- a/src/shared/bpf-dlopen.c
+++ b/src/shared/bpf-dlopen.c
@@ -8,13 +8,17 @@
static void *bpf_dl = NULL;
struct bpf_link* (*sym_bpf_program__attach_cgroup)(struct bpf_program *, int);
+struct bpf_link* (*sym_bpf_program__attach_lsm)(struct bpf_program *);
long (*sym_libbpf_get_error)(const void *);
int (*sym_bpf_link__fd)(const struct bpf_link *);
int (*sym_bpf_link__destroy)(struct bpf_link *);
int (*sym_bpf_map__fd)(const struct bpf_map *);
const char* (*sym_bpf_map__name)(const struct bpf_map *);
+int (*sym_bpf_create_map)(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags);
int (*sym_bpf_map__resize)(struct bpf_map *, __u32);
int (*sym_bpf_map_update_elem)(int, const void *, const void *, __u64);
+int (*sym_bpf_map_delete_elem)(int, const void *);
+int (*sym_bpf_map__set_inner_map_fd)(struct bpf_map *, int);
int (*sym_bpf_object__open_skeleton)(struct bpf_object_skeleton *, const struct bpf_object_open_opts *);
int (*sym_bpf_object__load_skeleton)(struct bpf_object_skeleton *);
int (*sym_bpf_object__attach_skeleton)(struct bpf_object_skeleton *);
@@ -30,8 +34,11 @@ int dlopen_bpf(void) {
DLSYM_ARG(bpf_link__fd),
DLSYM_ARG(bpf_map__fd),
DLSYM_ARG(bpf_map__name),
+ DLSYM_ARG(bpf_create_map),
DLSYM_ARG(bpf_map__resize),
DLSYM_ARG(bpf_map_update_elem),
+ DLSYM_ARG(bpf_map_delete_elem),
+ DLSYM_ARG(bpf_map__set_inner_map_fd),
DLSYM_ARG(bpf_object__open_skeleton),
DLSYM_ARG(bpf_object__load_skeleton),
DLSYM_ARG(bpf_object__attach_skeleton),
@@ -39,6 +46,7 @@ int dlopen_bpf(void) {
DLSYM_ARG(bpf_object__destroy_skeleton),
DLSYM_ARG(bpf_probe_prog_type),
DLSYM_ARG(bpf_program__attach_cgroup),
+ DLSYM_ARG(bpf_program__attach_lsm),
DLSYM_ARG(bpf_program__name),
DLSYM_ARG(libbpf_get_error));
}
diff --git a/src/shared/bpf-dlopen.h b/src/shared/bpf-dlopen.h
index fbb5531aff..713c41c3f4 100644
--- a/src/shared/bpf-dlopen.h
+++ b/src/shared/bpf-dlopen.h
@@ -7,13 +7,17 @@
#include
extern struct bpf_link* (*sym_bpf_program__attach_cgroup)(struct bpf_program *, int);
+extern struct bpf_link* (*sym_bpf_program__attach_lsm)(struct bpf_program *);
extern long (*sym_libbpf_get_error)(const void *);
extern int (*sym_bpf_link__fd)(const struct bpf_link *);
extern int (*sym_bpf_link__destroy)(struct bpf_link *);
extern int (*sym_bpf_map__fd)(const struct bpf_map *);
extern const char* (*sym_bpf_map__name)(const struct bpf_map *);
+extern int (*sym_bpf_create_map)(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags);
extern int (*sym_bpf_map__resize)(struct bpf_map *, __u32);
extern int (*sym_bpf_map_update_elem)(int, const void *, const void *, __u64);
+extern int (*sym_bpf_map_delete_elem)(int, const void *);
+extern int (*sym_bpf_map__set_inner_map_fd)(struct bpf_map *, int);
/* The *_skeleton APIs are autogenerated by bpftool, the targets can be found
* in ./build/src/core/bpf/socket_bind/socket-bind.skel.h */
extern int (*sym_bpf_object__open_skeleton)(struct bpf_object_skeleton *, const struct bpf_object_open_opts *);
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 01b78952cd..8b81e8058b 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -1386,6 +1386,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
}
if (STR_IN_SET(field, "RestrictAddressFamilies",
+ "RestrictFileSystems",
"SystemCallFilter",
"SystemCallLog",
"RestrictNetworkInterfaces")) {
diff --git a/src/shared/exit-status.c b/src/shared/exit-status.c
index db978fb896..9063f231e1 100644
--- a/src/shared/exit-status.c
+++ b/src/shared/exit-status.c
@@ -18,8 +18,8 @@ const ExitStatusMapping exit_status_mappings[256] = {
* 8…63 │ (Currently unmapped)
* 64…78 │ BSD defined exit codes
* 79…199 │ (Currently unmapped)
- * 200…243 │ systemd's private error codes (might be extended to 254 in future development)
- * 244…254 │ (Currently unmapped, but see above)
+ * 200…244 │ systemd's private error codes (might be extended to 254 in future development)
+ * 245…254 │ (Currently unmapped, but see above)
*
* 255 │ EXIT_EXCEPTION (We use this to propagate exit-by-signal events. It's frequently used by others apps (like bash)
* │ to indicate exit reason that cannot really be expressed in a single exit status value — such as a propagated
@@ -71,6 +71,7 @@ const ExitStatusMapping exit_status_mappings[256] = {
[EXIT_CONFIGURATION_DIRECTORY] = { "CONFIGURATION_DIRECTORY", EXIT_STATUS_SYSTEMD },
[EXIT_NUMA_POLICY] = { "NUMA_POLICY", EXIT_STATUS_SYSTEMD },
[EXIT_CREDENTIALS] = { "CREDENTIALS", EXIT_STATUS_SYSTEMD },
+ [EXIT_BPF] = { "BPF", EXIT_STATUS_SYSTEMD },
[EXIT_EXCEPTION] = { "EXCEPTION", EXIT_STATUS_SYSTEMD },
diff --git a/src/shared/exit-status.h b/src/shared/exit-status.h
index 05707bf722..3f9a2ad54f 100644
--- a/src/shared/exit-status.h
+++ b/src/shared/exit-status.h
@@ -71,6 +71,7 @@ enum {
EXIT_CONFIGURATION_DIRECTORY,
EXIT_NUMA_POLICY,
EXIT_CREDENTIALS,
+ EXIT_BPF,
EXIT_EXCEPTION = 255, /* Whenever we want to propagate an abnormal/signal exit, in line with bash */
};
diff --git a/src/systemctl/systemctl-show.c b/src/systemctl/systemctl-show.c
index d008c4172a..9e10560f80 100644
--- a/src/systemctl/systemctl-show.c
+++ b/src/systemctl/systemctl-show.c
@@ -1023,7 +1023,7 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m
return 1;
- } else if (STR_IN_SET(name, "SystemCallFilter", "SystemCallLog", "RestrictAddressFamilies", "RestrictNetworkInterfaces")) {
+ } else if (STR_IN_SET(name, "SystemCallFilter", "SystemCallLog", "RestrictAddressFamilies", "RestrictNetworkInterfaces", "RestrictFileSystems")) {
_cleanup_strv_free_ char **l = NULL;
int allow_list;
diff --git a/src/test/meson.build b/src/test/meson.build
index ee7c1a1c83..cef2532407 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -357,6 +357,17 @@ tests += [
[],
core_includes],
+ [['src/test/test-bpf-lsm.c'],
+ [libcore,
+ libshared],
+ [libmount,
+ threads,
+ librt,
+ libseccomp,
+ libselinux,
+ libblkid],
+ core_includes],
+
[['src/test/test-watch-pid.c'],
[libcore,
libshared],
diff --git a/src/test/test-bpf-lsm.c b/src/test/test-bpf-lsm.c
new file mode 100644
index 0000000000..258c2e575e
--- /dev/null
+++ b/src/test/test-bpf-lsm.c
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "bpf-lsm.h"
+#include "load-fragment.h"
+#include "manager.h"
+#include "process-util.h"
+#include "rlimit-util.h"
+#include "rm-rf.h"
+#include "service.h"
+#include "strv.h"
+#include "tests.h"
+#include "unit.h"
+#include "virt.h"
+
+static int test_restrict_filesystems(Manager *m, const char *unit_name, const char *file_path, char **allowed_filesystems) {
+ _cleanup_free_ char *exec_start = NULL;
+ _cleanup_(unit_freep) Unit *u = NULL;
+ ExecContext *ec = NULL;
+ char **allow_filesystem;
+ int cld_code, r;
+
+ assert_se(u = unit_new(m, sizeof(Service)));
+ assert_se(unit_add_name(u, unit_name) == 0);
+ assert_se(ec = unit_get_exec_context(u));
+
+ STRV_FOREACH(allow_filesystem, allowed_filesystems) {
+ r = config_parse_restrict_filesystems(
+ u->id, "filename", 1, "Service", 1, "RestrictFileSystems", 0,
+ *allow_filesystem, ec, u);
+ if (r < 0)
+ return log_unit_error_errno(u, r, "Failed to parse RestrictFileSystems: %m");
+ }
+
+ assert_se(exec_start = strjoin("cat ", file_path));
+ r = config_parse_exec(u->id, "filename", 1, "Service", 1, "ExecStart",
+ SERVICE_EXEC_START, exec_start, SERVICE(u)->exec_command, u);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse ExecStart");
+
+ SERVICE(u)->type = SERVICE_ONESHOT;
+ u->load_state = UNIT_LOADED;
+
+ r = unit_start(u);
+ if (r < 0)
+ return log_error_errno(r, "Unit start failed %m");
+
+ while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) {
+ r = sd_event_run(m->event, UINT64_MAX);
+ if (r < 0)
+ return log_error_errno(errno, "Event run failed %m");
+ }
+
+ cld_code = SERVICE(u)->exec_command[SERVICE_EXEC_START]->exec_status.code;
+ if (cld_code != CLD_EXITED) {
+ return log_error_errno(-SYNTHETIC_ERRNO(EBUSY), "ExecStart didn't exited, code='%s'", sigchld_code_to_string(cld_code));
+ }
+
+ if (SERVICE(u)->state != SERVICE_DEAD) {
+ return log_error_errno(-SYNTHETIC_ERRNO(EBUSY), "Service is not dead");
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL;
+ _cleanup_(manager_freep) Manager *m = NULL;
+ _cleanup_free_ char *unit_dir = NULL;
+ struct rlimit rl;
+ int r;
+
+ test_setup_logging(LOG_DEBUG);
+
+ if (getuid() != 0)
+ return log_tests_skipped("not running as root");
+
+ assert_se(getrlimit(RLIMIT_MEMLOCK, &rl) >= 0);
+ rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, CAN_MEMLOCK_SIZE);
+ (void) setrlimit_closest(RLIMIT_MEMLOCK, &rl);
+
+ if (!can_memlock())
+ return log_tests_skipped("Can't use mlock(), skipping.");
+
+ r = lsm_bpf_supported();
+ if (r <= 0)
+ return log_tests_skipped("LSM BPF hooks are not supported");
+
+ r = enter_cgroup_subroot(NULL);
+ if (r == -ENOMEDIUM)
+ return log_tests_skipped("cgroupfs not available");
+
+ assert_se(get_testdata_dir("units", &unit_dir) >= 0);
+ assert_se(set_unit_path(unit_dir) >= 0);
+ assert_se(runtime_dir = setup_fake_runtime_dir());
+
+ assert_se(manager_new(UNIT_FILE_SYSTEM, MANAGER_TEST_RUN_BASIC, &m) >= 0);
+ assert_se(manager_startup(m, NULL, NULL, NULL) >= 0);
+
+ /* We need to enable access to the filesystem where the binary is so we
+ * add @common-block */
+ assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("@common-block")) < 0);
+ assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("tracefs", "@common-block")) >= 0);
+ assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("tracefs", "@common-block", "~tracefs")) < 0);
+ assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/debug/sleep_time", STRV_MAKE("@common-block")) < 0);
+ assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/debug/sleep_time", STRV_MAKE("debugfs", "@common-block")) >= 0);
+ assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/debug/sleep_time", STRV_MAKE("~debugfs")) < 0);
+
+ return 0;
+}
diff --git a/test/fuzz/fuzz-unit-file/directives.mount b/test/fuzz/fuzz-unit-file/directives.mount
index bdb8f95917..a7ee579a9a 100644
--- a/test/fuzz/fuzz-unit-file/directives.mount
+++ b/test/fuzz/fuzz-unit-file/directives.mount
@@ -144,6 +144,7 @@ ReadWritePaths=
RemoveIPC=
RestartKillSignal=
RestrictAddressFamilies=
+RestrictFileSystems=
RestrictNamespaces=
RestrictNetworkInterfaces=
RestrictRealtime=
diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service
index d9781f0492..4615a2229a 100644
--- a/test/fuzz/fuzz-unit-file/directives.service
+++ b/test/fuzz/fuzz-unit-file/directives.service
@@ -275,6 +275,7 @@ RestartKillSignal=
RestartPreventExitStatus=
RestartSec=
RestrictAddressFamilies=
+RestrictFileSystems=
RestrictNamespaces=
RestrictNetworkInterfaces=
RestrictRealtime=
diff --git a/test/fuzz/fuzz-unit-file/directives.socket b/test/fuzz/fuzz-unit-file/directives.socket
index 04aeb476f1..df64142982 100644
--- a/test/fuzz/fuzz-unit-file/directives.socket
+++ b/test/fuzz/fuzz-unit-file/directives.socket
@@ -180,6 +180,7 @@ RemoveIPC=
RemoveOnStop=
RestartKillSignal=
RestrictAddressFamilies=
+RestrictFileSystems=
RestrictNamespaces=
RestrictNetworkInterfaces=
RestrictRealtime=
diff --git a/test/fuzz/fuzz-unit-file/directives.swap b/test/fuzz/fuzz-unit-file/directives.swap
index ff54ebfc18..1dfdffb379 100644
--- a/test/fuzz/fuzz-unit-file/directives.swap
+++ b/test/fuzz/fuzz-unit-file/directives.swap
@@ -141,6 +141,7 @@ ReadWritePaths=
RemoveIPC=
RestartKillSignal=
RestrictAddressFamilies=
+RestrictFileSystems=
RestrictNamespaces=
RestrictNetworkInterfaces=
RestrictRealtime=