public inbox for bpf@vger.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH 00/20] BPF interface for applying Landlock rulesets
@ 2026-04-07 20:01 Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 01/20] landlock: Move operations from syscall into ruleset code Justin Suess
                   ` (21 more replies)
  0 siblings, 22 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Hello,

This series lets sleepable BPF LSM programs apply an existing,
userspace-created Landlock ruleset to a program during exec.

The goal is not to move Landlock policy definition into BPF, nor to create a
second policy engine.  Instead, BPF is used only to select when an already
valid Landlock ruleset should be applied, based on runtime exec context.

Background
===

Landlock is primarily a syscall-driven, unprivileged-first LSM.  That model
works well when the application being sandboxed can create and enforce its own
rulesets, or when a trusted launcher can impose restrictions directly before
running a trusted target.

That becomes harder when the target program is not under first-party control,
for example:

1. third-party binaries,
2. unmodified container images,
3. programs reached through shells, wrappers, or service managers, and
4. user-supplied or otherwise untrusted code.

In these cases, an external supervisor may want to apply a Landlock ruleset to
the final executed program, while leaving unrelated parents or helper
processes alone.

Why external sandboxing is awkward today
===

There are two recurring problems.

First, userspace cannot reliably predict every file a target may need across
different systems, packaging layouts, and runtime conditions.  Shared
libraries, configuration files, interpreters, and helper binaries often depend
on details that are only known at runtime.

Second, Landlock inheritance is intentionally one-way.  Once a task is
restricted, descendants inherit that domain and may only become more
restricted.  This is exactly what Landlock should do, but it makes external
sandboxing awkward when the program of interest is buried inside a larger exec
chain.  Applying restrictions too early can affect unrelated intermediates;
applying them too late misses the target entirely.

This series addresses that target-selection problem.

Overview
===

This series adds a small BPF-to-Landlock bridge:

1. userspace creates a normal Landlock ruleset through the existing ABI;
2. userspace inserts that ruleset FD into a new
	BPF_MAP_TYPE_LANDLOCK_RULESET map;
3. a sleepable BPF LSM program attached to an exec-time hook looks up the
	ruleset; and
4. the program calls a kfunc to apply that ruleset to the new program's
	credentials before exec completes.

The important point is that BPF does not create, inspect, or mutate Landlock
policy here.  It only decides whether to apply a ruleset that was already
created and validated through Landlock's existing userspace API.

Interface
===

The series adds:

1. bpf_landlock_restrict_binprm(), which applies a referenced ruleset to
	struct linux_binprm credentials;
2. bpf_landlock_put_ruleset(), which releases a referenced ruleset; and
3. BPF_MAP_TYPE_LANDLOCK_RULESET, a specialized map type for holding
	references to Landlock rulesets originating from userspace file
	descriptors.
4. A new field in the linux_binprm struct to enable application of
   task_set_no_new_privs once execution is beyond the point of no return.

The kfuncs are restricted to sleepable BPF LSM programs attached to
bprm_creds_for_exec and bprm_creds_from_file, which are the points where the
new program's credentials may still be updated safely.

This series also adds LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS.  On the BPF path,
this is staged through the exec context and committed only after exec reaches
point-of-no-return.  This avoids side effects on failed executions while
ensuring that the resulting task cannot gain more privileges through later exec
transitions. This is done through the set_nnp_on_point_of_no_return field.

This has a little subtlety: LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS in the BPF
path will not stop the current execution from escalating at all; only subsequent
ones. This is intentional to allow landlock policies to be applied through a
setuid transition for instance, without affecting the current escalation.

Semantics
===

This proposal is intended to preserve Landlock semantics as much as practical
for an exec-time BPF attachment model:

1. only pre-existing Landlock rulesets may be applied;
2. BPF cannot construct, inspect, or modify rulesets;
3. enforcement still happens before the new program begins execution;
4. normal Landlock inheritance, layering, and future composition remain
	unchanged; and
5. this does not bypass Landlock's privilege checks for applying Landlock
    rulesets.

In other words, BPF acts as an external selector for when to apply Landlock,
not as a replacement for Landlock's enforcement engine.

All behavior, future access rights, and previous access rights are designed
to automatically be supported from either BPF or existing syscall contexts.

The main semantic difference is LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS on the BPF
path: it guarantees that the resulting task is pinned with no_new_privs before
it can perform later exec transitions, but it does not retroactively suppress
privilege gain for the current exec transition itself.

The other exception to semantics is the LANDLOCK_RESTRICT_SELF_TSYNC flag.
(see Points of Feedback section)

Patch layout
===

Patches 1-5 prepare the Landlock side by moving shared ruleset logic out of
syscalls.c, adding a no_new_privs flag for non-syscall callers, exposing
linux_binprm->set_nnp_on_point_of_no_return as an interface to set no_new_privs
on the point of no return, and making deferred ruleset destruction RCU-safe.

Patches 6-10 add the BPF-facing pieces: the Landlock kfuncs, the new map type,
syscall handling for that map, and verifier support.

Patches 11-15 add selftests and the small bpftool update needed for the new
map type.

Patches 16-20 add docs and bump the ABI version and update MAINTAINERS.

Feedback is especially welcome on the overall interface shape, the choice of
hooks, and the map semantics.

Testing
===

This patch series has two portions of tests.

One lives in the traditional Landlock selftests, for the new
LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS flag.

The other suite lives under the BPF selftests, and this tests the Landlock
kfuncs and the new BPF_MAP_TYPE_LANDLOCK_RULESET.

This patch series was run through BPF CI, the results of which are here. [1]

All mentioned tests are passing, as well as the BPF CI.

[1] : https://github.com/kernel-patches/bpf/pull/11562

Points of Feedback
===

First, the new set_nnp_on_point_of_no_return field in struct linux_binprm.
This field was needed to request that task_set_no_new_privs be set during an
execution, but only after the execution has proceeded beyond the point of no
return. I couldn't find a way to express this semantic without adding a new
bitfield to struct linux_binprm and a conditional in fs/exec.c. Please see
patch 2.

Feedback on the BPF testing harness, which was generated with AI assistance as
disclosed in the commit footer, is welcomed. I have only limited familiarity
with BPF testing practices. These tests were made with strong human supervision.
See patches 14 and 15.

Feedback on the NO_NEW_PRIVS situation is also welcomed. Because task_set_no_new_privs()
would otherwise leak state on failed executions or AT_EXECVE_CHECK, this series
stages no_new_privs through the exec context and only commits it after
point-of-no-return. This preserves failure behavior while still ensuring that
the resulting task cannot elevate further through later exec transitions.
When called from bprm_creds_from_file, this does not retroactively change the
privilege outcome of the current exec transition itself.

See patch 2 and 3.

Next, the RCU in the landlock_ruleset. Existing BPF maps use RCU to make sure maps
holding references stay valid. I altered the landlock ruleset to use rcu_work
to make sure that the rcu is synchronized before putting on a ruleset, and
acquire the rcu in the arraymap implementation. See patches 5-10.

Next, the semantics of the map. What operations should be supported from BPF
and userspace and what data types should they return? I consider the struct
bpf_landlock_ruleset to be opaque. Userspace can add items to the map via the
fd, delete items by their index, and BPF can delete and lookup items by their
index. Items cannot be updated, only swapped.

Finally, the handling of the LANDLOCK_RESTRICT_SELF_TSYNC flag. This flag has
no meaning in a pre-execution context, as the credentials during the designated
LSM hooks (bprm_creds_for_exec/creds_from_file) still represent the pre-execution
task. Therefore, this flag is invalidated and attempting to use it with
bpf_landlock_restrict_binprm will return -EINVAL. Otherwise, the flag would
result in applying the landlock ruleset to the wrong target in addition to the
intended one. (see patch 2). This behavior is validated with selftests.

Existing works / Credits
===

Mickaël Salaün created patchsets adding BPF tracepoints for landlock in [2] [3].

Mickaël also gave feedback on this feature and the idea in this GitHub thread. [4]

Günther Noack initially received and provided initial feedback on this idea as
an early prototype.

Liz Rice, author of "Learning eBPF: Programming the Linux Kernel for Enhanced
Observability, Networking, and Security" provided background and inspired me to
experiment with BPF and the BPF LSM. [5]

[2] : https://lore.kernel.org/all/20250523165741.693976-1-mic@digikod.net/
[3] : https://lore.kernel.org/linux-security-module/20260406143717.1815792-1-mic@digikod.net/
[4] : https://github.com/landlock-lsm/linux/issues/56
[5] : https://wellesleybooks.com/book/9781098135126

Kind Regards,
Justin Suess

Justin Suess (20):
  landlock: Move operations from syscall into ruleset code
  execve: Add set_nnp_on_point_of_no_return
  landlock: Implement LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
  selftests/landlock: Cover LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
  landlock: Make ruleset deferred free RCU safe
  bpf: lsm: Add Landlock kfuncs
  bpf: arraymap: Implement Landlock ruleset map
  bpf: Add Landlock ruleset map type
  bpf: syscall: Handle Landlock ruleset maps
  bpf: verifier: Add Landlock ruleset map support
  selftests/bpf: Add Landlock kfunc declarations
  selftests/landlock: Rename gettid wrapper for BPF reuse
  selftests/bpf: Enable Landlock in selftests kernel.
  selftests/bpf: Add Landlock kfunc test program
  selftests/bpf: Add Landlock kfunc test runner
  landlock: Bump ABI version
  tools: bpftool: Add documentation for landlock_ruleset
  landlock: Document LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
  bpf: Document BPF_MAP_TYPE_LANDLOCK_RULESET
  MAINTAINERS: update entry for the Landlock subsystem

 Documentation/bpf/map_landlock_ruleset.rst    | 181 +++++
 Documentation/userspace-api/landlock.rst      |  22 +-
 MAINTAINERS                                   |   4 +
 fs/exec.c                                     |   8 +
 include/linux/binfmts.h                       |   7 +-
 include/linux/bpf_lsm.h                       |  15 +
 include/linux/bpf_types.h                     |   1 +
 include/linux/landlock.h                      |  92 +++
 include/uapi/linux/bpf.h                      |   1 +
 include/uapi/linux/landlock.h                 |  14 +
 kernel/bpf/arraymap.c                         |  67 ++
 kernel/bpf/bpf_lsm.c                          | 145 ++++
 kernel/bpf/syscall.c                          |   4 +-
 kernel/bpf/verifier.c                         |  15 +-
 samples/landlock/sandboxer.c                  |   7 +-
 security/landlock/limits.h                    |   2 +-
 security/landlock/ruleset.c                   | 198 ++++-
 security/landlock/ruleset.h                   |  25 +-
 security/landlock/syscalls.c                  | 158 +---
 .../bpf/bpftool/Documentation/bpftool-map.rst |   2 +-
 tools/bpf/bpftool/map.c                       |   2 +-
 tools/include/uapi/linux/bpf.h                |   1 +
 tools/lib/bpf/libbpf.c                        |   1 +
 tools/lib/bpf/libbpf_probes.c                 |   6 +
 tools/testing/selftests/bpf/bpf_kfuncs.h      |  20 +
 tools/testing/selftests/bpf/config            |   5 +
 tools/testing/selftests/bpf/config.x86_64     |   1 -
 .../bpf/prog_tests/landlock_kfuncs.c          | 733 ++++++++++++++++++
 .../selftests/bpf/progs/landlock_kfuncs.c     |  92 +++
 tools/testing/selftests/landlock/base_test.c  |  10 +-
 tools/testing/selftests/landlock/common.h     |  28 +-
 tools/testing/selftests/landlock/fs_test.c    | 103 +--
 tools/testing/selftests/landlock/net_test.c   |  55 +-
 .../testing/selftests/landlock/ptrace_test.c  |  14 +-
 .../landlock/scoped_abstract_unix_test.c      |  51 +-
 .../selftests/landlock/scoped_base_variants.h |  23 +
 .../selftests/landlock/scoped_common.h        |   5 +-
 .../selftests/landlock/scoped_signal_test.c   |  30 +-
 tools/testing/selftests/landlock/wrappers.h   |   2 +-
 39 files changed, 1877 insertions(+), 273 deletions(-)
 create mode 100644 Documentation/bpf/map_landlock_ruleset.rst
 create mode 100644 include/linux/landlock.h
 create mode 100644 tools/testing/selftests/bpf/prog_tests/landlock_kfuncs.c
 create mode 100644 tools/testing/selftests/bpf/progs/landlock_kfuncs.c


base-commit: 8c6a27e02bc55ab110d1828610048b19f903aaec
-- 
2.53.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [RFC PATCH 01/20] landlock: Move operations from syscall into ruleset code
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 02/20] execve: Add set_nnp_on_point_of_no_return Justin Suess
                   ` (20 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Refactor syscall restriction code, associated constants and helpers,
into ruleset.h/c. This helps increase consistency by making syscall.c a
consumer of ruleset.h/c's logic. Subsequent patches in this series add
consumers of this logic.

Functions for getting and putting references on a landlock ruleset were
also exposed in the patch for the subsequent consumers, transitioning
them from static to linked functions with headers.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 include/linux/landlock.h     |  92 ++++++++++++++++++
 security/landlock/ruleset.c  | 179 +++++++++++++++++++++++++++++++++++
 security/landlock/ruleset.h  |  19 ++--
 security/landlock/syscalls.c | 151 +++--------------------------
 4 files changed, 296 insertions(+), 145 deletions(-)
 create mode 100644 include/linux/landlock.h

diff --git a/include/linux/landlock.h b/include/linux/landlock.h
new file mode 100644
index 000000000000..fae7d138ef8b
--- /dev/null
+++ b/include/linux/landlock.h
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock - Internal cross subsystem header
+ *
+ * Copyright © 2026 Justin Suess <utilityemal77@gmail.com>
+ */
+
+#ifndef _LINUX_LANDLOCK_H
+#define _LINUX_LANDLOCK_H
+
+#include <linux/cred.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/types.h>
+#include <uapi/linux/landlock.h>
+
+struct landlock_ruleset;
+
+#ifdef CONFIG_SECURITY_LANDLOCK
+
+/*
+ * Returns an owned ruleset from a FD. It is thus needed to call
+ * landlock_put_ruleset() on the returned value.
+ */
+struct landlock_ruleset *landlock_get_ruleset_from_fd(int fd, fmode_t mode);
+
+/*
+ * Acquires an additional reference to a ruleset if it is still alive.
+ */
+bool landlock_try_get_ruleset(struct landlock_ruleset *ruleset);
+
+/*
+ * Releases a previously acquired ruleset.
+ */
+void landlock_put_ruleset(struct landlock_ruleset *ruleset);
+
+/*
+ * Releases a previously acquired ruleset after an RCU-safe deferral.
+ */
+void landlock_put_ruleset_deferred(struct landlock_ruleset *ruleset);
+
+/*
+ * Restricts @cred with @ruleset and the supplied @flags.
+ *
+ * landlock_restrict_cred_precheck() must be called first.
+ *
+ * The caller owns @cred and is responsible for committing or aborting it.
+ * @ruleset may be NULL only with LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF.
+ */
+int landlock_restrict_cred_precheck(__u32 flags, bool in_task_context);
+
+int landlock_restrict_cred(struct cred *cred, struct landlock_ruleset *ruleset,
+			   __u32 flags);
+
+#else /* !CONFIG_SECURITY_LANDLOCK */
+
+static inline struct landlock_ruleset *
+landlock_get_ruleset_from_fd(int fd, fmode_t mode)
+{
+	return ERR_PTR(-EOPNOTSUPP);
+}
+
+static inline bool landlock_try_get_ruleset(struct landlock_ruleset *ruleset)
+{
+	return false;
+}
+
+static inline void landlock_put_ruleset(struct landlock_ruleset *ruleset)
+{
+}
+
+static inline void
+landlock_put_ruleset_deferred(struct landlock_ruleset *ruleset)
+{
+}
+
+static inline int landlock_restrict_cred(struct cred *cred,
+					 struct landlock_ruleset *ruleset,
+					 __u32 flags)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int landlock_restrict_cred_precheck(__u32 flags,
+					 bool in_task_context)
+{
+	return -EOPNOTSUPP;
+}
+
+#endif /* !CONFIG_SECURITY_LANDLOCK */
+
+#endif /* _LINUX_LANDLOCK_H */
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 181df7736bb9..2333a3dc5f33 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -8,25 +8,204 @@
 
 #include <linux/bits.h>
 #include <linux/bug.h>
+#include <linux/capability.h>
 #include <linux/cleanup.h>
 #include <linux/compiler_types.h>
 #include <linux/err.h>
 #include <linux/errno.h>
+#include <linux/fs.h>
 #include <linux/kernel.h>
 #include <linux/lockdep.h>
 #include <linux/mutex.h>
 #include <linux/overflow.h>
 #include <linux/rbtree.h>
 #include <linux/refcount.h>
+#include <linux/sched.h>
 #include <linux/slab.h>
 #include <linux/spinlock.h>
 #include <linux/workqueue.h>
 
 #include "access.h"
+#include "cred.h"
 #include "domain.h"
 #include "limits.h"
 #include "object.h"
 #include "ruleset.h"
+#include "setup.h"
+#include "tsync.h"
+
+static int fop_ruleset_release(struct inode *const inode,
+			       struct file *const filp)
+{
+	struct landlock_ruleset *ruleset = filp->private_data;
+
+	landlock_put_ruleset(ruleset);
+	return 0;
+}
+
+static ssize_t fop_dummy_read(struct file *const filp, char __user *const buf,
+			      const size_t size, loff_t *const ppos)
+{
+	/* Dummy handler to enable FMODE_CAN_READ. */
+	return -EINVAL;
+}
+
+static ssize_t fop_dummy_write(struct file *const filp,
+			       const char __user *const buf, const size_t size,
+			       loff_t *const ppos)
+{
+	/* Dummy handler to enable FMODE_CAN_WRITE. */
+	return -EINVAL;
+}
+
+/*
+ * A ruleset file descriptor enables to build a ruleset by adding (i.e.
+ * writing) rule after rule, without relying on the task's context.  This
+ * reentrant design is also used in a read way to enforce the ruleset on the
+ * current task.
+ */
+const struct file_operations ruleset_fops = {
+	.release = fop_ruleset_release,
+	.read = fop_dummy_read,
+	.write = fop_dummy_write,
+};
+
+/*
+ * Returns an owned ruleset from a FD. It is thus needed to call
+ * landlock_put_ruleset() on the return value.
+ */
+struct landlock_ruleset *landlock_get_ruleset_from_fd(const int fd,
+						      const fmode_t mode)
+{
+	CLASS(fd, ruleset_f)(fd);
+	struct landlock_ruleset *ruleset;
+
+	if (fd_empty(ruleset_f))
+		return ERR_PTR(-EBADF);
+
+	/* Checks FD type and access right. */
+	if (fd_file(ruleset_f)->f_op != &ruleset_fops)
+		return ERR_PTR(-EBADFD);
+	if (!(fd_file(ruleset_f)->f_mode & mode))
+		return ERR_PTR(-EPERM);
+	ruleset = fd_file(ruleset_f)->private_data;
+	if (WARN_ON_ONCE(ruleset->num_layers != 1))
+		return ERR_PTR(-EINVAL);
+	landlock_get_ruleset(ruleset);
+	return ruleset;
+}
+
+void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
+{
+	if (ruleset)
+		refcount_inc(&ruleset->usage);
+}
+
+bool landlock_try_get_ruleset(struct landlock_ruleset *const ruleset)
+{
+	return ruleset && refcount_inc_not_zero(&ruleset->usage);
+}
+
+int landlock_restrict_cred_precheck(const __u32 flags,
+				    const bool in_task_context)
+{
+	if (!landlock_initialized)
+		return -EOPNOTSUPP;
+
+	/*
+	 * LANDLOCK_RESTRICT_SELF_TSYNC requires that the current task is
+	 * the target of restriction.
+	 */
+	if ((flags & LANDLOCK_RESTRICT_SELF_TSYNC) && !in_task_context)
+		return -EINVAL;
+
+	/*
+	 * Similar checks as for seccomp(2), except that an -EPERM may be
+	 * returned.
+	 */
+	if (!task_no_new_privs(current) &&
+	    !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN)) {
+			return -EPERM;
+	}
+
+	if (flags & ~LANDLOCK_MASK_RESTRICT_SELF)
+		return -EINVAL;
+
+	return 0;
+}
+
+int landlock_restrict_cred(struct cred *const cred,
+			   struct landlock_ruleset *const ruleset,
+			   const __u32 flags)
+{
+	struct landlock_cred_security *new_llcred;
+	bool __maybe_unused log_same_exec, log_new_exec, log_subdomains,
+		prev_log_subdomains;
+
+	/*
+	 * It is allowed to set LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF without
+	 * a ruleset, optionally combined with LANDLOCK_RESTRICT_SELF_TSYNC, but
+	 * no other flag must be set.
+	 */
+	if (!ruleset &&
+	    (flags & ~LANDLOCK_RESTRICT_SELF_TSYNC) !=
+		    LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)
+		return -EINVAL;
+
+	/* Translates "off" flag to boolean. */
+	log_same_exec = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF);
+	/* Translates "on" flag to boolean. */
+	log_new_exec = !!(flags & LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON);
+	/* Translates "off" flag to boolean. */
+	log_subdomains = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF);
+
+	new_llcred = landlock_cred(cred);
+
+#ifdef CONFIG_AUDIT
+	prev_log_subdomains = !new_llcred->log_subdomains_off;
+	new_llcred->log_subdomains_off = !prev_log_subdomains ||
+					 !log_subdomains;
+#endif /* CONFIG_AUDIT */
+
+	/*
+	 * The only case when a ruleset may not be set is if
+	 * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF is set, optionally combined
+	 * with LANDLOCK_RESTRICT_SELF_TSYNC.
+	 * We could optimize this case by not committing @cred if this flag was
+	 * already set, but it is not worth the complexity.
+	 */
+	if (ruleset) {
+		struct landlock_ruleset *const new_dom =
+			landlock_merge_ruleset(new_llcred->domain, ruleset);
+
+		if (IS_ERR(new_dom))
+			return PTR_ERR(new_dom);
+
+#ifdef CONFIG_AUDIT
+		new_dom->hierarchy->log_same_exec = log_same_exec;
+		new_dom->hierarchy->log_new_exec = log_new_exec;
+		if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains)
+			new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED;
+#endif /* CONFIG_AUDIT */
+
+		landlock_put_ruleset(new_llcred->domain);
+		new_llcred->domain = new_dom;
+
+#ifdef CONFIG_AUDIT
+		new_llcred->domain_exec |= BIT(new_dom->num_layers - 1);
+#endif /* CONFIG_AUDIT */
+	}
+
+	if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) {
+		const int tsync_err =
+			landlock_restrict_sibling_threads(current_cred(), cred);
+
+		if (tsync_err)
+			return tsync_err;
+	}
+
+	return 0;
+}
 
 static struct landlock_ruleset *create_ruleset(const u32 num_layers)
 {
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 889f4b30301a..0facc5cb6555 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -11,6 +11,8 @@
 
 #include <linux/cleanup.h>
 #include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/landlock.h>
 #include <linux/mutex.h>
 #include <linux/rbtree.h>
 #include <linux/refcount.h>
@@ -20,6 +22,8 @@
 #include "limits.h"
 #include "object.h"
 
+extern const struct file_operations ruleset_fops;
+
 struct landlock_hierarchy;
 
 /**
@@ -194,6 +198,8 @@ landlock_create_ruleset(const access_mask_t access_mask_fs,
 			const access_mask_t access_mask_net,
 			const access_mask_t scope_mask);
 
+void landlock_get_ruleset(struct landlock_ruleset *ruleset);
+
 void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
 void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
 
@@ -204,6 +210,13 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
 			 const struct landlock_id id,
 			 const access_mask_t access);
 
+int landlock_restrict_cred_precheck(const __u32 flags,
+				    const bool in_task_context);
+
+int landlock_restrict_cred(struct cred *const cred,
+			   struct landlock_ruleset *const ruleset,
+			   const __u32 flags);
+
 struct landlock_ruleset *
 landlock_merge_ruleset(struct landlock_ruleset *const parent,
 		       struct landlock_ruleset *const ruleset);
@@ -212,12 +225,6 @@ const struct landlock_rule *
 landlock_find_rule(const struct landlock_ruleset *const ruleset,
 		   const struct landlock_id id);
 
-static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
-{
-	if (ruleset)
-		refcount_inc(&ruleset->usage);
-}
-
 /**
  * landlock_union_access_masks - Return all access rights handled in the
  *				 domain
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index accfd2e5a0cd..c710e8b16150 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -121,42 +121,6 @@ static void build_check_abi(void)
 
 /* Ruleset handling */
 
-static int fop_ruleset_release(struct inode *const inode,
-			       struct file *const filp)
-{
-	struct landlock_ruleset *ruleset = filp->private_data;
-
-	landlock_put_ruleset(ruleset);
-	return 0;
-}
-
-static ssize_t fop_dummy_read(struct file *const filp, char __user *const buf,
-			      const size_t size, loff_t *const ppos)
-{
-	/* Dummy handler to enable FMODE_CAN_READ. */
-	return -EINVAL;
-}
-
-static ssize_t fop_dummy_write(struct file *const filp,
-			       const char __user *const buf, const size_t size,
-			       loff_t *const ppos)
-{
-	/* Dummy handler to enable FMODE_CAN_WRITE. */
-	return -EINVAL;
-}
-
-/*
- * A ruleset file descriptor enables to build a ruleset by adding (i.e.
- * writing) rule after rule, without relying on the task's context.  This
- * reentrant design is also used in a read way to enforce the ruleset on the
- * current task.
- */
-static const struct file_operations ruleset_fops = {
-	.release = fop_ruleset_release,
-	.read = fop_dummy_read,
-	.write = fop_dummy_write,
-};
-
 /*
  * The Landlock ABI version should be incremented for each new Landlock-related
  * user space visible change (e.g. Landlock syscalls).  This version should
@@ -264,31 +228,6 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
 	return ruleset_fd;
 }
 
-/*
- * Returns an owned ruleset from a FD. It is thus needed to call
- * landlock_put_ruleset() on the return value.
- */
-static struct landlock_ruleset *get_ruleset_from_fd(const int fd,
-						    const fmode_t mode)
-{
-	CLASS(fd, ruleset_f)(fd);
-	struct landlock_ruleset *ruleset;
-
-	if (fd_empty(ruleset_f))
-		return ERR_PTR(-EBADF);
-
-	/* Checks FD type and access right. */
-	if (fd_file(ruleset_f)->f_op != &ruleset_fops)
-		return ERR_PTR(-EBADFD);
-	if (!(fd_file(ruleset_f)->f_mode & mode))
-		return ERR_PTR(-EPERM);
-	ruleset = fd_file(ruleset_f)->private_data;
-	if (WARN_ON_ONCE(ruleset->num_layers != 1))
-		return ERR_PTR(-EINVAL);
-	landlock_get_ruleset(ruleset);
-	return ruleset;
-}
-
 /* Path handling */
 
 /*
@@ -437,7 +376,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
 		return -EINVAL;
 
 	/* Gets and checks the ruleset. */
-	ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
+	ruleset = landlock_get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
 	if (IS_ERR(ruleset))
 		return PTR_ERR(ruleset);
 
@@ -487,33 +426,13 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
 SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
 		flags)
 {
-	struct landlock_ruleset *ruleset __free(landlock_put_ruleset) = NULL;
 	struct cred *new_cred;
-	struct landlock_cred_security *new_llcred;
-	bool __maybe_unused log_same_exec, log_new_exec, log_subdomains,
-		prev_log_subdomains;
-
-	if (!is_initialized())
-		return -EOPNOTSUPP;
-
-	/*
-	 * Similar checks as for seccomp(2), except that an -EPERM may be
-	 * returned.
-	 */
-	if (!task_no_new_privs(current) &&
-	    !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN))
-		return -EPERM;
-
-	if ((flags | LANDLOCK_MASK_RESTRICT_SELF) !=
-	    LANDLOCK_MASK_RESTRICT_SELF)
-		return -EINVAL;
+	struct landlock_ruleset *ruleset __free(landlock_put_ruleset) = NULL;
+	int err;
 
