From 3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 2 Sep 2026 15:31:14 -0700 Subject: treewide: refresh kmalloc_obj() conversions This is another run of the Coccinelle script for converting kmalloc() family of allocations to kmalloc_obj() via the existing rules in scripts/coccinelle/api/kmalloc_objs.cocci This catches both the set of kmalloc() uses added since the first kmalloc_obj() conversions in v7.0 and adds a large group missed in the first pass due to Coccinelle not interacting well with the cleanup.h scoped_...() family of macros[1]. I worked around this with spatch's "--macro-file" argument to a file with all the scoped_...() macros mapped to Coccinelle's YACFE_ITERATOR[2] as that was the closest viable control flow indicator I could find. Build tested allmodconfig on x86, arm64, arm, loongarch, mips, powerpc, riscv, and s390 with no new warnings. Link: https://lore.kernel.org/lkml/202609021314.8A9C0B8@keescook/ [1] Link: https://github.com/coccinelle/coccinelle/blob/master/standard.h [2] Signed-off-by: Kees Cook --- security/landlock/trace.c | 185 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 security/landlock/trace.c (limited to 'security/landlock/trace.c') diff --git a/security/landlock/trace.c b/security/landlock/trace.c new file mode 100644 index 000000000..2ea7aac8d --- /dev/null +++ b/security/landlock/trace.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock - Tracepoint helpers + * + * Copyright © 2025 Microsoft Corporation + * Copyright © 2026 Cloudflare, Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include "access.h" +#include "domain.h" +#include "fs.h" +#include "log.h" +#include "ruleset.h" +#include "trace.h" + +/* + * Generates the tracepoint definitions in this translation unit. The trace + * event header dereferences the traced objects in TP_fast_assign, so the full + * struct definitions (e.g. ruleset.h, domain.h) must be included before it. + */ +#define CREATE_TRACE_POINTS +#include + +/** + * landlock_trace_free_domain - Emit a tracepoint on domain deallocation + * + * @hierarchy: The domain's hierarchy being deallocated. + * + * Fires only for a hierarchy whose creation event was emitted, i.e. one that + * left LANDLOCK_LOG_UNCOMMITTED in landlock_restrict_self(). This keeps the + * create/free pair balanced: a hierarchy that never became observable is freed + * silently, while a domain that landlock_restrict_self() created and a + * thread-sync failure then aborted still fires free_domain, because its + * creation event already fired. + * + * Called from landlock_log_free_domain(). + */ +void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy) +{ + /* + * The log_status read is a correctness guard (keep the create/free pair + * balanced), not a cost guard, so this cold path needs no + * trace_..._enabled() check: the tracepoint is a static-branch no-op + * when disabled. The denial path guards trace_..._enabled() instead + * because it does expensive __getname()/path work before emitting. + */ + if (READ_ONCE(hierarchy->log_status) != LANDLOCK_LOG_UNCOMMITTED) + trace_landlock_free_domain(hierarchy); +} + +/** + * landlock_trace_denial - Emit a tracepoint for a denied access request + * + * @request: Detail of the user space request. + * @youngest_denied: The youngest hierarchy node that denied the access. + * @missing: The set of denied access rights. + * @same_exec: Whether the current task is the same executable that called + * landlock_restrict_self() for the denying domain, as computed + * by landlock_log_denial(). + * @logged: Whether the domain's policy selects this denial for logging, as + * computed by landlock_log_denial(). + * + * Emits the tracepoint matching @request->type when its event is enabled. + * Unlike audit, fires regardless of @logged; the value is recorded in the event + * so consumers can filter on it. + * + * Called from landlock_log_denial(). + */ +void landlock_trace_denial( + const struct landlock_request *const request, + const struct landlock_hierarchy *const youngest_denied, + const access_mask_t missing, const bool same_exec, const bool logged) +{ + switch (request->type) { + case LANDLOCK_REQUEST_FS_ACCESS: + case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: + if (trace_landlock_deny_access_fs_enabled()) { + char *buf __free(__putname) = __getname(); + struct path dentry_path; + const char *pathname; + const struct path *path = NULL; + + /* + * Selects the path from the audit data type, as + * dump_common_audit_data() does. A FS_ACCESS denial + * carries a file (hook_file_truncate) or an ioctl op + * (hook_file_ioctl) rather than a path; + * FS_CHANGE_TOPOLOGY carries a path or a bare dentry. + * Reading the wrong union member would dereference + * garbage, so every reachable type is handled here. + */ + switch (request->audit.type) { + case LSM_AUDIT_DATA_FILE: + path = &request->audit.u.file->f_path; + break; + case LSM_AUDIT_DATA_IOCTL_OP: + path = &request->audit.u.op->path; + break; + case LSM_AUDIT_DATA_DENTRY: + /* + * Build a path on the stack with the real + * dentry so TP_fast_assign can extract dev and + * ino; the mnt field is unused there. + */ + dentry_path = (struct path){ + .dentry = request->audit.u.dentry, + }; + path = &dentry_path; + break; + case LSM_AUDIT_DATA_PATH: + path = &request->audit.u.path; + break; + default: + WARN_ONCE(1, + "Unhandled Landlock FS audit type %d", + request->audit.type); + break; + } + + if (!path) + break; + + if (!buf) { + pathname = ""; + } else if (request->audit.type == + LSM_AUDIT_DATA_DENTRY) { + /* No vfsmount: render the dentry path alone. */ + pathname = dentry_path_raw( + request->audit.u.dentry, buf, PATH_MAX); + if (IS_ERR(pathname)) + pathname = + PTR_ERR(pathname) == + -ENAMETOOLONG ? + "" : + ""; + } else { + pathname = resolve_path_for_trace(path, buf); + } + + trace_landlock_deny_access_fs(youngest_denied, + same_exec, logged, + missing, path, pathname); + } + break; + case LANDLOCK_REQUEST_NET_ACCESS: + if (trace_landlock_deny_access_net_enabled()) + trace_landlock_deny_access_net( + youngest_denied, same_exec, logged, missing, + request->audit.u.net->sk, + ntohs(request->audit.u.net->sport), + ntohs(request->audit.u.net->dport)); + break; + case LANDLOCK_REQUEST_PTRACE: + if (trace_landlock_deny_ptrace_enabled()) + trace_landlock_deny_ptrace(youngest_denied, same_exec, + logged, + request->other_domain_id, + request->audit.u.tsk); + break; + case LANDLOCK_REQUEST_SCOPE_SIGNAL: + if (trace_landlock_deny_scope_signal_enabled()) + trace_landlock_deny_scope_signal( + youngest_denied, same_exec, logged, + request->other_domain_id, request->audit.u.tsk); + break; + case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: + if (trace_landlock_deny_scope_abstract_unix_socket_enabled()) + trace_landlock_deny_scope_abstract_unix_socket( + youngest_denied, same_exec, logged, + request->other_domain_id, + request->audit.u.net->sk); + break; + default: + WARN_ONCE(1, "Unhandled Landlock request type %d", + request->type); + break; + } +} -- cgit v1.3.1