]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: work around btf_ctx_access() rejection of const void * in BPF LSM
authorChristian Brauner <brauner@kernel.org>
Fri, 8 May 2026 08:53:16 +0000 (10:53 +0200)
committerChristian Brauner <brauner@kernel.org>
Wed, 13 May 2026 08:36:13 +0000 (10:36 +0200)
Kernels before v6.16 (missing commit 1271a40eeafa "bpf: Allow access to
const void pointer arguments in tracing programs") have a bug in
btf_ctx_access() where const void * parameters in LSM hook signatures
are not recognized as void pointers. The function checks t->type == 0
to detect void *, but for const void * the BTF chain is PTR -> CONST ->
void, so t->type points to the CONST node rather than directly to
type_id 0. This causes the verifier to reject any BPF program that
reads the const void *value argument of bdev_setintegrity:

  func 'bpf_lsm_bdev_setintegrity' arg2 type UNKNOWN is not a struct
  invalid bpf_context access off=16 size=8

Work around this by providing a compat variant of the
bdev_setintegrity BPF program that avoids reading the const void *value
argument entirely. Instead it reads the size argument (a scalar integer)
directly from the raw BPF context (ctx[3]), which is not subject to the
broken type check. This is safe because dm-verity guarantees that value
and size are always in lockstep: both NULL/0 for unsigned devices, both
non-zero for signed devices.

The loader tries the full version first (which reads both value and size
for defense-in-depth) and falls back to the compat variant if loading
fails. bpf_program__set_autoload(false) disables whichever variant is
not needed so the verifier never sees it.

This compat logic can be removed once the minimum kernel baseline
includes the 1271a40eeafa fix.

Signed-off-by: Christian Brauner <brauner@kernel.org>
src/bpf/restrict-fsaccess.bpf.c
src/core/bpf-restrict-fsaccess.c
src/shared/bpf-dlopen.c
src/shared/bpf-dlopen.h

index 538ddf3ef17b66618bee965b8e56330ed73431e5..13e11aff500bea480f68b616d2cf3bec7c144317 100644 (file)
@@ -69,6 +69,13 @@ volatile __u32 protected_link_ids[NUM_PROTECTED_OBJS];
 
 /* ---- Integrity tracking hooks ---- */
 
+/* Preferred version: reads both value and size for defense-in-depth.
+ * Requires kernel v6.16+ or the backport of 1271a40eeafa ("bpf: Allow
+ * access to const void pointer arguments in tracing programs").
+ * On older kernels btf_ctx_access() rejects loads from const void *
+ * arguments because it fails to skip the CONST modifier when checking
+ * for void pointers. prepare_restrict_fsaccess_bpf() tries this version
+ * first and falls back to the _compat variant below if loading fails. */
 SEC("lsm/bdev_setintegrity")
 int BPF_PROG(restrict_fsaccess_bdev_setintegrity, struct block_device *bdev,
              enum lsm_integrity_type type, const void *value, __u64 size)
@@ -82,6 +89,24 @@ int BPF_PROG(restrict_fsaccess_bdev_setintegrity, struct block_device *bdev,
         return 0;
 }
 
+/* Compatibility version for kernels without 1271a40eeafa: does not
+ * read the const void *value argument (ctx[2]) to avoid the verifier
+ * rejection. Reads size (ctx[3]) directly from the raw context instead.
+ * This is safe because dm-verity guarantees value!=NULL iff size>0. */
+#define BDEV_SETINTEGRITY_SIZE_CTX_IDX 3 /* bdev_setintegrity(bdev, type, value, size) */
+SEC("lsm/bdev_setintegrity")
+int BPF_PROG(restrict_fsaccess_bdev_setintegrity_compat, struct block_device *bdev,
+             enum lsm_integrity_type type)
+{
+        if (type == LSM_INT_DMVERITY_SIG_VALID) {
+                __u32 dev = bdev->bd_dev;
+                __u8 valid = ctx[BDEV_SETINTEGRITY_SIZE_CTX_IDX] > 0;
+                bpf_map_update_elem(&verity_devices, &dev, &valid, BPF_ANY);
+        }
+
+        return 0;
+}
+
 SEC("lsm/bdev_free_security")
 void BPF_PROG(restrict_fsaccess_bdev_free, struct block_device *bdev)
 {
index a38665a6e20d6e45b10de237fd0da0839d6cb7bd..be66f7da01c927f16629e5696612769868e9a7fc 100644 (file)
@@ -62,9 +62,13 @@ assert_cc(offsetof(struct restrict_fsaccess_bss, protected_map_id_verity) ==
 assert_cc(offsetof(struct restrict_fsaccess_bss, protected_map_id_bss) ==
           offsetof(typeof_field(struct restrict_fsaccess_bpf, bss[0]), protected_map_id_bss));
 
-/* Build the skeleton links array indexed by the link enum. */
+/* Build the skeleton links array indexed by the link enum.
+ * For BDEV_SETINTEGRITY, use whichever variant was loaded (full or compat).
+ * This compat logic can be removed once the kernel baseline includes
+ * 1271a40eeafa ("bpf: Allow access to const void pointer arguments"). */
 #define RESTRICT_FSACCESS_LINKS(obj) {                                                                      \
-        [RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_SETINTEGRITY] = (obj)->links.restrict_fsaccess_bdev_setintegrity,          \
+        [RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_SETINTEGRITY] = (obj)->links.restrict_fsaccess_bdev_setintegrity ?:        \
+                                                 (obj)->links.restrict_fsaccess_bdev_setintegrity_compat,   \
         [RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_FREE]         = (obj)->links.restrict_fsaccess_bdev_free,                  \
         [RESTRICT_FILESYSTEM_ACCESS_LINK_BPRM_CHECK]        = (obj)->links.restrict_fsaccess_bprm_check,                 \
         [RESTRICT_FILESYSTEM_ACCESS_LINK_MMAP_FILE]         = (obj)->links.restrict_fsaccess_mmap_file,                  \
@@ -109,6 +113,11 @@ int bpf_restrict_fsaccess_prepare(struct restrict_fsaccess_bpf **ret) {
 
         assert(ret);
 
+        /* Try the preferred version first — it reads the const void *value
+         * argument for defense-in-depth. On kernels before v6.16 (missing
+         * 1271a40eeafa) the verifier rejects loads from const void * context
+         * arguments, so we fall back to the _compat variant that only reads
+         * the size argument via raw ctx access. */
         obj = restrict_fsaccess_bpf__open();
         if (!obj)
                 return log_error_errno(errno, "bpf-restrict-fsaccess: Failed to open BPF object: %m");
@@ -117,10 +126,37 @@ int bpf_restrict_fsaccess_prepare(struct restrict_fsaccess_bpf **ret) {
         if (r < 0)
                 return log_error_errno(r, "bpf-restrict-fsaccess: Failed to size hash table: %m");
 
+        r = sym_bpf_program__set_autoload(obj->progs.restrict_fsaccess_bdev_setintegrity_compat, false);
+        if (r < 0)
+                return log_error_errno(r, "bpf-restrict-fsaccess: Failed to disable compat program: %m");
+
+        r = restrict_fsaccess_bpf__load(obj);
+        if (r >= 0) {
+                log_debug("bpf-restrict-fsaccess: Loaded with full const void * access.");
+                *ret = TAKE_PTR(obj);
+                return 0;
+        }
+
+        log_debug_errno(r, "bpf-restrict-fsaccess: Full version failed to load (%m), trying compat variant.");
+        obj = restrict_fsaccess_bpf_free(obj);
+
+        obj = restrict_fsaccess_bpf__open();
+        if (!obj)
+                return log_error_errno(errno, "bpf-restrict-fsaccess: Failed to reopen BPF object: %m");
+
+        r = sym_bpf_map__set_max_entries(obj->maps.verity_devices, DMVERITY_DEVICES_MAX);
+        if (r < 0)
+                return log_error_errno(r, "bpf-restrict-fsaccess: Failed to size hash table: %m");
+
+        r = sym_bpf_program__set_autoload(obj->progs.restrict_fsaccess_bdev_setintegrity, false);
+        if (r < 0)
+                return log_error_errno(r, "bpf-restrict-fsaccess: Failed to disable full program: %m");
+
         r = restrict_fsaccess_bpf__load(obj);
         if (r < 0)
-                return log_error_errno(r, "bpf-restrict-fsaccess: Failed to load BPF object: %m");
+                return log_error_errno(r, "bpf-restrict-fsaccess: Failed to load BPF object (compat): %m");
 
+        log_debug("bpf-restrict-fsaccess: Loaded with compat bdev_setintegrity.");
         *ret = TAKE_PTR(obj);
         return 0;
 }
@@ -271,6 +307,13 @@ int bpf_restrict_fsaccess_populate_guard(struct restrict_fsaccess_bpf *obj) {
         FOREACH_ELEMENT(link, links) {
                 size_t idx = link - links;
 
+                /* BDEV_SETINTEGRITY slot resolves via ?: between full and compat
+                 * variants; assert at least one was attached. */
+                if (!*link)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENODATA),
+                                               "bpf-restrict-fsaccess: %s link missing after attach.",
+                                               restrict_fsaccess_link_names[idx]);
+
                 r = bpf_get_link_ids(sym_bpf_link__fd(*link),
                                      &obj->bss->protected_link_ids[idx],
                                      &obj->bss->protected_prog_ids[idx]);
@@ -412,6 +455,13 @@ int bpf_restrict_fsaccess_setup(Manager *m) {
         FOREACH_ELEMENT(link, links) {
                 size_t idx = link - links;
 
+                if (!*link) {
+                        r = log_error_errno(SYNTHETIC_ERRNO(ENODATA),
+                                            "bpf-restrict-fsaccess: %s link missing after attach.",
+                                            restrict_fsaccess_link_names[idx]);
+                        goto fail;
+                }
+
                 m->restrict_fsaccess_link_fds[idx] = fcntl(sym_bpf_link__fd(*link), F_DUPFD_CLOEXEC, 3);
                 if (m->restrict_fsaccess_link_fds[idx] < 0) {
                         r = log_error_errno(errno, "bpf-restrict-fsaccess: Failed to dup link FD for %s: %m",
index c7d9dbdd5bfa6d8ef5bb49909814a037160f1562..ca746828233c715a81da961d14e0fbea428afb8c 100644 (file)
@@ -50,6 +50,7 @@ DLSYM_PROTOTYPE(bpf_program__attach) = NULL;
 DLSYM_PROTOTYPE(bpf_program__attach_cgroup) = NULL;
 DLSYM_PROTOTYPE(bpf_program__attach_lsm) = NULL;
 DLSYM_PROTOTYPE(bpf_program__name) = NULL;
+DLSYM_PROTOTYPE(bpf_program__set_autoload) = NULL;
 DLSYM_PROTOTYPE(libbpf_set_print) = NULL;
 DLSYM_PROTOTYPE(ring_buffer__epoll_fd) = NULL;
 DLSYM_PROTOTYPE(ring_buffer__free) = NULL;
@@ -174,6 +175,7 @@ int dlopen_bpf(int log_level) {
                         DLSYM_ARG_FORCE(bpf_program__attach_lsm),
 #endif
                         DLSYM_ARG(bpf_program__name),
+                        DLSYM_ARG(bpf_program__set_autoload),
                         DLSYM_ARG(libbpf_get_error),
                         DLSYM_ARG(libbpf_set_print),
                         DLSYM_ARG(ring_buffer__epoll_fd),
index 71e6ca5d1d65aa35297a9fbc675e61753782a978..62ea9cf707b8465a49ea8fc4368111a111c6ded7 100644 (file)
@@ -39,6 +39,7 @@ extern DLSYM_PROTOTYPE(bpf_program__attach);
 extern DLSYM_PROTOTYPE(bpf_program__attach_cgroup);
 extern DLSYM_PROTOTYPE(bpf_program__attach_lsm);
 extern DLSYM_PROTOTYPE(bpf_program__name);
+extern DLSYM_PROTOTYPE(bpf_program__set_autoload);
 extern DLSYM_PROTOTYPE(libbpf_set_print);
 extern DLSYM_PROTOTYPE(ring_buffer__epoll_fd);
 extern DLSYM_PROTOTYPE(ring_buffer__free);