[PATCH v2 09/15] selftests/bpf: Add tests for the LSM policy object kfuncs
From: Justin Suess
Date: Mon Aug 31 2026 - 13:59:58 EST
Test the properties of the policy object interface that hold
independently of any LSM implementing the hooks.
The failure programs pin down the verifier-side contract: the kfuncs
are rejected in tracing programs, the fd kfunc in LSM programs, the
apply kfunc in syscall programs, on non-bprm LSM hooks and in
non-sleepable programs, leaked references fail verification, and a
kptr loaded outside an RCU read-side section cannot be acquired.
The syscall program checks the runtime contract of
bpf_lsm_policy_from_fd(): a bad fd, a fd that is no LSM's policy
object, and a nonzero value of the reserved flags all resolve to
NULL.
Exercising the kfuncs against an LSM actually providing policy
objects is left to that LSM's own tests.
Signed-off-by: Justin Suess <utilityemal77@xxxxxxxxx>
---
.../bpf/prog_tests/lsm_policy_kfuncs.c | 54 ++++++
.../selftests/bpf/progs/lsm_policy_kfuncs.c | 52 ++++++
.../bpf/progs/lsm_policy_kfuncs_failure.c | 154 ++++++++++++++++++
3 files changed, 260 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/lsm_policy_kfuncs.c
create mode 100644 tools/testing/selftests/bpf/progs/lsm_policy_kfuncs.c
create mode 100644 tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c
diff --git a/tools/testing/selftests/bpf/prog_tests/lsm_policy_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/lsm_policy_kfuncs.c
new file mode 100644
index 000000000000..9f4ffb5f47be
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/lsm_policy_kfuncs.c
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright © 2026 Justin Suess <utilityemal77@xxxxxxxxx> */
+
+#include <test_progs.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "lsm_policy_kfuncs.skel.h"
+#include "lsm_policy_kfuncs_failure.skel.h"
+
+/*
+ * Runtime contract of bpf_lsm_policy_from_fd(), independent of any
+ * LSM implementing the policy object hooks: a bad fd, a fd that is no
+ * LSM's policy object, and a nonzero value of the reserved flags all
+ * resolve to NULL.
+ */
+static void test_from_fd_null(void)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, opts);
+ struct lsm_policy_kfuncs *skel;
+ char tmp_path[] = "/tmp/lsm_policy_kfuncs_XXXXXX";
+ int tmp_fd, err;
+
+ tmp_fd = mkstemp(tmp_path);
+ if (!ASSERT_GE(tmp_fd, 0, "mkstemp"))
+ return;
+
+ skel = lsm_policy_kfuncs__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
+ goto out_close;
+ skel->bss->plain_fd = tmp_fd;
+
+ err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.check_from_fd),
+ &opts);
+ if (!ASSERT_OK(err, "check_from_fd_run") ||
+ !ASSERT_OK(opts.retval, "check_from_fd_retval"))
+ goto out_destroy;
+
+ ASSERT_TRUE(skel->bss->got_null_for_bad_fd, "bad_fd_null");
+ ASSERT_TRUE(skel->bss->got_null_for_plain_fd, "plain_fd_null");
+ ASSERT_TRUE(skel->bss->got_null_for_bad_flags, "bad_flags_null");
+out_destroy:
+ lsm_policy_kfuncs__destroy(skel);
+out_close:
+ close(tmp_fd);
+ unlink(tmp_path);
+}
+
+void test_lsm_policy_kfuncs(void)
+{
+ if (test__start_subtest("from_fd_null"))
+ test_from_fd_null();
+ RUN_TESTS(lsm_policy_kfuncs_failure);
+}
diff --git a/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs.c b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs.c
new file mode 100644
index 000000000000..f084ccfcde91
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs.c
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright © 2026 Justin Suess <utilityemal77@xxxxxxxxx> */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+char _license[] SEC("license") = "GPL";
+
+extern struct lsm_policy_object *
+bpf_lsm_policy_from_fd(int fd, u32 flags) __ksym;
+extern void bpf_lsm_policy_release(struct lsm_policy_object *object) __ksym;
+
+int plain_fd;
+bool got_null_for_bad_fd;
+bool got_null_for_plain_fd;
+bool got_null_for_bad_flags;
+
+/*
+ * Runs in the test runner's context through BPF_PROG_RUN, where
+ * @plain_fd is meaningful.
+ */
+SEC("syscall")
+int check_from_fd(void *ctx)
+{
+ struct lsm_policy_object *object;
+
+ /* A fd not open in this task's fd table must resolve to NULL. */
+ object = bpf_lsm_policy_from_fd(-1, 0);
+ if (!object)
+ got_null_for_bad_fd = true;
+ else
+ bpf_lsm_policy_release(object);
+
+ /*
+ * A valid fd that is not any LSM's policy object must be
+ * declined by every LSM and resolve to NULL.
+ */
+ object = bpf_lsm_policy_from_fd(plain_fd, 0);
+ if (!object)
+ got_null_for_plain_fd = true;
+ else
+ bpf_lsm_policy_release(object);
+
+ /* The flags are reserved: any nonzero value must resolve to NULL. */
+ object = bpf_lsm_policy_from_fd(plain_fd, 1);
+ if (!object)
+ got_null_for_bad_flags = true;
+ else
+ bpf_lsm_policy_release(object);
+
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c
new file mode 100644
index 000000000000..04080838aefd
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright © 2026 Justin Suess <utilityemal77@xxxxxxxxx> */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+extern struct lsm_policy_object *
+bpf_lsm_policy_acquire(struct lsm_policy_object *object) __ksym;
+extern int bpf_lsm_policy_apply_bprm(struct lsm_policy_object *object,
+ struct linux_binprm *bprm,
+ u32 flags) __ksym;
+extern struct lsm_policy_object *
+bpf_lsm_policy_from_fd(int fd, u32 flags) __ksym;
+extern void bpf_lsm_policy_release(struct lsm_policy_object *object) __ksym;
+void bpf_rcu_read_lock(void) __ksym;
+void bpf_rcu_read_unlock(void) __ksym;
+
+struct policy_slot {
+ struct lsm_policy_object __kptr *object;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, struct policy_slot);
+} policy_map SEC(".maps");
+
+/*
+ * The LSM policy kfuncs are limited to LSM and syscall programs by
+ * the BPF-side kfunc filter: a tracing program calling one must fail
+ * verification.
+ */
+SEC("tp_btf/task_newtask")
+__failure __msg("calling kernel function bpf_lsm_policy_from_fd is not allowed")
+int BPF_PROG(tracing_prog, struct task_struct *task, u64 clone_flags)
+{
+ struct lsm_policy_object *object;
+
+ object = bpf_lsm_policy_from_fd(-1, 0);
+ if (object)
+ bpf_lsm_policy_release(object);
+ return 0;
+}
+
+/*
+ * The fd kfunc is exclusive to syscall programs: it must be rejected
+ * in an LSM program, even on an allowed hook.
+ */
+SEC("lsm.s/bprm_creds_for_exec")
+__failure __msg("calling kernel function bpf_lsm_policy_from_fd is not allowed")
+int BPF_PROG(lsm_get, struct linux_binprm *bprm)
+{
+ struct lsm_policy_object *object;
+
+ object = bpf_lsm_policy_from_fd(-1, 0);
+ if (object)
+ bpf_lsm_policy_release(object);
+ return 0;
+}
+
+/*
+ * The enforcement kfunc is exclusive to the sleepable bprm LSM
+ * hooks: it must be rejected in a syscall program.
+ */
+SEC("syscall")
+__failure __msg("calling kernel function bpf_lsm_policy_apply_bprm is not allowed")
+int syscall_restrict(void *ctx)
+{
+ return bpf_lsm_policy_apply_bprm(NULL, NULL, 0);
+}
+
+/*
+ * Any LSM attach point other than the sleepable bprm hooks must be
+ * rejected for the enforcement kfunc.
+ */
+SEC("lsm.s/file_open")
+__failure __msg("calling kernel function bpf_lsm_policy_apply_bprm is not allowed")
+int BPF_PROG(wrong_hook, struct file *file)
+{
+ return bpf_lsm_policy_apply_bprm(NULL, NULL, 0);
+}
+
+/*
+ * The enforcement kfunc may sleep: a non-sleepable program on an
+ * allowed hook must be rejected.
+ */
+SEC("lsm/bprm_creds_for_exec")
+__failure
+__msg("program must be sleepable to call sleepable kfunc bpf_lsm_policy_apply_bprm")
+int BPF_PROG(nonsleepable_prog, struct linux_binprm *bprm)
+{
+ return bpf_lsm_policy_apply_bprm(NULL, bprm, 0);
+}
+
+/* An acquired policy object reference must be released before returning. */
+SEC("syscall")
+__failure __msg("Unreleased reference")
+int leak_policy(void *ctx)
+{
+ bpf_lsm_policy_from_fd(-1, 0);
+ return 0;
+}
+
+/*
+ * A kptr loaded outside an RCU read-side critical section is
+ * untrusted: the acquire kfunc must reject it.
+ */
+SEC("lsm.s/file_open")
+__failure __msg("must be a rcu pointer")
+int BPF_PROG(acquire_untrusted, struct file *file)
+{
+ struct lsm_policy_object *object;
+ struct policy_slot *slot;
+ int key = 0;
+
+ slot = bpf_map_lookup_elem(&policy_map, &key);
+ if (!slot)
+ return 0;
+
+ object = slot->object;
+ if (!object)
+ return 0;
+
+ object = bpf_lsm_policy_acquire(object);
+ if (object)
+ bpf_lsm_policy_release(object);
+ return 0;
+}
+
+/* A reference acquired from a shared policy object must be released too. */
+SEC("lsm.s/file_open")
+__failure __msg("Unreleased reference")
+int BPF_PROG(leak_shared_policy, struct file *file)
+{
+ struct lsm_policy_object *object;
+ struct policy_slot *slot;
+ int key = 0;
+
+ slot = bpf_map_lookup_elem(&policy_map, &key);
+ if (!slot)
+ return 0;
+
+ bpf_rcu_read_lock();
+ object = slot->object;
+ if (object)
+ object = bpf_lsm_policy_acquire(object);
+ bpf_rcu_read_unlock();
+ return 0;
+}
--
2.55.0