History

landlock: Add landlock_deny_access_fs and landlock_deny_access_net

Add per-type tracepoints emitted from landlock_log_denial() when an access is denied: landlock_deny_access_fs for filesystem denials and landlock_deny_access_net for network denials. They use the "deny_" prefix (rather than "check_") to mark that they fire only on a denial, and they complement the check_rule events by making the denial-by-absence case explicit (when no rule matches, no check_rule event fires). Unlike the audit records, these events fire regardless of the audit configuration and the domain's log flags: the user's "disable logging" intent applies to audit records, not to kernel tracing. The logged field records whether the domain's log policy would submit the denial to audit; it is the decision computed once by landlock_log_denial() and passed to both the audit and the tracing emitter, so a stateless ftrace filter can select the audit-visible denials with logged==1. TP_PROTO passes the denying hierarchy node, not the task's current domain, so domain_id reports the specific node that blocked the access, matching audit record semantics. (check_rule instead passes the current domain, which it needs to size its per-layer array.) same_exec is also passed explicitly because it is computed from the credential bitmask and is not derivable from the hierarchy pointer alone. The denial field is named blockers to match the audit record field. The filesystem path comes from the request's audit data. Its type selects which union member holds the object, exactly as dump_common_audit_data() selects it (a path, a file's path, an ioctl op's path, or a bare dentry); reading the wrong member would dereference garbage, so every reachable type has an explicit case and an unexpected one is flagged with WARN_ONCE() instead of misread. Path-backed types resolve via d_absolute_path() (as landlock_add_rule_fs does) and the bare-dentry case via dentry_path_raw(). The inode number is read defensively. A filesystem denial can carry a negative dentry (no backing inode), for example a denied creation, so the event mirrors the guard in dump_common_audit_data() and reports inode 0 rather than dereferencing a NULL inode. The sibling fs tracepoints do not need the guard: a dentry that matches a rule during an access check, or one opened to add a rule, always has a backing inode. Landlock tracepoints are reachable by unprivileged sandboxees, so a denial on a negative dentry with the event enabled must not fault the kernel.

Mickaël Salaün committed 3 months ago

Commit 01ce2601 parent 3f1f1064 files · +265 −1Browse files

Unified · Split

4 files
  1. include/trace/events/landlock.h +129 −0
  2. security/landlock/log.c +2 −0
  3. security/landlock/trace.c +118 −1
  4. security/landlock/trace.h +16 −0
