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