-	/* Translates "off" flag to boolean. */
-	log_same_exec = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF);
-	/* Translates "on" flag to boolean. */
-	log_new_exec = !!(flags & LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON);
-	/* Translates "off" flag to boolean. */
-	log_subdomains = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF);
+	err = landlock_restrict_cred_precheck(flags, true);
+	if (err)
+		return err;
 
 	/*
 	 * It is allowed to set LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF with
@@ -525,7 +444,8 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
 	      (flags & ~LANDLOCK_RESTRICT_SELF_TSYNC) ==
 		      LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) {
 		/* Gets and checks the ruleset. */
-		ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_READ);
+		ruleset = landlock_get_ruleset_from_fd(ruleset_fd,
+						       FMODE_CAN_READ);
 		if (IS_ERR(ruleset))
 			return PTR_ERR(ruleset);
 	}
@@ -535,57 +455,10 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
 	if (!new_cred)
 		return -ENOMEM;
 
-	new_llcred = landlock_cred(new_cred);
-
-#ifdef CONFIG_AUDIT
-	prev_log_subdomains = !new_llcred->log_subdomains_off;
-	new_llcred->log_subdomains_off = !prev_log_subdomains ||
-					 !log_subdomains;
-#endif /* CONFIG_AUDIT */
-
-	/*
-	 * The only case when a ruleset may not be set is if
-	 * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF is set (optionally with
-	 * LANDLOCK_RESTRICT_SELF_TSYNC) and ruleset_fd is -1.  We could
-	 * optimize this case by not calling commit_creds() if this flag was
-	 * already set, but it is not worth the complexity.
-	 */
-	if (ruleset) {
-		/*
-		 * There is no possible race condition while copying and
-		 * manipulating the current credentials because they are
-		 * dedicated per thread.
-		 */
-		struct landlock_ruleset *const new_dom =
-			landlock_merge_ruleset(new_llcred->domain, ruleset);
-		if (IS_ERR(new_dom)) {
-			abort_creds(new_cred);
-			return PTR_ERR(new_dom);
-		}
-
-#ifdef CONFIG_AUDIT
-		new_dom->hierarchy->log_same_exec = log_same_exec;
-		new_dom->hierarchy->log_new_exec = log_new_exec;
-		if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains)
-			new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED;
-#endif /* CONFIG_AUDIT */
-
-		/* Replaces the old (prepared) domain. */
-		landlock_put_ruleset(new_llcred->domain);
-		new_llcred->domain = new_dom;
-
-#ifdef CONFIG_AUDIT
-		new_llcred->domain_exec |= BIT(new_dom->num_layers - 1);
-#endif /* CONFIG_AUDIT */
-	}
-
-	if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) {
-		const int err = landlock_restrict_sibling_threads(
-			current_cred(), new_cred);
-		if (err) {
-			abort_creds(new_cred);
-			return err;
-		}
+	err = landlock_restrict_cred(new_cred, ruleset, flags);
+	if (err) {
+		abort_creds(new_cred);
+		return err;
 	}
 
 	return commit_creds(new_cred);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 02/20] execve: Add set_nnp_on_point_of_no_return
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 01/20] landlock: Move operations from syscall into ruleset code Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 03/20] landlock: Implement LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS Justin Suess
                   ` (19 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Allow LSM hooks to set a new bitfield in the binprm, ensuring
that the next execution will run with task_set_no_new_privs by executing
task_set_no_new_privs only past the point of no return.

This differs semantically from task_set_no_new_privs, which is not safe
to set from bprm_creds_for_exec/creds_from_file because a failed
execution will result in no_new_privs being set on the original task.
The setting of this flag from the LSM hook will not alter the current
task's no_new_privs field until after the point of no return, so if we
have a failed execution in execve there will be no side effect.

Setting this field will not result in any change to the escalation or
LSM checks for the current execution transition, only for subsequent
ones.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 fs/exec.c               | 8 ++++++++
 include/linux/binfmts.h | 7 ++++++-
 2 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/fs/exec.c b/fs/exec.c
index 9ea3a775d51e..6ab700af57d9 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1111,6 +1111,14 @@ int begin_new_exec(struct linux_binprm * bprm)
 	 */
 	bprm->point_of_no_return = true;
 
+	/*
+	 * If requested that we set NO_NEW_PRIVS on the task, do so now that we're
+	 * committed to exec. We set it here in case it wasn't safe to set it
+	 * before the point of no return.
+	 */
+	if (bprm->set_nnp_on_point_of_no_return)
+		task_set_no_new_privs(current);
+
 	/* Make this the only thread in the thread group */
 	retval = de_thread(me);
 	if (retval)
diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h
index 65abd5ab8836..9e420b055c4a 100644
--- a/include/linux/binfmts.h
+++ b/include/linux/binfmts.h
@@ -49,7 +49,12 @@ struct linux_binprm {
 		 * Set by user space to check executability according to the
 		 * caller's environment.
 		 */
-		is_check:1;
+		is_check:1,
+		/*
+		 * Set when a NNP should be applied to the new program's
+		 * credentials during exec past the point of no return.
+		 */
+		set_nnp_on_point_of_no_return:1;
 	struct file *executable; /* Executable to pass to the interpreter */
 	struct file *interpreter;
 	struct file *file;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 03/20] landlock: Implement LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 01/20] landlock: Move operations from syscall into ruleset code Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 02/20] execve: Add set_nnp_on_point_of_no_return Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 04/20] selftests/landlock: Cover LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS Justin Suess
                   ` (18 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Add a flag LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS, which executes
task_set_no_new_privs on the current credentials, but only if
the process lacks the CAP_SYS_ADMIN capability.

While this operation is redundant for code running from userspace
(indeed callers may achieve the same logic by calling
prctl w/ PR_SET_NO_NEW_PRIVS), this flag enables callers without access
to the syscall abi (defined in subsequent patches) to restrict processes
from gaining additional capabilities. This is important to ensure that
consumers can meet the task_no_new_privs || CAP_SYS_ADMIN invariant
enforced by Landlock without having syscall access.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 include/uapi/linux/landlock.h | 14 ++++++++++++++
 security/landlock/limits.h    |  2 +-
 security/landlock/ruleset.c   | 12 +++++++++++-
 security/landlock/syscalls.c  |  7 +++++++
 4 files changed, 33 insertions(+), 2 deletions(-)

diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 10a346e55e95..de2537755bbe 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -131,12 +131,26 @@ struct landlock_ruleset_attr {
  *
  *     If the calling thread is running with no_new_privs, this operation
  *     enables no_new_privs on the sibling threads as well.
+ *
+ * %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
+ *    Sets no_new_privs on the calling thread before applying the Landlock domain.
+ *    This flag is useful for convenience as well as for applying a ruleset from
+ *    an outside context (e.g BPF). This flag only has an effect on when both
+ *    no_new_privs isn't already set and the caller doesn't possess CAP_SYS_ADMIN.
+ *
+ *    This flag has slightly different behavior when used from BPF. Instead of
+ *    setting no_new_privs on the current task, it sets a flag on the bprm so that
+ *    no_new_privs is set on the task at exec point-of-no-return. This guarantees
+ *    that the current execution is unaffected, and may escalate as usual until the
+ *    next exec, but the resulting task cannot gain more privileges through later
+ *    exec transitions.
  */
 /* clang-format off */
 #define LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF		(1U << 0)
 #define LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON			(1U << 1)
 #define LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF		(1U << 2)
 #define LANDLOCK_RESTRICT_SELF_TSYNC				(1U << 3)
+#define LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS			(1U << 4)
 /* clang-format on */
 
 /**
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index b454ad73b15e..9eafc64fba3f 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -31,7 +31,7 @@
 #define LANDLOCK_MASK_SCOPE		((LANDLOCK_LAST_SCOPE << 1) - 1)
 #define LANDLOCK_NUM_SCOPE		__const_hweight64(LANDLOCK_MASK_SCOPE)
 
-#define LANDLOCK_LAST_RESTRICT_SELF	LANDLOCK_RESTRICT_SELF_TSYNC
+#define LANDLOCK_LAST_RESTRICT_SELF	LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
 #define LANDLOCK_MASK_RESTRICT_SELF	((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1)
 
 /* clang-format on */
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 2333a3dc5f33..4f0305796165 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -121,10 +121,12 @@ int landlock_restrict_cred_precheck(const __u32 flags,
 
 	/*
 	 * Similar checks as for seccomp(2), except that an -EPERM may be
-	 * returned.
+	 * returned, or no_new_privs may be set by the caller via
+	 * LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS.
 	 */
 	if (!task_no_new_privs(current) &&
 	    !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN)) {
+		if (!(flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS))
 			return -EPERM;
 	}
 
@@ -197,6 +199,14 @@ int landlock_restrict_cred(struct cred *const cred,
 	}
 
 	if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) {
+		/*
+		 * We know we can set no_new_privs on the current task
+		 * because this path is only valid in the syscall context
+		 */
+		if ((flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS) &&
+		     !task_no_new_privs(current) &&
+		     !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN))
+			task_set_no_new_privs(current);
 		const int tsync_err =
 			landlock_restrict_sibling_threads(current_cred(), cred);
 
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index c710e8b16150..6723806723d5 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -402,6 +402,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
  *         - %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON
  *         - %LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF
  *         - %LANDLOCK_RESTRICT_SELF_TSYNC
+ *         - %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
  *
  * This system call enforces a Landlock ruleset on the current thread.
  * Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its
@@ -461,5 +462,11 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
 		return err;
 	}
 
