| /* 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[PIDTYPE_TGID]); | |
| __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 */ |