1b4540a72SMickaël Salaün // SPDX-License-Identifier: GPL-2.0-only 2b4540a72SMickaël Salaün /* 3b4540a72SMickaël Salaün * Landlock - Tracepoint helpers 4b4540a72SMickaël Salaün * 5b4540a72SMickaël Salaün * Copyright © 2025 Microsoft Corporation 6b4540a72SMickaël Salaün * Copyright © 2026 Cloudflare, Inc. 7b4540a72SMickaël Salaün */ 8b4540a72SMickaël Salaün 9*01ce260fSMickaël Salaün #include <linux/cleanup.h> 10*01ce260fSMickaël Salaün #include <linux/dcache.h> 11*01ce260fSMickaël Salaün #include <linux/err.h> 12*01ce260fSMickaël Salaün #include <linux/fs.h> 13*01ce260fSMickaël Salaün #include <linux/lsm_audit.h> 14*01ce260fSMickaël Salaün #include <net/sock.h> 15*01ce260fSMickaël Salaün 16*01ce260fSMickaël Salaün #include "access.h" 1767567f03SMickaël Salaün #include "domain.h" 18*01ce260fSMickaël Salaün #include "fs.h" 19*01ce260fSMickaël Salaün #include "log.h" 20b4540a72SMickaël Salaün #include "ruleset.h" 21*01ce260fSMickaël Salaün #include "trace.h" 22b4540a72SMickaël Salaün 23b4540a72SMickaël Salaün /* 24b4540a72SMickaël Salaün * Generates the tracepoint definitions in this translation unit. The trace 25b4540a72SMickaël Salaün * event header dereferences the traced objects in TP_fast_assign, so the full 2667567f03SMickaël Salaün * struct definitions (e.g. ruleset.h, domain.h) must be included before it. 27b4540a72SMickaël Salaün */ 28b4540a72SMickaël Salaün #define CREATE_TRACE_POINTS 29b4540a72SMickaël Salaün #include <trace/events/landlock.h> 3067567f03SMickaël Salaün 3167567f03SMickaël Salaün /** 3267567f03SMickaël Salaün * landlock_trace_free_domain - Emit a tracepoint on domain deallocation 3367567f03SMickaël Salaün * 3467567f03SMickaël Salaün * @hierarchy: The domain's hierarchy being deallocated. 3567567f03SMickaël Salaün * 3667567f03SMickaël Salaün * Fires only for a hierarchy whose creation event was emitted, i.e. one that 3767567f03SMickaël Salaün * left LANDLOCK_LOG_UNCOMMITTED in landlock_restrict_self(). This keeps the 3867567f03SMickaël Salaün * create/free pair balanced: a hierarchy that never became observable is freed 3967567f03SMickaël Salaün * silently, while a domain that landlock_restrict_self() created and a 4067567f03SMickaël Salaün * thread-sync failure then aborted still fires free_domain, because its 4167567f03SMickaël Salaün * creation event already fired. 4267567f03SMickaël Salaün * 4367567f03SMickaël Salaün * Called from landlock_log_free_domain(). 4467567f03SMickaël Salaün */ 4567567f03SMickaël Salaün void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy) 4667567f03SMickaël Salaün { 4767567f03SMickaël Salaün /* 4867567f03SMickaël Salaün * The log_status read is a correctness guard (keep the create/free pair 4967567f03SMickaël Salaün * balanced), not a cost guard, so this cold path needs no 5067567f03SMickaël Salaün * trace_..._enabled() check: the tracepoint is a static-branch no-op 5167567f03SMickaël Salaün * when disabled. The denial path guards trace_..._enabled() instead 5267567f03SMickaël Salaün * because it does expensive __getname()/path work before emitting. 5367567f03SMickaël Salaün */ 5467567f03SMickaël Salaün if (READ_ONCE(hierarchy->log_status) != LANDLOCK_LOG_UNCOMMITTED) 5567567f03SMickaël Salaün trace_landlock_free_domain(hierarchy); 5667567f03SMickaël Salaün } 57*01ce260fSMickaël Salaün 58*01ce260fSMickaël Salaün /** 59*01ce260fSMickaël Salaün * landlock_trace_denial - Emit a tracepoint for a denied access request 60*01ce260fSMickaël Salaün * 61*01ce260fSMickaël Salaün * @request: Detail of the user space request. 62*01ce260fSMickaël Salaün * @youngest_denied: The youngest hierarchy node that denied the access. 63*01ce260fSMickaël Salaün * @missing: The set of denied access rights. 64*01ce260fSMickaël Salaün * @same_exec: Whether the current task is the same executable that called 65*01ce260fSMickaël Salaün * landlock_restrict_self() for the denying domain, as computed 66*01ce260fSMickaël Salaün * by landlock_log_denial(). 67*01ce260fSMickaël Salaün * @logged: Whether the domain's policy selects this denial for logging, as 68*01ce260fSMickaël Salaün * computed by landlock_log_denial(). 69*01ce260fSMickaël Salaün * 70*01ce260fSMickaël Salaün * Emits the tracepoint matching @request->type when its event is enabled. 71*01ce260fSMickaël Salaün * Unlike audit, fires regardless of @logged; the value is recorded in the event 72*01ce260fSMickaël Salaün * so consumers can filter on it. 73*01ce260fSMickaël Salaün * 74*01ce260fSMickaël Salaün * Called from landlock_log_denial(). 75*01ce260fSMickaël Salaün */ 76*01ce260fSMickaël Salaün void landlock_trace_denial( 77*01ce260fSMickaël Salaün const struct landlock_request *const request, 78*01ce260fSMickaël Salaün const struct landlock_hierarchy *const youngest_denied, 79*01ce260fSMickaël Salaün const access_mask_t missing, const bool same_exec, const bool logged) 80*01ce260fSMickaël Salaün { 81*01ce260fSMickaël Salaün switch (request->type) { 82*01ce260fSMickaël Salaün case LANDLOCK_REQUEST_FS_ACCESS: 83*01ce260fSMickaël Salaün case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: 84*01ce260fSMickaël Salaün if (trace_landlock_deny_access_fs_enabled()) { 85*01ce260fSMickaël Salaün char *buf __free(__putname) = __getname(); 86*01ce260fSMickaël Salaün struct path dentry_path; 87*01ce260fSMickaël Salaün const char *pathname; 88*01ce260fSMickaël Salaün const struct path *path = NULL; 89*01ce260fSMickaël Salaün 90*01ce260fSMickaël Salaün /* 91*01ce260fSMickaël Salaün * Selects the path from the audit data type, as 92*01ce260fSMickaël Salaün * dump_common_audit_data() does. A FS_ACCESS denial 93*01ce260fSMickaël Salaün * carries a file (hook_file_truncate) or an ioctl op 94*01ce260fSMickaël Salaün * (hook_file_ioctl) rather than a path; 95*01ce260fSMickaël Salaün * FS_CHANGE_TOPOLOGY carries a path or a bare dentry. 96*01ce260fSMickaël Salaün * Reading the wrong union member would dereference 97*01ce260fSMickaël Salaün * garbage, so every reachable type is handled here. 98*01ce260fSMickaël Salaün */ 99*01ce260fSMickaël Salaün switch (request->audit.type) { 100*01ce260fSMickaël Salaün case LSM_AUDIT_DATA_FILE: 101*01ce260fSMickaël Salaün path = &request->audit.u.file->f_path; 102*01ce260fSMickaël Salaün break; 103*01ce260fSMickaël Salaün case LSM_AUDIT_DATA_IOCTL_OP: 104*01ce260fSMickaël Salaün path = &request->audit.u.op->path; 105*01ce260fSMickaël Salaün break; 106*01ce260fSMickaël Salaün case LSM_AUDIT_DATA_DENTRY: 107*01ce260fSMickaël Salaün /* 108*01ce260fSMickaël Salaün * Build a path on the stack with the real 109*01ce260fSMickaël Salaün * dentry so TP_fast_assign can extract dev and 110*01ce260fSMickaël Salaün * ino; the mnt field is unused there. 111*01ce260fSMickaël Salaün */ 112*01ce260fSMickaël Salaün dentry_path = (struct path){ 113*01ce260fSMickaël Salaün .dentry = request->audit.u.dentry, 114*01ce260fSMickaël Salaün }; 115*01ce260fSMickaël Salaün path = &dentry_path; 116*01ce260fSMickaël Salaün break; 117*01ce260fSMickaël Salaün case LSM_AUDIT_DATA_PATH: 118*01ce260fSMickaël Salaün path = &request->audit.u.path; 119*01ce260fSMickaël Salaün break; 120*01ce260fSMickaël Salaün default: 121*01ce260fSMickaël Salaün WARN_ONCE(1, 122*01ce260fSMickaël Salaün "Unhandled Landlock FS audit type %d", 123*01ce260fSMickaël Salaün request->audit.type); 124*01ce260fSMickaël Salaün break; 125*01ce260fSMickaël Salaün } 126*01ce260fSMickaël Salaün 127*01ce260fSMickaël Salaün if (!path) 128*01ce260fSMickaël Salaün break; 129*01ce260fSMickaël Salaün 130*01ce260fSMickaël Salaün if (!buf) { 131*01ce260fSMickaël Salaün pathname = "<no_mem>"; 132*01ce260fSMickaël Salaün } else if (request->audit.type == 133*01ce260fSMickaël Salaün LSM_AUDIT_DATA_DENTRY) { 134*01ce260fSMickaël Salaün /* No vfsmount: render the dentry path alone. */ 135*01ce260fSMickaël Salaün pathname = dentry_path_raw( 136*01ce260fSMickaël Salaün request->audit.u.dentry, buf, PATH_MAX); 137*01ce260fSMickaël Salaün if (IS_ERR(pathname)) 138*01ce260fSMickaël Salaün pathname = 139*01ce260fSMickaël Salaün PTR_ERR(pathname) == 140*01ce260fSMickaël Salaün -ENAMETOOLONG ? 141*01ce260fSMickaël Salaün "<too_long>" : 142*01ce260fSMickaël Salaün "<unreachable>"; 143*01ce260fSMickaël Salaün } else { 144*01ce260fSMickaël Salaün pathname = resolve_path_for_trace(path, buf); 145*01ce260fSMickaël Salaün } 146*01ce260fSMickaël Salaün 147*01ce260fSMickaël Salaün trace_landlock_deny_access_fs(youngest_denied, 148*01ce260fSMickaël Salaün same_exec, logged, 149*01ce260fSMickaël Salaün missing, path, pathname); 150*01ce260fSMickaël Salaün } 151*01ce260fSMickaël Salaün break; 152*01ce260fSMickaël Salaün case LANDLOCK_REQUEST_NET_ACCESS: 153*01ce260fSMickaël Salaün if (trace_landlock_deny_access_net_enabled()) 154*01ce260fSMickaël Salaün trace_landlock_deny_access_net( 155*01ce260fSMickaël Salaün youngest_denied, same_exec, logged, missing, 156*01ce260fSMickaël Salaün request->audit.u.net->sk, 157*01ce260fSMickaël Salaün ntohs(request->audit.u.net->sport), 158*01ce260fSMickaël Salaün ntohs(request->audit.u.net->dport)); 159*01ce260fSMickaël Salaün break; 160*01ce260fSMickaël Salaün default: 161*01ce260fSMickaël Salaün break; 162*01ce260fSMickaël Salaün } 163*01ce260fSMickaël Salaün } 164