mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 10:31:33 -04:00
landlock: Add landlock_enforce_domain tracepoint
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> Link: https://patch.msgid.link/20260811094338.288094-11-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>
This commit is contained in:
@@ -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
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user