summaryrefslogtreecommitdiff
path: root/security
diff options
context:
space:
mode:
authorMickaël Salaün <mic@digikod.net>2026-08-11 11:43:21 +0200
committerMickaël Salaün <mic@digikod.net>2026-08-17 10:17:13 +0200
commitb4540a72be4138a97c7cb74f803e57a7350a55ec (patch)
tree50fa5e5468f36182e64c72c9c6ceae06c9a9367d /security
parentbc62ec60343183713055cb6b6247efe0209d9cd6 (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/Kconfig2
-rw-r--r--security/landlock/Makefile2
-rw-r--r--security/landlock/ruleset.c8
-rw-r--r--security/landlock/ruleset.h9
-rw-r--r--security/landlock/syscalls.c11
-rw-r--r--security/landlock/trace.c17
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>