All of lore.kernel.org
 help / color / mirror / Atom feed
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 bpf-next 11/13] selftests/bpf: Add tests for the Landlock policy kfuncs
Date: Thu, 30 Jul 2026 22:20:44 -0400	[thread overview]
Message-ID: <20260731022047.189137-12-utilityemal77@gmail.com> (raw)
In-Reply-To: <20260731022047.189137-1-utilityemal77@gmail.com>

Exercise the LSM policy kfuncs end to end and cover the verifier-time
restrictions enforced by the BPF-side kfunc filter.

The success flow mirrors the intended deployment: a syscall program,
run by the test runner through BPF_PROG_RUN, acquires the ruleset
created by the runner from its fd (resolved in the runner's own fd
table) and parks it in a map kptr slot; a program attached to the
sleepable bprm_creds_for_exec() hook takes it from the map, enforces
it on the monitored execution with bpf_landlock_restrict_binprm()
with flags configured per scenario, and puts it back for the next
execution.  The runner creates a real Landlock ruleset handling
LANDLOCK_ACCESS_FS_WRITE_FILE without any rule and checks that:

- a monitored child ends up landlocked (writing to a tmp file fails
  while a control execution succeeds);
- the audit log flags are accepted;
- LANDLOCK_RESTRICT_SELF_TSYNC is rejected with -EINVAL and leaves
  the execution unrestricted;
- a second bpf_landlock_restrict_binprm() call on the same execution
  replaces the previously staged domain instead of failing or
  stacking;
- a staged restriction is discarded when the execution fails after
  the bprm hook: the child execs an ENOEXEC file with a restriction
  staged, and after the failed execve(2) verifies that it is not
  landlocked;
- a negative fd resolved through the acquire kfunc returns NULL.

The failure programs check that verification rejects:
- a tracing program calling the kfuncs (LSM and syscall programs
  only),
- an LSM program calling the acquire kfunc (syscall programs only,
  where the ruleset fd is meaningful),
- a syscall program calling the enforcement kfunc (sleepable bprm
  LSM hooks only),
- an LSM program on a hook other than the sleepable bprm hooks,
- a non-sleepable LSM program on an allowed hook,
- a program leaking the acquired ruleset reference.

The test needs CONFIG_SECURITY_LANDLOCK and the landlock LSM enabled
in the test kernel; the runner skips if the Landlock syscalls are
unavailable.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 tools/testing/selftests/bpf/config            |   1 +
 tools/testing/selftests/bpf/config.x86_64     |   2 +-
 .../bpf/prog_tests/lsm_policy_kfuncs.c        | 329 ++++++++++++++++++
 .../bpf/progs/lsm_policy_kfuncs_failure.c     | 100 ++++++
 .../bpf/progs/lsm_policy_kfuncs_success.c     | 107 ++++++
 5 files changed, 538 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/lsm_policy_kfuncs.c
 create mode 100644 tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c
 create mode 100644 tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c

diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config
index ea7044f30adc..2fa734497461 100644
--- a/tools/testing/selftests/bpf/config
+++ b/tools/testing/selftests/bpf/config
@@ -120,6 +120,7 @@ CONFIG_SAMPLES=y
 CONFIG_SAMPLE_LIVEPATCH=m
 CONFIG_SECURITY=y
 CONFIG_SECURITYFS=y
+CONFIG_SECURITY_LANDLOCK=y
 CONFIG_SYN_COOKIES=y
 CONFIG_TEST_BPF=m
 CONFIG_UDMABUF=y
diff --git a/tools/testing/selftests/bpf/config.x86_64 b/tools/testing/selftests/bpf/config.x86_64
index 523e0d29bbd4..2c4d857f69f5 100644
--- a/tools/testing/selftests/bpf/config.x86_64
+++ b/tools/testing/selftests/bpf/config.x86_64
@@ -125,7 +125,7 @@ CONFIG_LEGACY_VSYSCALL_NONE=y
 CONFIG_LOG_BUF_SHIFT=21
 CONFIG_LOG_CPU_MAX_BUF_SHIFT=0
 CONFIG_LOGO=y