+	/* In syscall context we can set no_new_privs directly. */
+	if ((flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS) &&
+	    !task_no_new_privs(current) &&
+	    !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN))
+		task_set_no_new_privs(current);
+
 	return commit_creds(new_cred);
 }
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 04/20] selftests/landlock: Cover LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (2 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 03/20] landlock: Implement LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 05/20] landlock: Make ruleset deferred free RCU safe Justin Suess
                   ` (17 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Add tests to cover LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS.

Add a new field to the scoped domain variant specifying whether
the test is to be run by manually calling
prctl(PR_SET_NO_NEW_PRIVS,...) or to call it with this flag. Add
variants for the scoped domain tests validating the flag works
identically to the manual prctl call for userspace code.

Fix a small issue in restrict_self_checks_ordering which assumed
-1 was always an invalid flag by properly computing an invalid
flag from the last known flag.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 tools/testing/selftests/landlock/base_test.c  |   8 +-
 tools/testing/selftests/landlock/common.h     |  24 +++-
 tools/testing/selftests/landlock/fs_test.c    | 103 ++++++++++--------
 tools/testing/selftests/landlock/net_test.c   |  55 ++++++----
 .../testing/selftests/landlock/ptrace_test.c  |  14 +--
 .../landlock/scoped_abstract_unix_test.c      |  51 ++++++---
 .../selftests/landlock/scoped_base_variants.h |  23 ++++
 .../selftests/landlock/scoped_common.h        |   5 +-
 .../selftests/landlock/scoped_signal_test.c   |  30 +++--
 9 files changed, 206 insertions(+), 107 deletions(-)

diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 30d37234086c..a4c38541de70 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -244,6 +244,8 @@ TEST(restrict_self_checks_ordering)
 	};
 	const int ruleset_fd =
 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	const int last_flag = LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS;
+	const int invalid_flag = last_flag << 1;
 
 	ASSERT_LE(0, ruleset_fd);
 	path_beneath_attr.parent_fd =
@@ -255,7 +257,7 @@ TEST(restrict_self_checks_ordering)
 
 	/* Checks unprivileged enforcement without no_new_privs. */
 	drop_caps(_metadata);
-	ASSERT_EQ(-1, landlock_restrict_self(-1, -1));
+	ASSERT_EQ(-1, landlock_restrict_self(-1, invalid_flag));
 	ASSERT_EQ(EPERM, errno);
 	ASSERT_EQ(-1, landlock_restrict_self(-1, 0));
 	ASSERT_EQ(EPERM, errno);
@@ -265,7 +267,7 @@ TEST(restrict_self_checks_ordering)
 	ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
 
 	/* Checks invalid flags. */
-	ASSERT_EQ(-1, landlock_restrict_self(-1, -1));
+	ASSERT_EQ(-1, landlock_restrict_self(-1, invalid_flag));
 	ASSERT_EQ(EINVAL, errno);
 
 	/* Checks invalid ruleset FD. */
@@ -306,7 +308,7 @@ TEST(restrict_self_fd_logging_flags)
 
 TEST(restrict_self_logging_flags)
 {
-	const __u32 last_flag = LANDLOCK_RESTRICT_SELF_TSYNC;
+	const __u32 last_flag = LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS;
 
 	/* Tests invalid flag combinations. */
 
diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
index 90551650299c..f6d6a6a99c52 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -194,11 +194,27 @@ static int __maybe_unused send_fd(int usock, int fd_tx)
 	return 0;
 }
 
+/*
+ * Scoped domain options
+ */
+struct scoped_domain_opts {
+	bool use_restrict_self_no_new_privs;
+};
+
+static const struct scoped_domain_opts default_scoped_domain_opts = { 0 };
+
 static void __maybe_unused
-enforce_ruleset(struct __test_metadata *const _metadata, const int ruleset_fd)
+enforce_ruleset(struct __test_metadata *const _metadata, const int ruleset_fd,
+		const struct scoped_domain_opts opts)
 {
-	ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
-	ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0))
+	/* Skip the explicit prctl() when the syscall flag sets no_new_privs. */
+	if (!opts.use_restrict_self_no_new_privs)
+		ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
+	ASSERT_EQ(0,
+		  landlock_restrict_self(ruleset_fd,
+					 opts.use_restrict_self_no_new_privs ?
+					 LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS :
+					 0))
 	{
 		TH_LOG("Failed to enforce ruleset: %s", strerror(errno));
 	}
@@ -216,7 +232,7 @@ drop_access_rights(struct __test_metadata *const _metadata,
 	{
 		TH_LOG("Failed to create a ruleset: %s", strerror(errno));
 	}
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 	EXPECT_EQ(0, close(ruleset_fd));
 }
 
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index cdb47fc1fc0a..b82b44405dbe 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -790,7 +790,18 @@ static void enforce_fs(struct __test_metadata *const _metadata,
 {
 	const int ruleset_fd = create_ruleset(_metadata, access_fs, rules);
 
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
+	EXPECT_EQ(0, close(ruleset_fd));
+}
+
+static void enforce_resolve_unix(struct __test_metadata *const _metadata,
+				 const struct rule rules[],
+				 const struct scoped_domain_opts opts)
+{
+	const int ruleset_fd =
+		create_ruleset(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, rules);
+
+	enforce_ruleset(_metadata, ruleset_fd, opts);
 	EXPECT_EQ(0, close(ruleset_fd));
 }
 
@@ -805,14 +816,15 @@ TEST_F_FORK(layout0, proc_nsfs)
 		{},
 	};
 	struct landlock_path_beneath_attr path_beneath;
-	const int ruleset_fd = create_ruleset(
-		_metadata, rules[0].access | LANDLOCK_ACCESS_FS_READ_DIR,
-		rules);
+	const int ruleset_fd =
+		create_ruleset(_metadata,
+			       rules[0].access | LANDLOCK_ACCESS_FS_READ_DIR,
+			       rules);
 
 	ASSERT_LE(0, ruleset_fd);
 	ASSERT_EQ(0, test_open("/proc/self/ns/mnt", O_RDONLY));
 
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 
 	ASSERT_EQ(EACCES, test_open("/", O_RDONLY));
 	ASSERT_EQ(EACCES, test_open("/dev", O_RDONLY));
@@ -862,7 +874,7 @@ TEST_F_FORK(layout0, unpriv)
 	ASSERT_EQ(EPERM, errno);
 
 	/* enforce_ruleset() calls prctl(no_new_privs). */
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 	ASSERT_EQ(0, close(ruleset_fd));
 }
 
@@ -1289,7 +1301,7 @@ TEST_F_FORK(layout1, inherit_subset)
 	};
 	const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
 
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 
 	ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY));
 	ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
@@ -1322,7 +1334,7 @@ TEST_F_FORK(layout1, inherit_subset)
 	 * LANDLOCK_ACCESS_FS_WRITE_FILE must not be allowed because it would
 	 * be a privilege escalation.
 	 */
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 
 	/* Same tests and results as above. */
 	ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY));
@@ -1343,7 +1355,7 @@ TEST_F_FORK(layout1, inherit_subset)
 	 * directory: dir_s1d1.
 	 */
 	add_path_beneath(_metadata, ruleset_fd, ACCESS_RW, dir_s1d1);
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 
 	/* Same tests and results as above. */
 	ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY));
@@ -1366,7 +1378,7 @@ TEST_F_FORK(layout1, inherit_subset)
 	 */
 	add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE,
 			 dir_s1d3);
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 	ASSERT_EQ(0, close(ruleset_fd));
 
 	/*
@@ -1404,7 +1416,7 @@ TEST_F_FORK(layout1, inherit_superset)
 	};
 	const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
 
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 
 	/* Readdir access is denied for dir_s1d2. */
 	ASSERT_EQ(EACCES, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY));
@@ -1418,7 +1430,7 @@ TEST_F_FORK(layout1, inherit_superset)
 			 LANDLOCK_ACCESS_FS_READ_FILE |
 				 LANDLOCK_ACCESS_FS_READ_DIR,
 			 dir_s1d2);
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 	EXPECT_EQ(0, close(ruleset_fd));
 
 	/* Readdir access is still denied for dir_s1d2. */
@@ -1442,7 +1454,8 @@ TEST_F_FORK(layout0, max_layers)
 	const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
 
 	for (i = 0; i < 16; i++)
-		enforce_ruleset(_metadata, ruleset_fd);
+		enforce_ruleset(_metadata, ruleset_fd,
+				default_scoped_domain_opts);
 
 	for (i = 0; i < 2; i++) {
 		err = landlock_restrict_self(ruleset_fd, 0);
@@ -1472,12 +1485,12 @@ TEST_F_FORK(layout1, empty_or_same_ruleset)
 	/* Nests a policy which denies read access to all directories. */
 	ruleset_fd =
 		create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_DIR, NULL);
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 	ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
 	ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY));
 
 	/* Enforces a second time with the same ruleset. */
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 	ASSERT_EQ(0, close(ruleset_fd));
 }
 
@@ -1725,7 +1738,7 @@ TEST_F_FORK(layout1, release_inodes)
 	ASSERT_EQ(0, umount(dir_s3d2));
 	clear_cap(_metadata, CAP_SYS_ADMIN);
 
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 	EXPECT_EQ(0, close(ruleset_fd));
 
 	ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY));
@@ -1766,7 +1779,7 @@ TEST_F_FORK(layout1, covered_rule)
 
 	ASSERT_EQ(0, test_open(dir_s3d2, O_RDONLY));
 
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 	ASSERT_EQ(0, close(ruleset_fd));
 
 	/* Checks that access to the new mount point is denied. */
@@ -1828,7 +1841,7 @@ static void test_relative_path(struct __test_metadata *const _metadata,
 	}
 
 	set_cap(_metadata, CAP_SYS_CHROOT);
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 
 	switch (rel) {
 	case REL_OPEN:
@@ -4402,9 +4415,9 @@ static void test_connect_to_parent(struct __test_metadata *const _metadata,
 	char buf[1];
 
 	if (variant->domain_both)
-		enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, NULL);
+		enforce_resolve_unix(_metadata, NULL, variant->domain_opts);
 	else if (flags & ENFORCE_ALL)
-		enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, rules);
+		enforce_resolve_unix(_metadata, rules, variant->domain_opts);
 
 	unlink(path);
 	ASSERT_EQ(0, pipe2(readiness_pipe, O_CLOEXEC));
@@ -4414,11 +4427,11 @@ static void test_connect_to_parent(struct __test_metadata *const _metadata,
 
 	if (child_pid == 0) {
 		if (variant->domain_child)
-			enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
-				   NULL);
+			enforce_resolve_unix(_metadata, NULL,
+					     variant->domain_opts);
 		else if (flags & ENFORCE_ALL)
-			enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
-				   rules);
+			enforce_resolve_unix(_metadata, rules,
+					     variant->domain_opts);
 
 		/* Wait for server to be available. */
 		EXPECT_EQ(0, close(readiness_pipe[1]));
@@ -4444,9 +4457,9 @@ static void test_connect_to_parent(struct __test_metadata *const _metadata,
 	}
 
 	if (variant->domain_parent)
-		enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, NULL);
+		enforce_resolve_unix(_metadata, NULL, variant->domain_opts);
 	else if (flags & ENFORCE_ALL)
-		enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, rules);
+		enforce_resolve_unix(_metadata, rules, variant->domain_opts);
 
 	srv_fd = set_up_named_unix_server(_metadata, sock_type, path);
 
@@ -4485,9 +4498,9 @@ static void test_connect_to_child(struct __test_metadata *const _metadata,
 	char buf[1];
 
 	if (variant->domain_both)
-		enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, NULL);
+		enforce_resolve_unix(_metadata, NULL, variant->domain_opts);
 	else if (flags & ENFORCE_ALL)
-		enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, rules);
+		enforce_resolve_unix(_metadata, rules, variant->domain_opts);
 
 	unlink(path);
 	ASSERT_EQ(0, pipe2(readiness_pipe, O_CLOEXEC));
@@ -4498,11 +4511,11 @@ static void test_connect_to_child(struct __test_metadata *const _metadata,
 
 	if (child_pid == 0) {
 		if (variant->domain_child)
-			enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
-				   NULL);
+			enforce_resolve_unix(_metadata, NULL,
+					     variant->domain_opts);
 		else if (flags & ENFORCE_ALL)
-			enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
-				   rules);
+			enforce_resolve_unix(_metadata, rules,
+					     variant->domain_opts);
 
 		srv_fd = set_up_named_unix_server(_metadata, sock_type, path);
 
@@ -4526,9 +4539,9 @@ static void test_connect_to_child(struct __test_metadata *const _metadata,
 	}
 
 	if (variant->domain_parent)
-		enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, NULL);
+		enforce_resolve_unix(_metadata, NULL, variant->domain_opts);
 	else if (flags & ENFORCE_ALL)
-		enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, rules);
+		enforce_resolve_unix(_metadata, rules, variant->domain_opts);
 
 	/* Wait for server to be available. */
 	EXPECT_EQ(0, close(readiness_pipe[1]));
@@ -5072,7 +5085,7 @@ TEST_F_FORK(layout1_bind, path_disconnected)
 		create_ruleset(_metadata, ACCESS_RW, layer3_only_s1d2);
 	int bind_s1d3_fd;
 
-	enforce_ruleset(_metadata, ruleset_fd_l1);
+	enforce_ruleset(_metadata, ruleset_fd_l1, default_scoped_domain_opts);
 	EXPECT_EQ(0, close(ruleset_fd_l1));
 
 	bind_s1d3_fd = open(bind_dir_s1d3, O_PATH | O_CLOEXEC);
@@ -5102,7 +5115,7 @@ TEST_F_FORK(layout1_bind, path_disconnected)
 		  test_open_rel(bind_s1d3_fd, "..", O_RDONLY | O_DIRECTORY));
 
 	/* This should still work with a narrower rule. */
-	enforce_ruleset(_metadata, ruleset_fd_l2);
+	enforce_ruleset(_metadata, ruleset_fd_l2, default_scoped_domain_opts);
 	EXPECT_EQ(0, close(ruleset_fd_l2));
 
 	EXPECT_EQ(0, test_open(file1_s4d1, O_RDONLY));
@@ -5114,7 +5127,7 @@ TEST_F_FORK(layout1_bind, path_disconnected)
 	EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
 	EXPECT_EQ(EACCES, test_open_rel(bind_s1d3_fd, file2_name, O_RDONLY));
 
-	enforce_ruleset(_metadata, ruleset_fd_l3);
+	enforce_ruleset(_metadata, ruleset_fd_l3, default_scoped_domain_opts);
 	EXPECT_EQ(0, close(ruleset_fd_l3));
 
 	EXPECT_EQ(EACCES, test_open(file1_s4d1, O_RDONLY));
@@ -5176,7 +5189,7 @@ TEST_F_FORK(layout1_bind, path_disconnected_rename)
 	ruleset_fd_l2 = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE,
 				       layer2_only_s1d2);
 
-	enforce_ruleset(_metadata, ruleset_fd_l1);
+	enforce_ruleset(_metadata, ruleset_fd_l1, default_scoped_domain_opts);
 	EXPECT_EQ(0, close(ruleset_fd_l1));
 
 	bind_s1d3_fd = open(bind_dir_s1d3, O_PATH | O_CLOEXEC);
@@ -5201,7 +5214,8 @@ TEST_F_FORK(layout1_bind, path_disconnected_rename)
 	child_pid = fork();
 	ASSERT_LE(0, child_pid);
 	if (child_pid == 0) {
-		enforce_ruleset(_metadata, ruleset_fd_l2);
+		enforce_ruleset(_metadata, ruleset_fd_l2,
+				default_scoped_domain_opts);
 		EXPECT_EQ(0, close(ruleset_fd_l2));
 		EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
 		EXPECT_EQ(EACCES, test_open(file1_s4d2, O_RDONLY));
@@ -5238,7 +5252,8 @@ TEST_F_FORK(layout1_bind, path_disconnected_rename)
 	child_pid = fork();
 	ASSERT_LE(0, child_pid);
 	if (child_pid == 0) {
-		enforce_ruleset(_metadata, ruleset_fd_l2);
+		enforce_ruleset(_metadata, ruleset_fd_l2,
+				default_scoped_domain_opts);
 		EXPECT_EQ(0, close(ruleset_fd_l2));
 		EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
 		EXPECT_EQ(0, test_open(file1_s1d3, O_RDONLY));
@@ -5290,7 +5305,7 @@ TEST_F_FORK(layout1_bind, path_disconnected_rename)
 	}
 
 	/* Checks again that we can access it under l2. */
-	enforce_ruleset(_metadata, ruleset_fd_l2);
+	enforce_ruleset(_metadata, ruleset_fd_l2, default_scoped_domain_opts);
 	EXPECT_EQ(0, close(ruleset_fd_l2));
 	EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
 	EXPECT_EQ(0, test_open(file1_s1d3, O_RDONLY));
@@ -5914,7 +5929,7 @@ TEST_F_FORK(layout4_disconnected_leafs, read_rename_exchange)
 	EXPECT_EQ(ENOENT, test_open_rel(s1d41_bind_fd, "..", O_DIRECTORY));
 	EXPECT_EQ(ENOENT, test_open_rel(s1d42_bind_fd, "..", O_DIRECTORY));
 
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 	EXPECT_EQ(0, close(ruleset_fd));
 
 	EXPECT_EQ(variant->expected_read_result,
@@ -6430,7 +6445,7 @@ TEST_F_FORK(layout5_disconnected_branch, read_rename_exchange)
 	EXPECT_EQ(0, test_open_rel(s1d3_bind_fd, "..", O_DIRECTORY));
 	EXPECT_EQ(ENOENT, test_open_rel(s1d3_bind_fd, "../..", O_DIRECTORY));
 
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 	EXPECT_EQ(0, close(ruleset_fd));
 
 	EXPECT_EQ(variant->expected_read_result,
@@ -7201,7 +7216,7 @@ TEST_F_FORK(layout3_fs, release_inodes)
 	ASSERT_EQ(0, mount_opt(&mnt_tmp, TMP_DIR));
 	clear_cap(_metadata, CAP_SYS_ADMIN);
 
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 	ASSERT_EQ(0, close(ruleset_fd));
 
 	/* Checks that access to the new mount point is denied. */
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index 4c528154ea92..33a39a264f6b 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -671,7 +671,8 @@ TEST_F(protocol, bind)
 			  landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
 					    &tcp_connect_p1, 0));
 
-		enforce_ruleset(_metadata, ruleset_fd);
+		enforce_ruleset(_metadata, ruleset_fd,
+				default_scoped_domain_opts);
 		EXPECT_EQ(0, close(ruleset_fd));
 	}
 
@@ -721,7 +722,8 @@ TEST_F(protocol, connect)
 			  landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
 					    &tcp_bind_p1, 0));
 
-		enforce_ruleset(_metadata, ruleset_fd);
+		enforce_ruleset(_metadata, ruleset_fd,
+				default_scoped_domain_opts);
 		EXPECT_EQ(0, close(ruleset_fd));
 	}
 
@@ -755,7 +757,8 @@ TEST_F(protocol, bind_unspec)
 		ASSERT_EQ(0,
 			  landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
 					    &tcp_bind, 0));
-		enforce_ruleset(_metadata, ruleset_fd);
+		enforce_ruleset(_metadata, ruleset_fd,
+				default_scoped_domain_opts);
 		EXPECT_EQ(0, close(ruleset_fd));
 	}
 
@@ -788,7 +791,8 @@ TEST_F(protocol, bind_unspec)
 		ASSERT_LE(0, ruleset_fd);
 
 		/* Denies bind. */
-		enforce_ruleset(_metadata, ruleset_fd);
+		enforce_ruleset(_metadata, ruleset_fd,
+				default_scoped_domain_opts);
 		EXPECT_EQ(0, close(ruleset_fd));
 	}
 
@@ -874,7 +878,8 @@ TEST_F(protocol, connect_unspec)
 			ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
 						       LANDLOCK_RULE_NET_PORT,
 						       &tcp_connect, 0));
-			enforce_ruleset(_metadata, ruleset_fd);
+			enforce_ruleset(_metadata, ruleset_fd,
+					default_scoped_domain_opts);
 			EXPECT_EQ(0, close(ruleset_fd));
 		}
 
@@ -902,7 +907,8 @@ TEST_F(protocol, connect_unspec)
 			ASSERT_LE(0, ruleset_fd);
 
 			/* Denies connect. */
-			enforce_ruleset(_metadata, ruleset_fd);
+			enforce_ruleset(_metadata, ruleset_fd,
+					default_scoped_domain_opts);
 			EXPECT_EQ(0, close(ruleset_fd));
 		}
 
@@ -1034,7 +1040,8 @@ TEST_F(ipv4, from_unix_to_inet)
 			  landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
 					    &tcp_bind_connect_p0, 0));
 
-		enforce_ruleset(_metadata, ruleset_fd);
+		enforce_ruleset(_metadata, ruleset_fd,
+				default_scoped_domain_opts);
 		EXPECT_EQ(0, close(ruleset_fd));
 	}
 
@@ -1181,7 +1188,8 @@ TEST_F(tcp_layers, ruleset_overlap)
 		ASSERT_EQ(0,
 			  landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
 					    &tcp_bind_connect, 0));
-		enforce_ruleset(_metadata, ruleset_fd);
+		enforce_ruleset(_metadata, ruleset_fd,
+				default_scoped_domain_opts);
 		EXPECT_EQ(0, close(ruleset_fd));
 	}
 
@@ -1197,7 +1205,8 @@ TEST_F(tcp_layers, ruleset_overlap)
 		ASSERT_EQ(0,
 			  landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
 					    &tcp_bind, 0));
-		enforce_ruleset(_metadata, ruleset_fd);
+		enforce_ruleset(_metadata, ruleset_fd,
+				default_scoped_domain_opts);
 		EXPECT_EQ(0, close(ruleset_fd));
 	}
 
@@ -1213,7 +1222,8 @@ TEST_F(tcp_layers, ruleset_overlap)
 		ASSERT_EQ(0,
 			  landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
 					    &tcp_bind_connect, 0));
-		enforce_ruleset(_metadata, ruleset_fd);
+		enforce_ruleset(_metadata, ruleset_fd,
+				default_scoped_domain_opts);
 		EXPECT_EQ(0, close(ruleset_fd));
 	}
 
@@ -1244,7 +1254,8 @@ TEST_F(tcp_layers, ruleset_expand)
 		ASSERT_EQ(0,
 			  landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
 					    &bind_srv0, 0));
-		enforce_ruleset(_metadata, ruleset_fd);
+		enforce_ruleset(_metadata, ruleset_fd,
+				default_scoped_domain_opts);
 		EXPECT_EQ(0, close(ruleset_fd));
 	}
 
@@ -1276,7 +1287,8 @@ TEST_F(tcp_layers, ruleset_expand)
 		ASSERT_EQ(0,
 			  landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
 					    &tcp_bind_p1, 0));
-		enforce_ruleset(_metadata, ruleset_fd);
+		enforce_ruleset(_metadata, ruleset_fd,
+				default_scoped_domain_opts);
 		EXPECT_EQ(0, close(ruleset_fd));
 	}
 
@@ -1298,7 +1310,8 @@ TEST_F(tcp_layers, ruleset_expand)
 		ASSERT_EQ(0,
 			  landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
 					    &tcp_bind_p0, 0));
-		enforce_ruleset(_metadata, ruleset_fd);
+		enforce_ruleset(_metadata, ruleset_fd,
+				default_scoped_domain_opts);
 		EXPECT_EQ(0, close(ruleset_fd));
 	}
 
@@ -1546,7 +1559,7 @@ TEST_F(mini, tcp_port_overflow)
 					&port_overflow4, 0));
 	EXPECT_EQ(EINVAL, errno);
 
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 
 	test_bind_and_connect(_metadata, &srv_denied, true, true);
 	test_bind_and_connect(_metadata, &srv_max_allowed, false, false);
@@ -1611,7 +1624,7 @@ TEST_F(ipv4_tcp, port_endianness)
 				       &connect_big_endian_p0, 0));
 	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
 				       &bind_connect_host_endian_p1, 0));
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 
 	/* No restriction for big endinan CPU. */
 	test_bind_and_connect(_metadata, &self->srv0, false, little_endian);
@@ -1652,7 +1665,7 @@ TEST_F(ipv4_tcp, with_fs)
 	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
 				       &tcp_bind, 0));
 
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 	EXPECT_EQ(0, close(ruleset_fd));
 
 	/* Tests file access. */
@@ -1766,7 +1779,8 @@ TEST_F(port_specific, bind_connect_zero)
 			  landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
 					    &tcp_bind_connect_zero, 0));
 
-		enforce_ruleset(_metadata, ruleset_fd);
+		enforce_ruleset(_metadata, ruleset_fd,
+				default_scoped_domain_opts);
 		EXPECT_EQ(0, close(ruleset_fd));
 	}
 
@@ -1843,7 +1857,8 @@ TEST_F(port_specific, bind_connect_1023)
 			  landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
 					    &tcp_bind_connect, 0));
 
-		enforce_ruleset(_metadata, ruleset_fd);
+		enforce_ruleset(_metadata, ruleset_fd,
+				default_scoped_domain_opts);
 		EXPECT_EQ(0, close(ruleset_fd));
 	}
 
@@ -1982,7 +1997,7 @@ TEST_F(audit, bind)
 	ruleset_fd =
 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
 	ASSERT_LE(0, ruleset_fd);
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 	EXPECT_EQ(0, close(ruleset_fd));
 
 	sock_fd = socket_variant(&self->srv0);
@@ -2010,7 +2025,7 @@ TEST_F(audit, connect)
 	ruleset_fd =
 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
 	ASSERT_LE(0, ruleset_fd);
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, default_scoped_domain_opts);
 	EXPECT_EQ(0, close(ruleset_fd));
 
 	sock_fd = socket_variant(&self->srv0);
diff --git a/tools/testing/selftests/landlock/ptrace_test.c b/tools/testing/selftests/landlock/ptrace_test.c
index 1b6c8b53bf33..1c29cde8707a 100644
--- a/tools/testing/selftests/landlock/ptrace_test.c
+++ b/tools/testing/selftests/landlock/ptrace_test.c
@@ -25,7 +25,8 @@
 #define YAMA_SCOPE_DISABLED 0
 #define YAMA_SCOPE_RELATIONAL 1
 
-static void create_domain(struct __test_metadata *const _metadata)
+static void create_domain(struct __test_metadata *const _metadata,
+			  const struct scoped_domain_opts opts)
 {
 	int ruleset_fd;
 	struct landlock_ruleset_attr ruleset_attr = {
@@ -38,8 +39,7 @@ static void create_domain(struct __test_metadata *const _metadata)
 	{
 		TH_LOG("Failed to create a ruleset: %s", strerror(errno));
 	}
-	EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
-	EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
+	enforce_ruleset(_metadata, ruleset_fd, opts);
 	EXPECT_EQ(0, close(ruleset_fd));
 }
 
@@ -169,7 +169,7 @@ TEST_F(scoped_domains, trace)
 	ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
 	ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
 	if (variant->domain_both) {
-		create_domain(_metadata);
+		create_domain(_metadata, variant->domain_opts);
 		if (!__test_passed(_metadata))
 			/* Aborts before forking. */
 			return;
@@ -183,7 +183,7 @@ TEST_F(scoped_domains, trace)
 		ASSERT_EQ(0, close(pipe_parent[1]));
 		ASSERT_EQ(0, close(pipe_child[0]));
 		if (variant->domain_child)
-			create_domain(_metadata);
+			create_domain(_metadata, variant->domain_opts);
 
 		/* Waits for the parent to be in a domain, if any. */
 		ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
@@ -238,7 +238,7 @@ TEST_F(scoped_domains, trace)
 	ASSERT_EQ(0, close(pipe_child[1]));
 	ASSERT_EQ(0, close(pipe_parent[0]));
 	if (variant->domain_parent)
-		create_domain(_metadata);
+		create_domain(_metadata, variant->domain_opts);
 
 	/* Signals that the parent is in a domain, if any. */
 	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
@@ -396,7 +396,7 @@ TEST_F(audit, trace)
 
 	ASSERT_EQ(0, close(pipe_child[1]));
 	ASSERT_EQ(0, close(pipe_parent[0]));
-	create_domain(_metadata);
+	create_domain(_metadata, default_scoped_domain_opts);
 
 	/* Signals that the parent is in a domain. */
 	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
index c47491d2d1c1..d89f54edf9d5 100644
--- a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
+++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
@@ -88,7 +88,8 @@ TEST_F(scoped_domains, connect_to_parent)
 	ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
 	if (variant->domain_both) {
 		create_scoped_domain(_metadata,
-				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+				     variant->domain_opts);
 		if (!__test_passed(_metadata))
 			return;
 	}
@@ -103,7 +104,8 @@ TEST_F(scoped_domains, connect_to_parent)
 		EXPECT_EQ(0, close(pipe_parent[1]));
 		if (variant->domain_child)
 			create_scoped_domain(
-				_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+				_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+				variant->domain_opts);
 
 		stream_client = socket(AF_UNIX, SOCK_STREAM, 0);
 		ASSERT_LE(0, stream_client);
@@ -138,7 +140,8 @@ TEST_F(scoped_domains, connect_to_parent)
 	EXPECT_EQ(0, close(pipe_parent[0]));
 	if (variant->domain_parent)
 		create_scoped_domain(_metadata,
-				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+				     variant->domain_opts);
 
 	stream_server = socket(AF_UNIX, SOCK_STREAM, 0);
 	ASSERT_LE(0, stream_server);
@@ -186,7 +189,8 @@ TEST_F(scoped_domains, connect_to_child)
 	ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
 	if (variant->domain_both) {
 		create_scoped_domain(_metadata,
-				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+				     variant->domain_opts);
 		if (!__test_passed(_metadata))
 			return;
 	}
@@ -200,7 +204,8 @@ TEST_F(scoped_domains, connect_to_child)
 		EXPECT_EQ(0, close(pipe_child[0]));
 		if (variant->domain_child)
 			create_scoped_domain(
-				_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+				_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+				variant->domain_opts);
 
 		/* Waits for the parent to be in a domain, if any. */
 		ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
@@ -231,7 +236,8 @@ TEST_F(scoped_domains, connect_to_child)
 
 	if (variant->domain_parent)
 		create_scoped_domain(_metadata,
-				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+				     variant->domain_opts);
 
 	/* Signals that the parent is in a domain, if any. */
 	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
@@ -344,7 +350,8 @@ TEST_F(scoped_audit, connect_to_child)
 	EXPECT_EQ(0, close(pipe_child[1]));
 	EXPECT_EQ(0, close(pipe_parent[0]));
 
-	create_scoped_domain(_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+	create_scoped_domain(_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+			     default_scoped_domain_opts);
 
 	/* Signals that the parent is in a domain, if any. */
 	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
@@ -429,7 +436,8 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
 		create_fs_domain(_metadata);
 	else if (variant->domain_all == SCOPE_SANDBOX)
 		create_scoped_domain(_metadata,
-				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+				     default_scoped_domain_opts);
 
 	child = fork();
 	ASSERT_LE(0, child);
@@ -444,7 +452,8 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
 			create_fs_domain(_metadata);
 		else if (variant->domain_children == SCOPE_SANDBOX)
 			create_scoped_domain(
-				_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+				_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+				default_scoped_domain_opts);
 
 		grand_child = fork();
 		ASSERT_LE(0, grand_child);
@@ -461,7 +470,8 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
 			else if (variant->domain_grand_child == SCOPE_SANDBOX)
 				create_scoped_domain(
 					_metadata,
-					LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+					LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+					default_scoped_domain_opts);
 
 			stream_client = socket(AF_UNIX, SOCK_STREAM, 0);
 			ASSERT_LE(0, stream_client);
@@ -525,7 +535,8 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
 			create_fs_domain(_metadata);
 		else if (variant->domain_child == SCOPE_SANDBOX)
 			create_scoped_domain(
-				_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+				_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+				default_scoped_domain_opts);
 
 		stream_server_child = socket(AF_UNIX, SOCK_STREAM, 0);
 		ASSERT_LE(0, stream_server_child);
@@ -552,7 +563,8 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
 		create_fs_domain(_metadata);
 	else if (variant->domain_parent == SCOPE_SANDBOX)
 		create_scoped_domain(_metadata,
-				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+				     default_scoped_domain_opts);
 
 	stream_server_parent = socket(AF_UNIX, SOCK_STREAM, 0);
 	ASSERT_LE(0, stream_server_parent);
@@ -656,7 +668,8 @@ TEST_F(outside_socket, socket_with_different_domain)
 
 		/* Client always has a domain. */
 		create_scoped_domain(_metadata,
-				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+				     default_scoped_domain_opts);
 
 		if (variant->child_socket) {
 			int data_socket, passed_socket, stream_server;
@@ -713,7 +726,8 @@ TEST_F(outside_socket, socket_with_different_domain)
 	ASSERT_LE(0, server_socket);
 
 	/* Server always has a domain. */
-	create_scoped_domain(_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+	create_scoped_domain(_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+			     default_scoped_domain_opts);
 
 	ASSERT_EQ(0, bind(server_socket, &self->address.unix_addr,
 			  self->address.unix_addr_len));
@@ -820,7 +834,8 @@ TEST_F(various_address_sockets, scoped_pathname_sockets)
 
 		if (variant->domain == SCOPE_SANDBOX)
 			create_scoped_domain(
-				_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+				_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+				default_scoped_domain_opts);
 		else if (variant->domain == OTHER_SANDBOX)
 			create_fs_domain(_metadata);
 
@@ -1027,7 +1042,8 @@ TEST(datagram_sockets)
 
 		/* Scopes the domain. */
 		create_scoped_domain(_metadata,
-				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+				     default_scoped_domain_opts);
 
 		/*
 		 * Connected socket sends data to the receiver, but the
@@ -1108,7 +1124,8 @@ TEST(self_connect)
 	if (child == 0) {
 		/* Child's domain is scoped. */
 		create_scoped_domain(_metadata,
-				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+				     default_scoped_domain_opts);
 
 		/*
 		 * The child inherits the sockets, and cannot connect or
diff --git a/tools/testing/selftests/landlock/scoped_base_variants.h b/tools/testing/selftests/landlock/scoped_base_variants.h
index 7116728ebc68..bbdf19ef18ef 100644
--- a/tools/testing/selftests/landlock/scoped_base_variants.h
+++ b/tools/testing/selftests/landlock/scoped_base_variants.h
@@ -20,6 +20,7 @@ FIXTURE_VARIANT(scoped_domains)
 	bool domain_both;
 	bool domain_parent;
 	bool domain_child;
+	struct scoped_domain_opts domain_opts;
 };
 
 /*
@@ -54,6 +55,17 @@ FIXTURE_VARIANT_ADD(scoped_domains, child_domain) {
 	.domain_child = true,
 };
 
+/* clang-format off */
+FIXTURE_VARIANT_ADD(scoped_domains, child_domain_restrict_self_no_new_privs) {
+	/* clang-format on */
+	.domain_both = false,
+	.domain_parent = false,
+	.domain_child = true,
+	.domain_opts = {
+		.use_restrict_self_no_new_privs = true,
+	},
+};
+
 /*
  *        Parent domain
  * .------.
@@ -70,6 +82,17 @@ FIXTURE_VARIANT_ADD(scoped_domains, parent_domain) {
 	.domain_child = false,
 };
 
+/* clang-format off */
+FIXTURE_VARIANT_ADD(scoped_domains, parent_domain_restrict_self_no_new_privs) {
+	/* clang-format on */
+	.domain_both = false,
+	.domain_parent = true,
+	.domain_child = false,
+	.domain_opts = {
+		.use_restrict_self_no_new_privs = true,
+	},
+};
+
 /*
  *        Parent + child domain (siblings)
  * .------.
diff --git a/tools/testing/selftests/landlock/scoped_common.h b/tools/testing/selftests/landlock/scoped_common.h
index a9a912d30c4d..23990758eef8 100644
--- a/tools/testing/selftests/landlock/scoped_common.h
+++ b/tools/testing/selftests/landlock/scoped_common.h
@@ -10,7 +10,8 @@
 #include <sys/types.h>
 
 static void create_scoped_domain(struct __test_metadata *const _metadata,
-				 const __u16 scope)
+				 const __u16 scope,
+				 const struct scoped_domain_opts opts)
 {
 	int ruleset_fd;
 	const struct landlock_ruleset_attr ruleset_attr = {
@@ -23,6 +24,6 @@ static void create_scoped_domain(struct __test_metadata *const _metadata,
 	{
 		TH_LOG("Failed to create a ruleset: %s", strerror(errno));
 	}
-	enforce_ruleset(_metadata, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd, opts);
 	EXPECT_EQ(0, close(ruleset_fd));
 }
diff --git a/tools/testing/selftests/landlock/scoped_signal_test.c b/tools/testing/selftests/landlock/scoped_signal_test.c
index d8bf33417619..dfda4a3e5374 100644
--- a/tools/testing/selftests/landlock/scoped_signal_test.c
+++ b/tools/testing/selftests/landlock/scoped_signal_test.c
@@ -111,7 +111,8 @@ TEST_F(scoping_signals, send_sig_to_parent)
 		ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
 		EXPECT_EQ(0, close(pipe_parent[0]));
 
-		create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
+		create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL,
+				     default_scoped_domain_opts);
 
 		/*
 		 * The child process cannot send signal to the parent
@@ -183,7 +184,8 @@ TEST_F(scoped_domains, check_access_signal)
 	can_signal_child = !variant->domain_parent;
 
 	if (variant->domain_both)
-		create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
+		create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL,
+				     variant->domain_opts);
 
 	ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
 	ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
@@ -197,7 +199,8 @@ TEST_F(scoped_domains, check_access_signal)
 		EXPECT_EQ(0, close(pipe_parent[1]));
 
 		if (variant->domain_child)
-			create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
+			create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL,
+					     variant->domain_opts);
 
 		ASSERT_EQ(1, write(pipe_child[1], ".", 1));
 		EXPECT_EQ(0, close(pipe_child[1]));
@@ -226,7 +229,8 @@ TEST_F(scoped_domains, check_access_signal)
 	EXPECT_EQ(0, close(pipe_child[1]));
 
 	if (variant->domain_parent)
-		create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
+		create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL,
+				     variant->domain_opts);
 
 	ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
 	EXPECT_EQ(0, close(pipe_child[0]));
@@ -280,7 +284,8 @@ TEST(signal_scoping_thread_before)
 				    &thread_pipe[0]));
 
 	/* Enforces restriction after creating the thread. */
-	create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
+	create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL,
+			     default_scoped_domain_opts);
 
 	EXPECT_EQ(0, pthread_kill(no_sandbox_thread, 0));
 	EXPECT_EQ(1, write(thread_pipe[1], ".", 1));
@@ -302,7 +307,8 @@ TEST(signal_scoping_thread_after)
 	ASSERT_EQ(0, pipe2(thread_pipe, O_CLOEXEC));
 
 	/* Enforces restriction before creating the thread. */
-	create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
+	create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL,
+			     default_scoped_domain_opts);
 
 	ASSERT_EQ(0, pthread_create(&scoped_thread, NULL, thread_sync,
 				    &thread_pipe[0]));
@@ -360,7 +366,8 @@ TEST(signal_scoping_thread_setuid)
 				    &arg));
 
 	/* Enforces restriction after creating the thread. */
-	create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
+	create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL,
+			     default_scoped_domain_opts);
 
 	EXPECT_NE(arg.new_uid, getuid());
 	EXPECT_EQ(0, setuid(arg.new_uid));
@@ -469,7 +476,8 @@ TEST_F(fown, sigurg_socket)
 	ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
 
 	if (variant->sandbox_setown == SANDBOX_BEFORE_FORK)
-		create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
+		create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL,
+				     default_scoped_domain_opts);
 
 	child = fork();
 	ASSERT_LE(0, child);
@@ -531,7 +539,8 @@ TEST_F(fown, sigurg_socket)
 	ASSERT_LE(0, recv_socket);
 
 	if (variant->sandbox_setown == SANDBOX_BEFORE_SETOWN)
-		create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
+		create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL,
+				     default_scoped_domain_opts);
 
 	/*
 	 * Sets the child to receive SIGURG for MSG_OOB.  This uncommon use is
@@ -540,7 +549,8 @@ TEST_F(fown, sigurg_socket)
 	ASSERT_EQ(0, fcntl(recv_socket, F_SETOWN, child));
 
 	if (variant->sandbox_setown == SANDBOX_AFTER_SETOWN)
-		create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
+		create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL,
+				     default_scoped_domain_opts);
 
 	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 05/20] landlock: Make ruleset deferred free RCU safe
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (3 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 04/20] selftests/landlock: Cover LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 06/20] bpf: lsm: Add Landlock kfuncs Justin Suess
                   ` (16 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Use INIT_RCU_WORK in the landlock deferred free function, ensuring that
deferred ruleset freeing is also RCU safe.

This is important for future consumers who may free a Landlock ruleset
under RCU in subsequent patches.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 security/landlock/ruleset.c | 9 +++++----
 security/landlock/ruleset.h | 6 +++---
 2 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 4f0305796165..5845cdc58d0d 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -699,16 +699,17 @@ static void free_ruleset_work(struct work_struct *const work)
 {
 	struct landlock_ruleset *ruleset;
 
-	ruleset = container_of(work, struct landlock_ruleset, work_free);
+	ruleset = container_of(to_rcu_work(work), struct landlock_ruleset,
+			       work_free);
 	free_ruleset(ruleset);
 }
 
-/* Only called by hook_cred_free(). */
+/* Called by deferred ruleset owners that cannot free from their context. */
 void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset)
 {
 	if (ruleset && refcount_dec_and_test(&ruleset->usage)) {
-		INIT_WORK(&ruleset->work_free, free_ruleset_work);
-		schedule_work(&ruleset->work_free);
+		INIT_RCU_WORK(&ruleset->work_free, free_ruleset_work);
+		queue_rcu_work(system_wq, &ruleset->work_free);
 	}
 }
 
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 0facc5cb6555..fbbd1b73476e 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -146,13 +146,13 @@ struct landlock_ruleset {
 	struct landlock_hierarchy *hierarchy;
 	union {
 		/**
-		 * @work_free: Enables to free a ruleset within a lockless
-		 * section.  This is only used by
+		 * @work_free: Enables to free a ruleset after an RCU grace
+		 * period from a sleepable context.  This is only used by
 		 * landlock_put_ruleset_deferred() when @usage reaches zero.
 		 * The fields @lock, @usage, @num_rules, @num_layers and
 		 * @access_masks are then unused.
 		 */
-		struct work_struct work_free;
+		struct rcu_work work_free;
 		struct {
 			/**
 			 * @lock: Protects against concurrent modifications of
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 06/20] bpf: lsm: Add Landlock kfuncs
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (4 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 05/20] landlock: Make ruleset deferred free RCU safe Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 07/20] bpf: arraymap: Implement Landlock ruleset map Justin Suess
                   ` (15 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Create 2 kfuncs exposing control over Landlock functionality to BPF
callers. Export an opaque struct bpf_landlock_ruleset preventing callers
from accessing unstable internal Landlock fields.

1) bpf_landlock_put_ruleset releases a reference on a bpf_landlock_ruleset.
This is properly passed to the verifier with the KF_RELEASE annotation.

2) bpf_landlock_restrict_binprm alters the pre-committed credentials in the
linux_binprm struct, ensuring the program will start with the specified
landlock ruleset. Normal domain inheritance, for existing and future
domains apply as normal.

To enable proper reference counting and destruction, a destructor is
registered for the bpf_landlock_ruleset.

Additionally, both kfuncs are restricted to LSM programs attached to
bprm_creds_for_exec or bprm_creds_from_file, and only sleepable varients
of these hooks. Landlock may block because a ruleset is protected by a
lock, so both of the above kfuncs may sleep and are KF_SLEEPABLE.

If RESTRICT_FLAGS_NO_NEW_PRIVS is set, and the task doesn't have
CAP_SYS_ADMIN or is not already running with no_new_privs, we set the
set_nnp_on_point_of_no_return to ensure that the next execution
transition (but not the current one) will be subject to no_new_privs.

Running task_set_no_new_privs directly is unsafe in this path, as a
failed execution will result in a lingering side effect of no_new_privs
being set on the original thread.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 include/linux/bpf_lsm.h |  15 +++++
 kernel/bpf/bpf_lsm.c    | 145 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 160 insertions(+)

diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
index 643809cc78c3..1fc019c0db44 100644
--- a/include/linux/bpf_lsm.h
+++ b/include/linux/bpf_lsm.h
@@ -31,6 +31,21 @@ int bpf_lsm_verify_prog(struct bpf_verifier_log *vlog,
 bool bpf_lsm_is_sleepable_hook(u32 btf_id);
 bool bpf_lsm_is_trusted(const struct bpf_prog *prog);
 
+/*
+ * Opaque type for BPF landlock ruleset.  This is used to prevent BPF programs
+ * from directly accessing the landlock_ruleset structure, which is not designed
+ * for external use and may change in the future.
+ */
+struct bpf_landlock_ruleset {};
+BTF_ID_LIST_SINGLE(bpf_landlock_ruleset_btf_ids, struct, bpf_landlock_ruleset)
+__bpf_kfunc void
+bpf_landlock_put_ruleset(const struct bpf_landlock_ruleset *ruleset);
+__bpf_kfunc int
+bpf_landlock_restrict_binprm(struct linux_binprm *bprm,
+			     const struct bpf_landlock_ruleset *ruleset,
+			     u32 flags);
+__bpf_kfunc void bpf_landlock_put_ruleset_dtor(void *ruleset);
+
 static inline struct bpf_storage_blob *bpf_inode(
 	const struct inode *inode)
 {
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 0c4a0c8e6f70..5da9950aa555 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -16,6 +16,7 @@
 #include <linux/btf_ids.h>
 #include <linux/ima.h>
 #include <linux/bpf-cgroup.h>
+#include <linux/landlock.h>
 
 /* For every LSM hook that allows attachment of BPF programs, declare a nop
  * function where a BPF program can be attached. Notably, we qualify each with
@@ -447,3 +448,147 @@ int bpf_lsm_get_retval_range(const struct bpf_prog *prog,
 	}
 	return 0;
 }
+
+BTF_SET_START(bpf_landlock_kfunc_hooks)
+BTF_ID(func, bpf_lsm_bprm_creds_for_exec)
+BTF_ID(func, bpf_lsm_bprm_creds_from_file)
+BTF_SET_END(bpf_landlock_kfunc_hooks)
+
+BTF_KFUNCS_START(bpf_landlock_kfunc_btf_ids)
+BTF_ID_FLAGS(func, bpf_landlock_put_ruleset, KF_RELEASE | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_landlock_restrict_binprm, KF_SLEEPABLE)
+BTF_KFUNCS_END(bpf_landlock_kfunc_btf_ids)
+
+BTF_ID_LIST(bpf_landlock_dtor_ids)
+BTF_ID(struct, bpf_landlock_ruleset)
+BTF_ID(func, bpf_landlock_put_ruleset_dtor)
+
+static int bpf_landlock_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)
+{
+	if (!btf_id_set8_contains(&bpf_landlock_kfunc_btf_ids, kfunc_id))
+		return 0;
+
+	/* BPF_LSM_CGROUP programs run under classic RCU and cannot sleep. */
+	if (prog->expected_attach_type == BPF_LSM_CGROUP)
+		return -EACCES;
+
+	if (!btf_id_set_contains(&bpf_landlock_kfunc_hooks,
+				 prog->aux->attach_btf_id))
+		return -EACCES;
+
+	return 0;
+}
+
+static const struct btf_kfunc_id_set bpf_landlock_kfunc_set = {
+	.owner = THIS_MODULE,
+	.set = &bpf_landlock_kfunc_btf_ids,
+	.filter = bpf_landlock_kfunc_filter,
+};
+
+static int __init bpf_landlock_kfunc_init(void)
+{
+	const struct btf_id_dtor_kfunc bpf_landlock_dtors[] = {
+		{
+			.btf_id = bpf_landlock_dtor_ids[0],
+			.kfunc_btf_id = bpf_landlock_dtor_ids[1],
+		},
+	};
+	int ret;
+
+	ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM,
+					&bpf_landlock_kfunc_set);
+	if (ret)
+		return ret;
+
+	return register_btf_id_dtor_kfuncs(bpf_landlock_dtors,
+					   ARRAY_SIZE(bpf_landlock_dtors),
+					   THIS_MODULE);
+}
+
+late_initcall(bpf_landlock_kfunc_init);
+
+__bpf_kfunc_start_defs();
+
+#if IS_ENABLED(CONFIG_SECURITY_LANDLOCK)
+
+/**
+ * bpf_landlock_put_ruleset - put a Landlock ruleset
+ * @ruleset: Landlock ruleset to put
+ */
+__bpf_kfunc void
+bpf_landlock_put_ruleset(const struct bpf_landlock_ruleset *ruleset)
+{
+	landlock_put_ruleset((struct landlock_ruleset *)ruleset);
+}
+
+/**
+ * bpf_landlock_restrict_binprm - enforce a Landlock ruleset on exec credentials
+ * @bprm: execution context providing the prepared credentials to restrict
+ * @ruleset: Landlock ruleset to enforce, may be NULL only with
+ *	LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF
+ * @flags: landlock_restrict_self() flags
+ *
+ * When @flags contains LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS, the request is
+ * staged through @bprm and committed only after exec reaches point-of-no-return.
+ * This guarantees that the resulting task cannot gain more privileges through
+ * later exec transitions, including when called from bprm_creds_from_file.
+ * The current execution is unaffected, and may escalate as usual until the next
+ * exec.
+ */
+__bpf_kfunc int
+bpf_landlock_restrict_binprm(struct linux_binprm *bprm,
+			     const struct bpf_landlock_ruleset *ruleset,
+			     u32 flags)
+{
+	int err = landlock_restrict_cred_precheck(flags, false);
+
+	if (err)
+		return err;
+
+	err = landlock_restrict_cred(bprm->cred,
+				     (struct landlock_ruleset *)ruleset,
+				     flags);
+
+	if (err)
+		return err;
+	/*
+	 * Stage no_new_privs through @bprm so exec can honor it without
+	 * mutating the current task before point-of-no-return.
+	 */
+	if ((flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS)
+	    && !task_no_new_privs(current)
+	    && !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN))
+		bprm->set_nnp_on_point_of_no_return = 1;
+
+	return err;
+}
+
+/* We define stubs for these to allow ebpf programs using landlock kfuncs to load
+ * even when CONFIG_SECURITY_LANDLOCK is not enabled.
+ */
+#else /* IS_ENABLED(CONFIG_SECURITY_LANDLOCK) */
+
+__bpf_kfunc void
+bpf_landlock_put_ruleset(const struct bpf_landlock_ruleset *ruleset)
+{
+}
+
+__bpf_kfunc int
+bpf_landlock_restrict_binprm(struct linux_binprm *bprm,
+			     const struct bpf_landlock_ruleset *ruleset,
+			     u32 flags)
+{
+	return -EOPNOTSUPP;
+}
+
+#endif /* IS_ENABLED(CONFIG_SECURITY_LANDLOCK) */
+
+/* Destructor does nothing when Landlock is not enabled */
+__bpf_kfunc void bpf_landlock_put_ruleset_dtor(void *ruleset)
+{
+	bpf_landlock_put_ruleset(ruleset);
+}
+
+CFI_NOSEAL(bpf_landlock_put_ruleset_dtor);
+
+__bpf_kfunc_end_defs();
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 07/20] bpf: arraymap: Implement Landlock ruleset map
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (5 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 06/20] bpf: lsm: Add Landlock kfuncs Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 08/20] bpf: Add Landlock ruleset map type Justin Suess
                   ` (14 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Implement a new BPF map BPF_MAP_LANDLOCK_RULESET. This specialized map type
is designed to store ruleset file descriptors, and uses the exposed
Landlock helper functions to ensure that the ruleset isn't freed
unexpectedly. This map type may only be inserted into from userspace,
and only with a file descriptor referring to a valid Landlock ruleset.
Updating a Landlock ruleset directly through a map is not supported,
as there are no fields that can be changed, but you may add rules from
userspace as long as the file descriptor is open, or replace the fd with
another. Elements in a Landlock ruleset map may be deleted from
BPF or userspace. Looking up an element is supported only in BPF, this
is enforced with the map_lookup_elem_sys_only field in the map ops.

Reuse the existing fd_array_map operations for inserting and deleting to
avoid code duplication with existing FD maps.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 kernel/bpf/arraymap.c | 67 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 67 insertions(+)

diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c
index 33de68c95ad8..f0da17e0e23e 100644
--- a/kernel/bpf/arraymap.c
+++ b/kernel/bpf/arraymap.c
@@ -8,6 +8,7 @@
 #include <linux/slab.h>
 #include <linux/mm.h>
 #include <linux/filter.h>
+#include <linux/landlock.h>
 #include <linux/perf_event.h>
 #include <uapi/linux/btf.h>
 #include <linux/rcupdate_trace.h>
@@ -1458,3 +1459,69 @@ const struct bpf_map_ops array_of_maps_map_ops = {
 	.map_mem_usage = array_map_mem_usage,
 	.map_btf_id = &array_map_btf_ids[0],
 };
+
+static int landlock_ruleset_map_alloc_check(union bpf_attr *attr)
+{
+	if (!IS_ENABLED(CONFIG_SECURITY_LANDLOCK))
+		return -EOPNOTSUPP;
+
+	return fd_array_map_alloc_check(attr);
+}
+
+static void landlock_ruleset_map_put_ptr(struct bpf_map *map, void *ptr,
+					 bool need_defer)
+{
+	if (!ptr)
+		return;
+
+	if (need_defer)
+		landlock_put_ruleset_deferred(ptr);
+	else
+		landlock_put_ruleset(ptr);
+}
+
+static void *landlock_ruleset_map_get_ptr(struct bpf_map *map,
+					  struct file *map_file, int fd)
+{
+	return landlock_get_ruleset_from_fd(fd, FMODE_CAN_READ);
+}
+
+static void *landlock_ruleset_map_lookup_elem(struct bpf_map *map, void *key)
+{
+	struct landlock_ruleset **elem, *ruleset;
+
+	rcu_read_lock();
+
+	elem = array_map_lookup_elem(map, key);
+	if (!elem) {
+		rcu_read_unlock();
+		return NULL;
+	}
+	ruleset = READ_ONCE(*elem);
+	if (!landlock_try_get_ruleset(ruleset))
+		ruleset = NULL;
+
+	rcu_read_unlock();
+
+	return ruleset;
+}
+
+static void landlock_ruleset_array_free(struct bpf_map *map)
+{
+	bpf_fd_array_map_clear(map, false);
+	fd_array_map_free(map);
+}
+
+const struct bpf_map_ops landlock_ruleset_map_ops = {
+	.map_alloc_check = landlock_ruleset_map_alloc_check,
+	.map_alloc = array_map_alloc,
+	.map_free = landlock_ruleset_array_free,
+	.map_get_next_key = bpf_array_get_next_key,
+	.map_lookup_elem_sys_only = fd_array_map_lookup_elem,
+	.map_lookup_elem = landlock_ruleset_map_lookup_elem,
+	.map_delete_elem = fd_array_map_delete_elem,
+	.map_fd_get_ptr = landlock_ruleset_map_get_ptr,
+	.map_fd_put_ptr = landlock_ruleset_map_put_ptr,
+	.map_mem_usage = array_map_mem_usage,
+	.map_btf_id = &array_map_btf_ids[0],
+};
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 08/20] bpf: Add Landlock ruleset map type
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (6 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 07/20] bpf: arraymap: Implement Landlock ruleset map Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 09/20] bpf: syscall: Handle Landlock ruleset maps Justin Suess
                   ` (13 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Expose the new BPF_MAP_TYPE_LANDLOCK_RULESET via headers, allowing
programs to utilize the map.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 include/linux/bpf_types.h      | 1 +
 include/uapi/linux/bpf.h       | 1 +
 tools/include/uapi/linux/bpf.h | 1 +
 tools/lib/bpf/libbpf.c         | 1 +
 tools/lib/bpf/libbpf_probes.c  | 6 ++++++
 5 files changed, 10 insertions(+)

diff --git a/include/linux/bpf_types.h b/include/linux/bpf_types.h
index b13de31e163f..0fa3b9031d90 100644
--- a/include/linux/bpf_types.h
+++ b/include/linux/bpf_types.h
@@ -134,6 +134,7 @@ BPF_MAP_TYPE(BPF_MAP_TYPE_BLOOM_FILTER, bloom_filter_map_ops)
 BPF_MAP_TYPE(BPF_MAP_TYPE_USER_RINGBUF, user_ringbuf_map_ops)
 BPF_MAP_TYPE(BPF_MAP_TYPE_ARENA, arena_map_ops)
 BPF_MAP_TYPE(BPF_MAP_TYPE_INSN_ARRAY, insn_array_map_ops)
+BPF_MAP_TYPE(BPF_MAP_TYPE_LANDLOCK_RULESET, landlock_ruleset_map_ops)
 
 BPF_LINK_TYPE(BPF_LINK_TYPE_RAW_TRACEPOINT, raw_tracepoint)
 BPF_LINK_TYPE(BPF_LINK_TYPE_TRACING, tracing)
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index c8d400b7680a..7e4478afa162 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -1046,6 +1046,7 @@ enum bpf_map_type {
 	BPF_MAP_TYPE_CGRP_STORAGE,
 	BPF_MAP_TYPE_ARENA,
 	BPF_MAP_TYPE_INSN_ARRAY,
+	BPF_MAP_TYPE_LANDLOCK_RULESET,
 	__MAX_BPF_MAP_TYPE
 };
 
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index 5e38b4887de6..6dd7d70b198a 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -1046,6 +1046,7 @@ enum bpf_map_type {
 	BPF_MAP_TYPE_CGRP_STORAGE,
 	BPF_MAP_TYPE_ARENA,
 	BPF_MAP_TYPE_INSN_ARRAY,
+	BPF_MAP_TYPE_LANDLOCK_RULESET,
 	__MAX_BPF_MAP_TYPE
 };
 
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 0be7017800fe..9ccd5df1ea6c 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -192,6 +192,7 @@ static const char * const map_type_name[] = {
 	[BPF_MAP_TYPE_CGRP_STORAGE]		= "cgrp_storage",
 	[BPF_MAP_TYPE_ARENA]			= "arena",
 	[BPF_MAP_TYPE_INSN_ARRAY]		= "insn_array",
+	[BPF_MAP_TYPE_LANDLOCK_RULESET]		= "landlock_ruleset",
 };
 
 static const char * const prog_type_name[] = {
diff --git a/tools/lib/bpf/libbpf_probes.c b/tools/lib/bpf/libbpf_probes.c
index bccf4bb747e1..1407d54aef67 100644
--- a/tools/lib/bpf/libbpf_probes.c
+++ b/tools/lib/bpf/libbpf_probes.c
@@ -367,6 +367,12 @@ static int probe_map_create(enum bpf_map_type map_type)
 	case BPF_MAP_TYPE_INSN_ARRAY:
 		key_size	= sizeof(__u32);
 		value_size	= sizeof(struct bpf_insn_array_value);
+		max_entries	= 1;
+		break;
+	case BPF_MAP_TYPE_LANDLOCK_RULESET:
+		key_size	= sizeof(__u32);
+		value_size	= sizeof(__u32);
+		max_entries	= 1;
 		break;
 	case BPF_MAP_TYPE_UNSPEC:
 	default:
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 09/20] bpf: syscall: Handle Landlock ruleset maps
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (7 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 08/20] bpf: Add Landlock ruleset map type Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 10/20] bpf: verifier: Add Landlock ruleset map support Justin Suess
                   ` (12 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Allow userspace to create maps of type BPF_MAP_TYPE_LANDLOCK_RULESET via
the bpf syscall. If CONFIG_SECURITY_LANDLOCK != y, these
programs will still be accepted by the verifier but return an error at
runtime.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 kernel/bpf/syscall.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 274039e36465..e885451b64a0 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -48,7 +48,8 @@
 
 #define IS_FD_ARRAY(map) ((map)->map_type == BPF_MAP_TYPE_PERF_EVENT_ARRAY || \
 			  (map)->map_type == BPF_MAP_TYPE_CGROUP_ARRAY || \
-			  (map)->map_type == BPF_MAP_TYPE_ARRAY_OF_MAPS)
+			  (map)->map_type == BPF_MAP_TYPE_ARRAY_OF_MAPS || \
+			  (map)->map_type == BPF_MAP_TYPE_LANDLOCK_RULESET)
 #define IS_FD_PROG_ARRAY(map) ((map)->map_type == BPF_MAP_TYPE_PROG_ARRAY)
 #define IS_FD_HASH(map) ((map)->map_type == BPF_MAP_TYPE_HASH_OF_MAPS)
 #define IS_FD_MAP(map) (IS_FD_ARRAY(map) || IS_FD_PROG_ARRAY(map) || \
@@ -1488,6 +1489,7 @@ static int map_create(union bpf_attr *attr, bpfptr_t uattr)
 	case BPF_MAP_TYPE_CPUMAP:
 	case BPF_MAP_TYPE_ARENA:
 	case BPF_MAP_TYPE_INSN_ARRAY:
+	case BPF_MAP_TYPE_LANDLOCK_RULESET:
 		if (!bpf_token_capable(token, CAP_BPF))
 			goto put_token;
 		break;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 10/20] bpf: verifier: Add Landlock ruleset map support
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (8 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 09/20] bpf: syscall: Handle Landlock ruleset maps Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 11/20] selftests/bpf: Add Landlock kfunc declarations Justin Suess
                   ` (11 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Add support for the BPF_MAP_TYPE_LANDLOCK_RULESET in the verifier. Mark
the map as storing a trusted pointer to a BTF object.

Specify the map as being usable from sleepable contexts.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 kernel/bpf/verifier.c | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index f108c01ff6d0..52ba58536387 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -493,7 +493,8 @@ static bool is_acquire_function(enum bpf_func_id func_id,
 
 	if (func_id == BPF_FUNC_map_lookup_elem &&
 	    (map_type == BPF_MAP_TYPE_SOCKMAP ||
-	     map_type == BPF_MAP_TYPE_SOCKHASH))
+	     map_type == BPF_MAP_TYPE_SOCKHASH ||
+	     map_type == BPF_MAP_TYPE_LANDLOCK_RULESET))
 		return true;
 
 	return false;
