Skip to content

Commit 2fc80c6

Browse files
committed
landlock: Log file-related denials
Add audit support for path_mkdir, path_mknod, path_symlink, path_unlink, path_rmdir, path_truncate, path_link, path_rename, and file_open hooks. The dedicated blockers are: - fs.execute - fs.write_file - fs.read_file - fs.read_dir - fs.remove_dir - fs.remove_file - fs.make_char - fs.make_dir - fs.make_reg - fs.make_sock - fs.make_fifo - fs.make_block - fs.make_sym - fs.refer - fs.truncate - fs.ioctl_dev Audit event sample for a denied link action: type=LANDLOCK_DENY msg=audit(1729738800.349:44): domain=195ba459b blockers=fs.refer path="/usr/bin" dev="vda2" ino=351 type=LANDLOCK_DENY msg=audit(1729738800.349:44): domain=195ba459b blockers=fs.make_reg,fs.refer path="/usr/local" dev="vda2" ino=365 We could pack blocker names (e.g. "fs:make_reg,refer") but that would increase complexity for the kernel and log parsers. Moreover, this could not handle blockers of different classes (e.g. fs and net). Make it simple and flexible instead. Add KUnit tests to check the identification from a layer_mask_t array of the first layer level denying such request. Cc: Günther Noack <[email protected]> Depends-on: 058518c ("landlock: Align partial refer access checks with final ones") Depends-on: d617f0d ("landlock: Optimize file path walks and prepare for audit support") Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Mickaël Salaün <[email protected]>
1 parent c56f649 commit 2fc80c6

File tree

3 files changed

+233
-16
lines changed

3 files changed

+233
-16
lines changed

security/landlock/audit.c

Lines changed: 171 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,77 @@
77

88
#include <kunit/test.h>
99
#include <linux/audit.h>
10+
#include <linux/bitops.h>
1011
#include <linux/lsm_audit.h>
1112
#include <linux/pid.h>
13+
#include <uapi/linux/landlock.h>
1214

1315
#include "audit.h"
16+
#include "common.h"
1417
#include "cred.h"
1518
#include "domain.h"
1619
#include "limits.h"
1720
#include "ruleset.h"
1821

19-
static const char *get_blocker(const enum landlock_request_type type)
22+
static const char *const fs_access_strings[] = {
23+
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = "fs.execute",
24+
[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = "fs.write_file",
25+
[BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = "fs.read_file",
26+
[BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = "fs.read_dir",
27+
[BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = "fs.remove_dir",
28+
[BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_FILE)] = "fs.remove_file",
29+
[BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_CHAR)] = "fs.make_char",
30+
[BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_DIR)] = "fs.make_dir",
31+
[BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_REG)] = "fs.make_reg",
32+
[BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SOCK)] = "fs.make_sock",
33+
[BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_FIFO)] = "fs.make_fifo",
34+
[BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_BLOCK)] = "fs.make_block",
35+
[BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SYM)] = "fs.make_sym",
36+
[BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = "fs.refer",
37+
[BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = "fs.truncate",
38+
[BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = "fs.ioctl_dev",
39+
};
40+
41+
static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS);
42+
43+
static __attribute_const__ const char *
44+
get_blocker(const enum landlock_request_type type,
45+
const unsigned long access_bit)
2046
{
2147
switch (type) {
2248
case LANDLOCK_REQUEST_PTRACE:
49+
WARN_ON_ONCE(access_bit != -1);
2350
return "ptrace";
2451

2552
case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY:
53+
WARN_ON_ONCE(access_bit != -1);
2654
return "fs.change_topology";
55+
56+
case LANDLOCK_REQUEST_FS_ACCESS:
57+
if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(fs_access_strings)))
58+
return "unknown";
59+
return fs_access_strings[access_bit];
2760
}
2861

2962
WARN_ON_ONCE(1);
3063
return "unknown";
3164
}
3265

