/* ---- 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)
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)
{
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, \
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");
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;
}
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]);
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",
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;
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),