-CONFIG_LSM="selinux,bpf,integrity"
+CONFIG_LSM="landlock,selinux,bpf,integrity"
 CONFIG_MAC_PARTITION=y
 CONFIG_MAGIC_SYSRQ=y
 CONFIG_MCORE2=y
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..c91929f98e88
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/lsm_policy_kfuncs.c
@@ -0,0 +1,329 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright © 2026 Justin Suess <utilityemal77@gmail.com> */
+
+#include <test_progs.h>
+#include <errno.h>
+#include <linux/landlock.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "lsm_policy_kfuncs_success.skel.h"
+#include "lsm_policy_kfuncs_failure.skel.h"
+
+/* Fallbacks for old system headers. */
+#ifndef LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON
+#define LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON (1U << 1)
+#endif
+#ifndef LANDLOCK_RESTRICT_SELF_TSYNC
+#define LANDLOCK_RESTRICT_SELF_TSYNC (1U << 3)
+#endif
+
+static int create_ruleset(void)
+{
+	const struct landlock_ruleset_attr attr = {
+		.handled_access_fs = LANDLOCK_ACCESS_FS_WRITE_FILE,
+	};
+
+	return syscall(__NR_landlock_create_ruleset, &attr, sizeof(attr), 0);
+}
+
+static void reset_prog_state(struct lsm_policy_kfuncs_success *skel)
+{
+	skel->bss->called = false;
+	skel->bss->no_ruleset = false;
+	skel->bss->restrict_err = -1;
+	skel->bss->restrict2_err = -1;
+	skel->bss->kfunc_flags = 0;
+	skel->bss->double_call = false;
+}
+
+/*
+ * Runs the syscall program that acquires the ruleset from
+ * @ruleset_fd, in the runner's fd table, and parks it in the map kptr
+ * slot for the LSM program.
+ */
+static int load_ruleset_into_map(struct lsm_policy_kfuncs_success *skel)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, opts);
+	int err;
+
+	err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.load_ruleset),
+				     &opts);
+	if (!ASSERT_OK(err, "load_ruleset_run"))
+		return -1;
+	if (!ASSERT_OK(opts.retval, "load_ruleset_retval"))
+		return -1;
+	ASSERT_TRUE(skel->bss->got_null_for_bad_fd, "bad_fd_null");
+	return 0;
+}
+
+/*
+ * Forks a child that execs "sh -c '<shell_cmd>'".  The monitored pid
+ * is only known, and can only be published to the BPF program, once
+ * the child exists: the child waits on a pipe until the parent has
+ * updated it.  Returns the child's exit status, or -1 on error.
+ */
+static int run_exec_child(struct lsm_policy_kfuncs_success *skel,
+			  bool monitored, const char *shell_cmd)
+{
+	int pipe_fds[2], status;
+	char buf = 0;
+	pid_t pid;
+
+	if (!ASSERT_OK(pipe(pipe_fds), "pipe"))
+		return -1;
+
+	pid = fork();
+	if (!ASSERT_GE(pid, 0, "fork")) {
+		close(pipe_fds[0]);
+		close(pipe_fds[1]);
+		return -1;
+	}
+	if (pid == 0) {
+		char *argv[] = { "sh", "-c", (char *)shell_cmd, NULL };
+
+		close(pipe_fds[1]);
+		read(pipe_fds[0], &buf, 1);
+		close(pipe_fds[0]);
+		execv("/bin/sh", argv);
+		exit(127);
+	}
+	close(pipe_fds[0]);
+	skel->bss->monitored_pid = monitored ? pid : 0;
+	write(pipe_fds[1], &buf, 1);
+	close(pipe_fds[1]);
+
+	if (!ASSERT_EQ(waitpid(pid, &status, 0), pid, "waitpid"))
+		return -1;
+	if (!ASSERT_TRUE(WIFEXITED(status), "child_exited"))
+		return -1;
+	return WEXITSTATUS(status);
+}
+
+/*
+ * Exit codes: 4 = unexpected write outcome, 0 = everything as
+ * expected.
+ */
+static void format_child_cmd(char *cmd, size_t len, bool expect_write_ok,
+			     const char *tmp_path)
+{
+	if (expect_write_ok)
+		snprintf(cmd, len, "echo x > %s || exit 4; exit 0", tmp_path);
+	else
+		snprintf(cmd, len,
+			 "if echo x > %s 2>/dev/null; then exit 4; fi; exit 0",
+			 tmp_path);
+}
+
+static void test_restrict_binprm(void)
+{
+	struct lsm_policy_kfuncs_success *skel = NULL;
+	char tmp_path[] = "/tmp/lsm_policy_kfuncs_XXXXXX";
+	char cmd[256];
+	int ruleset_fd, tmp_fd, ret;
+
+	tmp_fd = mkstemp(tmp_path);
+	if (!ASSERT_GE(tmp_fd, 0, "mkstemp"))
+		return;
+	close(tmp_fd);
+
+	ruleset_fd = create_ruleset();
+	if (ruleset_fd < 0) {
+		if (errno == EOPNOTSUPP || errno == ENOSYS)
+			test__skip();
+		else
+			ASSERT_GE(ruleset_fd, 0, "landlock_create_ruleset");
+		goto out_unlink;
+	}
+
+	skel = lsm_policy_kfuncs_success__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
+		goto out;
+	skel->bss->ruleset_fd = ruleset_fd;
+
+	if (!ASSERT_OK(lsm_policy_kfuncs_success__attach(skel), "skel_attach"))
+		goto out;
+
+	if (load_ruleset_into_map(skel))
+		goto out;
+
+	/* Control: an unmonitored execution may write to the tmp file. */
+	reset_prog_state(skel);
+	format_child_cmd(cmd, sizeof(cmd), true, tmp_path);
+	ret = run_exec_child(skel, false, cmd);
+	if (!ASSERT_EQ(ret, 0, "control_child_exit"))
+		goto out;
+	ASSERT_FALSE(skel->bss->called, "control_not_monitored");
+
+	/*
+	 * A monitored execution starts landlocked: the ruleset handles
+	 * LANDLOCK_ACCESS_FS_WRITE_FILE without any rule, so the write
+	 * must fail.
+	 */
+	reset_prog_state(skel);
+	format_child_cmd(cmd, sizeof(cmd), false, tmp_path);
+	ret = run_exec_child(skel, true, cmd);
+	if (!ASSERT_EQ(ret, 0, "restricted_child_exit"))
+		goto out;
+	ASSERT_TRUE(skel->bss->called, "lsm_prog_called");
+	ASSERT_FALSE(skel->bss->no_ruleset, "ruleset_in_map");
+	ASSERT_EQ(skel->bss->restrict_err, 0, "restrict_binprm");
+
+	/* The audit log flags of landlock_restrict_self(2) apply too. */
+	reset_prog_state(skel);
+	skel->bss->kfunc_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON;
+	format_child_cmd(cmd, sizeof(cmd), false, tmp_path);
+	ret = run_exec_child(skel, true, cmd);
+	if (!ASSERT_EQ(ret, 0, "log_flags_child_exit"))
+		goto out;
+	ASSERT_EQ(skel->bss->restrict_err, 0, "log_flags_restrict_binprm");
+
+	/*
+	 * LANDLOCK_RESTRICT_SELF_TSYNC targets the calling threads, not
+	 * an execution: the kfunc must reject it and the execution must
+	 * stay unrestricted.
+	 */
+	reset_prog_state(skel);
+	skel->bss->kfunc_flags = LANDLOCK_RESTRICT_SELF_TSYNC;
+	format_child_cmd(cmd, sizeof(cmd), true, tmp_path);
+	ret = run_exec_child(skel, true, cmd);
+	if (!ASSERT_EQ(ret, 0, "tsync_child_exit"))
+		goto out;
+	ASSERT_TRUE(skel->bss->called, "tsync_prog_called");
+	ASSERT_EQ(skel->bss->restrict_err, -EINVAL, "tsync_rejected");
+
+	/*
+	 * A second call on the same execution replaces the staged
+	 * domain (and releases the first one): the result is a single
+	 * restriction, not an error.
+	 */
+	reset_prog_state(skel);
+	skel->bss->double_call = true;
+	format_child_cmd(cmd, sizeof(cmd), false, tmp_path);
+	ret = run_exec_child(skel, true, cmd);
+	if (!ASSERT_EQ(ret, 0, "double_child_exit"))
+		goto out;
+	ASSERT_EQ(skel->bss->restrict_err, 0, "double_restrict_first");
+	ASSERT_EQ(skel->bss->restrict2_err, 0, "double_restrict_second");
+out:
+	lsm_policy_kfuncs_success__destroy(skel);
+	close(ruleset_fd);
+out_unlink:
+	unlink(tmp_path);
+}
+
+/*
+ * Checks that a staged restriction is discarded, and the staged
+ * domain released, when the execution fails after the bprm hook: the
+ * calling task must not end up landlocked.
+ */
+static void test_restrict_binprm_discard(void)
+{
+	struct lsm_policy_kfuncs_success *skel = NULL;
+	char tmp_path[] = "/tmp/lsm_policy_kfuncs_XXXXXX";
+	char garbage_path[] = "/tmp/lsm_policy_garbage_XXXXXX";
+	int ruleset_fd = -1, tmp_fd, garbage_fd, pipe_fds[2], status;
+	char buf = 0;
+	pid_t pid;
+
+	tmp_fd = mkstemp(tmp_path);
+	if (!ASSERT_GE(tmp_fd, 0, "mkstemp"))
+		return;
+	close(tmp_fd);
+
+	/*
+	 * An executable file that no binfmt handler accepts: the exec
+	 * fails with ENOEXEC after bprm_creds_for_exec() has run.
+	 */
+	garbage_fd = mkstemp(garbage_path);
+	if (!ASSERT_GE(garbage_fd, 0, "mkstemp_garbage"))
+		goto out_unlink;
+	if (!ASSERT_EQ(write(garbage_fd, "junk\n", 5), 5, "write_garbage")) {
+		close(garbage_fd);
+		goto out_unlink;
+	}
+	if (!ASSERT_OK(fchmod(garbage_fd, 0700), "chmod_garbage")) {
+		close(garbage_fd);
+		goto out_unlink;
+	}
+	close(garbage_fd);
+
+	ruleset_fd = create_ruleset();
+	if (ruleset_fd < 0) {
+		if (errno == EOPNOTSUPP || errno == ENOSYS)
+			test__skip();
+		else
+			ASSERT_GE(ruleset_fd, 0, "landlock_create_ruleset");
+		goto out_unlink;
+	}
+
+	skel = lsm_policy_kfuncs_success__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
+		goto out;
+	skel->bss->ruleset_fd = ruleset_fd;
+	reset_prog_state(skel);
+
+	if (!ASSERT_OK(lsm_policy_kfuncs_success__attach(skel), "skel_attach"))
+		goto out;
+
+	if (load_ruleset_into_map(skel))
+		goto out;
+
+	if (!ASSERT_OK(pipe(pipe_fds), "pipe"))
+		goto out;
+
+	pid = fork();
+	if (!ASSERT_GE(pid, 0, "fork"))
+		goto out;
+	if (pid == 0) {
+		char *argv[] = { "garbage", NULL };
+		int fd;
+
+		close(pipe_fds[1]);
+		read(pipe_fds[0], &buf, 1);
+		close(pipe_fds[0]);
+		execv(garbage_path, argv);
+		/*
+		 * The failed execution must leave no trace: no
+		 * Landlock domain, i.e. writing must still work
+		 * (exit 6).
+		 */
+		fd = open(tmp_path, O_WRONLY | O_TRUNC);
+		if (fd < 0)
+			exit(6);
+		close(fd);
+		exit(0);
+	}
+	close(pipe_fds[0]);
+	skel->bss->monitored_pid = pid;
+	write(pipe_fds[1], &buf, 1);
+	close(pipe_fds[1]);
+
+	if (!ASSERT_EQ(waitpid(pid, &status, 0), pid, "waitpid"))
+		goto out;
+	if (!ASSERT_TRUE(WIFEXITED(status), "child_exited"))
+		goto out;
+	ASSERT_EQ(WEXITSTATUS(status), 0, "discard_child_exit");
+	ASSERT_TRUE(skel->bss->called, "lsm_prog_called");
+	ASSERT_EQ(skel->bss->restrict_err, 0, "restrict_binprm");
+out:
+	lsm_policy_kfuncs_success__destroy(skel);
+	if (ruleset_fd >= 0)
+		close(ruleset_fd);
+out_unlink:
+	unlink(garbage_path);
+	unlink(tmp_path);
+}
+
+void test_lsm_policy_kfuncs(void)
+{
+	if (test__start_subtest("restrict_binprm"))
+		test_restrict_binprm();
+	if (test__start_subtest("restrict_binprm_discard"))
+		test_restrict_binprm_discard();
+	RUN_TESTS(lsm_policy_kfuncs_failure);
+}
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..0335db547040
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c
@@ -0,0 +1,100 @@
+// 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";
+
+struct bpf_landlock_ruleset;
+
+extern struct bpf_landlock_ruleset *
+bpf_landlock_get_ruleset_from_fd(int fd) __ksym;
+extern void
+bpf_landlock_put_ruleset(struct bpf_landlock_ruleset *ruleset) __ksym;
+extern int bpf_landlock_restrict_binprm(struct linux_binprm *bprm,
+					struct bpf_landlock_ruleset *ruleset,
+					u32 flags) __ksym;
+
+/*
+ * 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_landlock_get_ruleset_from_fd is not allowed")
+int BPF_PROG(tracing_prog, struct task_struct *task, u64 clone_flags)
+{
+	struct bpf_landlock_ruleset *ruleset;
+
+	ruleset = bpf_landlock_get_ruleset_from_fd(-1);
+	if (ruleset)
+		bpf_landlock_put_ruleset(ruleset);
+	return 0;
+}
+
+/*
+ * A ruleset fd is only meaningful in the fd table of the task that
+ * set the ruleset up: the acquire kfunc is exclusive to syscall
+ * programs and 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_landlock_get_ruleset_from_fd is not allowed")
+int BPF_PROG(lsm_get, struct linux_binprm *bprm)
+{
+	struct bpf_landlock_ruleset *ruleset;
+
+	ruleset = bpf_landlock_get_ruleset_from_fd(-1);
+	if (ruleset)
+		bpf_landlock_put_ruleset(ruleset);
+	return 0;
+}
+
+/*
+ * Enforcement needs an execution to restrict: the enforcement kfunc
+ * is exclusive to the sleepable bprm LSM hooks and must be rejected
+ * in a syscall program.
+ */
+SEC("syscall")
+__failure __msg("calling kernel function bpf_landlock_restrict_binprm is not allowed")
+int syscall_restrict(void *ctx)
+{
+	return bpf_landlock_restrict_binprm(NULL, NULL, 0);
+}
+
+/*
+ * Any LSM attach point other than the sleepable bprm hooks must be
+ * rejected.
+ */
+SEC("lsm.s/file_open")
+__failure __msg("calling kernel function bpf_landlock_put_ruleset is not allowed")
+int BPF_PROG(wrong_hook, struct file *file)
+{
+	bpf_landlock_put_ruleset(NULL);
+	return 0;
+}
+
+/*
+ * The kfuncs 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_landlock_put_ruleset")
+int BPF_PROG(nonsleepable_prog, struct linux_binprm *bprm)
+{
+	bpf_landlock_put_ruleset(NULL);
+	return 0;
+}
+
+/* An acquired ruleset reference must be released before returning. */
+SEC("syscall")
+__failure __msg("Unreleased reference")
+int leak_ruleset(void *ctx)
+{
+	bpf_landlock_get_ruleset_from_fd(-1);
+	return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c
new file mode 100644
index 000000000000..2107206ae144
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_success.c
@@ -0,0 +1,107 @@
+// 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>
+
+char _license[] SEC("license") = "GPL";
+
+struct bpf_landlock_ruleset;
+
+extern struct bpf_landlock_ruleset *
+bpf_landlock_get_ruleset_from_fd(int fd) __ksym;
+extern void
+bpf_landlock_put_ruleset(struct bpf_landlock_ruleset *ruleset) __ksym;
+extern int bpf_landlock_restrict_binprm(struct linux_binprm *bprm,
+					struct bpf_landlock_ruleset *ruleset,
+					u32 flags) __ksym;
+
+struct ruleset_slot {
+	struct bpf_landlock_ruleset __kptr *ruleset;
+};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__uint(max_entries, 1);
+	__type(key, int);
+	__type(value, struct ruleset_slot);
+} ruleset_map SEC(".maps");
+
+int monitored_pid;
+int ruleset_fd;
+u32 kfunc_flags;
+bool double_call;
+bool got_null_for_bad_fd;
+bool no_ruleset;
+int restrict_err;
+int restrict2_err;
+bool called;
+
+/*
+ * Runs in the test runner's context through BPF_PROG_RUN:
+ * @ruleset_fd is resolved in the runner's fd table and the acquired
+ * ruleset is handed to the LSM program through the map kptr slot.
+ */
+SEC("syscall")
+int load_ruleset(void *ctx)
+{
+	struct bpf_landlock_ruleset *ruleset, *old;
+	struct ruleset_slot *slot;
+	int key = 0;
+
+	slot = bpf_map_lookup_elem(&ruleset_map, &key);
+	if (!slot)
+		return 1;
+
+	/* A fd that is not a Landlock ruleset must resolve to NULL. */
+	ruleset = bpf_landlock_get_ruleset_from_fd(-1);
+	if (!ruleset)
+		got_null_for_bad_fd = true;
+	else
+		bpf_landlock_put_ruleset(ruleset);
+
+	ruleset = bpf_landlock_get_ruleset_from_fd(ruleset_fd);
+	if (!ruleset)
+		return 2;
+
+	old = bpf_kptr_xchg(&slot->ruleset, ruleset);
+	if (old)
+		bpf_landlock_put_ruleset(old);
+	return 0;
+}
+
+SEC("lsm.s/bprm_creds_for_exec")
+int BPF_PROG(restrict_exec, struct linux_binprm *bprm)
+{
+	struct bpf_landlock_ruleset *ruleset, *old;
+	struct ruleset_slot *slot;
+	int key = 0;
+
+	if (monitored_pid != (bpf_get_current_pid_tgid() >> 32))
+		return 0;
+
+	called = true;
+
+	slot = bpf_map_lookup_elem(&ruleset_map, &key);
+	if (!slot)
+		return 0;
+
+	ruleset = bpf_kptr_xchg(&slot->ruleset, NULL);
+	if (!ruleset) {
+		no_ruleset = true;
+		return 0;
+	}
+
+	restrict_err = bpf_landlock_restrict_binprm(bprm, ruleset, kfunc_flags);
+	if (double_call)
+		/* Replaces the domain staged by the first call. */
+		restrict2_err = bpf_landlock_restrict_binprm(bprm, ruleset,
+							     kfunc_flags);
+
+	/* Keep the ruleset for the next monitored execution. */
+	old = bpf_kptr_xchg(&slot->ruleset, ruleset);
+	if (old)
+		bpf_landlock_put_ruleset(old);
+	return 0;
+}
-- 
2.54.0


  parent reply	other threads:[~2026-07-31  2:21 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-31  2:20 [PATCH bpf-next 00/13] BPF interface for applying Landlock rulesets Justin Suess
2026-07-31  2:20 ` [PATCH bpf-next 01/13] lsm: Add LSM hook security_policy_kptr_from_fd Justin Suess
2026-07-31  2:20 ` [PATCH bpf-next 02/13] lsm: Add LSM hook security_policy_kptr_put Justin Suess
2026-07-31  2:44   ` sashiko-bot
2026-07-31  2:20 ` [PATCH bpf-next 03/13] lsm: Add LSM hook security_bprm_enforce_policy_kptr Justin Suess
2026-07-31  2:20 ` [PATCH bpf-next 04/13] landlock: Expose the ruleset fd lookup to the rest of Landlock Justin Suess
2026-07-31  2:20 ` [PATCH bpf-next 05/13] landlock: Factor the credential restriction out of landlock_restrict_self() Justin Suess
2026-07-31  2:20 ` [PATCH bpf-next 06/13] landlock: Implement the LSM policy kptr hooks Justin Suess
2026-07-31  2:20 ` [PATCH bpf-next 07/13] bpf: Add the LSM policy kfunc infrastructure Justin Suess
2026-07-31  2:20 ` [PATCH bpf-next 08/13] bpf: Add the bpf_landlock_put_ruleset kfunc and ruleset destructor Justin Suess
2026-07-31  2:46   ` sashiko-bot
2026-07-31  2:20 ` [PATCH bpf-next 09/13] bpf: Add the bpf_landlock_get_ruleset_from_fd kfunc Justin Suess
2026-07-31  2:20 ` [PATCH bpf-next 10/13] bpf: Add the bpf_landlock_restrict_binprm kfunc Justin Suess
2026-07-31  2:46   ` sashiko-bot
2026-07-31  2:20 ` Justin Suess [this message]
2026-07-31  2:20 ` [PATCH bpf-next 12/13] landlock: Document the BPF kfunc interface Justin Suess
2026-07-31  2:45   ` sashiko-bot
2026-07-31  2:20 ` [PATCH bpf-next 13/13] lsm: Document the LSM policy kptr hooks 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=20260731022047.189137-12-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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.