summaryrefslogtreecommitdiff
path: root/security
diff options
context:
space:
mode:
Diffstat (limited to 'security')
-rw-r--r--security/landlock/log.c2
-rw-r--r--security/landlock/trace.c119
-rw-r--r--security/landlock/trace.h16
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 */