@@ -2269,6 +2270,10 @@ static void mark_ptr_not_null_reg(struct bpf_reg_state *reg)
 		} else if (map->map_type == BPF_MAP_TYPE_SOCKMAP ||
 			   map->map_type == BPF_MAP_TYPE_SOCKHASH) {
 			reg->type = PTR_TO_SOCKET;
+		} else if (map->map_type == BPF_MAP_TYPE_LANDLOCK_RULESET) {
+			reg->type = PTR_TO_BTF_ID | PTR_TRUSTED;
+			reg->btf = btf_vmlinux;
+			reg->btf_id = *bpf_landlock_ruleset_btf_ids;
 		} else {
 			reg->type = PTR_TO_MAP_VALUE;
 		}
@@ -10238,6 +10243,13 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env,
 		    !may_update_sockmap(env, func_id))
 			goto error;
 		break;
+	case BPF_MAP_TYPE_LANDLOCK_RULESET:
+		if (resolve_prog_type(env->prog) != BPF_PROG_TYPE_LSM)
+			goto error;
+		if (func_id != BPF_FUNC_map_lookup_elem &&
+		    func_id != BPF_FUNC_map_delete_elem)
+			goto error;
+		break;
 	case BPF_MAP_TYPE_REUSEPORT_SOCKARRAY:
 		if (func_id != BPF_FUNC_sk_select_reuseport)
 			goto error;