include/trace/events/landlock.h+129−0
@@ -22,6 +22,7 @@ struct landlock_hierarchy; · 21 unmodified lines
22struct landlock_rule;22struct landlock_rule;
23struct landlock_ruleset;23struct landlock_ruleset;
24struct path;24struct path;
25struct sock;
25 26
26#ifdef CREATE_TRACE_POINTS27#ifdef CREATE_TRACE_POINTS
27 28
@@ -204,6 +205,20 @@ static inline const char *__trace_landlock_print_layers( · 176 unmodified lines
204 * rule, before the final allow-or-deny verdict. They share domain (the205 * rule, before the final allow-or-deny verdict. They share domain (the
205 * enforcing domain being evaluated), access_request (the access mask being206 * enforcing domain being evaluated), access_request (the access mask being
206 * checked), and rule (the matching rule, with per-layer access masks).207 * checked), and rule (the matching rule, with per-layer access masks).
208 *
209 * Denial fields
210 * ~~~~~~~~~~~~~
211 *
212 * Every denial event shares three fields. domain is the ID of the
213 * innermost domain that blocked the access. same_exec tells whether the
214 * current task is the same executable that entered that domain. logged is
215 * the domain's audit-logging decision for this denial (its log_status is
216 * enabled and the per-execution flag selected by same_exec is set); a
217 * stateless ftrace filter can select the denials the domain submits to
218 * audit with logged==1, without reconstructing it from the per-execution
219 * log flags. Denial events order their fields as domain, same_exec,
220 * logged, then blockers (deny_access events only), then the type-specific
221 * object fields, then any variable-length field.
207 */222 */
208 223
209/*224/*
@@ -628,6 +643,120 @@ TRACE_EVENT(landlock_check_rule_net, · 418 unmodified lines
628 __print_landlock_layers(grants, _LANDLOCK_ACCESS_NET_NAMES))643 __print_landlock_layers(grants, _LANDLOCK_ACCESS_NET_NAMES))
629);644);
630 645
646/**
647 * landlock_deny_access_fs - Filesystem access denied
648 *
649 * @hierarchy: Denying domain's hierarchy node (never NULL); its id is the
650 * domain field.
651 * @same_exec: Whether the current task entered the denying domain itself.
652 * @logged: The domain's audit-logging decision for this denial.
653 * @blockers: Access mask that was blocked (zero for a mount-topology
654 * change, whose only blocker is the operation itself).
655 * @path: Filesystem path that was denied (never NULL).
656 * @pathname: Resolved path string (never NULL; an error placeholder on
657 * resolution failure).
658 *
659 * Emitted when a Landlock domain denies a filesystem access.
660 */
661TRACE_EVENT(landlock_deny_access_fs,
662
663 TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,
664 bool logged, access_mask_t blockers, const struct path *path,
665 const char *pathname),
666
667 TP_ARGS(hierarchy, same_exec, logged, blockers, path, pathname),
668
669 TP_STRUCT__entry(
670 __field( __u64, domain_id )
671 __field( bool, same_exec )
672 __field( bool, logged )
673 __field( access_mask_t, blockers )
674 __field( dev_t, dev )
675 __field( ino_t, ino )
676 __string( pathname, pathname )
677 ),
678
679 TP_fast_assign(
680 const struct inode *inode = d_backing_inode(path->dentry);
681
682 __entry->domain_id = hierarchy->id;
683 __entry->same_exec = same_exec;
684 __entry->logged = logged;
685 __entry->blockers = blockers;
686 __entry->dev = path->dentry->d_sb->s_dev;
687 /*
688 * A negative dentry has no backing inode, so mirror the
689 * guard in dump_common_audit_data() and report inode 0.
690 */
691 __entry->ino = inode ? inode->i_ino : 0;
692 __assign_str(pathname);
693 ),
694
695 TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s dev=%u:%u ino=%lu path=%s",
696 __entry->domain_id, __entry->same_exec, __entry->logged,
697 __print_flags(__entry->blockers, "|", _LANDLOCK_ACCESS_FS_NAMES),
698 MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino,
699 __trace_print_untrusted_str(p, __get_str(pathname),
700 __get_dynamic_array_len(pathname) - 1))
701);
702
703/**
704 * landlock_deny_access_net - Network access denied
705 *
706 * @hierarchy: Denying domain's hierarchy node (never NULL); its id is the
707 * domain field.
708 * @same_exec: Whether the current task entered the denying domain itself.
709 * @logged: The domain's audit-logging decision for this denial.
710 * @blockers: Access mask that was blocked.
711 * @sk: Socket object (never NULL), read without a socket lock, so its
712 * fields are a best-effort snapshot. The denied endpoint is not
713 * available: the hook runs before :manpage:`bind(2)` /
714 * :manpage:`connect(2)` sets the socket addresses.
715 * @sport: Source port in host endianness, set for bind denials (zero for
716 * an autobind/ephemeral port); zero for connect and send denials.
717 * @dport: Destination port in host endianness, set for connect and send
718 * denials; zero for bind denials, and also zero for a UDP send to
719 * an AF_UNSPEC address on an IPv6 socket (indistinguishable from a
720 * real destination port 0). The bind-vs-connect direction is
721 * given by @blockers, not by which port is set.
722 *
723 * Emitted when a Landlock domain denies a network operation.
724 *
725 * The port fields are converted from the socket's network byte order to
726 * host endianness before emitting.
727 */
728TRACE_EVENT(landlock_deny_access_net,
729
730 TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,
731 bool logged, access_mask_t blockers, const struct sock *sk,
732 __u64 sport, __u64 dport),
733
734 TP_ARGS(hierarchy, same_exec, logged, blockers, sk, sport, dport),
735
736 TP_STRUCT__entry(
737 __field( __u64, domain_id )
738 __field( bool, same_exec )
739 __field( bool, logged )
740 __field( access_mask_t, blockers )
741 __field( __u64, sport )
742 __field( __u64, dport )
743 ),
744
745 TP_fast_assign(
746 __entry->domain_id = hierarchy->id;
747 __entry->same_exec = same_exec;
748 __entry->logged = logged;
749 __entry->blockers = blockers;
750 __entry->sport = sport;
751 __entry->dport = dport;
752 ),
753
754 TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s sport=%llu dport=%llu",
755 __entry->domain_id, __entry->same_exec, __entry->logged,
756 __print_flags(__entry->blockers, "|", _LANDLOCK_ACCESS_NET_NAMES),
757 __entry->sport, __entry->dport)
758);
759
631#undef _LANDLOCK_NAME_ENTRY760#undef _LANDLOCK_NAME_ENTRY
632 761
633#endif /* _TRACE_LANDLOCK_H */762#endif /* _TRACE_LANDLOCK_H */
security/landlock/log.c+2−0
@@ -544,6 +544,8 @@ void landlock_log_denial(const struct landlock_cred_security *const subject, · 543 unmodified lines
544 */544 */
545 atomic64_inc(&youngest_denied->num_denials);545 atomic64_inc(&youngest_denied->num_denials);
546 546
547 landlock_trace_denial(request, youngest_denied, missing, same_exec,
548 logged);
547 landlock_audit_denial(request, youngest_denied, missing, logged);549 landlock_audit_denial(request, youngest_denied, missing, logged);
548}550}
549 551
security/landlock/trace.c+118−1
@@ -6,9 +6,19 @@ · 5 unmodified lines
6 * Copyright © 2026 Cloudflare, Inc.6 * Copyright © 2026 Cloudflare, Inc.
7 */7 */
8 8
9#include "trace.h"9#include <linux/cleanup.h>
10#include <linux/dcache.h>
11#include <linux/err.h>
12#include <linux/fs.h>
13#include <linux/lsm_audit.h>
14#include <net/sock.h>
15
16#include "access.h"
10#include "domain.h"17#include "domain.h"
18#include "fs.h"
19#include "log.h"
11#include "ruleset.h"20#include "ruleset.h"
21#include "trace.h"
12 22
13/*23/*
14 * Generates the tracepoint definitions in this translation unit. The trace24 * 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 · 29 unmodified lines
44 if (READ_ONCE(hierarchy->log_status) != LANDLOCK_LOG_UNCOMMITTED)54 if (READ_ONCE(hierarchy->log_status) != LANDLOCK_LOG_UNCOMMITTED)
45 trace_landlock_free_domain(hierarchy);55 trace_landlock_free_domain(hierarchy);
46}56}
57
58/**
59 * landlock_trace_denial - Emit a tracepoint for a denied access request
60 *
61 * @request: Detail of the user space request.
62 * @youngest_denied: The youngest hierarchy node that denied the access.
63 * @missing: The set of denied access rights.
64 * @same_exec: Whether the current task is the same executable that called
65 * landlock_restrict_self() for the denying domain, as computed
66 * by landlock_log_denial().
67 * @logged: Whether the domain's policy selects this denial for logging, as
68 * computed by landlock_log_denial().
69 *
70 * Emits the tracepoint matching @request->type when its event is enabled.
71 * Unlike audit, fires regardless of @logged; the value is recorded in the event
72 * so consumers can filter on it.
73 *
74 * Called from landlock_log_denial().
75 */
76void landlock_trace_denial(
77 const struct landlock_request *const request,
78 const struct landlock_hierarchy *const youngest_denied,
79 const access_mask_t missing, const bool same_exec, const bool logged)
80{
81 switch (request->type) {
82 case LANDLOCK_REQUEST_FS_ACCESS:
83 case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY:
84 if (trace_landlock_deny_access_fs_enabled()) {
85 char *buf __free(__putname) = __getname();
86 struct path dentry_path;
87 const char *pathname;
88 const struct path *path = NULL;
89
90 /*
91 * Selects the path from the audit data type, as
92 * dump_common_audit_data() does. A FS_ACCESS denial
93 * carries a file (hook_file_truncate) or an ioctl op
94 * (hook_file_ioctl) rather than a path;
95 * FS_CHANGE_TOPOLOGY carries a path or a bare dentry.
96 * Reading the wrong union member would dereference
97 * garbage, so every reachable type is handled here.
98 */
99 switch (request->audit.type) {
100 case LSM_AUDIT_DATA_FILE:
101 path = &request->audit.u.file->f_path;
102 break;
103 case LSM_AUDIT_DATA_IOCTL_OP:
104 path = &request->audit.u.op->path;
105 break;
106 case LSM_AUDIT_DATA_DENTRY:
107 /*
108 * Build a path on the stack with the real
109 * dentry so TP_fast_assign can extract dev and
110 * ino; the mnt field is unused there.
111 */
112 dentry_path = (struct path){
113 .dentry = request->audit.u.dentry,
114 };
115 path = &dentry_path;
116 break;
117 case LSM_AUDIT_DATA_PATH:
118 path = &request->audit.u.path;
119 break;
120 default:
121 WARN_ONCE(1,
122 "Unhandled Landlock FS audit type %d",
123 request->audit.type);
124 break;
125 }
126
127 if (!path)
128 break;
129
130 if (!buf) {
131 pathname = "<no_mem>";
132 } else if (request->audit.type ==
133 LSM_AUDIT_DATA_DENTRY) {
134 /* No vfsmount: render the dentry path alone. */
135 pathname = dentry_path_raw(
136 request->audit.u.dentry, buf, PATH_MAX);
137 if (IS_ERR(pathname))
138 pathname =
139 PTR_ERR(pathname) ==
140 -ENAMETOOLONG ?
141 "<too_long>" :
142 "<unreachable>";
143 } else {
144 pathname = resolve_path_for_trace(path, buf);
145 }
146
147 trace_landlock_deny_access_fs(youngest_denied,
148 same_exec, logged,
149 missing, path, pathname);
150 }
151 break;
152 case LANDLOCK_REQUEST_NET_ACCESS:
153 if (trace_landlock_deny_access_net_enabled())
154 trace_landlock_deny_access_net(
155 youngest_denied, same_exec, logged, missing,
156 request->audit.u.net->sk,
157 ntohs(request->audit.u.net->sport),
158 ntohs(request->audit.u.net->dport));
159 break;
160 default:
161 break;
162 }
163}
security/landlock/trace.h+16−0
@@ -9,13 +9,21 @@ · 8 unmodified lines
9#ifndef _SECURITY_LANDLOCK_TRACE_H9#ifndef _SECURITY_LANDLOCK_TRACE_H
10#define _SECURITY_LANDLOCK_TRACE_H10#define _SECURITY_LANDLOCK_TRACE_H
11 11
12#include "access.h"
13
12struct landlock_hierarchy;14struct landlock_hierarchy;
15struct landlock_request;
13 16
14#ifdef CONFIG_TRACEPOINTS17#ifdef CONFIG_TRACEPOINTS
15 18
16void landlock_trace_free_domain(19void landlock_trace_free_domain(
17 const struct landlock_hierarchy *const hierarchy);20 const struct landlock_hierarchy *const hierarchy);
18 21
22void landlock_trace_denial(
23 const struct landlock_request *const request,
24 const struct landlock_hierarchy *const youngest_denied,
25 const access_mask_t missing, const bool same_exec, const bool logged);
26
19#else /* CONFIG_TRACEPOINTS */27#else /* CONFIG_TRACEPOINTS */
20 28
21static inline void29static inline void
@@ -23,6 +31,14 @@ landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy) · 1 unmodified line
23{31{
24}32}
25 33
34static inline void
35landlock_trace_denial(const struct landlock_request *const request,
36 const struct landlock_hierarchy *const youngest_denied,
37 const access_mask_t missing, const bool same_exec,
38 const bool logged)
39{
40}
41
26#endif /* CONFIG_TRACEPOINTS */42#endif /* CONFIG_TRACEPOINTS */
27 43
28#endif /* _SECURITY_LANDLOCK_TRACE_H */44#endif /* _SECURITY_LANDLOCK_TRACE_H */

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t