From: "Mickaël Salaün" <mic@digikod.net>
To: "Günther Noack" <gnoack@google.com>,
"Steven Rostedt" <rostedt@goodmis.org>
Cc: "Mickaël Salaün" <mic@digikod.net>,
"Christian Brauner" <brauner@kernel.org>,
"Jann Horn" <jannh@google.com>, "Jeff Xu" <jeffxu@google.com>,
"Justin Suess" <utilityemal77@gmail.com>,
"Kees Cook" <kees@kernel.org>,
"Masami Hiramatsu" <mhiramat@kernel.org>,
"Mathieu Desnoyers" <mathieu.desnoyers@efficios.com>,
"Matthieu Buffet" <matthieu@buffet.re>,
"Mikhail Ivanov" <ivanov.mikhail1@huawei-partners.com>,
"Tingmao Wang" <m@maowtm.org>,
kernel-team@cloudflare.com,
linux-security-module@vger.kernel.org,
linux-trace-kernel@vger.kernel.org
Subject: [PATCH v4 10/19] landlock: Add landlock_enforce_domain tracepoint
Date: Tue, 11 Aug 2026 11:43:24 +0200 [thread overview]
Message-ID: <20260811094338.288094-11-mic@digikod.net> (raw)
In-Reply-To: <20260811094338.288094-1-mic@digikod.net>
The landlock_create_domain event records that a domain was created,
once, before thread-sync. It cannot tell which threads end up enforcing
it: a successful landlock_restrict_self(2) with
LANDLOCK_RESTRICT_SELF_TSYNC applies the domain to the caller and every
eligible sibling. Creation (the operation) and enforcement (the
per-thread outcome) are distinct.
Add landlock_enforce_domain(domain, complete, process_wide), emitted
once per thread the domain is applied to, strictly after that thread's
commit_creds(), so it fires only for a thread that is enforcing the
domain, never speculatively; an aborted operation emits none. The
lifecycle now reads create -> enforce* -> free.
The two booleans name properties, not the implementation:
- complete: marks the single event that concludes the operation. It
names the outcome, the set is now enforced, not which thread
finishes, which the contract leaves unspecified.
- process_wide: means every eligible thread of the process is
covered. It is set race-free by either establishing path,
thread-sync or a single-threaded process, so
complete && process_wide is the whole-process-enforced guarantee.
The requesting thread and source ruleset are not repeated here: they are
on create_domain (joined via domain->hierarchy->id) and on the immutable
domain->hierarchy->details. Source ruleset means the ruleset_id and
ruleset_version recorded on create_domain, not the ruleset object, which
the caller may close before enforcement.
Cc: Günther Noack <gnoack@google.com>
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Tingmao Wang <m@maowtm.org>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
---
Changes since v3:
https://patch.msgid.link/20260722171159.2776765-12-mic@digikod.net
- Rebased onto landlock-next: emit the enforce_domain tracepoint
alongside the new LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS handling in
landlock_restrict_self().
- Add a no_new_privs field to landlock_enforce_domain recording the
thread's no_new_privs state (task_no_new_privs() at emission, for
both the caller and TSYNC-swept siblings), so consumers see whether
the domain is backed by no_new_privs or by CAP_SYS_ADMIN. The raw
restrict_self flags stay unexposed here (readable via the
syscall-entry tracepoint).
Changes since v2:
- New patch.
---
include/trace/events/landlock.h | 60 +++++++++++++++++++++++++++++++++
security/landlock/syscalls.c | 14 +++++++-
security/landlock/tsync.c | 16 +++++++++
3 files changed, 89 insertions(+), 1 deletion(-)
diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h
index 9f0d0436a8bb..43bf126eb449 100644
--- a/include/trace/events/landlock.h
+++ b/include/trace/events/landlock.h
@@ -339,6 +339,66 @@ TRACE_EVENT(landlock_create_domain,
__entry->ruleset_id, __entry->ruleset_version)
);
+/**
+ * landlock_enforce_domain - Domain enforced on a thread
+ *
+ * @domain: Domain now enforced on the current thread (never NULL,
+ * immutable; read locklessly). Correlate to
+ * landlock_create_domain via @domain->hierarchy->id for the
+ * source ruleset and requesting thread, or read
+ * @domain->hierarchy->details for the requesting process.
+ * @complete: Set on the single event that concludes the operation, after
+ * all its other enforcements; filter on it for one event per
+ * operation.
+ * @process_wide: The enforcement covers every eligible (non-exiting)
+ * thread of the process: set when the caller used
+ * %LANDLOCK_RESTRICT_SELF_TSYNC or the process is
+ * single-threaded. A lone thread whose group still
+ * holds a zombie leader is not counted single-threaded,
+ * so process_wide == 0 never proves the opposite.
+ * @no_new_privs: The enforcing thread's no_new_privs state at
+ * enforcement time: 1 if set (by a prior
+ * :manpage:`prctl(2)` %PR_SET_NO_NEW_PRIVS or by
+ * %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS), 0 if the domain
+ * was enforced with %CAP_SYS_ADMIN instead.
+ *
+ * Emitted for each thread sys_landlock_restrict_self() enforces the
+ * domain on, in that thread's own context, right after its
+ * commit_creds(), so it fires only once the thread is irreversibly
+ * enforcing the domain (aborted operations emit none). Not
+ * balanced; every enforcement falls between the domain's
+ * landlock_create_domain and landlock_free_domain events.
+ *
+ * @complete == 1 && @process_wide == 1 means the whole process is
+ * sandboxed by @domain, durably (Landlock domains are monotonic and
+ * inherited on :manpage:`clone(2)`).
+ */
+TRACE_EVENT(landlock_enforce_domain,
+
+ TP_PROTO(const struct landlock_domain *domain, bool complete,
+ bool process_wide, bool no_new_privs),
+
+ TP_ARGS(domain, complete, process_wide, no_new_privs),
+
+ TP_STRUCT__entry(
+ __field( __u64, domain_id )
+ __field( bool, complete )
+ __field( bool, process_wide )
+ __field( bool, no_new_privs )
+ ),
+
+ TP_fast_assign(
+ __entry->domain_id = domain->hierarchy->id;
+ __entry->complete = complete;
+ __entry->process_wide = process_wide;
+ __entry->no_new_privs = no_new_privs;
+ ),
+
+ TP_printk("domain=%llx complete=%d process_wide=%d no_new_privs=%d",
+ __entry->domain_id, __entry->complete, __entry->process_wide,
+ __entry->no_new_privs)
+);
+
/**
* landlock_free_domain - Domain freed
*
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index ba4193673c70..1d02d57f4c48 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -22,6 +22,7 @@
#include <linux/mount.h>
#include <linux/path.h>
#include <linux/sched.h>
+#include <linux/sched/signal.h>
#include <linux/security.h>
#include <linux/stddef.h>
#include <linux/syscalls.h>
@@ -546,6 +547,7 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
struct landlock_domain *new_dom = NULL;
struct cred *new_cred;
struct landlock_cred_security *new_llcred;
+ bool process_wide;
bool __maybe_unused log_same_exec, log_new_exec, log_subdomains,
prev_log_subdomains;
@@ -690,5 +692,15 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
if (flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS)
task_set_no_new_privs(current);
- return commit_creds(new_cred);
+ /* Whole process: thread-sync swept siblings, or single-threaded. */
+ process_wide = (flags & LANDLOCK_RESTRICT_SELF_TSYNC) ||
+ get_nr_threads(current) == 1;
+ commit_creds(new_cred);
+
+ /* The caller commits last, so its event concludes the operation. */
+ if (ruleset)
+ trace_landlock_enforce_domain(new_dom, true, process_wide,
+ task_no_new_privs(current));
+
+ return 0;
}
diff --git a/security/landlock/tsync.c b/security/landlock/tsync.c
index 0b71e158c3f5..cfa73a2a67ca 100644
--- a/security/landlock/tsync.c
+++ b/security/landlock/tsync.c
@@ -22,6 +22,8 @@
#include "cred.h"
#include "tsync.h"
+#include <trace/events/landlock.h>
+
/*
* Shared state between multiple threads which are enforcing Landlock rulesets
* in lockstep with each other.
@@ -79,6 +81,8 @@ struct tsync_work {
*/
static void restrict_one_thread(struct tsync_shared_context *ctx)
{
+ const struct landlock_domain *new_dom =
+ landlock_cred(ctx->new_cred)->domain;
int err;
struct cred *cred = NULL;
@@ -147,6 +151,18 @@ static void restrict_one_thread(struct tsync_shared_context *ctx)
commit_creds(cred);
+ /*
+ * Emitted strictly after commit_creds() and before the out: label, so
+ * it fires only for a thread now enforcing new_dom, and every
+ * non-concluding (complete == false) event happens-before the
+ * operation's single concluding one. Skipped on the flags-only path,
+ * where old_cred and new_cred carry the same domain. A sibling never
+ * concludes the operation and its enforcement is always process-wide.
+ */
+ if (new_dom != landlock_cred(ctx->old_cred)->domain)
+ trace_landlock_enforce_domain(new_dom, false, true,
+ task_no_new_privs(current));
+
out:
/* Notify the calling thread once all threads are done */
if (atomic_dec_return(&ctx->num_unfinished) == 0)
--
2.54.0
next prev parent reply other threads:[~2026-08-11 9:44 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-11 9:43 [PATCH v4 00/19] Landlock tracepoints Mickaël Salaün
2026-08-11 9:43 ` [PATCH v4 01/19] landlock: Prepare ruleset and domain type split Mickaël Salaün
2026-08-16 17:56 ` Tingmao Wang
2026-08-11 9:43 ` [PATCH v4 02/19] landlock: Move domain query functions to domain.c Mickaël Salaün
2026-08-11 9:43 ` [PATCH v4 03/19] landlock: Split struct landlock_domain from struct landlock_ruleset Mickaël Salaün
2026-08-20 10:45 ` Günther Noack
2026-08-11 9:43 ` [PATCH v4 04/19] landlock: Split denial logging from audit into common framework Mickaël Salaün
2026-08-11 9:43 ` [PATCH v4 05/19] landlock: Decouple the per-denial logging decision from CONFIG_AUDIT Mickaël Salaün
2026-08-16 22:39 ` Tingmao Wang
2026-08-11 9:43 ` [PATCH v4 06/19] landlock: Consolidate access-right and scope names in a shared header Mickaël Salaün
2026-08-11 9:43 ` [PATCH v4 07/19] landlock: Add create_ruleset and free_ruleset tracepoints Mickaël Salaün
2026-08-11 9:43 ` [PATCH v4 08/19] landlock: Add landlock_add_rule_fs and landlock_add_rule_net tracepoints Mickaël Salaün
2026-08-11 9:43 ` [PATCH v4 09/19] landlock: Add create_domain and free_domain tracepoints Mickaël Salaün
2026-08-20 10:36 ` Günther Noack
2026-08-11 9:43 ` Mickaël Salaün [this message]
2026-08-11 9:43 ` [PATCH v4 11/19] landlock: Add tracepoints for rule checking Mickaël Salaün
2026-08-11 9:43 ` [PATCH v4 12/19] landlock: Add landlock_deny_access_fs and landlock_deny_access_net Mickaël Salaün
2026-08-11 9:43 ` [PATCH v4 13/19] landlock: Add tracepoints for ptrace and scope denials Mickaël Salaün
2026-08-11 9:43 ` [PATCH v4 14/19] selftests/landlock: Add trace event test infrastructure and tests Mickaël Salaün
2026-08-11 9:43 ` [PATCH v4 15/19] selftests/landlock: Add filesystem tracepoint tests Mickaël Salaün
2026-08-11 9:43 ` [PATCH v4 16/19] selftests/landlock: Add network " Mickaël Salaün
2026-08-11 9:43 ` [PATCH v4 17/19] selftests/landlock: Add scope and ptrace " Mickaël Salaün
2026-08-11 9:43 ` [PATCH v4 18/19] selftests/landlock: Add landlock_enforce_domain trace tests Mickaël Salaün
2026-08-11 9:43 ` [PATCH v4 19/19] landlock: Document tracepoints Mickaël Salaün
2026-08-11 13:51 ` [PATCH v4 00/19] Landlock tracepoints Steven Rostedt
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=20260811094338.288094-11-mic@digikod.net \
--to=mic@digikod.net \
--cc=brauner@kernel.org \
--cc=gnoack@google.com \
--cc=ivanov.mikhail1@huawei-partners.com \
--cc=jannh@google.com \
--cc=jeffxu@google.com \
--cc=kees@kernel.org \
--cc=kernel-team@cloudflare.com \
--cc=linux-security-module@vger.kernel.org \
--cc=linux-trace-kernel@vger.kernel.org \
--cc=m@maowtm.org \
--cc=mathieu.desnoyers@efficios.com \
--cc=matthieu@buffet.re \
--cc=mhiramat@kernel.org \
--cc=rostedt@goodmis.org \
--cc=utilityemal77@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.