diff options
| author | Mickaël Salaün <mic@digikod.net> | 2026-08-11 11:43:22 +0200 |
|---|---|---|
| committer | Mickaël Salaün <mic@digikod.net> | 2026-08-17 10:17:14 +0200 |
| commit | 63747c94774d4a5a0a9d9e739da0df29937aebea (patch) | |
| tree | 58fc51aeda990c58383046cd4b8b70a689d59b9c /security | |
| parent | b4540a72be4138a97c7cb74f803e57a7350a55ec (diff) | |
| download | linux-stable-63747c94774d4a5a0a9d9e739da0df29937aebea.tar.gz linux-stable-63747c94774d4a5a0a9d9e739da0df29937aebea.zip | |
landlock: Add landlock_add_rule_fs and landlock_add_rule_net tracepoints
Add tracepoints for Landlock rule addition, landlock_add_rule_fs for
filesystem rules and landlock_add_rule_net for network rules, so trace
consumers can correlate filesystem objects and network ports with their
rulesets. Both are emitted under the ruleset lock (asserted in
TP_fast_assign) so an eBPF program reads the ruleset, including the rule
just inserted, in a consistent snapshot.
Add a version field to struct landlock_ruleset, gated on
CONFIG_TRACEPOINTS like the id field and incremented under the ruleset
lock on each successful landlock_add_rule(2), including when it only
extends an existing rule's access rights. It fills the existing 4-byte
hole after usage, so the struct does not grow. Pairing the ruleset ID
with the version lets a later restrict_self event record the exact
ruleset revision merged into a domain.
Resolve the filesystem rule's absolute path with d_absolute_path()
rather than the d_path() audit uses: d_absolute_path() produces
namespace-independent paths that do not depend on the tracer's chroot
state, making trace output deterministic regardless of mount namespace
configuration. Distinguish the error cases as "<too_long>"
(-ENAMETOOLONG) and "<unreachable>" (anonymous files or detached
mounts).
Also add __trace_print_untrusted_str(), a static inline helper in the
header guarded by CREATE_TRACE_POINTS: it escapes separators, quotes,
backslashes, and non-printable bytes via string_escape_mem() so an
untrusted string (the path here, process names in later denial events)
cannot inject field separators or control characters into the ftrace
text output.
Cc: Christian Brauner <brauner@kernel.org>
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-9-mic@digikod.net
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Diffstat (limited to 'security')
| -rw-r--r-- | security/landlock/fs.c | 19 | ||||
| -rw-r--r-- | security/landlock/fs.h | 32 | ||||
| -rw-r--r-- | security/landlock/net.c | 11 | ||||
| -rw-r--r-- | security/landlock/ruleset.c | 13 | ||||
| -rw-r--r-- | security/landlock/ruleset.h | 7 |
5 files changed, 80 insertions, 2 deletions
diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 3ff5fd24378f..3a33e7393e25 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -53,6 +53,8 @@ #include "ruleset.h" #include "setup.h" +#include <trace/events/landlock.h> + /* Underlying object management */ static void release_inode(struct landlock_object *const object) @@ -347,7 +349,24 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, return PTR_ERR(id.key.object); mutex_lock(&ruleset->lock); err = landlock_insert_rule(ruleset, id, access_rights, flags); + + /* + * Emit after the rule insertion succeeds, so every event corresponds to + * a rule that is actually in the ruleset. The ruleset lock is still + * held for BTF consistency (enforced by lockdep_assert_held in + * TP_fast_assign). + */ + if (!err && trace_landlock_add_rule_fs_enabled()) { + char *buffer __free(__putname) = __getname(); + const char *pathname = + buffer ? resolve_path_for_trace(path, buffer) : + "<no_mem>"; + + trace_landlock_add_rule_fs(ruleset, access_rights, path, + pathname); + } mutex_unlock(&ruleset->lock); + /* * No need to check for an error because landlock_insert_rule() * increments the refcount for the new object if needed. diff --git a/security/landlock/fs.h b/security/landlock/fs.h index c16f24e30bd5..50b3ddb8ac1c 100644 --- a/security/landlock/fs.h +++ b/security/landlock/fs.h @@ -11,6 +11,7 @@ #define _SECURITY_LANDLOCK_FS_H #include <linux/build_bug.h> +#include <linux/cleanup.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/rcupdate.h> @@ -20,6 +21,8 @@ #include "ruleset.h" #include "setup.h" +DEFINE_FREE(__putname, char *, if (_T) __putname(_T)) + /** * struct landlock_inode_security - Inode security blob * @@ -153,4 +156,33 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, const struct path *const path, access_mask_t access_hierarchy, const u32 flags); +/** + * resolve_path_for_trace - Resolve a path for tracepoint display + * + * @path: The path to resolve. + * @buf: A buffer of at least PATH_MAX bytes for the resolved path. + * + * Uses d_absolute_path() to produce a namespace-independent absolute path, + * unlike d_path() which resolves relative to the process's chroot. This + * ensures trace output is deterministic regardless of the tracer's mount + * namespace. + * + * Return: A pointer into @buf with the resolved path, or an error string + * ("<too_long>", "<unreachable>"). + */ +static inline const char *resolve_path_for_trace(const struct path *path, + char *buf) +{ + const char *p; + + p = d_absolute_path(path, buf, PATH_MAX); + if (!IS_ERR_OR_NULL(p)) + return p; + + if (PTR_ERR(p) == -ENAMETOOLONG) + return "<too_long>"; + + return "<unreachable>"; +} + #endif /* _SECURITY_LANDLOCK_FS_H */ diff --git a/security/landlock/net.c b/security/landlock/net.c index e27b3ba15664..ead97fcfdcff 100644 --- a/security/landlock/net.c +++ b/security/landlock/net.c @@ -20,6 +20,8 @@ #include "net.h" #include "ruleset.h" +#include <trace/events/landlock.h> + int landlock_append_net_rule(struct landlock_ruleset *const ruleset, const u16 port, access_mask_t access_rights, const u32 flags) @@ -37,6 +39,15 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset, mutex_lock(&ruleset->lock); err = landlock_insert_rule(ruleset, id, access_rights, flags); + + /* + * Emit after the rule insertion succeeds, so every event corresponds to + * a rule that is actually in the ruleset. The ruleset lock is still + * held for BTF consistency (enforced by lockdep_assert_held in + * TP_fast_assign). + */ + if (!err) + trace_landlock_add_rule_net(ruleset, access_rights, port); mutex_unlock(&ruleset->lock); return err; diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 30f3897a56d4..0d07707523cd 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -4,6 +4,7 @@ * * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> * Copyright © 2018-2020 ANSSI + * Copyright © 2026 Cloudflare, Inc. */ #include <linux/bits.h> @@ -306,11 +307,19 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset, .quiet = !!(flags & LANDLOCK_ADD_RULE_QUIET), }, } }; + int err; build_check_layer(); lockdep_assert_held(&ruleset->lock); - return landlock_store_rule(&ruleset->rules, id, &layers, - ARRAY_SIZE(layers)); + err = landlock_store_rule(&ruleset->rules, id, &layers, + ARRAY_SIZE(layers)); + +#ifdef CONFIG_TRACEPOINTS + if (!err) + ruleset->version++; +#endif /* CONFIG_TRACEPOINTS */ + + return err; } void landlock_free_rules(struct landlock_rules *const rules) diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index dcc363740b85..b536fa0425b7 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -168,6 +168,13 @@ struct landlock_ruleset { #ifdef CONFIG_TRACEPOINTS /** + * @version: Counter incremented on each successful + * landlock_add_rule(2), including when it only extends an existing + * rule's access rights. Used by tracepoints to correlate a domain with + * the exact ruleset state it was created from. Protected by @lock. + */ + u32 version; + /** * @id: Unique identifier for this ruleset, used for tracing. */ u64 id; |
