diff options
| author | Mickaël Salaün <mic@digikod.net> | 2026-08-11 11:43:21 +0200 |
|---|---|---|
| committer | Mickaël Salaün <mic@digikod.net> | 2026-08-17 10:17:13 +0200 |
| commit | b4540a72be4138a97c7cb74f803e57a7350a55ec (patch) | |
| tree | 50fa5e5468f36182e64c72c9c6ceae06c9a9367d /security | |
| parent | bc62ec60343183713055cb6b6247efe0209d9cd6 (diff) | |
landlock: Add create_ruleset and free_ruleset tracepoints
Add the first Landlock tracepoints, for ruleset lifecycle:
landlock_create_ruleset fires from the landlock_create_ruleset() syscall
handler, and landlock_free_ruleset fires in free_ruleset() before the
ruleset is freed.
These tracepoints, and the ones added by the following commits, share a
common design. Rather than one polymorphic event distinguished by a
status field (as audit uses a shared record type with a "status="
field), each lifecycle transition and denial type gets its own event
with a type-safe TP_PROTO, giving precise ftrace filtering by event name
and type-safe eBPF access. TP_PROTO passes the object pointer and the
fields are read from it in TP_fast_assign, so an eBPF program reads the
full object state (rules, access masks, hierarchy) via BTF from a single
pointer rather than from the flattened TP_STRUCT__entry fields. The
whole cost is paid only when a tracer is attached; the static branch is
not taken otherwise. Trace fields carry the bare access-right and scope
names (read_file), reusing the audit name tables; audit prepends the
category (fs.read_file), which the trace event name already conveys.
The trace header's DOC comment documents the consistency and locking
guarantees these events share.
create_ruleset needs no lock because the ruleset is not yet shared (its
file descriptor is not yet installed). The deallocation events use the
"free_" prefix, not "drop_", because they fire when the object is
actually freed.
Add trace.c, built for CONFIG_TRACEPOINTS, which defines
CREATE_TRACE_POINTS, and extend CONFIG_SECURITY_LANDLOCK_LOG to also be
selected by CONFIG_TRACEPOINTS so the common log framework is available
to a tracepoints-only build.
Add an id field to struct landlock_ruleset, gated on CONFIG_TRACEPOINTS
and assigned from landlock_get_id_range() at creation. Only the
tracepoints consume it (audit identifies domains, not rulesets), so it
does not exist in an audit-only build. The Landlock ID is a stable u64
that names the ruleset across the trace stream and uses the same scheme
as audit, so a ruleset can be correlated between trace and audit
records.
Cc: Günther Noack <gnoack@google.com>
Cc: Justin Suess <utilityemal77@gmail.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-8-mic@digikod.net
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Diffstat (limited to 'security')
| -rw-r--r-- | security/landlock/Kconfig | 2 | ||||
| -rw-r--r-- | security/landlock/Makefile | 2 | ||||
| -rw-r--r-- | security/landlock/ruleset.c | 8 | ||||
| -rw-r--r-- | security/landlock/ruleset.h | 9 | ||||
| -rw-r--r-- | security/landlock/syscalls.c | 11 | ||||
| -rw-r--r-- | security/landlock/trace.c | 17 |
6 files changed, 48 insertions, 1 deletions
diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig index b4e7f0e9ba9b..7aeac29160e8 100644 --- a/security/landlock/Kconfig +++ b/security/landlock/Kconfig @@ -24,7 +24,7 @@ config SECURITY_LANDLOCK config SECURITY_LANDLOCK_LOG bool depends on SECURITY_LANDLOCK - default y if AUDIT + default y if AUDIT || TRACEPOINTS config SECURITY_LANDLOCK_KUNIT_TEST bool "KUnit tests for Landlock" if !KUNIT_ALL_TESTS diff --git a/security/landlock/Makefile b/security/landlock/Makefile index 2462e6c5921e..2711f4876939 100644 --- a/security/landlock/Makefile +++ b/security/landlock/Makefile @@ -18,3 +18,5 @@ landlock-$(CONFIG_SECURITY_LANDLOCK_LOG) += \ log.o landlock-$(CONFIG_AUDIT) += audit.o + +landlock-$(CONFIG_TRACEPOINTS) += trace.o diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 2b74542ac3d1..30f3897a56d4 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -23,10 +23,13 @@ #include <uapi/linux/landlock.h> #include "access.h" +#include "id.h" #include "limits.h" #include "object.h" #include "ruleset.h" +#include <trace/events/landlock.h> + struct landlock_ruleset * landlock_create_ruleset(const access_mask_t fs_access_mask, const access_mask_t net_access_mask, @@ -50,6 +53,10 @@ landlock_create_ruleset(const access_mask_t fs_access_mask, new_ruleset->rules.root_net_port = RB_ROOT; #endif /* IS_ENABLED(CONFIG_INET) */ +#ifdef CONFIG_TRACEPOINTS + new_ruleset->id = landlock_get_id_range(1); +#endif /* CONFIG_TRACEPOINTS */ + /* Should already be checked in landlock_create_ruleset(). */ if (fs_access_mask) { const access_mask_t mask = fs_access_mask & @@ -325,6 +332,7 @@ void landlock_free_rules(struct landlock_rules *const rules) static void free_ruleset(struct landlock_ruleset *const ruleset) { might_sleep(); + trace_landlock_free_ruleset(ruleset); landlock_free_rules(&ruleset->rules); kfree(ruleset); } diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index 3a9fc39e947c..dcc363740b85 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -4,6 +4,7 @@ * * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> * Copyright © 2018-2020 ANSSI + * Copyright © 2026 Cloudflare, Inc. */ #ifndef _SECURITY_LANDLOCK_RULESET_H @@ -164,6 +165,14 @@ struct landlock_ruleset { * @usage: Number of file descriptors referencing this ruleset. */ refcount_t usage; + +#ifdef CONFIG_TRACEPOINTS + /** + * @id: Unique identifier for this ruleset, used for tracing. + */ + u64 id; +#endif /* CONFIG_TRACEPOINTS */ + /** * @quiet_masks: Stores the quiet flags for an unmerged ruleset. For a * merged domain, this is stored in each layer's struct diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 37cadb63db9f..7f168802b584 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -38,6 +38,8 @@ #include "setup.h" #include "tsync.h" +#include <trace/events/landlock.h> + static bool is_initialized(void) { if (likely(landlock_initialized)) @@ -281,6 +283,15 @@ SYSCALL_DEFINE3(landlock_create_ruleset, ruleset->quiet_masks.net = ruleset_attr.quiet_access_net; ruleset->quiet_masks.scope = ruleset_attr.quiet_scoped; + /* + * Emits before anon_inode_getfd() installs the file descriptor, while + * the ruleset is still private to this thread: no lock is needed, and + * the event cannot race a concurrent close() freeing the ruleset under + * the tracepoint's BTF read. This is the last point at which the + * ruleset is guaranteed alive and unshared. + */ + trace_landlock_create_ruleset(ruleset); + /* Creates anonymous FD referring to the ruleset. */ ruleset_fd = anon_inode_getfd("[landlock-ruleset]", &ruleset_fops, ruleset, O_RDWR | O_CLOEXEC); diff --git a/security/landlock/trace.c b/security/landlock/trace.c new file mode 100644 index 000000000000..aeb6eeebe42a --- /dev/null +++ b/security/landlock/trace.c @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock - Tracepoint helpers + * + * Copyright © 2025 Microsoft Corporation + * Copyright © 2026 Cloudflare, Inc. + */ + +#include "ruleset.h" + +/* + * Generates the tracepoint definitions in this translation unit. The trace + * event header dereferences the traced objects in TP_fast_assign, so the full + * struct definitions (e.g. ruleset.h) must be included before it. + */ +#define CREATE_TRACE_POINTS +#include <trace/events/landlock.h> |