3366
static void log_blockers(struct audit_buffer *const ab,
34-
const enum landlock_request_type type)
67+
const enum landlock_request_type type,
68+
const access_mask_t access)
3569
{
36-
audit_log_format(ab, "%s", get_blocker(type));
70+
const unsigned long access_mask = access;
71+
unsigned long access_bit;
72+
bool is_first = true;
73+
74+
for_each_set_bit(access_bit, &access_mask, BITS_PER_TYPE(access)) {
75+
audit_log_format(ab, "%s%s", is_first ? "" : ",",
76+
get_blocker(type, access_bit));
77+
is_first = false;
78+
}
79+
if (is_first)
80+
audit_log_format(ab, "%s", get_blocker(type, -1));
3781
}
3882

3983
static void log_domain(struct landlock_hierarchy *const hierarchy)
@@ -115,12 +159,113 @@ static void test_get_hierarchy(struct kunit *const test)
115159

116160
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
117161

162+
static size_t get_denied_layer(const struct landlock_ruleset *const domain,
163+
access_mask_t *const access_request,
164+
const layer_mask_t (*const layer_masks)[],
165+
const size_t layer_masks_size)
166+
{
167+
const unsigned long access_req = *access_request;
168+
unsigned long access_bit;
169+
access_mask_t missing = 0;
170+
long youngest_layer = -1;
171+
172+
for_each_set_bit(access_bit, &access_req, layer_masks_size) {
173+
const access_mask_t mask = (*layer_masks)[access_bit];
174+
long layer;
175+
176+
if (!mask)
177+
continue;
178+
179+
/* __fls(1) == 0 */
180+
layer = __fls(mask);
181+
if (layer > youngest_layer) {
182+
youngest_layer = layer;
183+
missing = BIT(access_bit);
184+
} else if (layer == youngest_layer) {
185+
missing |= BIT(access_bit);
186+
}
187+
}
188+
189+
*access_request = missing;
190+
if (youngest_layer == -1)
191+
return domain->num_layers - 1;
192+
193+
return youngest_layer;
194+
}
195+
196+
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
197+
198+
static void test_get_denied_layer(struct kunit *const test)
199+
{
200+
const struct landlock_ruleset dom = {
201+
.num_layers = 5,
202+
};
203+
const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
204+
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT(0),
205+
[BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = BIT(1),
206+
[BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = BIT(1) | BIT(0),
207+
[BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = BIT(2),
208+
};
209+
access_mask_t access;
210+
211+
access = LANDLOCK_ACCESS_FS_EXECUTE;
212+
KUNIT_EXPECT_EQ(test, 0,
213+
get_denied_layer(&dom, &access, &layer_masks,
214+
sizeof(layer_masks)));
215+
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_EXECUTE);
216+
217+
access = LANDLOCK_ACCESS_FS_READ_FILE;
218+
KUNIT_EXPECT_EQ(test, 1,
219+
get_denied_layer(&dom, &access, &layer_masks,
220+
sizeof(layer_masks)));
221+
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_FILE);
222+
223+
access = LANDLOCK_ACCESS_FS_READ_DIR;
224+
KUNIT_EXPECT_EQ(test, 1,
225+
get_denied_layer(&dom, &access, &layer_masks,
226+
sizeof(layer_masks)));
227+
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
228+
229+
access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
230+
KUNIT_EXPECT_EQ(test, 1,
231+
get_denied_layer(&dom, &access, &layer_masks,
232+
sizeof(layer_masks)));
233+
KUNIT_EXPECT_EQ(test, access,
234+
LANDLOCK_ACCESS_FS_READ_FILE |
235+
LANDLOCK_ACCESS_FS_READ_DIR);
236+
237+
access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_DIR;
238+
KUNIT_EXPECT_EQ(test, 1,
239+
get_denied_layer(&dom, &access, &layer_masks,
240+
sizeof(layer_masks)));
241+
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
242+
243+
access = LANDLOCK_ACCESS_FS_WRITE_FILE;
244+
KUNIT_EXPECT_EQ(test, 4,
245+
get_denied_layer(&dom, &access, &layer_masks,
246+
sizeof(layer_masks)));
247+
KUNIT_EXPECT_EQ(test, access, 0);
248+
}
249+
250+
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
251+
118252
static bool is_valid_request(const struct landlock_request *const request)
119253
{
120254
if (WARN_ON_ONCE(request->layer_plus_one > LANDLOCK_MAX_NUM_LAYERS))
121255
return false;
122256

123-
if (WARN_ON_ONCE(!request->layer_plus_one))
257+
if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access)))
258+
return false;
259+
260+
if (request->access) {
261+
if (WARN_ON_ONCE(!request->layer_masks))
262+
return false;
263+
} else {
264+
if (WARN_ON_ONCE(request->layer_masks))
265+
return false;
266+
}
267+
268+
if (WARN_ON_ONCE(!!request->layer_masks ^ !!request->layer_masks_size))
124269
return false;
125270

