| /* SPDX-License-Identifier: GPL-2.0 */ |
| /* |
| * Copyright © 2025 Microsoft Corporation |
| * Copyright © 2026 Cloudflare, Inc. |
| */ |
| |
| #undef TRACE_SYSTEM |
| #define TRACE_SYSTEM landlock |
| |
| #if !defined(_TRACE_LANDLOCK_H) || defined(TRACE_HEADER_MULTI_READ) |
| #define _TRACE_LANDLOCK_H |
| |
| #include <linux/in.h> |
| #include <linux/in6.h> |
| #include <linux/landlock.h> |
| #include <linux/socket.h> |
| #include <linux/string.h> |
| #include <linux/string_helpers.h> |
| #include <linux/tracepoint.h> |
| #include <linux/trace_seq.h> |
| #include <net/af_unix.h> |
| |
| enum landlock_request_type; |
| struct dentry; |
| struct landlock_blockers; |
| struct landlock_domain; |
| struct landlock_hierarchy; |
| struct landlock_rule; |
| struct landlock_ruleset; |
| struct path; |
| struct sock; |
| struct task_struct; |
| |
| static_assert(sizeof(access_mask_t) <= sizeof(u64)); |
| |
| TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY); |
| TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_ACCESS); |
| TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_NET_ACCESS); |
| |
| #ifdef CREATE_TRACE_POINTS |
| |
| /* About 6 KiB, leaving about 2 KiB for sibling helpers and fixed fields. */ |
| #define TRACE_UNTRUSTED_STR_OUTPUT_SIZE \ |
| (TRACE_SEQ_BUFFER_SIZE - TRACE_SEQ_BUFFER_SIZE / 4) |
| |
| /* |
| * A raw UTF-8 ellipsis (…) marks truncation and cannot collide with escaped |
| * input: ESCAPE_NAP renders every non-ASCII input byte in octal. |
| */ |
| #define TRACE_TRUNCATION_MARKER "\xe2\x80\xa6" |
| |
| /* |
| * Escapes @len bytes of an untrusted string into the trace sequence @p so it |
| * cannot inject field separators or control characters into the ftrace text |
| * output, and can be unambiguously recovered. Called from the TP_printk() of |
| * the tracepoints that expose paths and process names. @len is passed by the |
| * caller (rather than derived with strlen()) so a name that is not |
| * NUL-terminated or carries embedded NUL bytes (an abstract socket name) is |
| * escaped in full instead of being truncated at the first NUL. |
| * |
| * Strings that exceed the output limit retain the largest complete escaped |
| * prefix followed by the truncation marker. |
| * |
| * Return: a pointer into @p's buffer, or NULL if @src is NULL or the fixed |
| * output reservation is unavailable. |
| */ |
| static inline const char * |
| __trace_print_untrusted_str(struct trace_seq *p, const char *src, size_t len) |
| { |
| const unsigned int escape_flags = ESCAPE_SPACE | ESCAPE_SPECIAL | |
| ESCAPE_NAP | ESCAPE_APPEND | |
| ESCAPE_OCTAL; |
| const size_t marker_len = sizeof(TRACE_TRUNCATION_MARKER) - 1; |
| size_t buf_size, prefix_len, prefix_size; |
| int escaped_size; |
| char *buf; |
| const char *ret; |
| |
| buf_size = seq_buf_get_buf(&p->seq, &buf); |
| if (!src || buf_size < TRACE_UNTRUSTED_STR_OUTPUT_SIZE) |
| return NULL; |
| |
| ret = trace_seq_buffer_ptr(p); |
| escaped_size = string_escape_mem(src, len, buf, |
| TRACE_UNTRUSTED_STR_OUTPUT_SIZE, |
| escape_flags, " ='\"\\"); |
| if (likely(escaped_size < TRACE_UNTRUSTED_STR_OUTPUT_SIZE)) { |
| seq_buf_commit(&p->seq, escaped_size); |
| trace_seq_putc(p, 0); |
| return ret; |
| } |
| |
| prefix_len = 0; |
| prefix_size = 0; |
| while (prefix_len < len) { |
| const char *const src_char = src + prefix_len; |
| int char_size; |
| |
| char_size = string_escape_mem(src_char, 1, NULL, 0, |
| escape_flags, " ='\"\\"); |
| if (char_size > TRACE_UNTRUSTED_STR_OUTPUT_SIZE - marker_len - |
| 1 - prefix_size) |
| break; |
| prefix_size += char_size; |
| prefix_len++; |
| } |
| |
| escaped_size = string_escape_mem(src, prefix_len, buf, prefix_size, |
| escape_flags, " ='\"\\"); |
| if (WARN_ON_ONCE(escaped_size != prefix_size)) |
| return NULL; |
| memcpy(buf + prefix_size, TRACE_TRUNCATION_MARKER, marker_len); |
| seq_buf_commit(&p->seq, prefix_size + marker_len); |
| trace_seq_putc(p, 0); |
| return ret; |
| } |
| |
| /* |
| * Fills the dense per-domain-layer array layers (one access mask per layer, |
| * indexed by level - 1) from rule's sparse layer stack, keeping only the |
| * requested rights (access_request). Layers with no matching rule entry get |
| * a zero mask. Shared by the check_rule_inode and check_rule_net_port events. |
| * |
| * rule->layers is sorted by ascending level, with levels in the domain's |
| * [1, num_layers] range (see landlock_merge_ruleset()), so every entry maps |
| * to a slot. A leftover entry would be a malformed rule; the zero-filled |
| * slots keep the output and the array bounds safe regardless. |
| */ |
| static inline void |
| __trace_landlock_fill_layers(access_mask_t *const layers, |
| const size_t num_layers, |
| const struct landlock_rule *const rule, |
| const access_mask_t access_request) |
| { |
| size_t i = 0; |
| |
| for (size_t level = 1; level <= num_layers; level++) { |
| access_mask_t grants = 0; |
| |
| if (i < rule->num_layers && level == rule->layers[i].level) { |
| grants = rule->layers[i].access & access_request; |
| i++; |
| } |
| layers[level - 1] = grants; |
| } |
| |
| /* A leftover entry means an out-of-range or unsorted rule level. */ |
| WARN_ON_ONCE(i < rule->num_layers); |
| } |
| |
| /* |
| * Renders the dense per-domain-layer access array as symbolic flag names for |
| * the grants field: layers wrapped in "{}", flags within a layer joined by |
| * "|", layers separated by ",", an empty layer rendered as nothing. |
| * Open-codes the flag walk because trace_print_flags_seq() NUL-terminates per |
| * call and so cannot be chained into a single field. The shared names table |
| * covers every access right, so masked bits are always named. Returns the |
| * trace_seq position like __print_flags(). |
| */ |
| static inline const char *__trace_landlock_print_layers( |
| struct trace_seq *p, const access_mask_t *const layers, |
| const size_t num_layers, const struct trace_print_flags *const names, |
| const size_t names_size) |
| { |
| const char *const ret = trace_seq_buffer_ptr(p); |
| |
| trace_seq_putc(p, '{'); |
| for (size_t i = 0; i < num_layers; i++) { |
| access_mask_t mask = layers[i]; |
| bool first = true; |
| |
| if (i) |
| trace_seq_putc(p, ','); |
| for (size_t j = 0; mask && j < names_size; j++) { |
| if ((mask & names[j].mask) != names[j].mask) |
| continue; |
| if (!first) |
| trace_seq_putc(p, '|'); |
| trace_seq_puts(p, names[j].name); |
| mask &= ~names[j].mask; |
| first = false; |
| } |
| } |
| trace_seq_putc(p, '}'); |
| trace_seq_putc(p, 0); |
| return ret; |
| } |
| |
| #endif /* CREATE_TRACE_POINTS */ |
| |
| /* clang-format off */ |
| |
| /* Maps a shared _LANDLOCK_*_NAMES entry to a __print_flags() pair. */ |
| #define _LANDLOCK_NAME_ENTRY(mask, name) { mask, name } |
| |
| #define _LANDLOCK_FS_BLOCKER_TYPE_NAMES \ |
| { LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY, "change_topology" } |
| |
| /** |
| * DOC: Landlock trace events |
| * |
| * These guarantees and constraints hold for every Landlock tracepoint. |
| * A new tracepoint must uphold them, and an eBPF consumer can rely on |
| * them. |
| * |
| * Decision context |
| * ~~~~~~~~~~~~~~~~ |
| * |
| * A denial event identifies the domain whose policy denied the request, the |
| * Landlock operation and policy object that were checked, and the blocker or |
| * domain relationship responsible for the denial. When tracing starts with |
| * sandbox construction, ruleset and domain events provide the policy history |
| * needed to interpret these identifiers. The denying domain is the subject |
| * that enforced the policy, not necessarily current. Generic tracepoints can |
| * provide additional operational context. |
| * |
| * Lifecycle consistency |
| * ~~~~~~~~~~~~~~~~~~~~~~ |
| * |
| * Lifecycle emission is balanced: a creation event always has a matching |
| * deallocation event and vice versa. A consumer that observes an object's |
| * complete lifetime can model it from this pair; one that attaches late or |
| * loses exported records must reconcile incomplete state. A creation event |
| * fires while the object is still private to the calling thread |
| * (landlock_create_ruleset fires before the ruleset's file descriptor is |
| * installed, so it cannot race a concurrent :manpage:`close(2)`); if fd |
| * installation later fails and the ruleset is freed, free_ruleset still |
| * fires, keeping the pair balanced. The domain pair (create_domain and |
| * free_domain) is balanced the same way: create_domain fires when the |
| * domain is created (under the ruleset lock, before thread-sync), and |
| * free_domain fires when it is freed. A rare thread-sync failure aborts |
| * the just-created domain, which then emits both events (its creation, then |
| * an immediate free). Denial events fire only for denials that actually |
| * happen. |
| * |
| * Pointer access |
| * ~~~~~~~~~~~~~~ |
| * |
| * All pointer arguments in TP_PROTO are guaranteed non-NULL by the |
| * caller, but pointers reached through them may still be NULL (e.g., |
| * hierarchy->parent at a root domain) and must be checked. eBPF programs |
| * read these pointers via BTF for richer introspection than the |
| * TP_STRUCT__entry fields, which serve TP_printk display only. |
| * |
| * Mutable object pointers are passed while the caller holds the object's |
| * lock, so TP_fast_assign and a BTF reader see the exact object the event |
| * reports, a snapshot no concurrent writer can change: add_rule holds the |
| * modified ruleset's lock, and create_domain holds the ruleset lock across |
| * the emission (before the thread-sync wait) so the inspected ruleset is |
| * the one merged into the domain. Objects immutable at the emission site |
| * (a domain after creation, a hierarchy at its last reference) need no |
| * lock. A few values that no held lock protects are a best-effort |
| * lockless snapshot instead: a task's comm, and the deny_access_net struct |
| * sock (whose network hook holds no socket lock), matching how the sched |
| * and signal trace events sample comm. |
| * |
| * Field encoding |
| * ~~~~~~~~~~~~~~ |
| * |
| * Fields that mirror the Landlock UAPI preserve their widths and endianness |
| * (e.g. network ports are u64 in host endianness, like |
| * landlock_net_port_attr.port). Per-event details, such as where a value |
| * is byte-swapped, live in the field's own kdoc. |
| * |
| * Rule-check fields |
| * ~~~~~~~~~~~~~~~~~ |
| * |
| * The check_rule events fire during an access check, once per matching |
| * rule, before the final allow-or-deny verdict. They share domain (the |
| * enforcing domain being evaluated), access_request (the access mask being |
| * checked), and rule (the matching rule, with per-layer access masks). |
| * |
| * Denial fields |
| * ~~~~~~~~~~~~~ |
| * |
| * Every denial event shares three fields. domain is the ID of the |
| * innermost domain that blocked the access. same_exec tells whether the |
| * current task is the same executable that entered that domain. logged is |
| * the domain's audit-logging decision for this denial (its log_status is |
| * enabled and the per-execution flag selected by same_exec is set); a |
| * stateless ftrace filter can select the denials the domain submits to |
| * audit with logged==1, without reconstructing it from the per-execution |
| * log flags. Denial events order their fields as domain, same_exec, |
| * logged, then blockers (deny_access events only), then the type-specific |
| * object fields, then any variable-length field. |
| * |
| * Relational referents |
| * ~~~~~~~~~~~~~~~~~~~~~ |
| * |
| * A scope or ptrace verdict compares two domains, so the other party's |
| * domain is part of the decision context. It is exposed as a scalar |
| * domain ID (0 when that party is unsandboxed): target_domain (signal), |
| * peer_domain (abstract unix socket), tracee_domain (ptrace). With both |
| * IDs in the stream, a consumer that tracked domain creation can relate |
| * the two parties without kernel-internal state. The ID is a scalar |
| * snapshot, not a live domain pointer that could dangle: an optional |
| * relational referent is a scalar (0 sentinel), not a nullable pointer. |
| * Nonzero IDs are unique within one boot. For ptrace, same_exec instead |
| * describes the tracer, even for |
| * PTRACE_TRACEME, and may differ from the current task. |
| * |
| * Blocker fields |
| * ~~~~~~~~~~~~~~ |
| * |
| * The filesystem and network blocker arguments identify the request type |
| * and carry its final missing access subset when applicable. The type |
| * determines how to interpret the access value. |
| */ |
| |
| /* |
| * Prints a per-layer access mask array (the dynamic array @array) as symbolic |
| * flag names using the shared @flag_names list (a _LANDLOCK_*_NAMES macro). |
| * Stays outside CREATE_TRACE_POINTS: TP_printk is expanded in the print-output |
| * pass where that macro is undefined. |
| */ |
| #define __print_landlock_layers(array, flag_names...) \ |
| ({ \ |
| static const struct trace_print_flags __layer_names[] = { \ |
| flag_names \ |
| }; \ |
| __trace_landlock_print_layers( \ |
| p, __get_dynamic_array(array), \ |
| __get_dynamic_array_len(array) / \ |
| sizeof(access_mask_t), \ |
| __layer_names, ARRAY_SIZE(__layer_names)); \ |
| }) |
| |
| /** |
| * landlock_create_ruleset - New ruleset created |
| * |
| * @ruleset: Newly created ruleset (never NULL); not yet shared via an fd, |
| * so no lock is needed. |
| * |
| * Emitted by sys_landlock_create_ruleset() while the new ruleset is still |
| * private to the calling thread, before its file descriptor is installed, |
| * so it cannot race a concurrent :manpage:`close(2)`. Balanced by a |
| * matching landlock_free_ruleset event. |
| */ |
| TRACE_EVENT(landlock_create_ruleset, |
| |
| TP_PROTO(const struct landlock_ruleset *ruleset), |
| |
| TP_ARGS(ruleset), |
| |
| TP_STRUCT__entry( |
| __field( u64, ruleset_id ) |
| __field( u64, ruleset_version ) |
| __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->ruleset_version = ruleset->version; |
| __entry->handled_fs = ruleset->handled_masks.fs; |
| __entry->handled_net = ruleset->handled_masks.net; |
| __entry->scoped = ruleset->handled_masks.scope; |
| ), |
| |
| TP_printk("ruleset=%llx.%llu handled_fs=%s handled_net=%s scoped=%s", |
| __entry->ruleset_id, __entry->ruleset_version, |
| __print_flags(__entry->handled_fs, "|", _LANDLOCK_ACCESS_FS_NAMES), |
| __print_flags(__entry->handled_net, "|", _LANDLOCK_ACCESS_NET_NAMES), |
| __print_flags(__entry->scoped, "|", _LANDLOCK_SCOPE_NAMES)) |
| ); |
| |
| /** |
| * landlock_free_ruleset - Ruleset freed |
| * |
| * @ruleset: Ruleset being freed (never NULL); at its last reference, so no |
| * lock is needed. |
| * |
| * Emitted when a ruleset's last reference is dropped (typically when |
| * the creating process closes the ruleset file descriptor). Fires even |
| * when file-descriptor installation failed after creation, keeping the |
| * create/free pair balanced. |
| */ |
| TRACE_EVENT(landlock_free_ruleset, |
| |
| TP_PROTO(const struct landlock_ruleset *ruleset), |
| |
| TP_ARGS(ruleset), |
| |
| TP_STRUCT__entry( |
| __field( u64, ruleset_id ) |
| __field( u64, ruleset_version ) |
| ), |
| |
| TP_fast_assign( |
| __entry->ruleset_id = ruleset->id; |
| __entry->ruleset_version = ruleset->version; |
| ), |
| |
| TP_printk("ruleset=%llx.%llu", |
| __entry->ruleset_id, __entry->ruleset_version) |
| ); |
| |
| /** |
| * landlock_add_rule_path_beneath - Path-beneath rule added to a ruleset |
| * |
| * @ruleset: Source ruleset (never NULL). |
| * @flags: Complete validated landlock_add_rule_flags value supplied by this |
| * successful call, not the rule's accumulated quiet state. |
| * @access_rights: Canonical per-call access mask passed to |
| * landlock_insert_rule() after normalization, not the raw |
| * sys_landlock_add_rule() argument or accumulated rule. |
| * @path: Filesystem path for the rule (never NULL). |
| * @pathname: Resolved absolute path string (never NULL; error placeholder |
| * on resolution failure). |
| * |
| * Emitted by sys_landlock_add_rule() under the modified ruleset's lock, so |
| * the reported ruleset is a stable snapshot that no concurrent writer can |
| * change. |
| */ |
| TRACE_EVENT(landlock_add_rule_path_beneath, |
| |
| TP_PROTO(const struct landlock_ruleset *ruleset, u32 flags, |
| u64 access_rights, const struct path *path, |
| const char *pathname), |
| |
| TP_ARGS(ruleset, flags, access_rights, path, pathname), |
| |
| TP_STRUCT__entry( |
| __field( u64, ruleset_id ) |
| __field( u64, ruleset_version ) |
| __field( access_mask_t, access_rights ) |
| __field( dev_t, dev ) |
| __field( ino_t, ino ) |
| __string( pathname, pathname ) |
| ), |
| |
| TP_fast_assign( |
| lockdep_assert_held(&ruleset->lock); |
| __entry->ruleset_id = ruleset->id; |
| __entry->ruleset_version = ruleset->version; |
| __entry->access_rights = access_rights; |
| __entry->dev = path->dentry->d_sb->s_dev; |
| /* |
| * The inode number may not be the user-visible one, |
| * but it will be the same used by audit. |
| */ |
| __entry->ino = d_backing_inode(path->dentry)->i_ino; |
| __assign_str(pathname); |
| ), |
| |
| TP_printk("ruleset=%llx.%llu access_rights=%s dev=%u:%u ino=%lu path=%s", |
| __entry->ruleset_id, __entry->ruleset_version, |
| __print_flags(__entry->access_rights, "|", _LANDLOCK_ACCESS_FS_NAMES), |
| MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, |
| __trace_print_untrusted_str(p, __get_str(pathname), |
| __get_dynamic_array_len(pathname) - 1)) |
| ); |
| |
| /** |
| * landlock_add_rule_net_port - Network-port rule added to a ruleset |
| * |
| * @ruleset: Source ruleset (never NULL). |
| * @flags: Complete validated landlock_add_rule_flags value supplied by this |
| * successful call, not the rule's accumulated quiet state. |
| * @access_rights: Canonical per-call access mask passed to |
| * landlock_insert_rule() after normalization, not the raw |
| * sys_landlock_add_rule() argument or accumulated rule. |
| * @port: Network port in host endianness, forwarded directly from |
| * &landlock_net_port_attr.port. |
| * |
| * Emitted by sys_landlock_add_rule() under the modified ruleset's lock, so |
| * the reported ruleset is a stable snapshot that no concurrent writer can |
| * change. |
| */ |
| TRACE_EVENT(landlock_add_rule_net_port, |
| |
| TP_PROTO(const struct landlock_ruleset *ruleset, u32 flags, |
| u64 access_rights, u64 port), |
| |
| TP_ARGS(ruleset, flags, access_rights, port), |
| |
| TP_STRUCT__entry( |
| __field( u64, ruleset_id ) |
| __field( u64, ruleset_version ) |
| __field( access_mask_t, access_rights ) |
| __field( u64, port ) |
| ), |
| |
| TP_fast_assign( |
| lockdep_assert_held(&ruleset->lock); |
| __entry->ruleset_id = ruleset->id; |
| __entry->ruleset_version = ruleset->version; |
| __entry->access_rights = access_rights; |
| __entry->port = port; |
| ), |
| |
| TP_printk("ruleset=%llx.%llu access_rights=%s port=%llu", |
| __entry->ruleset_id, __entry->ruleset_version, |
| __print_flags(__entry->access_rights, "|", _LANDLOCK_ACCESS_NET_NAMES), |
| __entry->port) |
| ); |
| |
| /** |
| * landlock_create_domain - New domain created |
| * |
| * @domain: Newly created domain (never NULL, immutable after creation). |
| * @domain->hierarchy->id is its unique ID, shared with the |
| * landlock_enforce_domain and landlock_free_domain events; |
| * @domain->hierarchy->details holds the requesting process. |
| * @ruleset: Source ruleset frozen into the domain (never NULL). The |
| * ruleset lock is held across the emission, so a BPF program |
| * reading it via BTF sees the exact merged ruleset; |
| * @ruleset->id / @ruleset->version identify it. |
| * |
| * Emitted by sys_landlock_restrict_self() once, in the requesting |
| * thread's context, right after the merge and before thread-sync. The |
| * flags-only path (ruleset_fd == -1) creates no domain and does not |
| * emit this event. Paired with the per-thread landlock_enforce_domain |
| * (join on @domain->hierarchy->id) and balanced by a matching |
| * landlock_free_domain event. |
| */ |
| TRACE_EVENT(landlock_create_domain, |
| |
| TP_PROTO(const struct landlock_domain *domain, |
| const struct landlock_ruleset *ruleset), |
| |
| TP_ARGS(domain, ruleset), |
| |
| TP_STRUCT__entry( |
| __field( u64, domain_id ) |
| __field( u64, parent_id ) |
| __field( u64, ruleset_id ) |
| __field( u64, ruleset_version ) |
| ), |
| |
| TP_fast_assign( |
| lockdep_assert_held(&ruleset->lock); |
| __entry->domain_id = domain->hierarchy->id; |
| __entry->parent_id = domain->hierarchy->parent ? |
| domain->hierarchy->parent->id : 0; |
| __entry->ruleset_id = ruleset->id; |
| __entry->ruleset_version = ruleset->version; |
| ), |
| |
| TP_printk("domain=%llx parent=%llx ruleset=%llx.%llu", |
| __entry->domain_id, __entry->parent_id, |
| __entry->ruleset_id, __entry->ruleset_version) |
| ); |
| |
| /** |
| * landlock_enforce_domain - Domain enforced on a thread |
| * |
| * @domain: Domain now enforced on the current thread (never NULL, |
| * immutable; read locklessly). Correlate to |
| * landlock_create_domain via @domain->hierarchy->id for the |
| * source ruleset and requesting thread, or read |
| * @domain->hierarchy->details for the requesting process. |
| * @complete: Set on the single event that concludes the operation, after |
| * all its other enforcements; filter on it for one event per |
| * operation. |
| * @process_wide: The enforcement covers every eligible (non-exiting) |
| * thread of the process: set when the caller used |
| * %LANDLOCK_RESTRICT_SELF_TSYNC or the process is |
| * single-threaded. A lone thread whose group still |
| * holds a zombie leader is not counted single-threaded, |
| * so process_wide == 0 never proves the opposite. |
| * @no_new_privs: The enforcing thread's no_new_privs state at |
| * enforcement time: 1 if set (by a prior |
| * :manpage:`prctl(2)` %PR_SET_NO_NEW_PRIVS or by |
| * %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS), 0 if the domain |
| * was enforced with %CAP_SYS_ADMIN instead. |
| * |
| * Emitted for each thread sys_landlock_restrict_self() enforces the |
| * domain on, in that thread's own context, right after its |
| * commit_creds(), so it fires only once the thread is irreversibly |
| * enforcing the domain (aborted operations emit none). Not |
| * balanced; every enforcement falls between the domain's |
| * landlock_create_domain and landlock_free_domain events. |
| * |
| * @complete == 1 && @process_wide == 1 means the whole process is |
| * sandboxed by @domain, durably (Landlock domains are monotonic and |
| * inherited on :manpage:`clone(2)`). |
| */ |
| TRACE_EVENT(landlock_enforce_domain, |
| |
| TP_PROTO(const struct landlock_domain *domain, bool complete, |
| bool process_wide, bool no_new_privs), |
| |
| TP_ARGS(domain, complete, process_wide, no_new_privs), |
| |
| TP_STRUCT__entry( |
| __field( u64, domain_id ) |
| __field( bool, complete ) |
| __field( bool, process_wide ) |
| __field( bool, no_new_privs ) |
| ), |
| |
| TP_fast_assign( |
| __entry->domain_id = domain->hierarchy->id; |
| __entry->complete = complete; |
| __entry->process_wide = process_wide; |
| __entry->no_new_privs = no_new_privs; |
| ), |
| |
| TP_printk("domain=%llx complete=%d process_wide=%d no_new_privs=%d", |
| __entry->domain_id, __entry->complete, __entry->process_wide, |
| __entry->no_new_privs) |
| ); |
| |
| /** |
| * landlock_free_domain - Domain freed |
| * |
| * @hierarchy: Hierarchy node being freed (never NULL). |
| * |
| * Emitted when the hierarchy node's last reference is dropped: its |
| * refcount reaches zero after all child domains have released their |
| * parent reference. A committed domain is |
| * freed from a kworker via landlock_put_domain_deferred() (the credential |
| * free path runs in RCU context, where sleeping is forbidden), so the |
| * current task is not the sandboxed task that triggered the free. Balanced |
| * by a matching landlock_create_domain event. |
| */ |
| TRACE_EVENT(landlock_free_domain, |
| |
| TP_PROTO(const struct landlock_hierarchy *hierarchy), |
| |
| TP_ARGS(hierarchy), |
| |
| TP_STRUCT__entry( |
| __field( u64, domain_id ) |
| __field( u64, denials ) |
| ), |
| |
| TP_fast_assign( |
| __entry->domain_id = hierarchy->id; |
| __entry->denials = atomic64_read(&hierarchy->num_denials); |
| ), |
| |
| TP_printk("domain=%llx denials=%llu", |
| __entry->domain_id, __entry->denials) |
| ); |
| |
| /** |
| * landlock_check_rule_inode - Inode rule evaluated during access check |
| * |
| * @domain: Enforcing domain (never NULL). |
| * @rule: Matching rule with per-layer access masks (never NULL). |
| * @access_request: Access mask evaluated against the rule (the domain's |
| * handled mask during rename/link double-checks). |
| * @dentry: Filesystem dentry being checked (never NULL). |
| * |
| * Emitted for each rule that matches during a filesystem access check. |
| * The grants array shows the requested rights the rule grants at each |
| * domain layer. See Documentation/trace/events-landlock.rst for how to |
| * interpret it. |
| */ |
| TRACE_EVENT(landlock_check_rule_inode, |
| |
| TP_PROTO(const struct landlock_domain *domain, |
| const struct landlock_rule *rule, |
| u64 access_request, const struct dentry *dentry), |
| |
| TP_ARGS(domain, rule, access_request, dentry), |
| |
| TP_STRUCT__entry( |
| __field( u64, domain_id ) |
| __field( access_mask_t, access_request ) |
| __field( dev_t, dev ) |
| __field( ino_t, ino ) |
| __dynamic_array(access_mask_t, grants, |
| domain->num_layers) |
| ), |
| |
| TP_fast_assign( |
| __entry->domain_id = domain->hierarchy->id; |
| __entry->access_request = access_request; |
| __entry->dev = dentry->d_sb->s_dev; |
| __entry->ino = d_backing_inode(dentry)->i_ino; |
| |
| __trace_landlock_fill_layers(__get_dynamic_array(grants), |
| __get_dynamic_array_len(grants) / |
| sizeof(access_mask_t), |
| rule, |
| (access_mask_t)access_request); |
| ), |
| |
| TP_printk("domain=%llx access_request=%s dev=%u:%u ino=%lu grants=%s", |
| __entry->domain_id, |
| __print_flags(__entry->access_request, "|", _LANDLOCK_ACCESS_FS_NAMES), |
| MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, |
| __print_landlock_layers(grants, _LANDLOCK_ACCESS_FS_NAMES)) |
| ); |
| |
| /** |
| * landlock_check_rule_net_port - Network port rule evaluated |
| * |
| * @domain: Enforcing domain (never NULL). |
| * @rule: Matching rule with per-layer access masks (never NULL). |
| * @access_request: Access mask being requested. |
| * @port: Network port being checked (host endianness). |
| * |
| * Emitted for each rule that matches during a network access check. The |
| * grants array shows the requested rights the rule grants at each domain |
| * layer. See Documentation/trace/events-landlock.rst for how to |
| * interpret it. |
| */ |
| TRACE_EVENT(landlock_check_rule_net_port, |
| |
| TP_PROTO(const struct landlock_domain *domain, |
| const struct landlock_rule *rule, |
| u64 access_request, u64 port), |
| |
| TP_ARGS(domain, rule, access_request, port), |
| |
| TP_STRUCT__entry( |
| __field( u64, domain_id ) |
| __field( access_mask_t, access_request ) |
| __field( u64, port ) |
| __dynamic_array(access_mask_t, grants, |
| domain->num_layers) |
| ), |
| |
| TP_fast_assign( |
| __entry->domain_id = domain->hierarchy->id; |
| __entry->access_request = access_request; |
| __entry->port = port; |
| |
| __trace_landlock_fill_layers(__get_dynamic_array(grants), |
| __get_dynamic_array_len(grants) / |
| sizeof(access_mask_t), |
| rule, |
| (access_mask_t)access_request); |
| ), |
| |
| TP_printk("domain=%llx access_request=%s port=%llu grants=%s", |
| __entry->domain_id, |
| __print_flags(__entry->access_request, "|", _LANDLOCK_ACCESS_NET_NAMES), |
| __entry->port, |
| __print_landlock_layers(grants, _LANDLOCK_ACCESS_NET_NAMES)) |
| ); |
| |
| /** |
| * landlock_deny_access_fs - Filesystem access denied |
| * |
| * @hierarchy: Denying domain's hierarchy node (never NULL); its id is the |
| * domain field. |
| * @same_exec: Whether the current task entered the denying domain itself. |
| * @logged: The domain's audit-logging decision for this denial. |
| * @blockers: Request type and final missing access subset (never NULL). |
| * @path: Filesystem path that was denied (never NULL). |
| * @pathname: Resolved path string (never NULL; an error placeholder on |
| * resolution failure). |
| * |
| * Emitted when a Landlock domain denies a filesystem access. |
| */ |
| TRACE_EVENT(landlock_deny_access_fs, |
| |
| TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, |
| bool logged, const struct landlock_blockers *blockers, |
| const struct path *path, const char *pathname), |
| |
| TP_ARGS(hierarchy, same_exec, logged, blockers, path, pathname), |
| |
| TP_STRUCT__entry( |
| __field( u64, domain_id ) |
| __field( bool, same_exec ) |
| __field( bool, logged ) |
| __field( enum landlock_request_type, blockers_type ) |
| __field( access_mask_t, blockers_access ) |
| __field( dev_t, dev ) |
| __field( ino_t, ino ) |
| __string( pathname, pathname ) |
| ), |
| |
| TP_fast_assign( |
| const struct inode *inode = d_backing_inode(path->dentry); |
| |
| __entry->domain_id = hierarchy->id; |
| __entry->same_exec = same_exec; |
| __entry->logged = logged; |
| __entry->blockers_type = blockers->type; |
| __entry->blockers_access = blockers->access; |
| __entry->dev = path->dentry->d_sb->s_dev; |
| /* |
| * A negative dentry has no backing inode, so mirror the |
| * guard in dump_common_audit_data() and report inode 0. |
| */ |
| __entry->ino = inode ? inode->i_ino : 0; |
| __assign_str(pathname); |
| ), |
| |
| TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s dev=%u:%u ino=%lu path=%s", |
| __entry->domain_id, __entry->same_exec, __entry->logged, |
| __entry->blockers_type == LANDLOCK_REQUEST_FS_ACCESS ? |
| __print_flags(__entry->blockers_access, "|", _LANDLOCK_ACCESS_FS_NAMES) : |
| __print_symbolic(__entry->blockers_type, |
| _LANDLOCK_FS_BLOCKER_TYPE_NAMES), |
| MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, |
| __trace_print_untrusted_str(p, __get_str(pathname), |
| __get_dynamic_array_len(pathname) - 1)) |
| ); |
| |
| static_assert(offsetof(struct sockaddr_in, sin_port) == |
| offsetof(struct sockaddr_in6, sin6_port)); |
| static_assert(sizeof_field(struct sockaddr_in, sin_port) == |
| sizeof_field(struct sockaddr_in6, sin6_port)); |
| |
| /** |
| * landlock_deny_access_net - Network access denied |
| * |
| * @hierarchy: Denying domain's hierarchy node (never NULL); its id is the |
| * domain field. |
| * @same_exec: Whether the current task entered the denying domain itself. |
| * @logged: The domain's audit-logging decision for this denial. |
| * @blockers: Request type and final missing access subset (never NULL). |
| * @sk: Socket object (never NULL), read without a socket lock, so its fields |
| * are a best-effort snapshot. |
| * @socket_family: Socket-family snapshot used by the verdict. |
| * @address: Authoritative address checked by the verdict (never NULL). |
| * The producer copies @addrlen bytes from the checked address and |
| * zeroes the remaining storage before emission. The |
| * &sockaddr_in.sin_port or &sockaddr_in6.sin6_port member, when |
| * present, remains in network endianness. |
| * @addrlen: Validated signed length of @address. |
| * |
| * Emitted when a Landlock domain denies a network operation. The blocker |
| * identifies whether the address is a bind or connect/send policy object. |
| * The flattened port field is converted from the checked address to host |
| * endianness, or is -1 when no port was checked. Zero is a valid checked |
| * port. |
| */ |
| TRACE_EVENT(landlock_deny_access_net, |
| |
| TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, |
| bool logged, const struct landlock_blockers *blockers, |
| const struct sock *sk, u16 socket_family, |
| const struct sockaddr_storage *address, int addrlen), |
| |
| TP_ARGS(hierarchy, same_exec, logged, blockers, sk, socket_family, |
| address, addrlen), |
| |
| TP_STRUCT__entry( |
| __field( u64, domain_id ) |
| __field( bool, same_exec ) |
| __field( bool, logged ) |
| __field( enum landlock_request_type, blockers_type ) |
| __field( access_mask_t, blockers_access ) |
| __field( s64, port ) |
| ), |
| |
| TP_fast_assign( |
| const struct sockaddr *const addr = |
| (const struct sockaddr *)address; |
| const bool has_port = |
| addrlen >= (int)offsetofend(struct sockaddr_in, sin_port) && |
| (addr->sa_family == AF_INET || |
| addr->sa_family == AF_INET6 || |
| (addr->sa_family == AF_UNSPEC && |
| socket_family == AF_INET)); |
| |
| __entry->domain_id = hierarchy->id; |
| __entry->same_exec = same_exec; |
| __entry->logged = logged; |
| __entry->blockers_type = blockers->type; |
| __entry->blockers_access = blockers->access; |
| __entry->port = |
| has_port ? |
| ntohs(((const struct sockaddr_in *)addr)->sin_port) : |
| -1; |
| ), |
| |
| TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s port=%lld", |
| __entry->domain_id, __entry->same_exec, __entry->logged, |
| __entry->blockers_type == LANDLOCK_REQUEST_NET_ACCESS ? |
| __print_flags(__entry->blockers_access, "|", _LANDLOCK_ACCESS_NET_NAMES) : |
| "unknown", |
| __entry->port) |
| ); |
| |
| /** |
| * landlock_deny_ptrace - Ptrace access denied by a Landlock domain |
| * |
| * @hierarchy: Denying domain's hierarchy node (never NULL); its id is the |
| * domain field. |
| * @same_exec: Whether the tracer entered the denying domain itself. |
| * @logged: The domain's audit-logging decision for this denial. |
| * @tracee_domain_id: The tracee's Landlock domain ID, or 0 if the tracee |
| * is unsandboxed. |
| * @tracee: The target task ptrace acted on (never NULL). tracee_pid is |
| * the init-namespace TGID (like audit's opid). |
| * @tracer: The tracer or proposed tracer (never NULL); for PTRACE_TRACEME |
| * this is the parent, not the syscall caller. |
| * |
| * Emitted when a Landlock domain denies a ptrace operation. |
| */ |
| TRACE_EVENT(landlock_deny_ptrace, |
| |
| TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, |
| bool logged, u64 tracee_domain_id, |
| const struct task_struct *tracee, |
| const struct task_struct *tracer), |
| |
| TP_ARGS(hierarchy, same_exec, logged, tracee_domain_id, tracee, tracer), |
| |
| TP_STRUCT__entry( |
| __field( u64, domain_id ) |
| __field( bool, same_exec ) |
| __field( bool, logged ) |
| __field( u64, tracee_domain_id) |
| __field( pid_t, tracee_pid ) |
| __string( tracee_comm, tracee->comm ) |
| ), |
| |
| TP_fast_assign( |
| __entry->domain_id = hierarchy->id; |
| __entry->same_exec = same_exec; |
| __entry->logged = logged; |
| __entry->tracee_domain_id = tracee_domain_id; |
| __entry->tracee_pid = task_tgid_nr((struct task_struct *)tracee); |
| __assign_str(tracee_comm); |
| ), |
| |
| TP_printk("domain=%llx same_exec=%d logged=%d tracee_domain=%llx tracee_pid=%d tracee_comm=%s", |
| __entry->domain_id, __entry->same_exec, __entry->logged, |
| __entry->tracee_domain_id, __entry->tracee_pid, |
| __trace_print_untrusted_str(p, __get_str(tracee_comm), |
| __get_dynamic_array_len(tracee_comm) - 1)) |
| ); |
| |
| /** |
| * landlock_deny_scope_signal - Signal delivery denied by |
| * LANDLOCK_SCOPE_SIGNAL |
| * |
| * @hierarchy: Denying domain's hierarchy node (never NULL); its id is the |
| * domain field. |
| * @same_exec: Whether the policy subject entered the denying domain itself. |
| * @logged: The domain's audit-logging decision for this denial. |
| * @target_domain_id: The target's Landlock domain ID, or 0 if the target |
| * is unsandboxed. |
| * @target: The task the signal was aimed at (never NULL). target_pid is |
| * the init-namespace TGID (like audit's opid). |
| * @signal: The signal selected by the denied check. Zero is a permission |
| * probe, not an absent value. |
| * |
| * Emitted when a Landlock domain denies signal delivery to a scoped-out |
| * target. |
| */ |
| TRACE_EVENT(landlock_deny_scope_signal, |
| |
| TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, |
| bool logged, u64 target_domain_id, |
| const struct task_struct *target, int signal), |
| |
| TP_ARGS(hierarchy, same_exec, logged, target_domain_id, target, signal), |
| |
| TP_STRUCT__entry( |
| __field( u64, domain_id ) |
| __field( bool, same_exec ) |
| __field( bool, logged ) |
| __field( u64, target_domain_id) |
| __field( pid_t, target_pid ) |
| __string( target_comm, target->comm ) |
| ), |
| |
| TP_fast_assign( |
| __entry->domain_id = hierarchy->id; |
| __entry->same_exec = same_exec; |
| __entry->logged = logged; |
| __entry->target_domain_id = target_domain_id; |
| __entry->target_pid = task_tgid_nr((struct task_struct *)target); |
| __assign_str(target_comm); |
| ), |
| |
| TP_printk("domain=%llx same_exec=%d logged=%d target_domain=%llx target_pid=%d target_comm=%s", |
| __entry->domain_id, __entry->same_exec, __entry->logged, |
| __entry->target_domain_id, __entry->target_pid, |
| __trace_print_untrusted_str(p, __get_str(target_comm), |
| __get_dynamic_array_len(target_comm) - 1)) |
| ); |
| |
| /** |
| * landlock_deny_scope_abstract_unix_socket - Abstract unix socket access |
| * denied by LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
| * |
| * @hierarchy: Denying domain's hierarchy node (never NULL); its id is the |
| * domain field. |
| * @same_exec: Whether the current task entered the denying domain itself. |
| * @logged: The domain's audit-logging decision for this denial. |
| * @peer_domain_id: The peer's Landlock domain ID, or 0 if the peer is |
| * unsandboxed. |
| * @peer: Peer socket (never NULL). peer_pid is best-effort: it is 0 for |
| * a datagram peer (no SO_PEERCRED), so sun_path is the reliable |
| * peer identifier. |
| * |
| * Emitted when a Landlock domain denies access to a scoped-out abstract |
| * unix socket. |
| */ |
| TRACE_EVENT(landlock_deny_scope_abstract_unix_socket, |
| |
| TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, |
| bool logged, u64 peer_domain_id, const struct sock *peer), |
| |
| TP_ARGS(hierarchy, same_exec, logged, peer_domain_id, peer), |
| |
| TP_STRUCT__entry( |
| __field( u64, domain_id ) |
| __field( bool, same_exec ) |
| __field( bool, logged ) |
| __field( u64, peer_domain_id ) |
| __field( pid_t, peer_pid ) |
| /* |
| * Abstract socket names are untrusted binary data from |
| * user space. Use __string_len because abstract names |
| * are not NUL-terminated; their length is determined by |
| * addr->len. unix_sk(peer)->addr is stable here because |
| * the caller (hook_unix_stream_connect or |
| * hook_unix_may_send) holds unix_state_lock(peer). |
| */ |
| __string_len( sun_path, |
| unix_sk(peer)->addr ? |
| unix_sk(peer)->addr->name->sun_path + 1 : |
| "", |
| unix_sk(peer)->addr ? |
| unix_sk(peer)->addr->len - |
| offsetof(struct sockaddr_un, |
| sun_path) - 1 : |
| 0) |
| ), |
| |
| TP_fast_assign( |
| struct pid *peer_pid; |
| |
| lockdep_assert_held(&unix_sk(peer)->lock); |
| __entry->domain_id = hierarchy->id; |
| __entry->same_exec = same_exec; |
| __entry->logged = logged; |
| __entry->peer_domain_id = peer_domain_id; |
| /* |
| * Best-effort (0 for a datagram peer). The caller holds the |
| * peer's AF_UNIX state lock, serializing published peercred |
| * updates. The peer socket keeps a reference to sk_peer_pid |
| * through pid_nr(); sun_path is the reliable identifier. |
| */ |
| peer_pid = READ_ONCE(peer->sk_peer_pid); |
| __entry->peer_pid = peer_pid ? pid_nr(peer_pid) : 0; |
| __assign_str(sun_path); |
| ), |
| |
| TP_printk("domain=%llx same_exec=%d logged=%d peer_domain=%llx peer_pid=%d sun_path=%s", |
| __entry->domain_id, __entry->same_exec, __entry->logged, |
| __entry->peer_domain_id, __entry->peer_pid, |
| __trace_print_untrusted_str(p, __get_str(sun_path), |
| __get_dynamic_array_len(sun_path) - 1)) |
| ); |
| |
| #undef _LANDLOCK_FS_BLOCKER_TYPE_NAMES |
| #undef _LANDLOCK_NAME_ENTRY |
| |
| #endif /* _TRACE_LANDLOCK_H */ |
| |
| /* This part must be outside protection */ |
| #include <trace/define_trace.h> |
| |
| /* clang-format on */ |