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 /include | |
| parent | bc62ec60343183713055cb6b6247efe0209d9cd6 (diff) | |
| download | linux-b4540a72be4138a97c7cb74f803e57a7350a55ec.tar.gz linux-b4540a72be4138a97c7cb74f803e57a7350a55ec.zip | |
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 'include')
| -rw-r--r-- | include/linux/landlock.h | 1 | ||||
| -rw-r--r-- | include/trace/events/landlock.h | 143 |
2 files changed, 144 insertions, 0 deletions
diff --git a/include/linux/landlock.h b/include/linux/landlock.h index cabe6c784833..004cbd0b9298 100644 --- a/include/linux/landlock.h +++ b/include/linux/landlock.h @@ -9,6 +9,7 @@ #ifndef _LINUX_LANDLOCK_H #define _LINUX_LANDLOCK_H +#include <linux/types.h> #include <uapi/linux/landlock.h> /* diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h new file mode 100644 index 000000000000..02434f770455 --- /dev/null +++ b/include/trace/events/landlock.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright © 2025 Microsoft Corporation + * Copyright © 2026 Cloudflare, Inc. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM landlock + +#if !defined(_TRACE_LANDLOCK_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_LANDLOCK_H + +#include <linux/landlock.h> +#include <linux/tracepoint.h> + +struct landlock_ruleset; + +/* clang-format off */ + +/* Maps a shared _LANDLOCK_*_NAMES entry to a __print_flags() pair. */ +#define _LANDLOCK_NAME_ENTRY(mask, name) { mask, name } + +/** + * DOC: Landlock trace events + * + * These guarantees and constraints hold for every Landlock tracepoint. + * A new tracepoint must uphold them, and an eBPF consumer can rely on + * them. + * + * Lifecycle consistency + * ~~~~~~~~~~~~~~~~~~~~~~ + * + * Lifecycle events are balanced: a creation event always has a matching + * deallocation event and vice versa, so an eBPF program can model object + * lifetimes from the trace stream without reconciliation logic. A creation + * event fires while the object is still private to the calling thread + * (landlock_create_ruleset fires before the ruleset's file descriptor is + * installed, so it cannot race a concurrent :manpage:`close(2)`); if fd + * installation later fails and the ruleset is freed, free_ruleset still + * fires, keeping the pair balanced. The domain pair (create_domain and + * free_domain) is balanced the same way: create_domain fires when the + * domain is created (under the ruleset lock, before thread-sync), and + * free_domain fires when it is freed. A rare thread-sync failure aborts + * the just-created domain, which then emits both events (its creation, then + * an immediate free). Denial events fire only for denials that actually + * happen. + * + * Pointer access + * ~~~~~~~~~~~~~~ + * + * All pointer arguments in TP_PROTO are guaranteed non-NULL by the + * caller, but pointers reached through them may still be NULL (e.g., + * hierarchy->parent at a root domain) and must be checked. eBPF programs + * read these pointers via BTF for richer introspection than the + * TP_STRUCT__entry fields, which serve TP_printk display only. + * + * Mutable object pointers are passed while the caller holds the object's + * lock, so TP_fast_assign and a BTF reader see the exact object the event + * reports, a snapshot no concurrent writer can change: add_rule holds the + * modified ruleset's lock, and create_domain holds the ruleset lock across + * the emission (before the thread-sync wait) so the inspected ruleset is + * the one merged into the domain. Objects immutable at the emission site + * (a domain after creation, a hierarchy at its last reference) need no + * lock. A few values that no held lock protects are a best-effort + * lockless snapshot instead: a task's comm, and the deny_access_net struct + * sock (whose network hook holds no socket lock), matching how the sched + * and signal trace events sample comm. + */ + +/** + * landlock_create_ruleset - New ruleset created + * + * @ruleset: Newly created ruleset (never NULL); not yet shared via an fd, + * so no lock is needed. + * + * Emitted by sys_landlock_create_ruleset() while the new ruleset is still + * private to the calling thread, before its file descriptor is installed, + * so it cannot race a concurrent :manpage:`close(2)`. Balanced by a + * matching landlock_free_ruleset event. + */ +TRACE_EVENT(landlock_create_ruleset, + + TP_PROTO(const struct landlock_ruleset *ruleset), + + TP_ARGS(ruleset), + + TP_STRUCT__entry( + __field( __u64, ruleset_id ) + __field( access_mask_t, handled_fs ) + __field( access_mask_t, handled_net ) + __field( access_mask_t, scoped ) + ), + + TP_fast_assign( + __entry->ruleset_id = ruleset->id; + __entry->handled_fs = ruleset->handled_masks.fs; + __entry->handled_net = ruleset->handled_masks.net; + __entry->scoped = ruleset->handled_masks.scope; + ), + + TP_printk("ruleset=%llx handled_fs=%s handled_net=%s scoped=%s", + __entry->ruleset_id, + __print_flags(__entry->handled_fs, "|", _LANDLOCK_ACCESS_FS_NAMES), + __print_flags(__entry->handled_net, "|", _LANDLOCK_ACCESS_NET_NAMES), + __print_flags(__entry->scoped, "|", _LANDLOCK_SCOPE_NAMES)) +); + +/** + * landlock_free_ruleset - Ruleset freed + * + * @ruleset: Ruleset being freed (never NULL); at its last reference, so no + * lock is needed. + * + * Emitted when a ruleset's last reference is dropped (typically when + * the creating process closes the ruleset file descriptor). Fires even + * when file-descriptor installation failed after creation, keeping the + * create/free pair balanced. + */ +TRACE_EVENT(landlock_free_ruleset, + + TP_PROTO(const struct landlock_ruleset *ruleset), + + TP_ARGS(ruleset), + + TP_STRUCT__entry( + __field( __u64, ruleset_id ) + ), + + TP_fast_assign( + __entry->ruleset_id = ruleset->id; + ), + + TP_printk("ruleset=%llx", __entry->ruleset_id) +); + +#undef _LANDLOCK_NAME_ENTRY + +#endif /* _TRACE_LANDLOCK_H */ + +/* This part must be outside protection */ +#include <trace/define_trace.h> + +/* clang-format on */ |
