1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
|
// SPDX-License-Identifier: GPL-2.0-only
/*
* Landlock - Audit helpers
*
* Copyright © 2023-2025 Microsoft Corporation
*/
#include <linux/audit.h>
#include <linux/bitops.h>
#include <linux/landlock.h>
#include <linux/lsm_audit.h>
#include <linux/pid.h>
#include <uapi/linux/landlock.h>
#include "access.h"
#include "audit.h"
#include "common.h"
#include "cred.h"
#include "domain.h"
#include "limits.h"
#include "log.h"
/*
* Access-right and scope names are built from the lists shared with the trace
* events (see <linux/landlock.h>). The designated initializer places each name
* at its bit index, so the lookup stays O(1) and does not depend on the entry
* order. log_blockers() adds the "fs."/"net."/"scope." category prefix.
*/
#define _LANDLOCK_NAME_ENTRY(mask, name) [BIT_INDEX(mask)] = name
static const char *const fs_access_strings[] = { _LANDLOCK_ACCESS_FS_NAMES };
static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS);
static const char *const net_access_strings[] = { _LANDLOCK_ACCESS_NET_NAMES };
static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET);
static const char *const scope_strings[] = { _LANDLOCK_SCOPE_NAMES };
static_assert(ARRAY_SIZE(scope_strings) == LANDLOCK_NUM_SCOPE);
#undef _LANDLOCK_NAME_ENTRY
static __attribute_const__ const char *
get_blocker(const enum landlock_request_type type,
const unsigned long access_bit)
{
switch (type) {
case LANDLOCK_REQUEST_PTRACE:
WARN_ON_ONCE(access_bit != -1);
return "ptrace";
case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY:
WARN_ON_ONCE(access_bit != -1);
return "change_topology";
case LANDLOCK_REQUEST_FS_ACCESS:
if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(fs_access_strings)))
return "unknown";
return fs_access_strings[access_bit];
case LANDLOCK_REQUEST_NET_ACCESS:
if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(net_access_strings)))
return "unknown";
return net_access_strings[access_bit];
case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
WARN_ON_ONCE(access_bit != -1);
return scope_strings[BIT_INDEX(
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET)];
case LANDLOCK_REQUEST_SCOPE_SIGNAL:
WARN_ON_ONCE(access_bit != -1);
return scope_strings[BIT_INDEX(LANDLOCK_SCOPE_SIGNAL)];
}
WARN_ON_ONCE(1);
return "unknown";
}
/*
* Returns the audit category prefix prepended to the unprefixed blocker name
* returned by get_blocker() (filesystem and network access rights,
* change_topology, and scopes). The ptrace blocker is standalone and carries
* its full name in get_blocker(), so it uses no prefix.
*/
static __attribute_const__ const char *
blocker_prefix(const enum landlock_request_type type)
{
switch (type) {
case LANDLOCK_REQUEST_PTRACE:
return "";
case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY:
case LANDLOCK_REQUEST_FS_ACCESS:
return "fs.";
case LANDLOCK_REQUEST_NET_ACCESS:
return "net.";
case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
case LANDLOCK_REQUEST_SCOPE_SIGNAL:
return "scope.";
}
WARN_ON_ONCE(1);
return "";
}
static void log_blockers(struct audit_buffer *const ab,
const enum landlock_request_type type,
const access_mask_t access)
{
const unsigned long access_mask = access;
const char *const prefix = blocker_prefix(type);
unsigned long access_bit;
bool is_first = true;
for_each_set_bit(access_bit, &access_mask, BITS_PER_TYPE(access)) {
audit_log_format(ab, "%s%s%s", is_first ? "" : ",", prefix,
get_blocker(type, access_bit));
is_first = false;
}
if (is_first)
audit_log_format(ab, "%s%s", prefix, get_blocker(type, -1));
}
static void log_domain(struct landlock_hierarchy *const hierarchy)
{
struct audit_buffer *ab;
/* Ignores already logged domains. */
if (READ_ONCE(hierarchy->log_status) == LANDLOCK_LOG_RECORDED)
return;
/* Uses consistent allocation flags wrt common_lsm_audit(). */
ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
AUDIT_LANDLOCK_DOMAIN);
if (!ab)
return;
WARN_ON_ONCE(hierarchy->id == 0);
audit_log_format(
ab,
"domain=%llx status=allocated mode=enforcing pid=%d uid=%u exe=",
hierarchy->id, pid_nr(hierarchy->details->pid),
hierarchy->details->uid);
audit_log_untrustedstring(ab, hierarchy->details->exe_path);
audit_log_format(ab, " comm=");
audit_log_untrustedstring(ab, hierarchy->details->comm);
audit_log_end(ab);
/*
* There may be race condition leading to logging of the same domain
* several times but that is OK.
*/
WRITE_ONCE(hierarchy->log_status, LANDLOCK_LOG_RECORDED);
}
/**
* landlock_audit_denial - Create an audit record 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.
* @logged: Whether the denial is selected for logging, as computed by
* landlock_log_denial() (domain policy and quiet rules).
*
* Emits the record when audit is enabled and the denial is selected for
* logging.
*/
void landlock_audit_denial(const struct landlock_request *const request,
struct landlock_hierarchy *const youngest_denied,
const access_mask_t missing, const bool logged)
{
struct audit_buffer *ab;
if (!audit_enabled)
return;
/*
* Skips denials the domain's policy or a quiet rule excludes from
* logging (folded into @logged by landlock_log_denial()).
*/
if (!logged)
return;
/* Uses consistent allocation flags wrt common_lsm_audit(). */
ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
AUDIT_LANDLOCK_ACCESS);
if (!ab)
return;
audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id);
log_blockers(ab, request->type, missing);
audit_log_lsm_data(ab, &request->audit);
audit_log_end(ab);
/* Logs this domain the first time it shows in log. */
log_domain(youngest_denied);
}
/**
* landlock_audit_free_domain - Create an audit record on domain deallocation
*
* @hierarchy: The domain's hierarchy being deallocated.
*
* Only domains which previously appeared in the audit logs are logged again.
* This is useful to know when a domain will never show again in the audit log.
*
* Called from landlock_log_free_domain().
*/
void landlock_audit_free_domain(const struct landlock_hierarchy *const hierarchy)
{
struct audit_buffer *ab;
if (!audit_enabled)
return;
/* Ignores domains that were not logged. */
if (READ_ONCE(hierarchy->log_status) != LANDLOCK_LOG_RECORDED)
return;
/*
* If logging of domain allocation succeeded, warns about failure to log
* domain deallocation to highlight unbalanced domain lifetime logs.
*/
ab = audit_log_start(audit_context(), GFP_KERNEL,
AUDIT_LANDLOCK_DOMAIN);
if (!ab)
return;
audit_log_format(ab, "domain=%llx status=deallocated denials=%llu",
hierarchy->id, atomic64_read(&hierarchy->num_denials));
audit_log_end(ab);
}
|