@@ -21662,6 +21674,7 @@ static int check_map_prog_compatibility(struct bpf_verifier_env *env,
 		case BPF_MAP_TYPE_ARENA:
 		case BPF_MAP_TYPE_INSN_ARRAY:
 		case BPF_MAP_TYPE_PROG_ARRAY:
+		case BPF_MAP_TYPE_LANDLOCK_RULESET:
 			break;
 		default:
 			verbose(env,
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 11/20] selftests/bpf: Add Landlock kfunc declarations
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (9 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 10/20] bpf: verifier: Add Landlock ruleset map support Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 12/20] selftests/landlock: Rename gettid wrapper for BPF reuse Justin Suess
                   ` (10 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Expose the Landlock kfuncs to the BPF selftests.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 tools/testing/selftests/bpf/bpf_kfuncs.h | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/tools/testing/selftests/bpf/bpf_kfuncs.h b/tools/testing/selftests/bpf/bpf_kfuncs.h
index 7dad01439391..00f2b337a232 100644
--- a/tools/testing/selftests/bpf/bpf_kfuncs.h
+++ b/tools/testing/selftests/bpf/bpf_kfuncs.h
@@ -79,6 +79,26 @@ extern int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_ptr,
 				      struct bpf_dynptr *sig_ptr,
 				      struct bpf_key *trusted_keyring) __ksym;
 
+struct bpf_landlock_ruleset;
+/*
+ * Description
+ *  Put a Landlock ruleset obtained from a Landlock ruleset map lookup.
+ */
+
+void bpf_landlock_put_ruleset(const struct bpf_landlock_ruleset *ruleset)
+	__ksym __weak;
+/*
+ * Description
+ *  Modifies the credential of the passed binary parameters to enforce the
+ *  provided landlock ruleset on the new credentials. The ruleset should
+ *  have been obtained from a Landlock ruleset map lookup.
+ * Returns
+ *  Error code same as those returned by landlock_restrict_self
+ */
+int bpf_landlock_restrict_binprm(struct linux_binprm *bprm,
+		const struct bpf_landlock_ruleset *ruleset,
+		__u32 flags) __ksym __weak;
+
 struct dentry;
 /* Description
  *  Returns xattr of a dentry
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 12/20] selftests/landlock: Rename gettid wrapper for BPF reuse
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (10 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 11/20] selftests/bpf: Add Landlock kfunc declarations Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 13/20] selftests/bpf: Enable Landlock in selftests kernel Justin Suess
                   ` (9 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Prevent a name conflict when importing the Landlock wrappers header from
the Landlock selftests into the bpf selftests by renaming the gettid
syscall wrapper.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 tools/testing/selftests/landlock/common.h   | 4 ++--
 tools/testing/selftests/landlock/wrappers.h | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
index f6d6a6a99c52..5fe0158885e5 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -262,8 +262,8 @@ static void __maybe_unused set_unix_address(struct service_fixture *const srv,
 {
 	srv->unix_addr.sun_family = AF_UNIX;
 	sprintf(srv->unix_addr.sun_path,
-		"_selftests-landlock-abstract-unix-tid%d-index%d", sys_gettid(),
-		index);
+		"_selftests-landlock-abstract-unix-tid%d-index%d",
+		landlock_gettid(), index);
 	srv->unix_addr_len = SUN_LEN(&srv->unix_addr);
 	srv->unix_addr.sun_path[0] = '\0';
 }
diff --git a/tools/testing/selftests/landlock/wrappers.h b/tools/testing/selftests/landlock/wrappers.h
index 65548323e45d..114b8c60630d 100644
--- a/tools/testing/selftests/landlock/wrappers.h
+++ b/tools/testing/selftests/landlock/wrappers.h
@@ -41,7 +41,7 @@ static inline int landlock_restrict_self(const int ruleset_fd,
 }
 #endif
 
-static inline pid_t sys_gettid(void)
+static inline pid_t landlock_gettid(void)
 {
 	return syscall(__NR_gettid);
 }
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 13/20] selftests/bpf: Enable Landlock in selftests kernel.
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (11 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 12/20] selftests/landlock: Rename gettid wrapper for BPF reuse Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 14/20] selftests/bpf: Add Landlock kfunc test program Justin Suess
                   ` (8 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Enable the BPF selftests to build the kernel with Landlock built into
the kernel and enabled at boottime. Existing LSMs
(SELinux,BPF,integrity) were moved from x86_64 specific config to be
built into all architectures.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 tools/testing/selftests/bpf/config        | 5 +++++
 tools/testing/selftests/bpf/config.x86_64 | 1 -
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config
index 24855381290d..8eca3773e968 100644
--- a/tools/testing/selftests/bpf/config
+++ b/tools/testing/selftests/bpf/config
@@ -115,6 +115,11 @@ CONFIG_RC_CORE=y
 CONFIG_SAMPLES=y
 CONFIG_SAMPLE_LIVEPATCH=m
 CONFIG_SECURITY=y
+CONFIG_SECURITY_LANDLOCK=y
+CONFIG_SECURITY_NETWORK=y
+CONFIG_SECURITY_SELINUX=y
+CONFIG_SECURITY_PATH=y
+CONFIG_LSM="selinux,bpf,integrity,landlock"
 CONFIG_SECURITYFS=y
 CONFIG_SYN_COOKIES=y
 CONFIG_TEST_BPF=m
diff --git a/tools/testing/selftests/bpf/config.x86_64 b/tools/testing/selftests/bpf/config.x86_64
index 42ad817b00ae..8a6d2af3805c 100644
--- a/tools/testing/selftests/bpf/config.x86_64
+++ b/tools/testing/selftests/bpf/config.x86_64
@@ -126,7 +126,6 @@ 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_MAC_PARTITION=y
 CONFIG_MAGIC_SYSRQ=y
 CONFIG_MCORE2=y
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 14/20] selftests/bpf: Add Landlock kfunc test program
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (12 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 13/20] selftests/bpf: Enable Landlock in selftests kernel Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 15/20] selftests/bpf: Add Landlock kfunc test runner Justin Suess
                   ` (7 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Add a BPF program that enforces a Landlock ruleset at exec time for the
purposes of selftests. The program receives a PID and a Landlock ruleset
from userspace, and calls bpf_landlock_restrict_binprm to apply the
domain to the specified process. The program then calls
bpf_landlock_put_ruleset in order to release the ruleset.

Global counters are tracked to ensure proper execution via the test
harness.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 .../selftests/bpf/progs/landlock_kfuncs.c     | 92 +++++++++++++++++++
 1 file changed, 92 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/progs/landlock_kfuncs.c

diff --git a/tools/testing/selftests/bpf/progs/landlock_kfuncs.c b/tools/testing/selftests/bpf/progs/landlock_kfuncs.c
new file mode 100644
index 000000000000..7ca089716356
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/landlock_kfuncs.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_kfuncs.h"
+
+u32 target_pid;
+bool enable_bprm_creds_for_exec;
+bool enable_bprm_creds_from_file;
+u32 restrict_flags;
+
+int matched_pid;
+int bprm_creds_for_exec_hits;
+int bprm_creds_from_file_hits;
+int lookup_calls;
+int lookup_failed;
+int restrict_calls;
+int restrict_ret;
+int put_calls;
+
+struct {
+	__uint(type, BPF_MAP_TYPE_LANDLOCK_RULESET);
+	__uint(max_entries, 1);
+	__type(key, __u32);
+	__type(value, __u32);
+} ruleset_map SEC(".maps");
+
+char _license[] SEC("license") = "GPL";
+
+static __always_inline bool is_target_exec(void)
+{
+	u32 pid;
+
+	if (!target_pid)
+		return false;
+
+	pid = bpf_get_current_pid_tgid() >> 32;
+	if (pid != target_pid)
+		return false;
+
+	matched_pid = 1;
+	return true;
+}
+
+static __always_inline int apply_landlock_ruleset(struct linux_binprm *bprm,
+						  int *hook_hits)
+{
+	const struct bpf_landlock_ruleset *ruleset;
+	__u32 key = 0;
+
+	if (!is_target_exec())
+		return 0;
+
+	(*hook_hits)++;
+
+	lookup_calls++;
+	ruleset = bpf_map_lookup_elem(&ruleset_map, &key);
+	if (!ruleset) {
+		lookup_failed++;
+		return 0;
+	}
+
+	restrict_calls++;
+	restrict_ret =
+		bpf_landlock_restrict_binprm(bprm, ruleset, restrict_flags);
+	put_calls++;
+	bpf_landlock_put_ruleset(ruleset);
+
+	return 0;
+}
+
+SEC("lsm.s/bprm_creds_for_exec")
+int BPF_PROG(bprm_creds_for_exec, struct linux_binprm *bprm)
+{
+	if (!enable_bprm_creds_for_exec)
+		return 0;
+
+	return apply_landlock_ruleset(bprm, &bprm_creds_for_exec_hits);
+}
+
+SEC("lsm.s/bprm_creds_from_file")
+int BPF_PROG(bprm_creds_from_file, struct linux_binprm *bprm,
+	     const struct file *file)
+{
+	(void)file;
+
+	if (!enable_bprm_creds_from_file)
+		return 0;
+
+	return apply_landlock_ruleset(bprm, &bprm_creds_from_file_hits);
+}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 15/20] selftests/bpf: Add Landlock kfunc test runner
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (13 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 14/20] selftests/bpf: Add Landlock kfunc test program Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 16/20] landlock: Bump ABI version Justin Suess
                   ` (6 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Add a selftest program that loads the Landlock BPF program. The program
creates Landlock rulesets under two topologies (with and without parent
domain). It tests proper enforcement of Landlock rulesets by forking and
executing a child process while a ruleset is enforced by BPF. The result
of the operation is checked and the BPF program counters are verified to
ensure proper execution.

Assisted-by: OpenAI:GPT-5.4
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 .../bpf/prog_tests/landlock_kfuncs.c          | 733 ++++++++++++++++++
 1 file changed, 733 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/landlock_kfuncs.c

diff --git a/tools/testing/selftests/bpf/prog_tests/landlock_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/landlock_kfuncs.c
new file mode 100644
index 000000000000..a2f2a067b911
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/landlock_kfuncs.c
@@ -0,0 +1,733 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/limits.h>
+#include "../../../../../usr/include/linux/landlock.h"
+#include <sched.h>
+#include <stdbool.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <test_progs.h>
+#include <unistd.h>
+
+#include "landlock_kfuncs.skel.h"
+#include "../../landlock/wrappers.h"
+
+#ifndef BIT
+#define BIT(nr) (1U << (nr))
+#endif
+
+#ifndef LANDLOCK_RESTRICT_SELF_TSYNC
+#define LANDLOCK_RESTRICT_SELF_TSYNC BIT(3)
+#endif
+
+#ifndef LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
+#define LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS BIT(4)
+#endif
+
+#define LANDLOCK_EXEC_PATH "/bin/sh"
+#define MNT_TMP_DATA "size=4m,mode=700"
+
+enum previous_domain_kind {
+	PREV_DOMAIN_NONE,
+	PREV_DOMAIN_NESTED,
+};
+
+enum operation_kind {
+	OPERATION_READ,
+	OPERATION_WRITE,
+	OPERATION_CREATE,
+};
+
+struct hook_variant {
+	const char *name;
+	bool enable_bprm_creds_for_exec;
+	bool enable_bprm_creds_from_file;
+};
+
+struct restrict_variant {
+	const char *name;
+	enum previous_domain_kind previous_domain;
+	__u32 restrict_flags;
+	int expected_restrict_ret;
+	bool expect_enforced;
+};
+
+struct operation_case {
+	const char *name;
+	enum operation_kind kind;
+	__u64 handled_access_fs;
+	__u64 allowed_access_fs;
+};
+
+struct landlock_test_env {
+	char base_dir[PATH_MAX];
+	char allowed_dir[PATH_MAX];
+	char restricted_dir[PATH_MAX];
+	char allowed_file[PATH_MAX];
+	char restricted_file[PATH_MAX];
+	char created_file[PATH_MAX];
+};
+
+static const struct hook_variant hook_variants[] = {
+	{
+		.name = "bprm_creds_for_exec",
+		.enable_bprm_creds_for_exec = true,
+	},
+	{
+		.name = "bprm_creds_from_file",
+		.enable_bprm_creds_from_file = true,
+	},
+};
+
+static const struct restrict_variant domain_variants[] = {
+	{
+		.name = "no_previous_domain",
+		.previous_domain = PREV_DOMAIN_NONE,
+		.expect_enforced = true,
+	},
+	{
+		.name = "nested_previous_domain",
+		.previous_domain = PREV_DOMAIN_NESTED,
+		.expect_enforced = true,
+	},
+};
+
+static const struct restrict_variant flag_variants[] = {
+	{
+		.name = "flag_no_new_privs",
+		.previous_domain = PREV_DOMAIN_NONE,
+		.restrict_flags = LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS,
+		.expect_enforced = true,
+	},
+	{
+		.name = "flag_log_same_exec_off",
+		.previous_domain = PREV_DOMAIN_NONE,
+		.restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF,
+		.expect_enforced = true,
+	},
+	{
+		.name = "flag_log_new_exec_on",
+		.previous_domain = PREV_DOMAIN_NONE,
+		.restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON,
+		.expect_enforced = true,
+	},
+	{
+		.name = "flag_log_subdomains_off",
+		.previous_domain = PREV_DOMAIN_NONE,
+		.restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF,
+		.expect_enforced = true,
+	},
+	{
+		.name = "flag_tsync_rejected",
+		.previous_domain = PREV_DOMAIN_NONE,
+		.restrict_flags = LANDLOCK_RESTRICT_SELF_TSYNC,
+		.expected_restrict_ret = -EINVAL,
+		.expect_enforced = false,
+	},
+};
+
+static const struct operation_case operation_cases[] = {
+	{
+		.name = "read_file",
+		.kind = OPERATION_READ,
+		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
+		.allowed_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
+	},
+	{
+		.name = "write_file",
+		.kind = OPERATION_WRITE,
+		.handled_access_fs = LANDLOCK_ACCESS_FS_WRITE_FILE,
+		.allowed_access_fs = LANDLOCK_ACCESS_FS_WRITE_FILE,
+	},
+	{
+		.name = "make_reg",
+		.kind = OPERATION_CREATE,
+		.handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_REG |
+				     LANDLOCK_ACCESS_FS_WRITE_FILE,
+		.allowed_access_fs = LANDLOCK_ACCESS_FS_MAKE_REG |
+				     LANDLOCK_ACCESS_FS_WRITE_FILE,
+	},
+};
+
+static int landlock_version(void)
+{
+	return landlock_create_ruleset(NULL, 0,
+				       LANDLOCK_CREATE_RULESET_VERSION);
+}
+
+static int write_all(int fd, const char *buf, size_t len)
+{
+	while (len > 0) {
+		ssize_t written;
+
+		written = write(fd, buf, len);
+		if (written < 0) {
+			if (errno == EINTR)
+				continue;
+			return -errno;
+		}
+		buf += written;
+		len -= written;
+	}
+
+	return 0;
+}
+
+static int write_text_file(const char *path, const char *contents)
+{
+	int err;
+	int fd;
+
+	fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600);
+	if (fd < 0)
+		return -errno;
+
+	err = write_all(fd, contents, strlen(contents));
+	close(fd);
+	return err;
+}
+
+static int read_text_file(const char *path, char *buf, size_t len)
+{
+	ssize_t bytes;
+	int fd;
+
+	fd = open(path, O_RDONLY | O_CLOEXEC);
+	if (fd < 0)
+		return -errno;
+
+	bytes = read(fd, buf, len - 1);
+	close(fd);
+	if (bytes < 0)
+		return -errno;
+
+	buf[bytes] = '\0';
+	return 0;
+}
+
+static bool path_exists(const char *path)
+{
+	return access(path, F_OK) == 0;
+}
+
+static int delete_ruleset_map_elem(struct landlock_kfuncs *skel)
+{
+	__u32 key = 0;
+	int err;
+
+	err = bpf_map_delete_elem(bpf_map__fd(skel->maps.ruleset_map), &key);
+	if (!err || errno == ENOENT)
+		return 0;
+	return -errno;
+}
+
+static int update_ruleset_map(struct landlock_kfuncs *skel, int ruleset_fd)
+{
+	__u32 key = 0;
+
+	if (bpf_map_update_elem(bpf_map__fd(skel->maps.ruleset_map), &key,
+				&ruleset_fd, BPF_ANY))
+		return -errno;
+
+	return 0;
+}
+
+static void reset_bss(struct landlock_kfuncs *skel)
+{
+	skel->bss->target_pid = 0;
+	skel->bss->enable_bprm_creds_for_exec = false;
+	skel->bss->enable_bprm_creds_from_file = false;
+	skel->bss->restrict_flags = 0;
+
+	skel->bss->matched_pid = 0;
+	skel->bss->bprm_creds_for_exec_hits = 0;
+	skel->bss->bprm_creds_from_file_hits = 0;
+	skel->bss->lookup_calls = 0;
+	skel->bss->lookup_failed = 0;
+	skel->bss->restrict_calls = 0;
+	skel->bss->restrict_ret = 0;
+	skel->bss->put_calls = 0;
+}
+
+static int add_path_rule(int ruleset_fd, const char *path, __u64 access)
+{
+	struct landlock_path_beneath_attr path_beneath = {
+		.allowed_access = access,
+	};
+	int err;
+	int parent_fd;
+
+	parent_fd = open(path, O_PATH | O_CLOEXEC | O_DIRECTORY);
+	if (parent_fd < 0)
+		return -errno;
+
+	path_beneath.parent_fd = parent_fd;
+	err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
+				&path_beneath, 0);
+	err = err ? -errno : 0;
+	close(parent_fd);
+	return err;
+}
+
+static int create_exec_ruleset(const struct landlock_test_env *env,
+			       const struct operation_case *op)
+{
+	struct landlock_ruleset_attr attr = {
+		.handled_access_fs = op->handled_access_fs,
+	};
+	int err;
+	int ruleset_fd;
+
+	ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
+	if (ruleset_fd < 0)
+		return -errno;
+
+	err = add_path_rule(ruleset_fd, env->allowed_dir,
+			    op->allowed_access_fs);
+	if (err) {
+		close(ruleset_fd);
+		return err;
+	}
+
+	return ruleset_fd;
+}
+
+static int create_and_apply_previous_domain(const struct landlock_test_env *env,
+					    enum previous_domain_kind kind,
+					    __u64 handled_access_fs)
+{
+	struct landlock_ruleset_attr attr = {};
+	int err;
+	int ruleset_fd;
+
+	if (kind == PREV_DOMAIN_NONE)
+		return 0;
+
+	attr.handled_access_fs = handled_access_fs;
+
+	ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
+	if (ruleset_fd < 0)
+		return -errno;
+
+	if (kind == PREV_DOMAIN_NESTED) {
+		err = add_path_rule(ruleset_fd, env->base_dir,
+				    handled_access_fs);
+		if (err) {
+			close(ruleset_fd);
+			return err;
+		}
+	}
+
+	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
+		return -errno;
+
+	err = landlock_restrict_self(ruleset_fd, 0);
+	err = err ? -errno : 0;
+	close(ruleset_fd);
+	return err;
+}
+
+static int prepare_layout(struct landlock_test_env *env)
+{
+	char template[] = "/tmp/landlock_kfuncsXXXXXX";
+	int err;
+
+	if (!mkdtemp(template))
+		return -errno;
+
+	err = snprintf(env->base_dir, sizeof(env->base_dir), "%s", template);
+	if (err < 0 || err >= (int)sizeof(env->base_dir))
+		return -ENAMETOOLONG;
+
+	if (unshare(CLONE_NEWNS))
+		return -errno;
+
+	if (mount("tmpfs", env->base_dir, "tmpfs", 0, MNT_TMP_DATA))
+		return -errno;
+
+	if (mount(NULL, env->base_dir, NULL, MS_PRIVATE | MS_REC, NULL))
+		return -errno;
+
+	err = snprintf(env->allowed_dir, sizeof(env->allowed_dir), "%s/allowed",
+		       env->base_dir);
+	if (err < 0 || err >= (int)sizeof(env->allowed_dir))
+		return -ENAMETOOLONG;
+
+	err = snprintf(env->restricted_dir, sizeof(env->restricted_dir),
+		       "%s/restricted", env->base_dir);
+	if (err < 0 || err >= (int)sizeof(env->restricted_dir))
+		return -ENAMETOOLONG;
+
+	err = snprintf(env->allowed_file, sizeof(env->allowed_file), "%s/file",
+		       env->allowed_dir);
+	if (err < 0 || err >= (int)sizeof(env->allowed_file))
+		return -ENAMETOOLONG;
+
+	err = snprintf(env->restricted_file, sizeof(env->restricted_file),
+		       "%s/file", env->restricted_dir);
+	if (err < 0 || err >= (int)sizeof(env->restricted_file))
+		return -ENAMETOOLONG;
+
+	err = snprintf(env->created_file, sizeof(env->created_file),
+		       "%s/created", env->restricted_dir);
+	if (err < 0 || err >= (int)sizeof(env->created_file))
+		return -ENAMETOOLONG;
+
+	if (mkdir(env->allowed_dir, 0700))
+		return -errno;
+	if (mkdir(env->restricted_dir, 0700))
+		return -errno;
+
+	err = write_text_file(env->allowed_file, "allowed\n");
+	if (err)
+		return err;
+
+	err = write_text_file(env->restricted_file, "restricted\n");
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static void cleanup_layout(const struct landlock_test_env *env)
+{
+	umount(env->base_dir);
+	rmdir(env->base_dir);
+}
+
+static int seed_operation_state(const struct landlock_test_env *env,
+				const struct operation_case *op)
+{
+	int err;
+
+	err = write_text_file(env->allowed_file, "allowed\n");
+	if (err)
+		return err;
+
+	err = write_text_file(env->restricted_file, "restricted\n");
+	if (err)
+		return err;
+
+	if (op->kind == OPERATION_CREATE && unlink(env->created_file) &&
+	    errno != ENOENT)
+		return -errno;
+
+	return 0;
+}
+
+static int build_command(char *buf, size_t len,
+			 const struct landlock_test_env *env,
+			 const struct operation_case *op)
+{
+	switch (op->kind) {
+	case OPERATION_READ:
+		return snprintf(buf, len, "cat '%s' >/dev/null",
+				env->restricted_file);
+	case OPERATION_WRITE:
+		return snprintf(buf, len, "printf 'written\\n' >> '%s'",
+				env->restricted_file);
+	case OPERATION_CREATE:
+		return snprintf(buf, len, "printf 'created\\n' > '%s'",
+				env->created_file);
+	}
+
+	return -EINVAL;
+}
+
+static int run_exec_attempt(struct landlock_kfuncs *skel,
+			    const struct landlock_test_env *env,
+			    const struct operation_case *op,
+			    const struct hook_variant *hook,
+			    const struct restrict_variant *variant,
+			    bool enable_bpf, int ruleset_fd, int *child_status)
+{
+	char command[PATH_MAX * 2];
+	char signal_byte = 1;
+	pid_t child;
+	pid_t target_pid;
+	int go_pipe[2];
+	int err;
+
+	err = build_command(command, sizeof(command), env, op);
+	if (err < 0 || err >= (int)sizeof(command))
+		return -ENAMETOOLONG;
+
+	if (pipe(go_pipe))
+		return -errno;
+
+	child = fork();
+	if (child < 0) {
+		err = -errno;
+		goto out_close_pipe;
+	}
+	target_pid = child;
+
+	if (child == 0) {
+		close(go_pipe[1]);
+
+		err = create_and_apply_previous_domain(env,
+					     variant->previous_domain,
+					     op->handled_access_fs);
+		if (err)
+			_exit(-err);
+
+		if (read(go_pipe[0], &signal_byte, sizeof(signal_byte)) !=
+		    sizeof(signal_byte))
+			_exit(200);
+
+		execl(LANDLOCK_EXEC_PATH, "sh", "-ec", command, NULL);
+		_exit(errno);
+	}
+
+	close(go_pipe[0]);
+	reset_bss(skel);
+
+	if (enable_bpf) {
+		skel->bss->target_pid = target_pid;
+		skel->bss->enable_bprm_creds_for_exec =
+			hook->enable_bprm_creds_for_exec;
+		skel->bss->enable_bprm_creds_from_file =
+			hook->enable_bprm_creds_from_file;
+		skel->bss->restrict_flags = variant->restrict_flags;
+
+		err = update_ruleset_map(skel, ruleset_fd);
+		if (err)
+			goto out_kill_child;
+	}
+
+	if (write(go_pipe[1], &signal_byte, sizeof(signal_byte)) !=
+	    sizeof(signal_byte)) {
+		err = -errno;
+		goto out_kill_child;
+	}
+	close(go_pipe[1]);
+
+	if (waitpid(child, child_status, 0) != child)
+		return -errno;
+
+	return 0;
+
+out_kill_child:
+	close(go_pipe[1]);
+	kill(child, SIGKILL);
+	waitpid(child, NULL, 0);
+	return err;
+
+out_close_pipe:
+	close(go_pipe[0]);
+	close(go_pipe[1]);
+	return err;
+}
+
+static void assert_operation_outcome(const struct landlock_test_env *env,
+				     const struct operation_case *op,
+				     bool expect_success, int child_status)
+{
+	char contents[256];
+
+	ASSERT_TRUE(WIFEXITED(child_status), "child_exited");
+	if (expect_success)
+		ASSERT_EQ(WEXITSTATUS(child_status), 0, "child_exit_code");
+	else
+		ASSERT_NEQ(WEXITSTATUS(child_status), 0, "child_exit_code");
+
+	switch (op->kind) {
+	case OPERATION_READ:
+		ASSERT_OK(read_text_file(env->restricted_file, contents,
+					 sizeof(contents)),
+			  "read_restricted_file");
+		ASSERT_STREQ(contents, "restricted\n", "restricted_contents");
+		break;
+	case OPERATION_WRITE:
+		ASSERT_OK(read_text_file(env->restricted_file, contents,
+					 sizeof(contents)),
+			  "read_restricted_file");
+		if (expect_success) {
+			ASSERT_STREQ(contents, "restricted\nwritten\n",
+				     "restricted_contents");
+		} else {
+			ASSERT_STREQ(contents, "restricted\n",
+				     "restricted_contents");
+		}
+		break;
+	case OPERATION_CREATE:
+		if (expect_success) {
+			ASSERT_TRUE(path_exists(env->created_file),
+				    "created_file_exists");
+			ASSERT_OK(read_text_file(env->created_file, contents,
+						 sizeof(contents)),
+				  "read_created_file");
+			ASSERT_STREQ(contents, "created\n", "created_contents");
+		} else {
+			ASSERT_FALSE(path_exists(env->created_file),
+				     "created_file_exists");
+		}
+		break;
+	}
+}
+
+static void assert_bpf_state(const struct landlock_kfuncs *skel,
+			     const struct hook_variant *hook, bool expect_bpf,
+			     int expected_restrict_ret)
+{
+	if (!expect_bpf) {
+		ASSERT_EQ(skel->bss->matched_pid, 0, "matched_pid");
+		ASSERT_EQ(skel->bss->bprm_creds_for_exec_hits, 0,
+			  "bprm_creds_for_exec_hits");
+		ASSERT_EQ(skel->bss->bprm_creds_from_file_hits, 0,
+			  "bprm_creds_from_file_hits");
+		ASSERT_EQ(skel->bss->lookup_calls, 0, "lookup_calls");
+		ASSERT_EQ(skel->bss->lookup_failed, 0, "lookup_failed");
+		ASSERT_EQ(skel->bss->restrict_calls, 0, "restrict_calls");
+		ASSERT_EQ(skel->bss->put_calls, 0, "put_calls");
+		return;
+	}
+
+	ASSERT_EQ(skel->bss->matched_pid, 1, "matched_pid");
+	ASSERT_EQ(skel->bss->lookup_calls, 1, "lookup_calls");
+	ASSERT_EQ(skel->bss->lookup_failed, 0, "lookup_failed");
+	ASSERT_EQ(skel->bss->restrict_calls, 1, "restrict_calls");
+	ASSERT_EQ(skel->bss->restrict_ret, expected_restrict_ret,
+		  "restrict_ret");
+	ASSERT_EQ(skel->bss->put_calls, 1, "put_calls");
+
+	if (hook->enable_bprm_creds_for_exec) {
+		ASSERT_EQ(skel->bss->bprm_creds_for_exec_hits, 1,
+			  "bprm_creds_for_exec_hits");
+		ASSERT_EQ(skel->bss->bprm_creds_from_file_hits, 0,
+			  "bprm_creds_from_file_hits");
+	} else {
+		ASSERT_EQ(skel->bss->bprm_creds_for_exec_hits, 0,
+			  "bprm_creds_for_exec_hits");
+		ASSERT_EQ(skel->bss->bprm_creds_from_file_hits, 1,
+			  "bprm_creds_from_file_hits");
+	}
+}
+
+static void
+run_case(struct landlock_kfuncs *skel, const struct landlock_test_env *env,
+	 const struct hook_variant *hook, const struct operation_case *op,
+	 const struct restrict_variant *variant, const char *subtest_name)
+{
+	int child_status;
+	int err;
+	int ruleset_fd;
+
+	if (!test__start_subtest(subtest_name))
+		return;
+
+	err = seed_operation_state(env, op);
+	if (!ASSERT_OK(err, "seed_baseline"))
+		return;
+
+	err = run_exec_attempt(skel, env, op, hook, variant, false, -1,
+			       &child_status);
+	if (!ASSERT_OK(err, "baseline_exec"))
+		return;
+	assert_operation_outcome(env, op, true, child_status);
+	assert_bpf_state(skel, hook, false, 0);
+
+	err = seed_operation_state(env, op);
+	if (!ASSERT_OK(err, "seed_enforced"))
+		return;
+
+	ruleset_fd = create_exec_ruleset(env, op);
+	if (!ASSERT_GE(ruleset_fd, 0, "create_ruleset"))
+		return;
+
+	err = run_exec_attempt(skel, env, op, hook, variant, true, ruleset_fd,
+			       &child_status);
+	close(ruleset_fd);
+	if (!ASSERT_OK(err, "enforced_exec"))
+		return;
+
+	assert_operation_outcome(env, op, !variant->expect_enforced,
+				 child_status);
+	assert_bpf_state(skel, hook, true, variant->expected_restrict_ret);
+	ASSERT_OK(delete_ruleset_map_elem(skel), "delete_ruleset_map_elem");
+}
+
+void test_landlock_kfuncs(void)
+{
+	struct landlock_test_env env = {};
+	struct landlock_kfuncs *skel = NULL;
+	int err;
+	int version;
+	size_t i;
+	size_t j;
+
+	version = landlock_version();
+	if (version < 1) {
+		test__skip();
+		return;
+	}
+
+	skel = landlock_kfuncs__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "open_and_load"))
+		return;
+
+	err = landlock_kfuncs__attach(skel);
+	if (!ASSERT_OK(err, "attach"))
+		goto out;
+
+	err = prepare_layout(&env);
+	if (!ASSERT_OK(err, "prepare_layout"))
+		goto out;
+
+	ASSERT_OK(delete_ruleset_map_elem(skel), "delete_ruleset_map_elem");
+	reset_bss(skel);
+
+	for (i = 0; i < ARRAY_SIZE(hook_variants); i++) {
+		for (j = 0; j < ARRAY_SIZE(operation_cases); j++) {
+			char name[128];
+
+			ASSERT_LT(snprintf(name, sizeof(name),
+					   "%s/%s/no_previous_domain",
+					   hook_variants[i].name,
+					   operation_cases[j].name),
+				  (int)sizeof(name), "subtest_name_len");
+			run_case(skel, &env, &hook_variants[i],
+				 &operation_cases[j], &domain_variants[0],
+				 name);
+		}
+
+		for (j = 0; j < ARRAY_SIZE(domain_variants); j++) {
+			char name[128];
+
+			ASSERT_LT(snprintf(name, sizeof(name),
+					   "%s/write_file/%s",
+					   hook_variants[i].name,
+					   domain_variants[j].name),
+				  (int)sizeof(name), "subtest_name_len");
+			run_case(skel, &env, &hook_variants[i],
+				 &operation_cases[1], &domain_variants[j],
+				 name);
+		}
+
+		for (j = 0; j < ARRAY_SIZE(flag_variants); j++) {
+			char name[128];
+
+			ASSERT_LT(snprintf(name, sizeof(name),
+					   "%s/write_file/%s",
+					   hook_variants[i].name,
+					   flag_variants[j].name),
+				  (int)sizeof(name), "subtest_name_len");
+			run_case(skel, &env, &hook_variants[i],
+				 &operation_cases[1], &flag_variants[j], name);
+		}
+	}
+
+out:
+	if (env.base_dir[0])
+		cleanup_layout(&env);
+	landlock_kfuncs__destroy(skel);
+}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 16/20] landlock: Bump ABI version
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (14 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 15/20] selftests/bpf: Add Landlock kfunc test runner Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 17/20] tools: bpftool: Add documentation for landlock_ruleset Justin Suess
                   ` (5 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Bump the ABI version in the kernel for the new
RESTRICT_SELF_NO_NEW_PRIVS flag.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 samples/landlock/sandboxer.c                 | 7 ++++++-
 security/landlock/syscalls.c                 | 2 +-
 tools/testing/selftests/landlock/base_test.c | 2 +-
 3 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index 66e56ae275c6..53bd77e55855 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -301,7 +301,7 @@ static bool check_ruleset_scope(const char *const env_var,
 
 /* clang-format on */
 
-#define LANDLOCK_ABI_LAST 9
+#define LANDLOCK_ABI_LAST 10
 
 #define XSTR(s) #s
 #define STR(s) XSTR(s)
@@ -444,6 +444,11 @@ int main(const int argc, char *const argv[], char *const *const envp)
 		/* Removes LANDLOCK_ACCESS_FS_RESOLVE_UNIX for ABI < 9 */
 		ruleset_attr.handled_access_fs &=
 			~LANDLOCK_ACCESS_FS_RESOLVE_UNIX;
+		__attribute__((fallthrough));
+	case 9:
+		/* Removes LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS for ABI < 10 */
+		supported_restrict_flags &=
+			~LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS;
 		/* Must be printed for any ABI < LANDLOCK_ABI_LAST. */
 		fprintf(stderr,
 			"Hint: You should update the running kernel "
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 6723806723d5..790ac046542f 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -130,7 +130,7 @@ static void build_check_abi(void)
  * If the change involves a fix that requires userspace awareness, also update
  * the errata documentation in Documentation/userspace-api/landlock.rst .
  */
-const int landlock_abi_version = 9;
+const int landlock_abi_version = 10;
 
 /**
  * sys_landlock_create_ruleset - Create a new ruleset
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index a4c38541de70..51c72064c190 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -76,7 +76,7 @@ TEST(abi_version)
 	const struct landlock_ruleset_attr ruleset_attr = {
 		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
 	};
-	ASSERT_EQ(9, landlock_create_ruleset(NULL, 0,
+	ASSERT_EQ(10, landlock_create_ruleset(NULL, 0,
 					     LANDLOCK_CREATE_RULESET_VERSION));
 
 	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 17/20] tools: bpftool: Add documentation for landlock_ruleset
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (15 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 16/20] landlock: Bump ABI version Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 18/20] landlock: Document LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS Justin Suess
                   ` (4 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Add BPF_MAP_TYPE_LANDLOCK_RULESET to bpftool listing.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 tools/bpf/bpftool/Documentation/bpftool-map.rst | 2 +-
 tools/bpf/bpftool/map.c                         | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/tools/bpf/bpftool/Documentation/bpftool-map.rst b/tools/bpf/bpftool/Documentation/bpftool-map.rst
index 1af3305ea2b2..48bf6ee36fa4 100644
--- a/tools/bpf/bpftool/Documentation/bpftool-map.rst
+++ b/tools/bpf/bpftool/Documentation/bpftool-map.rst
@@ -56,7 +56,7 @@ MAP COMMANDS
 |     | **cgroup_storage** | **reuseport_sockarray** | **percpu_cgroup_storage**
 |     | **queue** | **stack** | **sk_storage** | **struct_ops** | **ringbuf** | **inode_storage**
 |     | **task_storage** | **bloom_filter** | **user_ringbuf** | **cgrp_storage** | **arena**
-|     | **insn_array** }
+|     | **landlock_ruleset** | **insn_array** }
 
 DESCRIPTION
 ===========
diff --git a/tools/bpf/bpftool/map.c b/tools/bpf/bpftool/map.c
index 7ebf7dbcfba4..0fe391a3ce73 100644
--- a/tools/bpf/bpftool/map.c
+++ b/tools/bpf/bpftool/map.c
@@ -1478,7 +1478,7 @@ static int do_help(int argc, char **argv)
 		"                 cgroup_storage | reuseport_sockarray | percpu_cgroup_storage |\n"
 		"                 queue | stack | sk_storage | struct_ops | ringbuf | inode_storage |\n"
 		"                 task_storage | bloom_filter | user_ringbuf | cgrp_storage | arena |\n"
-		"                 insn_array }\n"
+		"                 insn_array | landlock_ruleset }\n"
 		"       " HELP_SPEC_OPTIONS " |\n"
 		"                    {-f|--bpffs} | {-n|--nomount} }\n"
 		"",
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 18/20] landlock: Document LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (16 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 17/20] tools: bpftool: Add documentation for landlock_ruleset Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 19/20] bpf: Document BPF_MAP_TYPE_LANDLOCK_RULESET Justin Suess
                   ` (3 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Document the new LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS flag, and explain
how its designed primarily for BPF-side use cases for Landlock.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 Documentation/userspace-api/landlock.rst | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index fd8b78c31f2f..82c88d75ef21 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -204,7 +204,8 @@ similar backwards compatibility check is needed for the restrict flags
 
     __u32 restrict_flags =
         LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON |
-        LANDLOCK_RESTRICT_SELF_TSYNC;
+        LANDLOCK_RESTRICT_SELF_TSYNC |
+        LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS;
     switch (abi) {
     case 1 ... 6:
         /* Removes logging flags for ABI < 7 */
@@ -223,10 +224,18 @@ similar backwards compatibility check is needed for the restrict flags
          * children (and not for all threads, including parents and siblings).
          */
         restrict_flags &= ~LANDLOCK_RESTRICT_SELF_TSYNC;
+        __attribute__((fallthrough));
+    case 8:
+    case 9:
+        /* Removes no_new_privs convenience flag for ABI < 10 */
+        restrict_flags &= ~LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS;
     }
 
 The next step is to restrict the current thread from gaining more privileges
-(e.g. through a SUID binary).  We now have a ruleset with the first rule
+(e.g. through a SUID binary).  When supported, this can be folded into
+``landlock_restrict_self()`` with ``LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS``;
+otherwise, user space must still call :manpage:`prctl(2)` explicitly.  We now
+have a ruleset with the first rule
 allowing read and execute access to ``/usr`` while denying all other handled
 accesses for the filesystem, and a second rule allowing HTTPS connections.
 
@@ -716,6 +725,15 @@ Starting with the Landlock ABI version 9, it is possible to restrict
 connections to pathname UNIX domain sockets (:manpage:`unix(7)`) using
 the new ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX`` right.
 
+No New Privs flag (ABI < 10)
+----------------------------------------
+
+Starting with the Landlock ABI version 10, it is possible to request
+``no_new_privs`` as part of ``landlock_restrict_self()`` by passing the
+``LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS`` flag.  This lets user space request
+the prerequisite from the Landlock API itself, which is especially useful when
+the restriction is applied from an external context such as BPF.
+
 .. _kernel_support:
 
 Kernel support
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 19/20] bpf: Document BPF_MAP_TYPE_LANDLOCK_RULESET
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (17 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 18/20] landlock: Document LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-07 20:01 ` [RFC PATCH 20/20] MAINTAINERS: update entry for the Landlock subsystem Justin Suess
                   ` (2 subsequent siblings)
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Document the BPF_MAP_TYPE_LANDLOCK_RULESET map type and explain the
kfuncs it is associated with.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 Documentation/bpf/map_landlock_ruleset.rst | 181 +++++++++++++++++++++
 1 file changed, 181 insertions(+)
 create mode 100644 Documentation/bpf/map_landlock_ruleset.rst

diff --git a/Documentation/bpf/map_landlock_ruleset.rst b/Documentation/bpf/map_landlock_ruleset.rst
new file mode 100644
index 000000000000..90f3141a829b
--- /dev/null
+++ b/Documentation/bpf/map_landlock_ruleset.rst
@@ -0,0 +1,181 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+==============================
+BPF_MAP_TYPE_LANDLOCK_RULESET
+==============================
+
+``BPF_MAP_TYPE_LANDLOCK_RULESET`` is a specialized, array-backed map for
+holding references to Landlock rulesets that were created from userspace.
+It is meant to bridge BPF LSM policy selection with Landlock policy
+enforcement: userspace creates a normal Landlock ruleset, inserts its file
+descriptor into the map, and a BPF LSM program later looks up that ruleset and
+applies it with a Landlock kfunc during ``execve()`` preparation.
+
+BPF programs cannot create, inspect, or modify Landlock policy through this
+map.  The looked-up object is exposed only as an opaque
+``struct bpf_landlock_ruleset`` reference.
+
+The map uses ``__u32`` keys as array indexes and stores one ruleset reference
+per slot.  Like other array maps, its size is fixed at creation time and its
+elements are preallocated.
+
+Usage
+=====
+
+Kernel BPF
+----------
+
+.. note::
+   This map type is only supported for BPF LSM programs.  In practice, it is
+   useful for sleepable BPF LSM programs attached to
+   ``bprm_creds_for_exec`` or ``bprm_creds_from_file``, because those are the
+   hooks where the associated Landlock kfuncs are available.
+
+bpf_map_lookup_elem()
+~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: c
+
+   void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)
+
+Lookup returns a trusted pointer to an opaque ``struct bpf_landlock_ruleset``.
+The verifier treats the result as a referenced BTF object, not as a pointer to
+the raw ``__u32`` map value declared in the map definition.
+
+Each successful lookup acquires a ruleset reference.  The BPF program must
+release that reference with ``bpf_landlock_put_ruleset()`` on all paths after
+the lookup succeeds.
+
+The returned pointer is intended to be passed to
+``bpf_landlock_restrict_binprm()``.  It is opaque and cannot be dereferenced
+or inspected from BPF.
+
+bpf_map_delete_elem()
+~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: c
+
+   long bpf_map_delete_elem(struct bpf_map *map, const void *key)
+
+Delete removes the ruleset reference stored in the selected slot and drops the
+map's own reference to that ruleset.
+
+Landlock kfuncs
+---------------
+
+The map contains objects designed to work with the following Landlock kfuncs:
+
+.. code-block:: c
+
+   void bpf_landlock_put_ruleset(const struct bpf_landlock_ruleset *ruleset)
+
+.. code-block:: c
+
+   int bpf_landlock_restrict_binprm(struct linux_binprm *bprm,
+                                    const struct bpf_landlock_ruleset *ruleset,
+                                    __u32 flags)
+
+``bpf_landlock_restrict_binprm()`` applies the looked-up ruleset to the new
+program credentials that are being prepared for ``execve()``.  The ``flags``
+argument uses the same Landlock restriction flags as
+``landlock_restrict_self()``, including ``LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS``.
+When this flag is used from BPF, ``no_new_privs`` is staged through the exec
+context and committed only after exec reaches point-of-no-return.  This avoids
+side effects on failed executions or ``AT_EXECVE_CHECK`` while ensuring that
+the resulting task cannot gain more privileges through later exec transitions.
+
+Userspace
+---------
+
+bpf_map_update_elem()
+~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: c
+
+   int bpf_map_update_elem(int fd, const void *key, const void *value, __u64 flags)
+
+Userspace populates the map by writing a Landlock ruleset file descriptor into
+the selected slot.  The map uses FD-array update semantics:
+
+- ``key`` points to a ``__u32`` array index.
+- ``value`` points to a ``__u32`` containing the ruleset file descriptor.
+- ``flags`` must be ``BPF_ANY``.
+
+The supplied file descriptor must refer to a valid Landlock ruleset.
+
+Userspace lookup of map contents is not supported for this map type.
+
+Example
+=======
+
+Kernel BPF
+----------
+
+The following snippet shows a sleepable BPF LSM program that looks up a
+ruleset, applies it during exec credential preparation, and releases the
+lookup reference.
+
+.. code-block:: c
+
+   struct {
+           __uint(type, BPF_MAP_TYPE_LANDLOCK_RULESET);
+           __uint(max_entries, 1);
+           __type(key, __u32);
+           __type(value, __u32);
+   } ruleset_map SEC(".maps");
+
+   SEC("lsm.s/bprm_creds_for_exec")
+   int BPF_PROG(apply_ruleset, struct linux_binprm *bprm)
+   {
+           const struct bpf_landlock_ruleset *ruleset;
+           __u32 key = 0;
+
+           ruleset = bpf_map_lookup_elem(&ruleset_map, &key);
+           if (!ruleset)
+                   return 0;
+
+           bpf_landlock_restrict_binprm(
+                   bprm, ruleset, LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS);
+           bpf_landlock_put_ruleset(ruleset);
+           return 0;
+   }
+
+Userspace
+---------
+
+The following snippet shows how to insert a previously created Landlock
+ruleset into the map.
+
+.. code-block:: c
+
+   int populate_ruleset_map(int map_fd, int ruleset_fd)
+   {
+           __u32 key = 0;
+           __u32 value = ruleset_fd;
+
+           return bpf_map_update_elem(map_fd, &key, &value, BPF_ANY);
+   }
+
+Semantics
+=========
+
+- Map creation requires ``CONFIG_SECURITY_LANDLOCK``.  Otherwise,
+  ``BPF_MAP_CREATE`` for this type fails with ``-EOPNOTSUPP``.
+- Map definitions use ``sizeof(__u32)`` for both keys and values because
+  userspace writes ruleset file descriptors into the map.
+- From BPF, only ``bpf_map_lookup_elem()`` and ``bpf_map_delete_elem()`` are
+  supported for this map type.
+- From userspace, insertion is done with ``bpf_map_update_elem()`` using a
+  Landlock ruleset FD.
+- The looked-up value is an opaque, trusted BTF object reference, so BPF must
+  treat it as a handle and release it with ``bpf_landlock_put_ruleset()``.
+- ``LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS`` on the BPF path pins the resulting
+   task with ``no_new_privs`` after exec is committed.  When used from
+   ``bprm_creds_from_file``, this does not retroactively suppress privilege gain
+   for the current exec transition itself.
+- If Landlock support is disabled in the running kernel, programs using the
+  associated Landlock kfuncs may still load, but the kfunc call returns
+  ``-EOPNOTSUPP`` at runtime.
+
+See ``tools/testing/selftests/bpf/progs/landlock_kfuncs.c`` for a complete
+example.
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [RFC PATCH 20/20] MAINTAINERS: update entry for the Landlock subsystem
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (18 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 19/20] bpf: Document BPF_MAP_TYPE_LANDLOCK_RULESET Justin Suess
@ 2026-04-07 20:01 ` Justin Suess
  2026-04-08  4:40 ` [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Ihor Solodrai
  2026-04-08 14:00 ` Mickaël Salaün
  21 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-07 20:01 UTC (permalink / raw)
  To: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Justin Suess

Update the maintainers file to reflect the new selftest files,
cross-subsystem, documentation, and kernel-internal Landlock headers.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 MAINTAINERS | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index c3fe46d7c4bc..e9ad2ed1237a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14386,12 +14386,16 @@ S:	Supported
 W:	https://landlock.io
 T:	git https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git
 F:	Documentation/admin-guide/LSM/landlock.rst
+F:	Documentation/bpf/map_landlock_ruleset.rst
 F:	Documentation/security/landlock.rst
 F:	Documentation/userspace-api/landlock.rst
 F:	fs/ioctl.c
+F:	include/linux/landlock.h
 F:	include/uapi/linux/landlock.h
 F:	samples/landlock/
 F:	security/landlock/
+F:	tools/testing/selftests/bpf/prog_tests/landlock_kfuncs.c
+F:	tools/testing/selftests/bpf/progs/landlock_kfuncs.c
 F:	tools/testing/selftests/landlock/
 K:	landlock
 K:	LANDLOCK
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [RFC PATCH 00/20] BPF interface for applying Landlock rulesets
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (19 preceding siblings ...)
  2026-04-07 20:01 ` [RFC PATCH 20/20] MAINTAINERS: update entry for the Landlock subsystem Justin Suess
@ 2026-04-08  4:40 ` Ihor Solodrai
  2026-04-08 11:41   ` Justin Suess
  2026-04-08 14:00 ` Mickaël Salaün
  21 siblings, 1 reply; 26+ messages in thread
From: Ihor Solodrai @ 2026-04-08  4:40 UTC (permalink / raw)
  To: Justin Suess, ast, daniel, andrii, kpsingh, paul, mic, viro,
	brauner, kees
  Cc: gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel



On 4/7/26 1:01 PM, Justin Suess wrote:
> Hello,
> 
> This series lets sleepable BPF LSM programs apply an existing,
> userspace-created Landlock ruleset to a program during exec.
> 
> The goal is not to move Landlock policy definition into BPF, nor to create a
> second policy engine.  Instead, BPF is used only to select when an already
> valid Landlock ruleset should be applied, based on runtime exec context.
> 
> Background
> ===
> 
> Landlock is primarily a syscall-driven, unprivileged-first LSM.  That model
> works well when the application being sandboxed can create and enforce its own
> rulesets, or when a trusted launcher can impose restrictions directly before
> running a trusted target.
> 
> That becomes harder when the target program is not under first-party control,
> for example:
> 
> 1. third-party binaries,
> 2. unmodified container images,
> 3. programs reached through shells, wrappers, or service managers, and
> 4. user-supplied or otherwise untrusted code.
> 
> In these cases, an external supervisor may want to apply a Landlock ruleset to
> the final executed program, while leaving unrelated parents or helper
> processes alone.
> 
> Why external sandboxing is awkward today
> ===
> 
> There are two recurring problems.
> 
> First, userspace cannot reliably predict every file a target may need across
> different systems, packaging layouts, and runtime conditions.  Shared
> libraries, configuration files, interpreters, and helper binaries often depend
> on details that are only known at runtime.
> 
> Second, Landlock inheritance is intentionally one-way.  Once a task is
> restricted, descendants inherit that domain and may only become more
> restricted.  This is exactly what Landlock should do, but it makes external
> sandboxing awkward when the program of interest is buried inside a larger exec
> chain.  Applying restrictions too early can affect unrelated intermediates;
> applying them too late misses the target entirely.
> 
> This series addresses that target-selection problem.
> 
> Overview
> ===
> 
> This series adds a small BPF-to-Landlock bridge:
> 
> 1. userspace creates a normal Landlock ruleset through the existing ABI;
> 2. userspace inserts that ruleset FD into a new
> 	BPF_MAP_TYPE_LANDLOCK_RULESET map;
> 3. a sleepable BPF LSM program attached to an exec-time hook looks up the
> 	ruleset; and
> 4. the program calls a kfunc to apply that ruleset to the new program's
> 	credentials before exec completes.
> 
> The important point is that BPF does not create, inspect, or mutate Landlock
> policy here.  It only decides whether to apply a ruleset that was already
> created and validated through Landlock's existing userspace API.
> 
> Interface
> ===
> 
> The series adds:
> 
> 1. bpf_landlock_restrict_binprm(), which applies a referenced ruleset to
> 	struct linux_binprm credentials;
> 2. bpf_landlock_put_ruleset(), which releases a referenced ruleset; and
> 3. BPF_MAP_TYPE_LANDLOCK_RULESET, a specialized map type for holding
> 	references to Landlock rulesets originating from userspace file
> 	descriptors.
> 4. A new field in the linux_binprm struct to enable application of
>    task_set_no_new_privs once execution is beyond the point of no return.
> 
> The kfuncs are restricted to sleepable BPF LSM programs attached to
> bprm_creds_for_exec and bprm_creds_from_file, which are the points where the
> new program's credentials may still be updated safely.
> 
> This series also adds LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS.  On the BPF path,
> this is staged through the exec context and committed only after exec reaches
> point-of-no-return.  This avoids side effects on failed executions while
> ensuring that the resulting task cannot gain more privileges through later exec
> transitions. This is done through the set_nnp_on_point_of_no_return field.
> 
> This has a little subtlety: LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS in the BPF
> path will not stop the current execution from escalating at all; only subsequent
> ones. This is intentional to allow landlock policies to be applied through a
> setuid transition for instance, without affecting the current escalation.
> 
> Semantics
> ===
> 
> This proposal is intended to preserve Landlock semantics as much as practical
> for an exec-time BPF attachment model:
> 
> 1. only pre-existing Landlock rulesets may be applied;
> 2. BPF cannot construct, inspect, or modify rulesets;
> 3. enforcement still happens before the new program begins execution;
> 4. normal Landlock inheritance, layering, and future composition remain
> 	unchanged; and
> 5. this does not bypass Landlock's privilege checks for applying Landlock
>     rulesets.
> 
> In other words, BPF acts as an external selector for when to apply Landlock,
> not as a replacement for Landlock's enforcement engine.
> 
> All behavior, future access rights, and previous access rights are designed
> to automatically be supported from either BPF or existing syscall contexts.
> 
> The main semantic difference is LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS on the BPF
> path: it guarantees that the resulting task is pinned with no_new_privs before
> it can perform later exec transitions, but it does not retroactively suppress
> privilege gain for the current exec transition itself.
> 
> The other exception to semantics is the LANDLOCK_RESTRICT_SELF_TSYNC flag.
> (see Points of Feedback section)
> 
> Patch layout
> ===
> 
> Patches 1-5 prepare the Landlock side by moving shared ruleset logic out of
> syscalls.c, adding a no_new_privs flag for non-syscall callers, exposing
> linux_binprm->set_nnp_on_point_of_no_return as an interface to set no_new_privs
> on the point of no return, and making deferred ruleset destruction RCU-safe.
> 
> Patches 6-10 add the BPF-facing pieces: the Landlock kfuncs, the new map type,
> syscall handling for that map, and verifier support.
> 
> Patches 11-15 add selftests and the small bpftool update needed for the new
> map type.
> 
> Patches 16-20 add docs and bump the ABI version and update MAINTAINERS.
> 
> Feedback is especially welcome on the overall interface shape, the choice of
> hooks, and the map semantics.
> 
> Testing
> ===
> 
> This patch series has two portions of tests.
> 
> One lives in the traditional Landlock selftests, for the new
> LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS flag.
> 
> The other suite lives under the BPF selftests, and this tests the Landlock
> kfuncs and the new BPF_MAP_TYPE_LANDLOCK_RULESET.
> 
> This patch series was run through BPF CI, the results of which are here. [1]
> 
> All mentioned tests are passing, as well as the BPF CI.
> 
> [1] : https://github.com/kernel-patches/bpf/pull/11562

Hello Justin.

I regret to disappoint you with a lame piece of feedback, but the
series hasn't been picked up by automated BPF CI pipeline properly:
https://github.com/kernel-patches/bpf/pull/11709

I suggest you rebase on top of bpf-next/master [1], and re-submit to
the mailing list with a bpf-next tag in subject:
   "[RFC PATCH bpf-next ...] bpf: ..."

I'm pretty sure AI bot will find something annoying to address.

Other than that, please be patient. It'll probably take a while for
maintainers and reviewers to digest this work before anyone can
meaningfully comment. Thanks!

[1] https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/

> 
> Points of Feedback
> ===
> 
> First, the new set_nnp_on_point_of_no_return field in struct linux_binprm.
> This field was needed to request that task_set_no_new_privs be set during an
> execution, but only after the execution has proceeded beyond the point of no
> return. I couldn't find a way to express this semantic without adding a new
> bitfield to struct linux_binprm and a conditional in fs/exec.c. Please see
> patch 2.
> 
> Feedback on the BPF testing harness, which was generated with AI assistance as
> disclosed in the commit footer, is welcomed. I have only limited familiarity
> with BPF testing practices. These tests were made with strong human supervision.
> See patches 14 and 15.
> 
> Feedback on the NO_NEW_PRIVS situation is also welcomed. Because task_set_no_new_privs()
> would otherwise leak state on failed executions or AT_EXECVE_CHECK, this series
> stages no_new_privs through the exec context and only commits it after
> point-of-no-return. This preserves failure behavior while still ensuring that
> the resulting task cannot elevate further through later exec transitions.
> When called from bprm_creds_from_file, this does not retroactively change the
> privilege outcome of the current exec transition itself.
> 
> See patch 2 and 3.
> 
> Next, the RCU in the landlock_ruleset. Existing BPF maps use RCU to make sure maps
> holding references stay valid. I altered the landlock ruleset to use rcu_work
> to make sure that the rcu is synchronized before putting on a ruleset, and
> acquire the rcu in the arraymap implementation. See patches 5-10.
> 
> Next, the semantics of the map. What operations should be supported from BPF
> and userspace and what data types should they return? I consider the struct
> bpf_landlock_ruleset to be opaque. Userspace can add items to the map via the
> fd, delete items by their index, and BPF can delete and lookup items by their
> index. Items cannot be updated, only swapped.
> 
> Finally, the handling of the LANDLOCK_RESTRICT_SELF_TSYNC flag. This flag has
> no meaning in a pre-execution context, as the credentials during the designated
> LSM hooks (bprm_creds_for_exec/creds_from_file) still represent the pre-execution
> task. Therefore, this flag is invalidated and attempting to use it with
> bpf_landlock_restrict_binprm will return -EINVAL. Otherwise, the flag would
> result in applying the landlock ruleset to the wrong target in addition to the
> intended one. (see patch 2). This behavior is validated with selftests.
> 
> Existing works / Credits
> ===
> 
> Mickaël Salaün created patchsets adding BPF tracepoints for landlock in [2] [3].
> 
> Mickaël also gave feedback on this feature and the idea in this GitHub thread. [4]
> 
> Günther Noack initially received and provided initial feedback on this idea as
> an early prototype.
> 
> Liz Rice, author of "Learning eBPF: Programming the Linux Kernel for Enhanced
> Observability, Networking, and Security" provided background and inspired me to
> experiment with BPF and the BPF LSM. [5]
> 
> [2] : https://lore.kernel.org/all/20250523165741.693976-1-mic@digikod.net/
> [3] : https://lore.kernel.org/linux-security-module/20260406143717.1815792-1-mic@digikod.net/
> [4] : https://github.com/landlock-lsm/linux/issues/56
> [5] : https://wellesleybooks.com/book/9781098135126
> 
> Kind Regards,
> Justin Suess
> 
> Justin Suess (20):
>   landlock: Move operations from syscall into ruleset code
>   execve: Add set_nnp_on_point_of_no_return
>   landlock: Implement LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
>   selftests/landlock: Cover LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
>   landlock: Make ruleset deferred free RCU safe
>   bpf: lsm: Add Landlock kfuncs
>   bpf: arraymap: Implement Landlock ruleset map
>   bpf: Add Landlock ruleset map type
>   bpf: syscall: Handle Landlock ruleset maps
>   bpf: verifier: Add Landlock ruleset map support
>   selftests/bpf: Add Landlock kfunc declarations
>   selftests/landlock: Rename gettid wrapper for BPF reuse
>   selftests/bpf: Enable Landlock in selftests kernel.
>   selftests/bpf: Add Landlock kfunc test program
>   selftests/bpf: Add Landlock kfunc test runner
>   landlock: Bump ABI version
>   tools: bpftool: Add documentation for landlock_ruleset
>   landlock: Document LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
>   bpf: Document BPF_MAP_TYPE_LANDLOCK_RULESET
>   MAINTAINERS: update entry for the Landlock subsystem
> 
>  Documentation/bpf/map_landlock_ruleset.rst    | 181 +++++
>  Documentation/userspace-api/landlock.rst      |  22 +-
>  MAINTAINERS                                   |   4 +
>  fs/exec.c                                     |   8 +
>  include/linux/binfmts.h                       |   7 +-
>  include/linux/bpf_lsm.h                       |  15 +
>  include/linux/bpf_types.h                     |   1 +
>  include/linux/landlock.h                      |  92 +++
>  include/uapi/linux/bpf.h                      |   1 +
>  include/uapi/linux/landlock.h                 |  14 +
>  kernel/bpf/arraymap.c                         |  67 ++
>  kernel/bpf/bpf_lsm.c                          | 145 ++++
>  kernel/bpf/syscall.c                          |   4 +-
>  kernel/bpf/verifier.c                         |  15 +-
>  samples/landlock/sandboxer.c                  |   7 +-
>  security/landlock/limits.h                    |   2 +-
>  security/landlock/ruleset.c                   | 198 ++++-
>  security/landlock/ruleset.h                   |  25 +-
>  security/landlock/syscalls.c                  | 158 +---
>  .../bpf/bpftool/Documentation/bpftool-map.rst |   2 +-
>  tools/bpf/bpftool/map.c                       |   2 +-
>  tools/include/uapi/linux/bpf.h                |   1 +
>  tools/lib/bpf/libbpf.c                        |   1 +
>  tools/lib/bpf/libbpf_probes.c                 |   6 +
>  tools/testing/selftests/bpf/bpf_kfuncs.h      |  20 +
>  tools/testing/selftests/bpf/config            |   5 +
>  tools/testing/selftests/bpf/config.x86_64     |   1 -
>  .../bpf/prog_tests/landlock_kfuncs.c          | 733 ++++++++++++++++++
>  .../selftests/bpf/progs/landlock_kfuncs.c     |  92 +++
>  tools/testing/selftests/landlock/base_test.c  |  10 +-
>  tools/testing/selftests/landlock/common.h     |  28 +-
>  tools/testing/selftests/landlock/fs_test.c    | 103 +--
>  tools/testing/selftests/landlock/net_test.c   |  55 +-
>  .../testing/selftests/landlock/ptrace_test.c  |  14 +-
>  .../landlock/scoped_abstract_unix_test.c      |  51 +-
>  .../selftests/landlock/scoped_base_variants.h |  23 +
>  .../selftests/landlock/scoped_common.h        |   5 +-
>  .../selftests/landlock/scoped_signal_test.c   |  30 +-
>  tools/testing/selftests/landlock/wrappers.h   |   2 +-
>  39 files changed, 1877 insertions(+), 273 deletions(-)
>  create mode 100644 Documentation/bpf/map_landlock_ruleset.rst
>  create mode 100644 include/linux/landlock.h
>  create mode 100644 tools/testing/selftests/bpf/prog_tests/landlock_kfuncs.c
>  create mode 100644 tools/testing/selftests/bpf/progs/landlock_kfuncs.c
> 
> 
> base-commit: 8c6a27e02bc55ab110d1828610048b19f903aaec


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [RFC PATCH 00/20] BPF interface for applying Landlock rulesets
  2026-04-08  4:40 ` [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Ihor Solodrai
@ 2026-04-08 11:41   ` Justin Suess
  0 siblings, 0 replies; 26+ messages in thread
From: Justin Suess @ 2026-04-08 11:41 UTC (permalink / raw)
  To: Ihor Solodrai
  Cc: ast, daniel, andrii, kpsingh, paul, mic, viro, brauner, kees,
	gnoack, jack, jmorris, serge, song, yonghong.song, martin.lau, m,
	eddyz87, john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel

On Tue, Apr 07, 2026 at 09:40:07PM -0700, Ihor Solodrai wrote:
> 
> 
> On 4/7/26 1:01 PM, Justin Suess wrote:
> > Hello,
> > 
> > This series lets sleepable BPF LSM programs apply an existing,
> > userspace-created Landlock ruleset to a program during exec.
> > 
> > The goal is not to move Landlock policy definition into BPF, nor to create a
> > second policy engine.  Instead, BPF is used only to select when an already
> > valid Landlock ruleset should be applied, based on runtime exec context.
> > 
> > Background
> > ===
> > 
> > Landlock is primarily a syscall-driven, unprivileged-first LSM.  That model
> > works well when the application being sandboxed can create and enforce its own
> > rulesets, or when a trusted launcher can impose restrictions directly before
> > running a trusted target.
> > 
> > That becomes harder when the target program is not under first-party control,
> > for example:
> > 
> > 1. third-party binaries,
> > 2. unmodified container images,
> > 3. programs reached through shells, wrappers, or service managers, and
> > 4. user-supplied or otherwise untrusted code.
> > 
> > In these cases, an external supervisor may want to apply a Landlock ruleset to
> > the final executed program, while leaving unrelated parents or helper
> > processes alone.
> > 
> > Why external sandboxing is awkward today
> > ===
> > 
> > There are two recurring problems.
> > 
> > First, userspace cannot reliably predict every file a target may need across
> > different systems, packaging layouts, and runtime conditions.  Shared
> > libraries, configuration files, interpreters, and helper binaries often depend
> > on details that are only known at runtime.
> > 
> > Second, Landlock inheritance is intentionally one-way.  Once a task is
> > restricted, descendants inherit that domain and may only become more
> > restricted.  This is exactly what Landlock should do, but it makes external
> > sandboxing awkward when the program of interest is buried inside a larger exec
> > chain.  Applying restrictions too early can affect unrelated intermediates;
> > applying them too late misses the target entirely.
> > 
> > This series addresses that target-selection problem.
> > 
> > Overview
> > ===
> > 
> > This series adds a small BPF-to-Landlock bridge:
> > 
> > 1. userspace creates a normal Landlock ruleset through the existing ABI;
> > 2. userspace inserts that ruleset FD into a new
> > 	BPF_MAP_TYPE_LANDLOCK_RULESET map;
> > 3. a sleepable BPF LSM program attached to an exec-time hook looks up the
> > 	ruleset; and
> > 4. the program calls a kfunc to apply that ruleset to the new program's
> > 	credentials before exec completes.
> > 
> > The important point is that BPF does not create, inspect, or mutate Landlock
> > policy here.  It only decides whether to apply a ruleset that was already
> > created and validated through Landlock's existing userspace API.
> > 
> > Interface
> > ===
> > 
> > The series adds:
> > 
> > 1. bpf_landlock_restrict_binprm(), which applies a referenced ruleset to
> > 	struct linux_binprm credentials;
> > 2. bpf_landlock_put_ruleset(), which releases a referenced ruleset; and
> > 3. BPF_MAP_TYPE_LANDLOCK_RULESET, a specialized map type for holding
> > 	references to Landlock rulesets originating from userspace file
> > 	descriptors.
> > 4. A new field in the linux_binprm struct to enable application of
> >    task_set_no_new_privs once execution is beyond the point of no return.
> > 
> > The kfuncs are restricted to sleepable BPF LSM programs attached to
> > bprm_creds_for_exec and bprm_creds_from_file, which are the points where the
> > new program's credentials may still be updated safely.
> > 
> > This series also adds LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS.  On the BPF path,
> > this is staged through the exec context and committed only after exec reaches
> > point-of-no-return.  This avoids side effects on failed executions while
> > ensuring that the resulting task cannot gain more privileges through later exec
> > transitions. This is done through the set_nnp_on_point_of_no_return field.
> > 
> > This has a little subtlety: LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS in the BPF
> > path will not stop the current execution from escalating at all; only subsequent
> > ones. This is intentional to allow landlock policies to be applied through a
> > setuid transition for instance, without affecting the current escalation.
> > 
> > Semantics
> > ===
> > 
> > This proposal is intended to preserve Landlock semantics as much as practical
> > for an exec-time BPF attachment model:
> > 
> > 1. only pre-existing Landlock rulesets may be applied;
> > 2. BPF cannot construct, inspect, or modify rulesets;
> > 3. enforcement still happens before the new program begins execution;
> > 4. normal Landlock inheritance, layering, and future composition remain
> > 	unchanged; and
> > 5. this does not bypass Landlock's privilege checks for applying Landlock
> >     rulesets.
> > 
> > In other words, BPF acts as an external selector for when to apply Landlock,
> > not as a replacement for Landlock's enforcement engine.
> > 
> > All behavior, future access rights, and previous access rights are designed
> > to automatically be supported from either BPF or existing syscall contexts.
> > 
> > The main semantic difference is LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS on the BPF
> > path: it guarantees that the resulting task is pinned with no_new_privs before
> > it can perform later exec transitions, but it does not retroactively suppress
> > privilege gain for the current exec transition itself.
> > 
> > The other exception to semantics is the LANDLOCK_RESTRICT_SELF_TSYNC flag.
> > (see Points of Feedback section)
> > 
> > Patch layout
> > ===
> > 
> > Patches 1-5 prepare the Landlock side by moving shared ruleset logic out of
> > syscalls.c, adding a no_new_privs flag for non-syscall callers, exposing
> > linux_binprm->set_nnp_on_point_of_no_return as an interface to set no_new_privs
> > on the point of no return, and making deferred ruleset destruction RCU-safe.
> > 
> > Patches 6-10 add the BPF-facing pieces: the Landlock kfuncs, the new map type,
> > syscall handling for that map, and verifier support.
> > 
> > Patches 11-15 add selftests and the small bpftool update needed for the new
> > map type.
> > 
> > Patches 16-20 add docs and bump the ABI version and update MAINTAINERS.
> > 
> > Feedback is especially welcome on the overall interface shape, the choice of
> > hooks, and the map semantics.
> > 
> > Testing
> > ===
> > 
> > This patch series has two portions of tests.
> > 
> > One lives in the traditional Landlock selftests, for the new
> > LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS flag.
> > 
> > The other suite lives under the BPF selftests, and this tests the Landlock
> > kfuncs and the new BPF_MAP_TYPE_LANDLOCK_RULESET.
> > 
> > This patch series was run through BPF CI, the results of which are here. [1]
> > 
> > All mentioned tests are passing, as well as the BPF CI.
> > 
> > [1] : https://github.com/kernel-patches/bpf/pull/11562
> 
> Hello Justin.
> 
> I regret to disappoint you with a lame piece of feedback, but the
> series hasn't been picked up by automated BPF CI pipeline properly:
> https://github.com/kernel-patches/bpf/pull/11709
>
Apologies.
> I suggest you rebase on top of bpf-next/master [1], and re-submit to
> the mailing list with a bpf-next tag in subject:
>    "[RFC PATCH bpf-next ...] bpf: ..."
>
No problem. Sorry about that I based it off the Landlock-next branch.
My fault, I thought the CI was to be manually initiated... oh well.

I'll resubmit soon. Looks like a perfectly clean rebase luckily.
> I'm pretty sure AI bot will find something annoying to address.
> 
> Other than that, please be patient. It'll probably take a while for
> maintainers and reviewers to digest this work before anyone can
> meaningfully comment. Thanks!
>
Thank you for your time and help!

> [1] https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/
> 
> > 
> > Points of Feedback
> > ===
> > 
> > First, the new set_nnp_on_point_of_no_return field in struct linux_binprm.
> > This field was needed to request that task_set_no_new_privs be set during an
> > execution, but only after the execution has proceeded beyond the point of no
> > return. I couldn't find a way to express this semantic without adding a new
> > bitfield to struct linux_binprm and a conditional in fs/exec.c. Please see
> > patch 2.
> > 
> > Feedback on the BPF testing harness, which was generated with AI assistance as
> > disclosed in the commit footer, is welcomed. I have only limited familiarity
> > with BPF testing practices. These tests were made with strong human supervision.
> > See patches 14 and 15.
> > 
> > Feedback on the NO_NEW_PRIVS situation is also welcomed. Because task_set_no_new_privs()
> > would otherwise leak state on failed executions or AT_EXECVE_CHECK, this series
> > stages no_new_privs through the exec context and only commits it after
> > point-of-no-return. This preserves failure behavior while still ensuring that
> > the resulting task cannot elevate further through later exec transitions.
> > When called from bprm_creds_from_file, this does not retroactively change the
> > privilege outcome of the current exec transition itself.
> > 
> > See patch 2 and 3.
> > 
> > Next, the RCU in the landlock_ruleset. Existing BPF maps use RCU to make sure maps
> > holding references stay valid. I altered the landlock ruleset to use rcu_work
> > to make sure that the rcu is synchronized before putting on a ruleset, and
> > acquire the rcu in the arraymap implementation. See patches 5-10.
> > 
> > Next, the semantics of the map. What operations should be supported from BPF
> > and userspace and what data types should they return? I consider the struct
> > bpf_landlock_ruleset to be opaque. Userspace can add items to the map via the
> > fd, delete items by their index, and BPF can delete and lookup items by their
> > index. Items cannot be updated, only swapped.
> > 
> > Finally, the handling of the LANDLOCK_RESTRICT_SELF_TSYNC flag. This flag has
> > no meaning in a pre-execution context, as the credentials during the designated
> > LSM hooks (bprm_creds_for_exec/creds_from_file) still represent the pre-execution
> > task. Therefore, this flag is invalidated and attempting to use it with
> > bpf_landlock_restrict_binprm will return -EINVAL. Otherwise, the flag would
> > result in applying the landlock ruleset to the wrong target in addition to the
> > intended one. (see patch 2). This behavior is validated with selftests.
> > 
> > Existing works / Credits
> > ===
> > 
> > Mickaël Salaün created patchsets adding BPF tracepoints for landlock in [2] [3].
> > 
> > Mickaël also gave feedback on this feature and the idea in this GitHub thread. [4]
> > 
> > Günther Noack initially received and provided initial feedback on this idea as
> > an early prototype.
> > 
> > Liz Rice, author of "Learning eBPF: Programming the Linux Kernel for Enhanced
> > Observability, Networking, and Security" provided background and inspired me to
> > experiment with BPF and the BPF LSM. [5]
> > 
> > [2] : https://lore.kernel.org/all/20250523165741.693976-1-mic@digikod.net/
> > [3] : https://lore.kernel.org/linux-security-module/20260406143717.1815792-1-mic@digikod.net/
> > [4] : https://github.com/landlock-lsm/linux/issues/56
> > [5] : https://wellesleybooks.com/book/9781098135126
> > 
> > Kind Regards,
> > Justin Suess
> > 
> > Justin Suess (20):
> >   landlock: Move operations from syscall into ruleset code
> >   execve: Add set_nnp_on_point_of_no_return
> >   landlock: Implement LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
> >   selftests/landlock: Cover LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
> >   landlock: Make ruleset deferred free RCU safe
> >   bpf: lsm: Add Landlock kfuncs
> >   bpf: arraymap: Implement Landlock ruleset map
> >   bpf: Add Landlock ruleset map type
> >   bpf: syscall: Handle Landlock ruleset maps
> >   bpf: verifier: Add Landlock ruleset map support
> >   selftests/bpf: Add Landlock kfunc declarations
> >   selftests/landlock: Rename gettid wrapper for BPF reuse
> >   selftests/bpf: Enable Landlock in selftests kernel.
> >   selftests/bpf: Add Landlock kfunc test program
> >   selftests/bpf: Add Landlock kfunc test runner
> >   landlock: Bump ABI version
> >   tools: bpftool: Add documentation for landlock_ruleset
> >   landlock: Document LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
> >   bpf: Document BPF_MAP_TYPE_LANDLOCK_RULESET
> >   MAINTAINERS: update entry for the Landlock subsystem
> > 
> >  Documentation/bpf/map_landlock_ruleset.rst    | 181 +++++
> >  Documentation/userspace-api/landlock.rst      |  22 +-
> >  MAINTAINERS                                   |   4 +
> >  fs/exec.c                                     |   8 +
> >  include/linux/binfmts.h                       |   7 +-
> >  include/linux/bpf_lsm.h                       |  15 +
> >  include/linux/bpf_types.h                     |   1 +
> >  include/linux/landlock.h                      |  92 +++
> >  include/uapi/linux/bpf.h                      |   1 +
> >  include/uapi/linux/landlock.h                 |  14 +
> >  kernel/bpf/arraymap.c                         |  67 ++
> >  kernel/bpf/bpf_lsm.c                          | 145 ++++
> >  kernel/bpf/syscall.c                          |   4 +-
> >  kernel/bpf/verifier.c                         |  15 +-
> >  samples/landlock/sandboxer.c                  |   7 +-
> >  security/landlock/limits.h                    |   2 +-
> >  security/landlock/ruleset.c                   | 198 ++++-
> >  security/landlock/ruleset.h                   |  25 +-
> >  security/landlock/syscalls.c                  | 158 +---
> >  .../bpf/bpftool/Documentation/bpftool-map.rst |   2 +-
> >  tools/bpf/bpftool/map.c                       |   2 +-
> >  tools/include/uapi/linux/bpf.h                |   1 +
> >  tools/lib/bpf/libbpf.c                        |   1 +
> >  tools/lib/bpf/libbpf_probes.c                 |   6 +
> >  tools/testing/selftests/bpf/bpf_kfuncs.h      |  20 +
> >  tools/testing/selftests/bpf/config            |   5 +
> >  tools/testing/selftests/bpf/config.x86_64     |   1 -
> >  .../bpf/prog_tests/landlock_kfuncs.c          | 733 ++++++++++++++++++
> >  .../selftests/bpf/progs/landlock_kfuncs.c     |  92 +++
> >  tools/testing/selftests/landlock/base_test.c  |  10 +-
> >  tools/testing/selftests/landlock/common.h     |  28 +-
> >  tools/testing/selftests/landlock/fs_test.c    | 103 +--
> >  tools/testing/selftests/landlock/net_test.c   |  55 +-
> >  .../testing/selftests/landlock/ptrace_test.c  |  14 +-
> >  .../landlock/scoped_abstract_unix_test.c      |  51 +-
> >  .../selftests/landlock/scoped_base_variants.h |  23 +
> >  .../selftests/landlock/scoped_common.h        |   5 +-
> >  .../selftests/landlock/scoped_signal_test.c   |  30 +-
> >  tools/testing/selftests/landlock/wrappers.h   |   2 +-
> >  39 files changed, 1877 insertions(+), 273 deletions(-)
> >  create mode 100644 Documentation/bpf/map_landlock_ruleset.rst
> >  create mode 100644 include/linux/landlock.h
> >  create mode 100644 tools/testing/selftests/bpf/prog_tests/landlock_kfuncs.c
> >  create mode 100644 tools/testing/selftests/bpf/progs/landlock_kfuncs.c
> > 
> > 
> > base-commit: 8c6a27e02bc55ab110d1828610048b19f903aaec
> 

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [RFC PATCH 00/20] BPF interface for applying Landlock rulesets
  2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
                   ` (20 preceding siblings ...)
  2026-04-08  4:40 ` [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Ihor Solodrai
@ 2026-04-08 14:00 ` Mickaël Salaün
  2026-04-08 17:10   ` Justin Suess
  21 siblings, 1 reply; 26+ messages in thread
From: Mickaël Salaün @ 2026-04-08 14:00 UTC (permalink / raw)
  To: Justin Suess
  Cc: ast, daniel, andrii, kpsingh, paul, viro, brauner, kees, gnoack,
	jack, jmorris, serge, song, yonghong.song, martin.lau, m, eddyz87,
	john.fastabend, sdf, skhan, bpf, linux-security-module,
	linux-kernel, linux-fsdevel, Frederick Lawler

Thanks for this RFC.

On Tue, Apr 07, 2026 at 04:01:22PM -0400, Justin Suess wrote:
> Hello,
> 
> This series lets sleepable BPF LSM programs apply an existing,
> userspace-created Landlock ruleset to a program during exec.
> 
> The goal is not to move Landlock policy definition into BPF, nor to create a
> second policy engine.  Instead, BPF is used only to select when an already
> valid Landlock ruleset should be applied, based on runtime exec context.
> 
> Background
> ===
> 
> Landlock is primarily a syscall-driven, unprivileged-first LSM.  That model
> works well when the application being sandboxed can create and enforce its own
> rulesets, or when a trusted launcher can impose restrictions directly before
> running a trusted target.
> 
> That becomes harder when the target program is not under first-party control,
> for example:
> 
> 1. third-party binaries,
> 2. unmodified container images,
> 3. programs reached through shells, wrappers, or service managers, and
> 4. user-supplied or otherwise untrusted code.
> 
> In these cases, an external supervisor may want to apply a Landlock ruleset to
> the final executed program, while leaving unrelated parents or helper
> processes alone.
> 
> Why external sandboxing is awkward today
> ===
> 
> There are two recurring problems.
> 
> First, userspace cannot reliably predict every file a target may need across
> different systems, packaging layouts, and runtime conditions.  Shared
> libraries, configuration files, interpreters, and helper binaries often depend
> on details that are only known at runtime.

Agreed, it would make sense to leverage eBPF for this context
identification rather than implementing a Landlock-specfic feature.

> 
> Second, Landlock inheritance is intentionally one-way.  Once a task is
> restricted, descendants inherit that domain and may only become more
> restricted.  This is exactly what Landlock should do, but it makes external
> sandboxing awkward when the program of interest is buried inside a larger exec
> chain.  Applying restrictions too early can affect unrelated intermediates;
> applying them too late misses the target entirely.

This makes sense too.

> 
> This series addresses that target-selection problem.
> 
> Overview
> ===
> 
> This series adds a small BPF-to-Landlock bridge:
> 
> 1. userspace creates a normal Landlock ruleset through the existing ABI;
> 2. userspace inserts that ruleset FD into a new
> 	BPF_MAP_TYPE_LANDLOCK_RULESET map;
> 3. a sleepable BPF LSM program attached to an exec-time hook looks up the
> 	ruleset; and
> 4. the program calls a kfunc to apply that ruleset to the new program's
> 	credentials before exec completes.
> 
> The important point is that BPF does not create, inspect, or mutate Landlock
> policy here.  It only decides whether to apply a ruleset that was already
> created and validated through Landlock's existing userspace API.

I like this approach.  It makes it possible for users enforce Landlock
security policies on arbitrary new executions.  Sandboxing at this
specific point is the best time because it ensures a consistency for the
whole lifetime of the process, whereas applying new restriction in the
middle of an execution would make the process unstable (if the request
doesn't come from the process itself).

> 
> Interface
> ===
> 
> The series adds:
> 
> 1. bpf_landlock_restrict_binprm(), which applies a referenced ruleset to
> 	struct linux_binprm credentials;
> 2. bpf_landlock_put_ruleset(), which releases a referenced ruleset; and
> 3. BPF_MAP_TYPE_LANDLOCK_RULESET, a specialized map type for holding
> 	references to Landlock rulesets originating from userspace file
> 	descriptors.
> 4. A new field in the linux_binprm struct to enable application of
>    task_set_no_new_privs once execution is beyond the point of no return.

This "beyond the point of no return" is indeed important, and it would
be nice to also have this property for Landlock restriction i.e., only
create a Landlock domain if we know that the execution will succeed (or
if the caller will exit).  This is especially important for
logging/tracing event consistency.

> 
> The kfuncs are restricted to sleepable BPF LSM programs attached to
> bprm_creds_for_exec and bprm_creds_from_file, which are the points where the
> new program's credentials may still be updated safely.
> 
> This series also adds LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS.  On the BPF path,
> this is staged through the exec context and committed only after exec reaches
> point-of-no-return.  This avoids side effects on failed executions while
> ensuring that the resulting task cannot gain more privileges through later exec
> transitions. This is done through the set_nnp_on_point_of_no_return field.
> 
> This has a little subtlety: LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS in the BPF
> path will not stop the current execution from escalating at all; only subsequent
> ones.

This makes sense too, but it needs to be documented.

> This is intentional to allow landlock policies to be applied through a

s/landlock/Landlock/g in every text/comment/commit description please.

> setuid transition for instance, without affecting the current escalation.
> 
> Semantics
> ===
> 
> This proposal is intended to preserve Landlock semantics as much as practical
> for an exec-time BPF attachment model:
> 
> 1. only pre-existing Landlock rulesets may be applied;
> 2. BPF cannot construct, inspect, or modify rulesets;

Inspection will be possible with tracepoints, but it is orthogonal to
this series.

> 3. enforcement still happens before the new program begins execution;
> 4. normal Landlock inheritance, layering, and future composition remain
> 	unchanged; and
> 5. this does not bypass Landlock's privilege checks for applying Landlock
>     rulesets.
> 
> In other words, BPF acts as an external selector for when to apply Landlock,
> not as a replacement for Landlock's enforcement engine.
> 
> All behavior, future access rights, and previous access rights are designed
> to automatically be supported from either BPF or existing syscall contexts.
> 
> The main semantic difference is LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS on the BPF
> path: it guarantees that the resulting task is pinned with no_new_privs before
> it can perform later exec transitions, but it does not retroactively suppress
> privilege gain for the current exec transition itself.
> 
> The other exception to semantics is the LANDLOCK_RESTRICT_SELF_TSYNC flag.
> (see Points of Feedback section)
> 
> Patch layout
> ===
> 
> Patches 1-5 prepare the Landlock side by moving shared ruleset logic out of
> syscalls.c, adding a no_new_privs flag for non-syscall callers, exposing
> linux_binprm->set_nnp_on_point_of_no_return as an interface to set no_new_privs
> on the point of no return, and making deferred ruleset destruction RCU-safe.
> 
> Patches 6-10 add the BPF-facing pieces: the Landlock kfuncs, the new map type,
> syscall handling for that map, and verifier support.
> 
> Patches 11-15 add selftests and the small bpftool update needed for the new
> map type.
> 
> Patches 16-20 add docs and bump the ABI version and update MAINTAINERS.
> 
> Feedback is especially welcome on the overall interface shape, the choice of
> hooks, and the map semantics.

I'll review each patch separately, but this approach is promising.

I think it would be simpler to have a dedicated patch series for
LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS, and then send another series
specific to the eBPF side (kfunc, tests, doc...).  I'm not sure what is
the best way to deal with dependencies across Landlock and BPF though.
What is the policy for BPF next wrt other next branches?

> 
> Testing
> ===
> 
> This patch series has two portions of tests.
> 
> One lives in the traditional Landlock selftests, for the new
> LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS flag.
> 
> The other suite lives under the BPF selftests, and this tests the Landlock
> kfuncs and the new BPF_MAP_TYPE_LANDLOCK_RULESET.
> 
> This patch series was run through BPF CI, the results of which are here. [1]
> 
> All mentioned tests are passing, as well as the BPF CI.
> 
> [1] : https://github.com/kernel-patches/bpf/pull/11562
> 
> Points of Feedback
> ===
> 
> First, the new set_nnp_on_point_of_no_return field in struct linux_binprm.
> This field was needed to request that task_set_no_new_privs be set during an
> execution, but only after the execution has proceeded beyond the point of no
> return. I couldn't find a way to express this semantic without adding a new
> bitfield to struct linux_binprm and a conditional in fs/exec.c. Please see
> patch 2.

What about using security_bprm_committing_creds()?

> 
> Feedback on the BPF testing harness, which was generated with AI assistance as
> disclosed in the commit footer, is welcomed. I have only limited familiarity
> with BPF testing practices. These tests were made with strong human supervision.
> See patches 14 and 15.
> 
> Feedback on the NO_NEW_PRIVS situation is also welcomed. Because task_set_no_new_privs()
> would otherwise leak state on failed executions or AT_EXECVE_CHECK, this series
> stages no_new_privs through the exec context and only commits it after
> point-of-no-return. This preserves failure behavior while still ensuring that
> the resulting task cannot elevate further through later exec transitions.
> When called from bprm_creds_from_file, this does not retroactively change the
> privilege outcome of the current exec transition itself.
> 
> See patch 2 and 3.
> 
> Next, the RCU in the landlock_ruleset. Existing BPF maps use RCU to make sure maps
> holding references stay valid. I altered the landlock ruleset to use rcu_work
> to make sure that the rcu is synchronized before putting on a ruleset, and
> acquire the rcu in the arraymap implementation. See patches 5-10.
> 
> Next, the semantics of the map. What operations should be supported from BPF
> and userspace and what data types should they return? I consider the struct
> bpf_landlock_ruleset to be opaque. Userspace can add items to the map via the
> fd, delete items by their index, and BPF can delete and lookup items by their
> index. Items cannot be updated, only swapped.
> 
> Finally, the handling of the LANDLOCK_RESTRICT_SELF_TSYNC flag. This flag has
> no meaning in a pre-execution context, as the credentials during the designated
> LSM hooks (bprm_creds_for_exec/creds_from_file) still represent the pre-execution
> task. Therefore, this flag is invalidated and attempting to use it with
> bpf_landlock_restrict_binprm will return -EINVAL. Otherwise, the flag would
> result in applying the landlock ruleset to the wrong target in addition to the
> intended one. (see patch 2). This behavior is validated with selftests.
> 
> Existing works / Credits
> ===
> 
> Mickaël Salaün created patchsets adding BPF tracepoints for landlock in [2] [3].
> 
> Mickaël also gave feedback on this feature and the idea in this GitHub thread. [4]
> 
> Günther Noack initially received and provided initial feedback on this idea as
> an early prototype.
> 
> Liz Rice, author of "Learning eBPF: Programming the Linux Kernel for Enhanced
> Observability, Networking, and Security" provided background and inspired me to
> experiment with BPF and the BPF LSM. [5]
> 
> [2] : https://lore.kernel.org/all/20250523165741.693976-1-mic@digikod.net/
> [3] : https://lore.kernel.org/linux-security-module/20260406143717.1815792-1-mic@digikod.net/
> [4] : https://github.com/landlock-lsm/linux/issues/56
> [5] : https://wellesleybooks.com/book/9781098135126
> 
> Kind Regards,
> Justin Suess
> 
> Justin Suess (20):
>   landlock: Move operations from syscall into ruleset code
>   execve: Add set_nnp_on_point_of_no_return
>   landlock: Implement LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
>   selftests/landlock: Cover LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
>   landlock: Make ruleset deferred free RCU safe
>   bpf: lsm: Add Landlock kfuncs
>   bpf: arraymap: Implement Landlock ruleset map
>   bpf: Add Landlock ruleset map type
>   bpf: syscall: Handle Landlock ruleset maps
>   bpf: verifier: Add Landlock ruleset map support
>   selftests/bpf: Add Landlock kfunc declarations
>   selftests/landlock: Rename gettid wrapper for BPF reuse
>   selftests/bpf: Enable Landlock in selftests kernel.
>   selftests/bpf: Add Landlock kfunc test program
>   selftests/bpf: Add Landlock kfunc test runner
>   landlock: Bump ABI version
>   tools: bpftool: Add documentation for landlock_ruleset
>   landlock: Document LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
>   bpf: Document BPF_MAP_TYPE_LANDLOCK_RULESET
>   MAINTAINERS: update entry for the Landlock subsystem
> 
>  Documentation/bpf/map_landlock_ruleset.rst    | 181 +++++
>  Documentation/userspace-api/landlock.rst      |  22 +-
>  MAINTAINERS                                   |   4 +
>  fs/exec.c                                     |   8 +
>  include/linux/binfmts.h                       |   7 +-
>  include/linux/bpf_lsm.h                       |  15 +
>  include/linux/bpf_types.h                     |   1 +
>  include/linux/landlock.h                      |  92 +++
>  include/uapi/linux/bpf.h                      |   1 +
>  include/uapi/linux/landlock.h                 |  14 +
>  kernel/bpf/arraymap.c                         |  67 ++
>  kernel/bpf/bpf_lsm.c                          | 145 ++++
>  kernel/bpf/syscall.c                          |   4 +-
>  kernel/bpf/verifier.c                         |  15 +-
>  samples/landlock/sandboxer.c                  |   7 +-
>  security/landlock/limits.h                    |   2 +-
>  security/landlock/ruleset.c                   | 198 ++++-
>  security/landlock/ruleset.h                   |  25 +-
>  security/landlock/syscalls.c                  | 158 +---
>  .../bpf/bpftool/Documentation/bpftool-map.rst |   2 +-
>  tools/bpf/bpftool/map.c                       |   2 +-
>  tools/include/uapi/linux/bpf.h                |   1 +
>  tools/lib/bpf/libbpf.c                        |   1 +
>  tools/lib/bpf/libbpf_probes.c                 |   6 +
>  tools/testing/selftests/bpf/bpf_kfuncs.h      |  20 +
>  tools/testing/selftests/bpf/config            |   5 +
>  tools/testing/selftests/bpf/config.x86_64     |   1 -
>  .../bpf/prog_tests/landlock_kfuncs.c          | 733 ++++++++++++++++++
>  .../selftests/bpf/progs/landlock_kfuncs.c     |  92 +++
>  tools/testing/selftests/landlock/base_test.c  |  10 +-
>  tools/testing/selftests/landlock/common.h     |  28 +-
>  tools/testing/selftests/landlock/fs_test.c    | 103 +--
>  tools/testing/selftests/landlock/net_test.c   |  55 +-
>  .../testing/selftests/landlock/ptrace_test.c  |  14 +-
>  .../landlock/scoped_abstract_unix_test.c      |  51 +-
>  .../selftests/landlock/scoped_base_variants.h |  23 +
>  .../selftests/landlock/scoped_common.h        |   5 +-
>  .../selftests/landlock/scoped_signal_test.c   |  30 +-
>  tools/testing/selftests/landlock/wrappers.h   |   2 +-
>  39 files changed, 1877 insertions(+), 273 deletions(-)
>  create mode 100644 Documentation/bpf/map_landlock_ruleset.rst
>  create mode 100644 include/linux/landlock.h
>  create mode 100644 tools/testing/selftests/bpf/prog_tests/landlock_kfuncs.c
>  create mode 100644 tools/testing/selftests/bpf/progs/landlock_kfuncs.c
> 
> 
> base-commit: 8c6a27e02bc55ab110d1828610048b19f903aaec
> -- 
> 2.53.0
> 
> 

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [RFC PATCH 00/20] BPF interface for applying Landlock rulesets
  2026-04-08 14:00 ` Mickaël Salaün
@ 2026-04-08 17:10   ` Justin Suess
  2026-04-08 19:21     ` Mickaël Salaün
  0 siblings, 1 reply; 26+ messages in thread
From: Justin Suess @ 2026-04-08 17:10 UTC (permalink / raw)
  To: mic
  Cc: andrii, ast, bpf, brauner, daniel, eddyz87, fred, gnoack, jack,
	jmorris, john.fastabend, kees, kpsingh, linux-fsdevel,
	linux-kernel, linux-security-module, m, martin.lau, paul,
	Justin Suess


Add a flag LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS, which executes
task_set_no_new_privs on the current credentials, but only if
the process lacks the CAP_SYS_ADMIN capability.

While this operation is redundant for code running from userspace
(indeed callers may achieve the same logic by calling
prctl w/ PR_SET_NO_NEW_PRIVS), this flag enables callers without access
to the syscall abi (defined in subsequent patches) to restrict processes
from gaining additional capabilities. This is important to ensure that
consumers can meet the task_no_new_privs || CAP_SYS_ADMIN invariant
enforced by Landlock without having syscall access.

This is done by hooking bprm_committing_creds along with a
landlock_cred_security flag to indicate that the next execution should
task_set_no_new_privs if the process doesn't possess CAP_SYS_ADMIN. This
is done to ensure that task_set_no_new_privs is being done past the
point of no return.

Cc: Mickaël Salaün <mic@digikod.net>
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---

On Wed, Apr 08, 2026 at 02:00:00 -0000, Mickaël Salaün wrote:
> > Points of Feedback
> > ===
> > 
> > First, the new set_nnp_on_point_of_no_return field in struct linux_binprm.
> > This field was needed to request that task_set_no_new_privs be set during an
> > execution, but only after the execution has proceeded beyond the point of no
> > return. I couldn't find a way to express this semantic without adding a new
> > bitfield to struct linux_binprm and a conditional in fs/exec.c. Please see
> > patch 2.

> What about using security_bprm_committing_creds()?

Good idea. Definitely cleaner.

Something like this? Then dropping the "execve: Add set_nnp_on_point_of_no_return"
commit.

This adds a bitfield to the landlock_cred_security struct to indicate that the flag
should be set on the next exec(s).

 include/uapi/linux/landlock.h | 14 ++++++++++++++
 security/landlock/cred.c      | 13 +++++++++++++
 security/landlock/cred.h      |  7 +++++++
 security/landlock/limits.h    |  2 +-
 security/landlock/ruleset.c   | 15 ++++++++++++---
 security/landlock/syscalls.c  |  5 +++++
 6 files changed, 52 insertions(+), 4 deletions(-)

diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index f88fa1f68b77..edd9d9a7f60e 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -129,12 +129,26 @@ struct landlock_ruleset_attr {
  *
  *     If the calling thread is running with no_new_privs, this operation
  *     enables no_new_privs on the sibling threads as well.
+ *
+ * %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
+ *    Sets no_new_privs on the calling thread before applying the Landlock domain.
+ *    This flag is useful for convenience as well as for applying a ruleset from
+ *    an outside context (e.g BPF). This flag only has an effect on when both
+ *    no_new_privs isn't already set and the caller doesn't possess CAP_SYS_ADMIN.
+ *
+ *    This flag has slightly different behavior when used from BPF. Instead of
+ *    setting no_new_privs on the current task, it sets a flag on the bprm so that
+ *    no_new_privs is set on the task at exec point-of-no-return. This guarantees
+ *    that the current execution is unaffected, and may escalate as usual until the
+ *    next exec, but the resulting task cannot gain more privileges through later
+ *    exec transitions.
  */
 /* clang-format off */
 #define LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF		(1U << 0)
 #define LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON			(1U << 1)
 #define LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF		(1U << 2)
 #define LANDLOCK_RESTRICT_SELF_TSYNC				(1U << 3)
+#define LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS			(1U << 4)
 /* clang-format on */
 
 /**
diff --git a/security/landlock/cred.c b/security/landlock/cred.c
index 0cb3edde4d18..bcc9b716916f 100644
--- a/security/landlock/cred.c
+++ b/security/landlock/cred.c
@@ -43,6 +43,18 @@ static void hook_cred_free(struct cred *const cred)
 		landlock_put_ruleset_deferred(dom);
 }
 
+static void hook_bprm_committing_creds(const struct linux_binprm *bprm)
+{
+	struct landlock_cred_security *const llcred = landlock_cred(bprm->cred);
+
+	if (llcred->set_nnp_on_committing_creds &&
+	    !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN)) {
+		task_set_no_new_privs(current);
+		/* Don't need to set it again for subsequent execution. */
+		llcred->set_nnp_on_committing_creds = false;
+	}
+}
+
 #ifdef CONFIG_AUDIT
 
 static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm)