126271
return true;
@@ -138,6 +283,7 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
138283
struct audit_buffer *ab;
139284
struct landlock_hierarchy *youngest_denied;
140285
size_t youngest_layer;
286+
access_mask_t missing;
141287

142288
if (WARN_ON_ONCE(!subject || !subject->domain ||
143289
!subject->domain->hierarchy || !request))
@@ -146,8 +292,25 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
146292
if (!is_valid_request(request))
147293
return;
148294

149-
youngest_layer = request->layer_plus_one - 1;
150-
youngest_denied = get_hierarchy(subject->domain, youngest_layer);
295+
missing = request->access;
296+
if (missing) {
297+
/* Gets the nearest domain that denies the request. */
298+
if (request->layer_masks) {
299+
youngest_layer = get_denied_layer(
300+
subject->domain, &missing, request->layer_masks,
301+
request->layer_masks_size);
302+
} else {
303+
/* This will change with the next commit. */
304+
WARN_ON_ONCE(1);
305+
youngest_layer = subject->domain->num_layers;
306+
}
307+
youngest_denied =
308+
get_hierarchy(subject->domain, youngest_layer);
309+
} else {
310+
youngest_layer = request->layer_plus_one - 1;
311+
youngest_denied =
312+
get_hierarchy(subject->domain, youngest_layer);
313+
}
151314

152315
/*
153316
* Consistently keeps track of the number of denied access requests
@@ -171,7 +334,7 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
171334
return;
172335

173336
audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id);
174-
log_blockers(ab, request->type);
337+
log_blockers(ab, request->type, missing);
175338
audit_log_lsm_data(ab, &request->audit);
176339
audit_log_end(ab);
177340

@@ -223,6 +386,7 @@ void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)
223386
static struct kunit_case test_cases[] = {
224387
/* clang-format off */
225388
KUNIT_CASE(test_get_hierarchy),
389+
KUNIT_CASE(test_get_denied_layer),
226390
{}
227391
/* clang-format on */
228392
};

security/landlock/audit.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
#include <linux/audit.h>
1212
#include <linux/lsm_audit.h>
1313

14+
#include "access.h"
1415
#include "cred.h"
1516

1617
enum landlock_request_type {
1718
LANDLOCK_REQUEST_PTRACE = 1,
1819
LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY,
20+
LANDLOCK_REQUEST_FS_ACCESS,
1921
};
2022

2123
/*
@@ -33,6 +35,13 @@ struct landlock_request {
3335
* extra one is useful to detect uninitialized field.
3436
*/
3537
size_t layer_plus_one;
38+
39+
/* Required field for configurable access control. */
40+
access_mask_t access;
41+
42+
/* Required fields for requests with layer masks. */
43+
const layer_mask_t (*layer_masks)[];
44+
size_t layer_masks_size;
3645
};
3746

3847
#ifdef CONFIG_AUDIT

0 commit comments

Comments
 (0)