From 2ac5f90d59f6aba8de4fcf883c78875e86492f53 Mon Sep 17 00:00:00 2001 From: Iago Lopez Galeiras Date: Thu, 19 Aug 2021 11:26:49 +0200 Subject: [PATCH 01/19] basic: move CIFS magic number to missing_magic.h It fits better there. --- src/basic/missing_fs.h | 5 ----- src/basic/missing_magic.h | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) 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..8408c4d6e6 100644 --- a/src/basic/missing_magic.h +++ b/src/basic/missing_magic.h @@ -37,3 +37,8 @@ #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 From 3ef4e91abe19f5aa89e1c022bb5438716c07144f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iago=20L=C3=B3pez=20Galeiras?= Date: Mon, 8 Feb 2021 18:32:22 +0100 Subject: [PATCH 02/19] missing_magic: add several filesystems They were failing on CI. --- src/basic/missing_magic.h | 130 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/src/basic/missing_magic.h b/src/basic/missing_magic.h index 8408c4d6e6..1f50e565a2 100644 --- a/src/basic/missing_magic.h +++ b/src/basic/missing_magic.h @@ -42,3 +42,133 @@ #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 From 1315ce3120c2e6850e9386b64fbc4e5c7f34048c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iago=20L=C3=B3pez=20Galeiras?= Date: Mon, 8 Feb 2021 19:54:24 +0100 Subject: [PATCH 03/19] basic: add filesystem database Stores filesystem_name -> magic_number(s). --- src/basic/check-filesystems.sh | 36 +++++++ src/basic/filesystems-gperf.gperf | 112 ++++++++++++++++++++++ src/basic/filesystems.c | 131 ++++++++++++++++++++++++++ src/basic/filesystems.h | 38 ++++++++ src/basic/generate-filesystem-list.py | 12 +++ src/basic/meson.build | 2 + 6 files changed, 331 insertions(+) create mode 100755 src/basic/check-filesystems.sh create mode 100644 src/basic/filesystems-gperf.gperf create mode 100644 src/basic/filesystems.c create mode 100644 src/basic/filesystems.h create mode 100755 src/basic/generate-filesystem-list.py 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..4f172b48e9 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 From 659d19243c9229caec5521ef9c6a5a1fd32235d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iago=20L=C3=B3pez=20Galeiras?= Date: Mon, 8 Feb 2021 15:14:20 +0100 Subject: [PATCH 04/19] basic: use filesystem database --- src/basic/meson.build | 28 ++++++++++++++++++++ src/basic/mountpoint-util.c | 52 ++++++++++++++----------------------- src/basic/stat-util.c | 15 ++++------- 3 files changed, 53 insertions(+), 42 deletions(-) diff --git a/src/basic/meson.build b/src/basic/meson.build index 4f172b48e9..3bc5196fa0 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -375,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/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) { From 021d1e96123289182565f0b3ce5a705b0e84fe48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iago=20L=C3=B3pez=20Galeiras?= Date: Fri, 11 Dec 2020 12:40:33 +0100 Subject: [PATCH 05/19] bpf: add restrict_fs BPF program It hooks into the file_open LSM hook and allows only when the filesystem where the open will take place is present in a BPF map for a particular cgroup. The BPF map used is a hash of maps with the following structure: cgroupID -> (s_magic -> uint32) The inner map is effectively a set. The entry at key 0 in the inner map encodes whether the program behaves as an allow list or a deny list: if its value is 0 it is a deny list, otherwise it is an allow list. When the cgroupID is present in the map, the program checks the inner map for the magic number of the filesystem associated with the file that's being opened. When the program behaves as an allow list, if that magic number is present it allows the open to succeed, when the program behaves as a deny list, it only allows access if the that magic number is NOT present. When access is denied the program returns -EPERM. The BPF program uses CO-RE (Compile-Once Run-Everywhere) to access internal kernel structures without needing kernel headers present at runtime. --- src/core/bpf/restrict_fs/meson.build | 14 ++++ src/core/bpf/restrict_fs/restrict-fs.bpf.c | 78 ++++++++++++++++++++++ src/core/meson.build | 2 + 3 files changed, 94 insertions(+) create mode 100644 src/core/bpf/restrict_fs/meson.build create mode 100644 src/core/bpf/restrict_fs/restrict-fs.bpf.c 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/meson.build b/src/core/meson.build index 4b53d7e43b..62151e1678 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -134,6 +134,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') From 535e3dd091aedaa47a5e349146d264f8769844c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iago=20L=C3=B3pez=20Galeiras?= Date: Fri, 11 Dec 2020 13:15:25 +0100 Subject: [PATCH 06/19] cgroup-util: add cg_path_get_cgroupid() It returns the cgroupID from a cgroup path. --- src/basic/cgroup-util.c | 23 +++++++++++++++++++++++ src/basic/cgroup-util.h | 1 + 2 files changed, 24 insertions(+) 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..eec13e18f1 100644 --- a/src/basic/cgroup-util.h +++ b/src/basic/cgroup-util.h @@ -236,6 +236,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); From d13b60d8af64b2c13e858f6573de9debbf5f5f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iago=20L=C3=B3pez=20Galeiras?= Date: Fri, 11 Dec 2020 13:24:13 +0100 Subject: [PATCH 07/19] exit-status: add EXIT_BPF It will be used later. --- src/shared/exit-status.c | 5 +++-- src/shared/exit-status.h | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) 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 */ }; From 510cdbeb5ba841c9e9d9fa62303ecb2673b77b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iago=20L=C3=B3pez=20Galeiras?= Date: Mon, 12 Jul 2021 12:18:31 +0200 Subject: [PATCH 08/19] shared/bpf-dlopen: expose more libbpf functions They're needed for the LSM BPF feature. --- src/shared/bpf-dlopen.c | 8 ++++++++ src/shared/bpf-dlopen.h | 4 ++++ 2 files changed, 12 insertions(+) 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 *); From 184b4f78cfbded54a6e06bbe1152256c204a7a73 Mon Sep 17 00:00:00 2001 From: Iago Lopez Galeiras Date: Tue, 13 Jul 2021 09:51:06 +0200 Subject: [PATCH 09/19] core: add BPF LSM functions This adds 6 functions to implement RestrictFileSystems= * lsm_bpf_supported() checks if LSM BPF is supported. It checks that cgroupv2 is used, that BPF LSM is enabled, and tries to load the BPF LSM program which makes sure BTF and hash of maps are supported, and BPF LSM programs can be loaded. * lsm_bpf_setup() loads and attaches the LSM BPF program. * lsm_bpf_unit_restrict_filesystems() populates the hash of maps BPF map with the cgroupID and the set of allowed or denied filesystems. * lsm_bpf_cleanup() removes a cgroupID entry from the hash of maps. * lsm_bpf_map_restrict_fs_fd() is a helper function to get the file descriptor of the BPF map. * lsm_bpf_destroy() is a wrapper around the destroy function of the BPF skeleton file. --- src/core/bpf-lsm.c | 327 +++++++++++++++++++++++++++++++++++++++++++ src/core/bpf-lsm.h | 16 +++ src/core/cgroup.c | 14 ++ src/core/manager.h | 3 + src/core/meson.build | 2 + src/core/unit.h | 1 + 6 files changed, 363 insertions(+) create mode 100644 src/core/bpf-lsm.c create mode 100644 src/core/bpf-lsm.h diff --git a/src/core/bpf-lsm.c b/src/core/bpf-lsm.c new file mode 100644 index 0000000000..3e480c6257 --- /dev/null +++ b/src/core/bpf-lsm.c @@ -0,0 +1,327 @@ +/* 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 diff --git a/src/core/bpf-lsm.h b/src/core/bpf-lsm.h new file mode 100644 index 0000000000..625fb32b50 --- /dev/null +++ b/src/core/bpf-lsm.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "hashmap.h" + +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); diff --git a/src/core/cgroup.c b/src/core/cgroup.c index a5770c0332..1559259622 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -2102,6 +2102,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 +2122,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); 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 62151e1678..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 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 */ From b1994387d3cb50b212fc4815941a8ff40d60cd85 Mon Sep 17 00:00:00 2001 From: Iago Lopez Galeiras Date: Tue, 5 Oct 2021 13:18:49 +0200 Subject: [PATCH 10/19] core: use LSM BPF functions to implement RestrictFileSystems= It attaches the LSM BPF program when the system manager starts up. It populates the hash of maps BPF map when services that have RestrictFileSystems= set start. It cleans up the hash of maps when the unit cgroup is pruned. To pass the file descriptor of the BPF map we add it to the keep_fds array. --- src/basic/cgroup-util.h | 3 ++ src/core/cgroup.c | 10 +++++++ src/core/cgroup.h | 1 + src/core/execute.c | 61 ++++++++++++++++++++++++++++++++++++++++- src/core/execute.h | 10 +++++++ src/core/main.c | 3 ++ src/core/manager.c | 12 ++++++++ 7 files changed, 99 insertions(+), 1 deletion(-) diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h index eec13e18f1..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, diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 1559259622..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 @@ -2736,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/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/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); } From e59ccd035c94a8448d9b99bb0b8056ed3d3a339c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iago=20L=C3=B3pez=20Galeiras?= Date: Thu, 11 Feb 2021 16:59:30 +0100 Subject: [PATCH 11/19] core: add RestrictFileSystems= fragment parser It takes an allow or deny list of filesystems services should have access to. --- src/core/bpf-lsm.c | 54 ++++++++++++++++ src/core/bpf-lsm.h | 12 ++++ src/core/load-fragment-gperf.gperf.in | 1 + src/core/load-fragment.c | 72 +++++++++++++++++++++ src/core/load-fragment.h | 1 + src/systemctl/systemctl-show.c | 2 +- test/fuzz/fuzz-unit-file/directives.mount | 1 + test/fuzz/fuzz-unit-file/directives.service | 1 + test/fuzz/fuzz-unit-file/directives.socket | 1 + test/fuzz/fuzz-unit-file/directives.swap | 1 + 10 files changed, 145 insertions(+), 1 deletion(-) diff --git a/src/core/bpf-lsm.c b/src/core/bpf-lsm.c index 3e480c6257..46c2446849 100644 --- a/src/core/bpf-lsm.c +++ b/src/core/bpf-lsm.c @@ -325,3 +325,57 @@ 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 index 625fb32b50..8bd58a29e5 100644 --- a/src/core/bpf-lsm.h +++ b/src/core/bpf-lsm.h @@ -3,6 +3,12 @@ #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; @@ -14,3 +20,9 @@ int lsm_bpf_unit_restrict_filesystems(Unit *u, const Set *filesystems, bool allo 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/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/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/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= From cc86a278de81873242df232fb966357ac1ad4f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iago=20L=C3=B3pez=20Galeiras?= Date: Fri, 11 Dec 2020 13:20:23 +0100 Subject: [PATCH 12/19] core: add dbus RestrictFileSystems= properties --- man/org.freedesktop.systemd1.xml | 24 ++++++++ src/core/dbus-execute.c | 100 +++++++++++++++++++++++++++++++ src/shared/bus-unit-util.c | 1 + 3 files changed, 125 insertions(+) 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/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/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")) { From af112391966610d5ec3a4360f490222b15a61895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iago=20L=C3=B3pez=20Galeiras?= Date: Fri, 11 Dec 2020 13:03:54 +0100 Subject: [PATCH 13/19] mkosi: add libbpf dependency For distros that ship libbpf >=0.2.0. --- mkosi.default.d/arch/mkosi.arch | 4 ++++ mkosi.default.d/debian/mkosi.debian | 5 +++++ 2 files changed, 9 insertions(+) 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 From a6826f6b8e9a765485bd045201af8164a02a63d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iago=20L=C3=B3pez=20Galeiras?= Date: Mon, 8 Feb 2021 15:06:22 +0100 Subject: [PATCH 14/19] man: add RestrictFileSystems= documentation --- man/systemd.exec.xml | 94 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 3cef36d3c3..b534454396 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= From d6d6f55d8e78e2d758fc20cf1906ad7eb4698328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iago=20L=C3=B3pez=20Galeiras?= Date: Mon, 8 Feb 2021 15:06:29 +0100 Subject: [PATCH 15/19] man: document EXIT_BPF status --- man/systemd.exec.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index b534454396..2b7129eb40 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -3855,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. + From 8216741cf9df00f7d71fa52ae933ecd129f94265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iago=20L=C3=B3pez=20Galeiras?= Date: Fri, 11 Dec 2020 17:32:54 +0100 Subject: [PATCH 16/19] test: add test-bpf-lsm --- src/test/meson.build | 11 ++++ src/test/test-bpf-lsm.c | 109 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 src/test/test-bpf-lsm.c 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; +} From ec31dd5a9865fccfa5fa295b5e17ddae61f19468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iago=20L=C3=B3pez=20Galeiras?= Date: Tue, 22 Dec 2020 20:27:50 +0100 Subject: [PATCH 17/19] README: document LSM BPF requirements --- README | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 From b41711cd46b7c643396f4d784bd35f1fa9211215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iago=20L=C3=B3pez=20Galeiras?= Date: Mon, 8 Feb 2021 15:07:19 +0100 Subject: [PATCH 18/19] analyze: add filesystems command --- src/analyze/analyze.c | 173 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) 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 }, From 20080622de114a7d297c21187f471463c5437eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iago=20L=C3=B3pez=20Galeiras?= Date: Mon, 8 Feb 2021 15:10:13 +0100 Subject: [PATCH 19/19] man: document systemd-analyze filesystems --- man/systemd-analyze.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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. + + <command>systemd-analyze filesystems <optional><replaceable>SET</replaceable>...</optional></command> + + 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. + + <command>systemd-analyze calendar <replaceable>EXPRESSION</replaceable>...</command>