[RFC PATCH 03/20] landlock: Implement LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS

Justin Suess
2026-04-07T20:02:14Z

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