From: Justin Suess <utilityemal77@gmail.com>
To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org,
kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net,
viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org
Cc: gnoack@google.com, jack@suse.cz, song@kernel.org,
yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org,
bpf@vger.kernel.org, linux-security-module@vger.kernel.org,
linux-kernel@vger.kernel.org,
Justin Suess <utilityemal77@gmail.com>
Subject: [PATCH bpf-next 05/13] landlock: Factor the credential restriction out of landlock_restrict_self()
Date: Thu, 30 Jul 2026 22:20:38 -0400 [thread overview]
Message-ID: <20260731022047.189137-6-utilityemal77@gmail.com> (raw)
In-Reply-To: <20260731022047.189137-1-utilityemal77@gmail.com>
Split the core of landlock_restrict_self() into two credential
helpers:
landlock_prepare_restriction() - translate the
landlock_restrict_self(2) flags, merge the ruleset with the
credentials' domain, and configure the new domain's audit log
state, producing a struct landlock_restriction: the complete
new state that the enforcement gives to a credential.
landlock_apply_restriction() - enforce a computed restriction on
credentials exclusively owned by the caller. This step cannot
fail, so a caller may run it past its last point of failure.
The syscall behaves exactly as before: prepare and apply run back to
back on the prepared credentials. The no_new_privs/CAP_SYS_ADMIN
precheck, the flag mask check, and the TSYNC handling are syscall
policy and stay in place.
The point of the split is that application is decoupled from
computation: a following commit restricts an execution from a BPF
kfunc by staging a prepared restriction in the binprm credentials and
applying it at the exec point of no return. With the flag
translation, domain merge, and audit log configuration in one shared
place.
Cc: Mickaël Salaün <mic@digikod.net>
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
security/landlock/cred.c | 100 +++++++++++++++++++++++++++++++++++
security/landlock/cred.h | 33 ++++++++++++
security/landlock/syscalls.c | 61 +++++----------------
3 files changed, 147 insertions(+), 47 deletions(-)
diff --git a/security/landlock/cred.c b/security/landlock/cred.c
index cc419de75cd6..13b3952c31c5 100644
--- a/security/landlock/cred.c
+++ b/security/landlock/cred.c
@@ -8,14 +8,114 @@
*/
#include <linux/binfmts.h>
+#include <linux/bits.h>
#include <linux/cred.h>
+#include <linux/err.h>
+#include <linux/errno.h>
#include <linux/lsm_hooks.h>
+#include <uapi/linux/landlock.h>
#include "common.h"
#include "cred.h"
+#include "domain.h"
#include "ruleset.h"
#include "setup.h"
+/**
+ * landlock_prepare_restriction - Compute a credential restriction
+ *
+ * @llcred: Landlock credentials to restrict: provides the parent domain and
+ * the previous log configuration. Not modified.
+ * @ruleset: Ruleset to enforce, or NULL for a log-configuration-only change.
+ * @flags: landlock_restrict_self(2) flags. The caller is responsible for
+ * validating them against the set of flags it supports.
+ * @restriction: Computed restriction. On success, holds a reference on
+ * @restriction->domain (if any), which
+ * landlock_apply_restriction() transfers to the restricted
+ * credentials.
+ *
+ * The restriction builds on @llcred's current state: the caller must apply
+ * it to (or stage it for) these same credentials.
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+int landlock_prepare_restriction(
+ const struct landlock_cred_security *const llcred,
+ struct landlock_ruleset *const ruleset, const u32 flags,
+ struct landlock_restriction *const restriction)
+{
+#ifdef CONFIG_AUDIT
+ /* Translates "off" and "on" flags to booleans. */
+ const bool log_same_exec =
+ !(flags & LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF);
+ const bool log_new_exec =
+ !!(flags & LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON);
+ const bool log_subdomains =
+ !(flags & LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF);
+ const bool prev_log_subdomains = !llcred->log_subdomains_off;
+#endif /* CONFIG_AUDIT */
+
+ *restriction = (struct landlock_restriction){};
+
+#ifdef CONFIG_AUDIT
+ restriction->log_subdomains_off = !prev_log_subdomains ||
+ !log_subdomains;
+#endif /* CONFIG_AUDIT */
+
+ if (!ruleset)
+ return 0;
+
+ restriction->domain = landlock_merge_ruleset(llcred->domain, ruleset);
+ if (IS_ERR(restriction->domain)) {
+ const int err = PTR_ERR(restriction->domain);
+
+ restriction->domain = NULL;
+ return err;
+ }
+
+#ifdef CONFIG_AUDIT
+ restriction->domain->hierarchy->log_same_exec = log_same_exec;
+ restriction->domain->hierarchy->log_new_exec = log_new_exec;
+ if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains)
+ restriction->domain->hierarchy->log_status =
+ LANDLOCK_LOG_DISABLED;
+#endif /* CONFIG_AUDIT */
+
+ return 0;
+}
+
+/**
+ * landlock_apply_restriction - Enforce a computed restriction on credentials
+ *
+ * @llcred: Landlock credentials to restrict, exclusively owned by the caller
+ * (prepared and not yet committed).
+ * @restriction: Restriction computed by landlock_prepare_restriction()
+ * against the same credential state; its domain reference is
+ * transferred to @llcred.
+ *
+ * Cannot fail, so that a caller may apply a restriction past its last point
+ * of failure, e.g. an exec point of no return.
+ */
+void landlock_apply_restriction(struct landlock_cred_security *const llcred,
+ struct landlock_restriction *const restriction)
+{
+#ifdef CONFIG_AUDIT
+ llcred->log_subdomains_off = restriction->log_subdomains_off;
+#endif /* CONFIG_AUDIT */
+
+ if (!restriction->domain)
+ return;
+
+ /* Replaces the old domain. */
+ landlock_put_ruleset(llcred->domain);
+ llcred->domain = restriction->domain;
+ restriction->domain = NULL;
+
+#ifdef CONFIG_AUDIT
+ llcred->domain_exec |= BIT(llcred->domain->num_layers - 1);
+#endif /* CONFIG_AUDIT */
+}
+
static void hook_cred_transfer(struct cred *const new,
const struct cred *const old)
{
diff --git a/security/landlock/cred.h b/security/landlock/cred.h
index f287c56b5fd4..1d5039b46ce7 100644
--- a/security/landlock/cred.h
+++ b/security/landlock/cred.h
@@ -20,6 +20,31 @@
#include "ruleset.h"
#include "setup.h"
+/**
+ * struct landlock_restriction - Computed credential restriction
+ *
+ * The result of landlock_prepare_restriction(): the new state that
+ * enforcing a ruleset with a set of landlock_restrict_self(2) flags
+ * gives to a credential, decoupled from its application. It is
+ * enforced with landlock_apply_restriction(), either right away
+ * (landlock_restrict_self(2)) or after a staging period (restriction
+ * of an execution).
+ */
+struct landlock_restriction {
+ /**
+ * @domain: New domain to enforce, owning a reference. NULL if the
+ * restriction only carries a log configuration change.
+ */
+ struct landlock_ruleset *domain;
+#ifdef CONFIG_AUDIT
+ /**
+ * @log_subdomains_off: New value of the credentials'
+ * @landlock_cred_security.log_subdomains_off.
+ */
+ u8 log_subdomains_off : 1;
+#endif /* CONFIG_AUDIT */
+};
+
/**
* struct landlock_cred_security - Credential security blob
*
@@ -153,6 +178,14 @@ landlock_get_applicable_subject(const struct cred *const cred,
return NULL;
}
+int landlock_prepare_restriction(
+ const struct landlock_cred_security *const llcred,
+ struct landlock_ruleset *const ruleset, const u32 flags,
+ struct landlock_restriction *const restriction);
+
+void landlock_apply_restriction(struct landlock_cred_security *const llcred,
+ struct landlock_restriction *const restriction);
+
__init void landlock_add_cred_hooks(void);
#endif /* _SECURITY_LANDLOCK_CRED_H */
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 9af2407274b2..899601af7c4e 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -528,9 +528,8 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
{
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;
+ struct landlock_restriction restriction;
+ int err;
if (!is_initialized())
return -EOPNOTSUPP;
@@ -547,13 +546,6 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
LANDLOCK_MASK_RESTRICT_SELF)
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);
-
/*
* It is allowed to set LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF with
* -1 as ruleset_fd, optionally combined with
@@ -575,53 +567,28 @@ 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.
+ *
+ * There is no possible race condition while copying and manipulating
+ * the current credentials because they are dedicated per thread.
*/
- 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 */
+ err = landlock_prepare_restriction(landlock_cred(new_cred), ruleset,
+ flags, &restriction);
+ if (err) {
+ abort_creds(new_cred);
+ return err;
}
+ landlock_apply_restriction(landlock_cred(new_cred), &restriction);
+
if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) {
- const int err = landlock_restrict_sibling_threads(
- current_cred(), new_cred);
+ err = landlock_restrict_sibling_threads(current_cred(),
+ new_cred);
if (err) {
abort_creds(new_cred);
return err;
--
2.54.0
next prev parent reply other threads:[~2026-07-31 2:21 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-31 2:20 [PATCH bpf-next 00/13] BPF interface for applying Landlock rulesets Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 01/13] lsm: Add LSM hook security_policy_kptr_from_fd Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 02/13] lsm: Add LSM hook security_policy_kptr_put Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 03/13] lsm: Add LSM hook security_bprm_enforce_policy_kptr Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 04/13] landlock: Expose the ruleset fd lookup to the rest of Landlock Justin Suess
2026-07-31 2:20 ` Justin Suess [this message]
2026-07-31 2:20 ` [PATCH bpf-next 06/13] landlock: Implement the LSM policy kptr hooks Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 07/13] bpf: Add the LSM policy kfunc infrastructure Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 08/13] bpf: Add the bpf_landlock_put_ruleset kfunc and ruleset destructor Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 09/13] bpf: Add the bpf_landlock_get_ruleset_from_fd kfunc Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 10/13] bpf: Add the bpf_landlock_restrict_binprm kfunc Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 11/13] selftests/bpf: Add tests for the Landlock policy kfuncs Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 12/13] landlock: Document the BPF kfunc interface Justin Suess
2026-07-31 2:20 ` [PATCH bpf-next 13/13] lsm: Document the LSM policy kptr hooks Justin Suess
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260731022047.189137-6-utilityemal77@gmail.com \
--to=utilityemal77@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=brauner@kernel.org \
--cc=daniel@iogearbox.net \
--cc=gnoack@google.com \
--cc=jack@suse.cz \
--cc=kees@kernel.org \
--cc=kpsingh@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=m@maowtm.org \
--cc=martin.lau@linux.dev \
--cc=mic@digikod.net \
--cc=paul@paul-moore.com \
--cc=song@kernel.org \
--cc=viro@zeniv.linux.org.uk \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox