]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
bpf: add restrict_fs BPF program
authorIago López Galeiras <iagol@microsoft.com>
Fri, 11 Dec 2020 11:40:33 +0000 (12:40 +0100)
committerIago Lopez Galeiras <iagol@microsoft.com>
Wed, 6 Oct 2021 08:52:14 +0000 (10:52 +0200)
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 [new file with mode: 0644]
src/core/bpf/restrict_fs/restrict-fs.bpf.c [new file with mode: 0644]
src/core/meson.build

diff --git a/src/core/bpf/restrict_fs/meson.build b/src/core/bpf/restrict_fs/meson.build
new file mode 100644 (file)
index 0000000..7a21f52
--- /dev/null
@@ -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 (file)
index 0000000..cdc0613
--- /dev/null
@@ -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 <linux/types.h>
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_core_read.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdint.h>
+
+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";
index 4b53d7e43b5aa442745fb4460959333d00f47c7f..62151e1678ed115954af7cdb980f2cc29d0f0a4d 100644 (file)
@@ -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')