@@ -55,6 +67,7 @@ static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm)
 #endif /* CONFIG_AUDIT */
 
 static struct security_hook_list landlock_hooks[] __ro_after_init = {
+	LSM_HOOK_INIT(bprm_committing_creds, hook_bprm_committing_creds),
 	LSM_HOOK_INIT(cred_prepare, hook_cred_prepare),
 	LSM_HOOK_INIT(cred_transfer, hook_cred_transfer),
 	LSM_HOOK_INIT(cred_free, hook_cred_free),
diff --git a/security/landlock/cred.h b/security/landlock/cred.h
index c10a06727eb1..7ec6dd12ebc3 100644
--- a/security/landlock/cred.h
+++ b/security/landlock/cred.h
@@ -49,6 +49,13 @@ struct landlock_cred_security {
 	 * not require a current domain.
 	 */
 	u8 log_subdomains_off : 1;
+	/**
+	 * @set_nnp_on_committing_creds: Set if the domain should set NO_NEW_PRIVS on the
+	 * execution past the point of no return in security_bprm_committing_creds().
+	 * This is not a hierarchy configuration because the nnp state is inherited by
+	 * exec and doesn't need further configuration.
+	 */
+	u8 set_nnp_on_committing_creds : 1;
 #endif /* CONFIG_AUDIT */
 } __packed;
 
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index eb584f47288d..d298086a4180 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -31,7 +31,7 @@
 #define LANDLOCK_MASK_SCOPE		((LANDLOCK_LAST_SCOPE << 1) - 1)
 #define LANDLOCK_NUM_SCOPE		__const_hweight64(LANDLOCK_MASK_SCOPE)
 
-#define LANDLOCK_LAST_RESTRICT_SELF	LANDLOCK_RESTRICT_SELF_TSYNC
+#define LANDLOCK_LAST_RESTRICT_SELF	LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
 #define LANDLOCK_MASK_RESTRICT_SELF	((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1)
 
 /* clang-format on */
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 1d6fa74f2a52..ad0bd5994ec5 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -121,11 +121,13 @@ int landlock_restrict_cred_precheck(const __u32 flags,
 
 	/*
 	 * Similar checks as for seccomp(2), except that an -EPERM may be
-	 * returned.
+	 * returned, or no_new_privs may be set by the caller via
+	 * LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS.
 	 */
 	if (!task_no_new_privs(current) &&
 	    !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN)) {
-		return -EPERM;
+		if (!(flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS))
+			return -EPERM;
 	}
 
 	if (flags & ~LANDLOCK_MASK_RESTRICT_SELF)
@@ -140,7 +142,7 @@ int landlock_restrict_cred(struct cred *const cred,
 {
 	struct landlock_cred_security *new_llcred;
 	bool __maybe_unused log_same_exec, log_new_exec, log_subdomains,
-		prev_log_subdomains;
+		prev_log_subdomains, set_nnp_on_committing_creds;
 
 	/*
 	 * It is allowed to set LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF without
@@ -157,6 +159,12 @@ int landlock_restrict_cred(struct cred *const cred,
 	log_new_exec = !!(flags & LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON);
 	/* Translates "off" flag to boolean. */
 	log_subdomains = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF);
+	/*
+	 * Translates "on" flag to boolean. This flag is not inherited by exec,
+	 * but the resulting nnp state is.
+	 */
+	set_nnp_on_committing_creds =
+		!!(flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS);
 
 	new_llcred = landlock_cred(cred);
 
@@ -165,6 +173,7 @@ int landlock_restrict_cred(struct cred *const cred,
 	new_llcred->log_subdomains_off = !prev_log_subdomains ||
 					 !log_subdomains;
 #endif /* CONFIG_AUDIT */
+	new_llcred->set_nnp_on_committing_creds = set_nnp_on_committing_creds;
 
 	/*
 	 * The only case when a ruleset may not be set is if
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index c6c7be7698a2..f3520c764360 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -397,6 +397,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
  *         - %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON
  *         - %LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF
  *         - %LANDLOCK_RESTRICT_SELF_TSYNC
+ *         - %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
  *
  * This system call enforces a Landlock ruleset on the current thread.
  * Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its
@@ -450,6 +451,10 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
 	if (!new_cred)
 		return -ENOMEM;
 
+	if (flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS &&
+	    !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN))
+		task_set_no_new_privs(current);
+
 	err = landlock_restrict_cred(new_cred, ruleset, flags);
 	if (err) {
 		abort_creds(new_cred);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [RFC PATCH 00/20] BPF interface for applying Landlock rulesets
  2026-04-08 17:10   ` Justin Suess
@ 2026-04-08 19:21     ` Mickaël Salaün
  0 siblings, 0 replies; 26+ messages in thread
From: Mickaël Salaün @ 2026-04-08 19:21 UTC (permalink / raw)
  To: Justin Suess
  Cc: andrii, ast, bpf, brauner, daniel, eddyz87, fred, gnoack, jack,
	jmorris, john.fastabend, kees, kpsingh, linux-fsdevel,
	linux-kernel, linux-security-module, m, martin.lau, paul

On Wed, Apr 08, 2026 at 01:10:28PM -0400, Justin Suess wrote:
> 
> Add a flag LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS, which executes
> task_set_no_new_privs on the current credentials, but only if
> the process lacks the CAP_SYS_ADMIN capability.
> 
> While this operation is redundant for code running from userspace
> (indeed callers may achieve the same logic by calling
> prctl w/ PR_SET_NO_NEW_PRIVS), this flag enables callers without access
> to the syscall abi (defined in subsequent patches) to restrict processes
> from gaining additional capabilities. This is important to ensure that
> consumers can meet the task_no_new_privs || CAP_SYS_ADMIN invariant
> enforced by Landlock without having syscall access.
> 
> This is done by hooking bprm_committing_creds along with a
> landlock_cred_security flag to indicate that the next execution should
> task_set_no_new_privs if the process doesn't possess CAP_SYS_ADMIN. This
> is done to ensure that task_set_no_new_privs is being done past the
> point of no return.
> 
> Cc: Mickaël Salaün <mic@digikod.net>
> Signed-off-by: Justin Suess <utilityemal77@gmail.com>
> ---
> 
> On Wed, Apr 08, 2026 at 02:00:00 -0000, Mickaël Salaün wrote:
> > > Points of Feedback
> > > ===
> > > 
> > > First, the new set_nnp_on_point_of_no_return field in struct linux_binprm.
> > > This field was needed to request that task_set_no_new_privs be set during an
> > > execution, but only after the execution has proceeded beyond the point of no
> > > return. I couldn't find a way to express this semantic without adding a new
> > > bitfield to struct linux_binprm and a conditional in fs/exec.c. Please see
> > > patch 2.
> 
> > What about using security_bprm_committing_creds()?
> 
> Good idea. Definitely cleaner.
> 
> Something like this? Then dropping the "execve: Add set_nnp_on_point_of_no_return"
> commit.
> 
> This adds a bitfield to the landlock_cred_security struct to indicate that the flag
> should be set on the next exec(s).
> 
>  include/uapi/linux/landlock.h | 14 ++++++++++++++
>  security/landlock/cred.c      | 13 +++++++++++++
>  security/landlock/cred.h      |  7 +++++++
>  security/landlock/limits.h    |  2 +-
>  security/landlock/ruleset.c   | 15 ++++++++++++---
>  security/landlock/syscalls.c  |  5 +++++
>  6 files changed, 52 insertions(+), 4 deletions(-)
> 
> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
> index f88fa1f68b77..edd9d9a7f60e 100644
> --- a/include/uapi/linux/landlock.h
> +++ b/include/uapi/linux/landlock.h
> @@ -129,12 +129,26 @@ struct landlock_ruleset_attr {
>   *
>   *     If the calling thread is running with no_new_privs, this operation
>   *     enables no_new_privs on the sibling threads as well.
> + *
> + * %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
> + *    Sets no_new_privs on the calling thread before applying the Landlock domain.
> + *    This flag is useful for convenience as well as for applying a ruleset from
> + *    an outside context (e.g BPF). This flag only has an effect on when both
> + *    no_new_privs isn't already set and the caller doesn't possess CAP_SYS_ADMIN.
> + *
> + *    This flag has slightly different behavior when used from BPF. Instead of
> + *    setting no_new_privs on the current task, it sets a flag on the bprm so that
> + *    no_new_privs is set on the task at exec point-of-no-return. This guarantees
> + *    that the current execution is unaffected, and may escalate as usual until the
> + *    next exec, but the resulting task cannot gain more privileges through later
> + *    exec transitions.
>   */
>  /* clang-format off */
>  #define LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF		(1U << 0)
>  #define LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON			(1U << 1)
>  #define LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF		(1U << 2)
>  #define LANDLOCK_RESTRICT_SELF_TSYNC				(1U << 3)
> +#define LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS			(1U << 4)
>  /* clang-format on */
>  
>  /**
> diff --git a/security/landlock/cred.c b/security/landlock/cred.c
> index 0cb3edde4d18..bcc9b716916f 100644
> --- a/security/landlock/cred.c
> +++ b/security/landlock/cred.c
> @@ -43,6 +43,18 @@ static void hook_cred_free(struct cred *const cred)
>  		landlock_put_ruleset_deferred(dom);
>  }
>  
> +static void hook_bprm_committing_creds(const struct linux_binprm *bprm)
> +{
> +	struct landlock_cred_security *const llcred = landlock_cred(bprm->cred);
> +
> +	if (llcred->set_nnp_on_committing_creds &&
> +	    !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN)) {

If asked by the caller, NNP must be set, whatever the capabilities of
the task.

> +		task_set_no_new_privs(current);
> +		/* Don't need to set it again for subsequent execution. */
> +		llcred->set_nnp_on_committing_creds = false;
> +	}

Thinking more about it, it would make more sense to add another flag to
enforce restriction on the next exec.  This new cred bit would then be
generic and enforce both NNP (if set) and the domain once we know the
execution is ok.  That should also bring the required plumbing to
create the domain at syscall (or kfunc) time and handle memory
allocation issue there, but only enforce it at exec time with
security_bprm_committing_creds() (without any possible error).

> +}
> +
>  #ifdef CONFIG_AUDIT
>  
>  static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm)
> @@ -55,6 +67,7 @@ static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm)
>  #endif /* CONFIG_AUDIT */
>  
>  static struct security_hook_list landlock_hooks[] __ro_after_init = {
> +	LSM_HOOK_INIT(bprm_committing_creds, hook_bprm_committing_creds),
>  	LSM_HOOK_INIT(cred_prepare, hook_cred_prepare),
>  	LSM_HOOK_INIT(cred_transfer, hook_cred_transfer),
>  	LSM_HOOK_INIT(cred_free, hook_cred_free),
> diff --git a/security/landlock/cred.h b/security/landlock/cred.h
> index c10a06727eb1..7ec6dd12ebc3 100644
> --- a/security/landlock/cred.h
> +++ b/security/landlock/cred.h
> @@ -49,6 +49,13 @@ struct landlock_cred_security {
>  	 * not require a current domain.
>  	 */
>  	u8 log_subdomains_off : 1;
> +	/**
> +	 * @set_nnp_on_committing_creds: Set if the domain should set NO_NEW_PRIVS on the
> +	 * execution past the point of no return in security_bprm_committing_creds().
> +	 * This is not a hierarchy configuration because the nnp state is inherited by
> +	 * exec and doesn't need further configuration.
> +	 */
> +	u8 set_nnp_on_committing_creds : 1;
>  #endif /* CONFIG_AUDIT */
>  } __packed;
>  
> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
> index eb584f47288d..d298086a4180 100644
> --- a/security/landlock/limits.h
> +++ b/security/landlock/limits.h
> @@ -31,7 +31,7 @@
>  #define LANDLOCK_MASK_SCOPE		((LANDLOCK_LAST_SCOPE << 1) - 1)
>  #define LANDLOCK_NUM_SCOPE		__const_hweight64(LANDLOCK_MASK_SCOPE)
>  
> -#define LANDLOCK_LAST_RESTRICT_SELF	LANDLOCK_RESTRICT_SELF_TSYNC
> +#define LANDLOCK_LAST_RESTRICT_SELF	LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
>  #define LANDLOCK_MASK_RESTRICT_SELF	((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1)
>  
>  /* clang-format on */
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index 1d6fa74f2a52..ad0bd5994ec5 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -121,11 +121,13 @@ int landlock_restrict_cred_precheck(const __u32 flags,
>  
>  	/*
>  	 * Similar checks as for seccomp(2), except that an -EPERM may be
> -	 * returned.
> +	 * returned, or no_new_privs may be set by the caller via
> +	 * LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS.
>  	 */
>  	if (!task_no_new_privs(current) &&
>  	    !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN)) {
> -		return -EPERM;
> +		if (!(flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS))
> +			return -EPERM;
>  	}
>  
>  	if (flags & ~LANDLOCK_MASK_RESTRICT_SELF)
> @@ -140,7 +142,7 @@ int landlock_restrict_cred(struct cred *const cred,
>  {
>  	struct landlock_cred_security *new_llcred;
>  	bool __maybe_unused log_same_exec, log_new_exec, log_subdomains,
> -		prev_log_subdomains;
> +		prev_log_subdomains, set_nnp_on_committing_creds;
>  
>  	/*
>  	 * It is allowed to set LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF without
> @@ -157,6 +159,12 @@ int landlock_restrict_cred(struct cred *const cred,
>  	log_new_exec = !!(flags & LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON);
>  	/* Translates "off" flag to boolean. */
>  	log_subdomains = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF);
> +	/*
> +	 * Translates "on" flag to boolean. This flag is not inherited by exec,
> +	 * but the resulting nnp state is.
> +	 */
> +	set_nnp_on_committing_creds =
> +		!!(flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS);
>  
>  	new_llcred = landlock_cred(cred);
>  
> @@ -165,6 +173,7 @@ int landlock_restrict_cred(struct cred *const cred,
>  	new_llcred->log_subdomains_off = !prev_log_subdomains ||
>  					 !log_subdomains;
>  #endif /* CONFIG_AUDIT */
> +	new_llcred->set_nnp_on_committing_creds = set_nnp_on_committing_creds;
>  
>  	/*
>  	 * The only case when a ruleset may not be set is if
> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> index c6c7be7698a2..f3520c764360 100644
> --- a/security/landlock/syscalls.c
> +++ b/security/landlock/syscalls.c
> @@ -397,6 +397,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
>   *         - %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON
>   *         - %LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF
>   *         - %LANDLOCK_RESTRICT_SELF_TSYNC
> + *         - %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
>   *
>   * This system call enforces a Landlock ruleset on the current thread.
>   * Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its
> @@ -450,6 +451,10 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
>  	if (!new_cred)
>  		return -ENOMEM;
>  
> +	if (flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS &&
> +	    !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN))
> +		task_set_no_new_privs(current);
> +
>  	err = landlock_restrict_cred(new_cred, ruleset, flags);
>  	if (err) {
>  		abort_creds(new_cred);
> -- 
> 2.53.0
> 
> 

^ permalink raw reply	[flat|nested] 26+ messages in thread

end of thread, other threads:[~2026-04-08 19:21 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-07 20:01 [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Justin Suess
2026-04-07 20:01 ` [RFC PATCH 01/20] landlock: Move operations from syscall into ruleset code Justin Suess
2026-04-07 20:01 ` [RFC PATCH 02/20] execve: Add set_nnp_on_point_of_no_return Justin Suess
2026-04-07 20:01 ` [RFC PATCH 03/20] landlock: Implement LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS Justin Suess
2026-04-07 20:01 ` [RFC PATCH 04/20] selftests/landlock: Cover LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS Justin Suess
2026-04-07 20:01 ` [RFC PATCH 05/20] landlock: Make ruleset deferred free RCU safe Justin Suess
2026-04-07 20:01 ` [RFC PATCH 06/20] bpf: lsm: Add Landlock kfuncs Justin Suess
2026-04-07 20:01 ` [RFC PATCH 07/20] bpf: arraymap: Implement Landlock ruleset map Justin Suess
2026-04-07 20:01 ` [RFC PATCH 08/20] bpf: Add Landlock ruleset map type Justin Suess
2026-04-07 20:01 ` [RFC PATCH 09/20] bpf: syscall: Handle Landlock ruleset maps Justin Suess
2026-04-07 20:01 ` [RFC PATCH 10/20] bpf: verifier: Add Landlock ruleset map support Justin Suess
2026-04-07 20:01 ` [RFC PATCH 11/20] selftests/bpf: Add Landlock kfunc declarations Justin Suess
2026-04-07 20:01 ` [RFC PATCH 12/20] selftests/landlock: Rename gettid wrapper for BPF reuse Justin Suess
2026-04-07 20:01 ` [RFC PATCH 13/20] selftests/bpf: Enable Landlock in selftests kernel Justin Suess
2026-04-07 20:01 ` [RFC PATCH 14/20] selftests/bpf: Add Landlock kfunc test program Justin Suess
2026-04-07 20:01 ` [RFC PATCH 15/20] selftests/bpf: Add Landlock kfunc test runner Justin Suess
2026-04-07 20:01 ` [RFC PATCH 16/20] landlock: Bump ABI version Justin Suess
2026-04-07 20:01 ` [RFC PATCH 17/20] tools: bpftool: Add documentation for landlock_ruleset Justin Suess
2026-04-07 20:01 ` [RFC PATCH 18/20] landlock: Document LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS Justin Suess
2026-04-07 20:01 ` [RFC PATCH 19/20] bpf: Document BPF_MAP_TYPE_LANDLOCK_RULESET Justin Suess
2026-04-07 20:01 ` [RFC PATCH 20/20] MAINTAINERS: update entry for the Landlock subsystem Justin Suess
2026-04-08  4:40 ` [RFC PATCH 00/20] BPF interface for applying Landlock rulesets Ihor Solodrai
2026-04-08 11:41   ` Justin Suess
2026-04-08 14:00 ` Mickaël Salaün
2026-04-08 17:10   ` Justin Suess
2026-04-08 19:21     ` Mickaël Salaün

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox