From: Justin Suess <utilityemal77@gmail.com>
To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org,
kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net,
viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org
Cc: gnoack@google.com, jack@suse.cz, song@kernel.org,
yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org,
bpf@vger.kernel.org, linux-security-module@vger.kernel.org,
linux-kernel@vger.kernel.org,
Justin Suess <utilityemal77@gmail.com>
Subject: [PATCH v2 09/15] selftests/bpf: Add tests for the LSM policy object kfuncs
Date: Mon, 31 Aug 2026 10:58:51 -0400 [thread overview]
Message-ID: <20260831145858.3869191-10-utilityemal77@gmail.com> (raw)
In-Reply-To: <20260831145858.3869191-1-utilityemal77@gmail.com>
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@gmail.com>
---
.../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@gmail.com> */
+
+#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@gmail.com> */
+
+#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@gmail.com> */
+
+#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
next prev parent reply other threads:[~2026-08-31 15:00 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-31 14:58 [PATCH v2 00/15] BPF interface for applying Landlock rulesets Justin Suess
2026-08-31 14:58 ` [PATCH v2 01/15] lsm: Add the LSM policy object lifetime hooks Justin Suess
2026-08-31 17:17 ` Casey Schaufler
2026-08-31 17:41 ` Justin Suess
2026-09-02 13:05 ` Justin Suess
2026-09-02 17:51 ` Casey Schaufler
2026-09-02 18:28 ` Justin Suess
2026-08-31 14:58 ` [PATCH v2 02/15] lsm: Add the bprm_apply_policy_object LSM hook Justin Suess
2026-08-31 14:58 ` [PATCH v2 03/15] lsm: Move the lsm_for_each_hook() macro to security/lsm.h Justin Suess
2026-08-31 14:58 ` [PATCH v2 04/15] lsm: Add the bpf_lsm_policy_release kfunc and policy object destructor Justin Suess
2026-08-31 14:58 ` [PATCH v2 05/15] lsm: Add the bpf_lsm_policy_from_fd kfunc Justin Suess
2026-08-31 14:58 ` [PATCH v2 06/15] lsm: Add the bpf_lsm_policy_acquire kfunc Justin Suess
2026-08-31 14:58 ` [PATCH v2 07/15] lsm: Add the bpf_lsm_policy_apply_bprm kfunc Justin Suess
2026-08-31 14:58 ` [PATCH v2 08/15] lsm: Document the LSM policy object interface Justin Suess
2026-08-31 14:58 ` Justin Suess [this message]
2026-08-31 14:58 ` [PATCH v2 10/15] landlock: Expose the ruleset fd lookup to the rest of Landlock Justin Suess
2026-08-31 14:58 ` [PATCH v2 11/15] landlock: Factor the credential restriction out of landlock_restrict_self() Justin Suess
2026-08-31 14:58 ` [PATCH v2 12/15] landlock: Free rulesets after an RCU grace period Justin Suess
2026-08-31 14:58 ` [PATCH v2 13/15] landlock: Implement the LSM policy object hooks Justin Suess
2026-08-31 14:58 ` [PATCH v2 14/15] selftests/bpf: Test the LSM policy object kfuncs with Landlock Justin Suess
2026-08-31 19:53 ` sashiko-bot
2026-09-02 12:24 ` Justin Suess
2026-08-31 14:58 ` [PATCH v2 15/15] landlock: Document the BPF policy interface Justin Suess
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260831145858.3869191-10-utilityemal77@gmail.com \
--to=utilityemal77@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=brauner@kernel.org \
--cc=daniel@iogearbox.net \
--cc=gnoack@google.com \
--cc=jack@suse.cz \
--cc=kees@kernel.org \
--cc=kpsingh@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=m@maowtm.org \
--cc=martin.lau@linux.dev \
--cc=mic@digikod.net \
--cc=paul@paul-moore.com \
--cc=song@kernel.org \
--cc=viro@zeniv.linux.org.uk \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox