xref: /linux/security/landlock/trace.c (revision 01ce260f5ccf0fe7e38d2fd548e776f594409cf6)
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