xref: /linux/Documentation/trace/events-landlock.rst (revision 3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d)
1.. SPDX-License-Identifier: GPL-2.0
2.. Copyright © 2026 Cloudflare, Inc.
3
4=====================
5Landlock Trace Events
6=====================
7
8:Author: Mickaël Salaün
9:Date: August 2026
10
11Landlock emits trace events for sandbox lifecycle operations and access
12denials.  These events can be consumed by ftrace (for human-readable
13trace output and filtering) and by eBPF programs (for programmatic
14introspection via BTF).
15
16User space documentation can be found here:
17Documentation/userspace-api/landlock.rst
18
19.. warning::
20
21   Landlock trace events, like audit records, expose sensitive
22   information about all sandboxed processes on the system.  See
23   :ref:`landlock_observability_security` for security considerations
24   and privilege requirements.
25
26Event overview
27==============
28
29Landlock trace events are organized in four categories:
30
31**Syscall events** are emitted during Landlock system calls:
32
33- ``landlock_create_ruleset``: a new ruleset is created
34- ``landlock_add_rule_fs``: a filesystem rule is added to a ruleset
35- ``landlock_add_rule_net``: a network port rule is added to a ruleset
36- ``landlock_create_domain``: a new domain is created from a ruleset
37- ``landlock_enforce_domain``: a domain is enforced on a thread
38
39**Denial events** are emitted when an access is denied:
40
41- ``landlock_deny_access_fs``: filesystem access denied
42- ``landlock_deny_access_net``: network access denied
43- ``landlock_deny_ptrace``: ptrace access denied
44- ``landlock_deny_scope_signal``: signal delivery denied
45- ``landlock_deny_scope_abstract_unix_socket``: abstract unix socket
46  access denied
47
48**Rule evaluation events** are emitted during rule matching:
49
50- ``landlock_check_rule_fs``: a filesystem rule is evaluated
51- ``landlock_check_rule_net``: a network port rule is evaluated
52
53**Lifecycle events**:
54
55- ``landlock_free_domain``: a domain is freed
56- ``landlock_free_ruleset``: a ruleset is freed
57
58Enabling events
59===============
60
61Enable all Landlock events::
62
63    echo 1 > /sys/kernel/tracing/events/landlock/enable
64
65Enable a specific event::
66
67    echo 1 > /sys/kernel/tracing/events/landlock/landlock_deny_access_fs/enable
68
69Read the trace output::
70
71    cat /sys/kernel/tracing/trace_pipe
72
73Event samples
74=============
75
76A fully unprivileged program is sandboxed so that it can still run (its
77binary and shared libraries stay readable) and write only ``/tmp``, then
78it is denied reading ``/etc/passwd``, which lies outside its read-only
79set.  ``/etc/passwd`` is world-readable, so the denial comes solely from
80Landlock, not from regular file permissions::
81
82  $ cd /sys/kernel/tracing/events/landlock/
83  $ echo 1 | tee landlock_{create_ruleset,create_domain,enforce_domain,deny_access_fs,free_domain}/enable >/dev/null
84  $ LC_ALL=C LL_FS_RO=/usr:/lib:/lib64:/bin:/etc/ld.so.cache LL_FS_RW=/tmp \
85      ./sandboxer cat /etc/passwd
86  $ cat /sys/kernel/tracing/trace_pipe
87  cat-127 [...] landlock_create_ruleset: ruleset=195cc6b76.0 handled_fs=execute|write_file|read_file|read_dir|remove_dir|remove_file|make_char|make_dir|make_reg|make_sock|make_fifo|make_block|make_sym|refer|truncate|ioctl_dev|resolve_unix handled_net= scoped=
88  cat-127 [...] landlock_create_domain: domain=195cc6b7c parent=0 ruleset=195cc6b76.6
89  cat-127 [...] landlock_enforce_domain: domain=195cc6b7c complete=1 process_wide=1 no_new_privs=1
90  cat-127 [...] landlock_deny_access_fs: domain=195cc6b7c same_exec=0 logged=0 blockers=read_file dev=0:17 ino=5901179 path=/etc/passwd
91  kworker/0:1-11 [...] landlock_free_domain: domain=195cc6b7c denials=1
92
93The ``[...]`` replaces the ftrace CPU, flags, and timestamp columns.  The
94first four events share the ``cat`` command name and PID because the
95sandboxer replaces itself with ``cat`` via ``execve()`` before the
96denial, and ftrace resolves a recorded PID to its latest command name.
97``landlock_free_domain`` fires later from a kworker thread, so it carries
98that thread's name instead.
99
100Here ``logged=0`` shows that audit would not record this cross-execution
101denial under the default flags, yet the ``deny_access_fs`` event still
102appears.
103
104Differences from audit records
105==============================
106
107Tracepoints and audit records both log Landlock denials, but differ
108in some field formats:
109
110- **Paths**: Most filesystem tracepoints resolve the path with
111  ``d_absolute_path()`` (namespace-independent absolute paths), while
112  mount-topology denials that carry only a dentry use ``dentry_path_raw()``.
113  Audit uses ``d_path()`` (relative to the process's chroot).  A resolution
114  failure is reported as ``<no_mem>``, ``<too_long>``, or ``<unreachable>``.
115  Path-based tracepoint output is deterministic regardless of the tracer's
116  mount namespace.
117
118- **Device names**: Tracepoints use numeric ``dev=<major>:<minor>``.
119  Audit uses string ``dev="<s_id>"``.  Numeric format is more precise
120  for machine parsing.
121
122- **Denied access field**: The ``deny_access_fs`` and ``deny_access_net``
123  tracepoints use the ``blockers=`` field name (same as audit).  Both
124  render the blocked access rights as names: audit prefixes the category
125  and separates with commas (e.g., ``blockers=fs.read_file``), while the
126  tracepoints omit the category (carried by the event name) and separate
127  with ``|`` (e.g., ``blockers=read_file``).  Scope and ptrace
128  tracepoints omit ``blockers`` because the event name identifies the
129  denial type.
130
131- **Scope and ptrace target names**: Tracepoints use role-specific field
132  names (``tracee_pid``, ``target_pid``, ``peer_pid``) that reflect the
133  semantic of each event.  Audit uses generic names (``opid``, ``ocomm``)
134  because the audit log format is not event-type-specific.
135
136- **Process name**: The ptrace and signal denial tracepoints include the
137  role-prefixed ``tracee_comm=`` and ``target_comm=`` labels in the
138  printk output for stateless consumers (each matches its sibling
139  ``tracee_pid=``/``target_pid=`` field).  eBPF consumers can read
140  ``comm`` directly from the task_struct via BTF.  The ``comm`` value is
141  treated as untrusted input and escaped in the trace text output so it
142  cannot inject field separators or control characters.
143
144- **Other party's domain**: A scope or ptrace denial compares the
145  subject's denying domain (``domain=``, always the enforcing domain and
146  never the current task) with the other party's domain, so these
147  tracepoints also report the other party's domain as a scalar ID:
148  ``tracee_domain=`` (ptrace), ``target_domain=`` (signal), and
149  ``peer_domain=`` (abstract unix socket).  It is ``0`` when the other
150  party is unsandboxed, and otherwise a domain ID that a consumer resolves
151  against the ``landlock_create_ruleset`` and ``landlock_create_domain``
152  events it recorded.  Because a scope or ptrace verdict is decided by
153  comparing the two domains, resolving both the subject ``domain=`` and
154  this other-party ID against those lifecycle events lets a consumer
155  verify or reproduce the verdict by redoing the same two-domain
156  comparison, rather than only noting which boundary was crossed.  Audit
157  records do not carry the other party's domain.
158
159Ruleset versioning
160==================
161
162Syscall events include a ruleset version (``ruleset=<hex_id>.<version>``)
163that tracks the number of rules added to the ruleset.  The version is
164incremented on each ``landlock_add_rule()`` call and frozen at
165``landlock_restrict_self()`` time.  This enables trace consumers to
166correlate a domain with the exact set of rules it was created from.
167
168Domain enforcement
169==================
170
171The whole-process-enforced guarantee (``complete=1 && process_wide=1``)
172is the observable outcome of a successful
173``landlock_restrict_self(..., LANDLOCK_RESTRICT_SELF_TSYNC)``; see the
174thread synchronization section of
175Documentation/userspace-api/landlock.rst.
176
177The Landlock events and the generic syscall tracepoints are
178complementary: the Landlock events expose the *semantic effect* of an
179operation (the domain, its scope, the resulting ``no_new_privs`` state),
180while ``raw_syscalls:sys_enter``/``sys_exit`` (or the per-syscall
181``syscalls:sys_{enter,exit}_landlock_*`` under
182``CONFIG_FTRACE_SYSCALLS``) expose the *raw API* -- the exact
183``landlock_restrict_self()`` flags, arguments, and return value.
184Correlate them by thread; a ``LANDLOCK_RESTRICT_SELF_TSYNC`` operation
185also enforces the domain on the sibling threads, whose
186``landlock_enforce_domain`` events fire in each sibling's own context
187rather than the caller's, so correlate those to the syscall by domain ID.
188
189Interpreting check_rule events
190==============================
191
192The ``check_rule_fs`` and ``check_rule_net`` events expose the per-layer
193rule evaluation, which is useful for understanding *why* a specific
194access is allowed or denied.
195
196.. warning::
197
198   These events fire on the access-check hot path, once per matching rule
199   per check.  On a busy sandboxed workload this can be very high
200   frequency.  Enable them only for targeted debugging, ideally combined
201   with an ftrace filter (for example on ``ino`` or ``domain_id``), and
202   expect tracing overhead while they are enabled.
203
204Two output fields carry the evaluation:
205
206- ``access_request=`` is the set of access rights being evaluated against the
207  rule, rendered as ``|``-separated names.  For most checks this is the
208  access the operation requested.  For filesystem ``rename`` and ``link``
209  double-checks it is the domain's full handled mask, because those
210  operations re-evaluate every handled right.
211
212- ``grants=`` is a per-layer breakdown of the requested rights that this
213  rule grants, in the form ``{<layer>,<layer>,...}``:
214
215  - The braces wrap one comma-separated group per domain layer, ordered
216    from the outermost (least nested) sandbox layer to the innermost.
217  - Each group lists the requested rights the rule grants at that layer,
218    joined by ``|``.
219  - An empty group (for example the middle layer in
220    ``{read_file,,read_file}``) means the rule grants none of the
221    requested rights at that layer.
222
223A Landlock domain allows an access only when, for every requested right,
224every layer that handles that right has at least one matching rule
225granting it.  A single ``check_rule`` event therefore shows one rule's
226contribution, not the final decision:
227
228- If a right appears in every layer's group, this rule alone is
229  sufficient to allow that right.
230- If a right is missing from some layer's group, that layer must grant it
231  through another matching rule, or the right is denied and appears in the
232  ``blockers=`` field of the corresponding ``deny_access`` event.
233
234To reconstruct the decision for an object, aggregate the ``grants=``
235groups of all ``check_rule`` events emitted for that object during the
236check.
237
238.. note::
239
240   Because a verdict requires aggregating ``grants=`` across all matching
241   rules of one access check, a stateless ftrace filter on a single
242   ``check_rule`` event cannot distinguish an allowed access from a
243   denied one.
244
245For example, a program sandboxed with read and execute access to the
246whole filesystem reads ``/etc/passwd``; both the ``execve()`` and the
247read match the rule covering ``/`` (inode 2), so ``check_rule_fs`` fires
248with the requested rights intersected against what that rule grants.
249The ``access_request=`` mask includes ``truncate`` because the file-open hook
250evaluates that optional right alongside the required access, but the
251rule does not grant it, so ``truncate`` never appears in ``grants=``::
252
253  cat-127 [...] landlock_check_rule_fs: domain=1e40cb56f access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file}
254  cat-127 [...] landlock_check_rule_fs: domain=1e40cb56f access_request=read_file|truncate dev=0:17 ino=2 grants={read_file}
255
256The ``[...]`` replaces the ftrace CPU, flags, and timestamp columns.  A
257single ``grants=`` group means the enforcing domain has one layer.  With
258two nested sandboxes that each grant the same rights, the rule spans both
259layers, so ``grants=`` has one group per layer::
260
261  cat-128 [...] landlock_check_rule_fs: domain=184788b52 access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file,execute|read_file}
262  cat-128 [...] landlock_check_rule_fs: domain=184788b52 access_request=read_file|truncate dev=0:17 ino=2 grants={read_file,read_file}
263
264eBPF access
265===========
266
267eBPF programs attached via ``BPF_RAW_TRACEPOINT`` can access the
268tracepoint arguments directly through BTF.  The arguments include both
269standard kernel objects and Landlock-internal objects:
270
271- Standard kernel objects (``struct task_struct``, ``struct sock``,
272  ``struct path``, ``struct dentry``) can be used with existing BPF
273  helpers.
274- Landlock-internal objects (``struct landlock_domain``,
275  ``struct landlock_ruleset``, ``struct landlock_rule``,
276  ``struct landlock_hierarchy``) can be read via ``BPF_CORE_READ``.
277  Internal struct layouts may change between kernel versions; use CO-RE
278  for field relocation.
279
280A stateful eBPF program observes the full event stream and maintains
281per-domain state in BPF maps:
282
2831. On ``landlock_create_domain``: record the domain ID and parent (the
284   per-domain Landlock log flags are not event fields; read them from
285   ``struct landlock_hierarchy`` via BTF if needed).
2862. On ``landlock_enforce_domain``: record the sandboxed thread under the
287   ``domain=`` key (join to the ``create_domain`` recorded in step 1),
288   building the per-domain thread set; filter ``complete==1`` for a
289   one-event-per-operation summary.
2903. On ``landlock_deny_access_*``: look up the domain, decide whether
291   to count, alert, or ignore the denial based on custom policy.
2924. On ``landlock_free_domain``: clean up the per-domain state, log
293   final statistics.
294
295This approach requires no kernel modification and no Landlock-specific
296BPF helpers.  The Landlock IDs serve as correlation keys across events.
297
298Audit filtering equivalence
299===========================
300
301The ``logged`` field reflects the domain's log policy but not the global
302``audit_enabled`` toggle, so it does not change when audit is turned on
303or off.  When audit is enabled, ``logged==1`` selects the denials the
304domain submits to audit (audit-side rate-limiting and exclude rules may
305still drop some), so a stateless ftrace filter can select them::
306
307    # Show only denials that audit would also log:
308    echo 'logged==1' > \
309        /sys/kernel/tracing/events/landlock/landlock_deny_access_fs/filter
310
311Event reference
312===============
313
314.. kernel-doc:: include/trace/events/landlock.h
315    :doc: Landlock trace events
316
317.. kernel-doc:: include/trace/events/landlock.h
318    :internal:
319
320Additional documentation
321========================
322
323* Documentation/userspace-api/landlock.rst
324* Documentation/admin-guide/LSM/landlock.rst
325* Documentation/security/landlock.rst
326* https://landlock.io
327