On Mon, Apr 06, 2026 at 04:37:04PM +0200, Mickaël Salaün wrote:
> Add tracepoints for ruleset lifecycle events: landlock_create_ruleset
> fires from the landlock_create_ruleset() syscall handler, logging the
> ruleset Landlock ID and handled access masks; landlock_free_ruleset
> fires in free_ruleset() before the ruleset is freed, so eBPF programs
> can access the full ruleset state via BTF.
>
> The create_ruleset TP_PROTO takes only the ruleset pointer. The handled
> access masks are read from the ruleset in TP_fast_assign rather than
> passed as scalar arguments, so eBPF programs can access the full ruleset
> state (rules, access masks) via BTF on a single pointer. No lock is
> needed because the ruleset is not yet shared (the file descriptor has
> not been installed).
>
> Create the trace header with a DOC comment documenting the consistency
> guarantees, locking conventions, TP_PROTO safety, and security
> considerations shared by all Landlock tracepoints. Add
> CREATE_TRACE_POINTS in log.c to generate the tracepoint implementations.
>
> Add an id field to struct landlock_ruleset, assigned from
> landlock_get_id_range() at creation time. Extend the CONFIG guard on
> landlock_get_id_range() from CONFIG_AUDIT to
> CONFIG_SECURITY_LANDLOCK_LOG so that IDs are available for tracing even
> without audit support.
>
> The deallocation events use the "free_" prefix (rather than "drop_")
> because they fire when the object is actually freed. There is no need
> for allocated/deallocated symmetry because ruleset creation happens with
> the landlock_create_ruleset tracepoint.
>
> landlock_create_ruleset tracepoint.
>
> Unlike audit records which share a record type and need a "status="
> field to distinguish allocation from deallocation, tracepoints provide
> one event type per lifecycle transition, each with a type-safe TP_PROTO
> matching the specific transition. This enables type-safe eBPF BTF
> access and precise ftrace filtering by event name.
>
> Cc: Günther Noack <[email protected]>
> Cc: Justin Suess <[email protected]>
> Cc: Masami Hiramatsu <[email protected]>
> Cc: Mathieu Desnoyers <[email protected]>
> Cc: Steven Rostedt <[email protected]>
> Cc: Tingmao Wang <[email protected]>
> Signed-off-by: Mickaël Salaün <[email protected]>
> ---
>
> Changes since v1:
> - New patch (split from the v1 add_rule_fs tracepoint patch).
> ---
> MAINTAINERS | 1 +
> include/trace/events/landlock.h | 94 +++++++++++++++++++++++++++++++++
> security/landlock/id.h | 6 +--
> security/landlock/log.c | 5 ++
> security/landlock/ruleset.c | 8 +++
> security/landlock/ruleset.h | 9 ++++
> security/landlock/syscalls.c | 5 ++
> 7 files changed, 125 insertions(+), 3 deletions(-)
> create mode 100644 include/trace/events/landlock.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index c3fe46d7c4bc..51104faa3951 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14389,6 +14389,7 @@ F: Documentation/admin-guide/LSM/landlock.rst
> F: Documentation/security/landlock.rst
> F: Documentation/userspace-api/landlock.rst
> F: fs/ioctl.c
> +F: include/trace/events/landlock.h
> F: include/uapi/linux/landlock.h
> F: samples/landlock/
> F: security/landlock/
> diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h
> new file mode 100644
> index 000000000000..5e847844fbf7
> --- /dev/null
> +++ b/include/trace/events/landlock.h
> @@ -0,0 +1,94 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright © 2025 Microsoft Corporation
> + * Copyright © 2026 Cloudflare
> + */
> +
> +#undef TRACE_SYSTEM
> +#define TRACE_SYSTEM landlock
> +
> +#if !defined(_TRACE_LANDLOCK_H) || defined(TRACE_HEADER_MULTI_READ)
> +#define _TRACE_LANDLOCK_H
> +
> +#include <linux/tracepoint.h>
> +
> +struct landlock_ruleset;
> +
> +/**
> + * DOC: Landlock trace events
> + *
> + * Consistency guarantee: every trace event corresponds to an operation
> + * that has irrevocably succeeded. Lifecycle events fire only after
> + * the point of no return; denial events fire only for denials that
> + * actually happen. This guarantees that eBPF programs observing the
> + * trace stream can build a faithful model of Landlock state without
> + * reconciliation logic.
> + *
> + * Mutable object pointers in TP_PROTO (e.g., struct landlock_ruleset
> + * for add_rule events) are passed while the caller holds the object's
> + * lock, so that TP_fast_assign and eBPF programs reading via BTF see a
> + * consistent snapshot. For objects that are immutable at the emission
> + * site (e.g., a domain after creation), no lock is needed.
> + *
> + * All pointer arguments in TP_PROTO are guaranteed non-NULL by the
> + * caller. eBPF programs can access these pointers via BTF for richer
> + * introspection than the TP_STRUCT__entry fields provide.
> + *
> + * TP_STRUCT__entry fields serve TP_printk display only. eBPF programs
> + * access the raw TP_PROTO arguments directly.
> + *
> + * Security: as for audit, Landlock trace events may expose sensitive
> + * information about all sandboxed processes on the system. See
> + * Documentation/admin-guide/LSM/landlock.rst for security considerations
> + * and privilege requirements.
> + */
> +
> +/**
> + * landlock_create_ruleset - new ruleset created
> + * @ruleset: Newly created ruleset (never NULL); not yet shared via an fd,
> + * so no lock is needed. eBPF programs can read the full ruleset
> + * state via BTF.
> + */
> +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->layer.fs;
> + __entry->handled_net = ruleset->layer.net;
> + __entry->scoped = ruleset->layer.scope;),
> +
> + TP_printk("ruleset=%llx handled_fs=0x%x handled_net=0x%x scoped=0x%x",
> + __entry->ruleset_id, __entry->handled_fs,
> + __entry->handled_net, __entry->scoped));
> +
> +/**
> + * landlock_free_ruleset - Ruleset freed
> + *
> + * Emitted when a ruleset's last reference is dropped (typically when
> + * the creating process closes the ruleset file descriptor).
> + */
> +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));
> +
> +#endif /* _TRACE_LANDLOCK_H */
> +
> +/* This part must be outside protection */
> +#include <trace/define_trace.h>
> diff --git a/security/landlock/id.h b/security/landlock/id.h
> index 45dcfb9e9a8b..2a43c2b523a8 100644
> --- a/security/landlock/id.h
> +++ b/security/landlock/id.h
> @@ -8,18 +8,18 @@
> #ifndef _SECURITY_LANDLOCK_ID_H
> #define _SECURITY_LANDLOCK_ID_H
>
> -#ifdef CONFIG_AUDIT
> +#ifdef CONFIG_SECURITY_LANDLOCK_LOG
>
> void __init landlock_init_id(void);
>
> u64 landlock_get_id_range(size_t number_of_ids);
>
> -#else /* CONFIG_AUDIT */
> +#else /* CONFIG_SECURITY_LANDLOCK_LOG */
>
> static inline void __init landlock_init_id(void)
> {
> }
>
> -#endif /* CONFIG_AUDIT */
> +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
>
> #endif /* _SECURITY_LANDLOCK_ID_H */
> diff --git a/security/landlock/log.c b/security/landlock/log.c
> index c9b506707af0..ef79e4ed0037 100644
> --- a/security/landlock/log.c
> +++ b/security/landlock/log.c
> @@ -174,6 +174,11 @@ static void audit_denial(const struct
> landlock_cred_security *const subject,
>
> #endif /* CONFIG_AUDIT */
>
> +#ifdef CONFIG_TRACEPOINTS
> +#define CREATE_TRACE_POINTS
> +#include <trace/events/landlock.h>
> +#endif /* CONFIG_TRACEPOINTS */
> +
> static struct landlock_hierarchy *
> get_hierarchy(const struct landlock_domain *const domain, const size_t layer)
> {
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index c220e0f9cf5f..0d1e3dadb318 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -22,10 +22,13 @@
> #include <linux/spinlock.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,
> @@ -49,6 +52,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_SECURITY_LANDLOCK_LOG
> + new_ruleset->id = landlock_get_id_range(1);
> +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
The addition of IDs to rulesets for logging makes sense.
But it is limited in usefulness without some form of introspection to be
able to correlate it to a specific userspace ruleset.
If a program creates multiple Landlock rulesets, and wishes to trace and
correlate which ruleset FD corresponds to the log/tracepoint, it is
difficult when no form of introspection exists.
Maybe a syscall flag or ioctl to retrieve the identifier for correlation
would be useful?
Justin
> +
> /* Should already be checked in sys_landlock_create_ruleset(). */
> if (fs_access_mask) {
> WARN_ON_ONCE(fs_access_mask !=
> @@ -312,6 +319,7 @@ void landlock_free_rules(struct landlock_rules *const
> rules)
> static void free_ruleset(struct landlock_ruleset *const ruleset)
> [...]