diff options
Diffstat (limited to 'security')
| -rw-r--r-- | security/landlock/log.c | 2 | ||||
| -rw-r--r-- | security/landlock/trace.c | 119 | ||||
| -rw-r--r-- | security/landlock/trace.h | 16 |
3 files changed, 136 insertions, 1 deletions
diff --git a/security/landlock/log.c b/security/landlock/log.c index ad4e3ae99d3a..a8578a6f2ce9 100644 --- a/security/landlock/log.c +++ b/security/landlock/log.c @@ -544,6 +544,8 @@ void landlock_log_denial(const struct landlock_cred_security *const subject, */ atomic64_inc(&youngest_denied->num_denials); + landlock_trace_denial(request, youngest_denied, missing, same_exec, + logged); landlock_audit_denial(request, youngest_denied, missing, logged); } diff --git a/security/landlock/trace.c b/security/landlock/trace.c index 5e6df313c7d2..4c4229d4ffdf 100644 --- a/security/landlock/trace.c +++ b/security/landlock/trace.c @@ -6,9 +6,19 @@ * Copyright © 2026 Cloudflare, Inc. */ -#include "trace.h" +#include <linux/cleanup.h> +#include <linux/dcache.h> +#include <linux/err.h> +#include <linux/fs.h> +#include <linux/lsm_audit.h> +#include <net/sock.h> + +#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 @@ -44,3 +54,110 @@ void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy 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 = "<no_mem>"; + } 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 ? + "<too_long>" : + "<unreachable>"; + } 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; + default: + break; + } +} diff --git a/security/landlock/trace.h b/security/landlock/trace.h index 59c8ea348625..7be98e748855 100644 --- a/security/landlock/trace.h +++ b/security/landlock/trace.h @@ -9,13 +9,21 @@ #ifndef _SECURITY_LANDLOCK_TRACE_H #define _SECURITY_LANDLOCK_TRACE_H +#include "access.h" + struct landlock_hierarchy; +struct landlock_request; #ifdef CONFIG_TRACEPOINTS void landlock_trace_free_domain( const struct landlock_hierarchy *const hierarchy); +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); + #else /* CONFIG_TRACEPOINTS */ static inline void @@ -23,6 +31,14 @@ landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy) { } +static inline 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) +{ +} + #endif /* CONFIG_TRACEPOINTS */ #endif /* _SECURITY_LANDLOCK_TRACE_H */ |
