Skip to content

Commit b4007fd

Browse files
committed
landlock: Add support for KUnit tests
Add the SECURITY_LANDLOCK_KUNIT_TEST option to enable KUnit tests for Landlock. The minimal required configuration is listed in the security/landlock/.kunitconfig file. Add an initial landlock_fs KUnit test suite with 7 test cases for filesystem helpers. These are related to the LANDLOCK_ACCESS_FS_REFER right. There is one KUnit test case per: * mutated state (e.g. test_scope_to_request_*) or, * shared state between tests (e.g. test_is_eaccess_*). Add macros to improve readability of tests (i.e. one per line). Test cases are collocated with the tested functions to help maintenance and improve documentation. This is why SECURITY_LANDLOCK_KUNIT_TEST cannot be set as module. This is a nice complement to Landlock's user space kselftests. We expect new Landlock features to come with KUnit tests as well. Thanks to UML support, we can run all KUnit tests for Landlock with: ./tools/testing/kunit/kunit.py run --kunitconfig security/landlock [00:00:00] ======================= landlock_fs ======================= [00:00:00] [PASSED] test_no_more_access [00:00:00] [PASSED] test_scope_to_request_with_exec_none [00:00:00] [PASSED] test_scope_to_request_with_exec_some [00:00:00] [PASSED] test_scope_to_request_without_access [00:00:00] [PASSED] test_is_eacces_with_none [00:00:00] [PASSED] test_is_eacces_with_refer [00:00:00] [PASSED] test_is_eacces_with_write [00:00:00] =================== [PASSED] landlock_fs =================== [00:00:00] ============================================================ [00:00:00] Testing complete. Ran 7 tests: passed: 7 Cc: Konstantin Meskhidze <[email protected]> Reviewed-by: Günther Noack <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Mickaël Salaün <[email protected]>
1 parent a3f1629 commit b4007fd

File tree

5 files changed

+256
-0
lines changed

5 files changed

+256
-0
lines changed

security/landlock/.kunitconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CONFIG_KUNIT=y
2+
CONFIG_SECURITY=y
3+
CONFIG_SECURITY_LANDLOCK=y
4+
CONFIG_SECURITY_LANDLOCK_KUNIT_TEST=y

security/landlock/Kconfig

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,18 @@ config SECURITY_LANDLOCK
2020
If you are unsure how to answer this question, answer N. Otherwise,
2121
you should also prepend "landlock," to the content of CONFIG_LSM to
2222
enable Landlock at boot time.
23+
24+
config SECURITY_LANDLOCK_KUNIT_TEST
25+
bool "KUnit tests for Landlock" if !KUNIT_ALL_TESTS
26+
depends on KUNIT=y
27+
depends on SECURITY_LANDLOCK
28+
default KUNIT_ALL_TESTS
29+
help
30+
Build KUnit tests for Landlock.
31+
32+
See the KUnit documentation in Documentation/dev-tools/kunit
33+
34+
Run all KUnit tests for Landlock with:
35+
./tools/testing/kunit/kunit.py run --kunitconfig security/landlock
36+
37+
If you are unsure how to answer this question, answer N.

security/landlock/common.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,6 @@
1717

1818
#define pr_fmt(fmt) LANDLOCK_NAME ": " fmt
1919

20+
#define BIT_INDEX(bit) HWEIGHT(bit - 1)
21+
2022
#endif /* _SECURITY_LANDLOCK_COMMON_H */

security/landlock/fs.c

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* Copyright © 2021-2022 Microsoft Corporation
88
*/
99

10+
#include <kunit/test.h>
1011
#include <linux/atomic.h>
1112
#include <linux/bitops.h>
1213
#include <linux/bits.h>
@@ -311,6 +312,119 @@ static bool no_more_access(
311312
return true;
312313
}
313314

315+
#define NMA_TRUE(...) KUNIT_EXPECT_TRUE(test, no_more_access(__VA_ARGS__))
316+
#define NMA_FALSE(...) KUNIT_EXPECT_FALSE(test, no_more_access(__VA_ARGS__))
317+
318+
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
319+
320+
static void test_no_more_access(struct kunit *const test)
321+
{
322+
const layer_mask_t rx0[LANDLOCK_NUM_ACCESS_FS] = {
323+
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0),
324+
[BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = BIT_ULL(0),
325+
};
326+
const layer_mask_t mx0[LANDLOCK_NUM_ACCESS_FS] = {
327+
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0),
328+
[BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_REG)] = BIT_ULL(0),
329+
};
330+
const layer_mask_t x0[LANDLOCK_NUM_ACCESS_FS] = {
331+
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0),
332+
};
333+
const layer_mask_t x1[LANDLOCK_NUM_ACCESS_FS] = {
334+
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(1),
335+
};
336+
const layer_mask_t x01[LANDLOCK_NUM_ACCESS_FS] = {
337+
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0) |
338+
BIT_ULL(1),
339+
};
340+
const layer_mask_t allows_all[LANDLOCK_NUM_ACCESS_FS] = {};
341+
342+
/* Checks without restriction. */
343+
NMA_TRUE(&x0, &allows_all, false, &allows_all, NULL, false);
344+
NMA_TRUE(&allows_all, &x0, false, &allows_all, NULL, false);
345+
NMA_FALSE(&x0, &x0, false, &allows_all, NULL, false);
346+
347+
/*
348+
* Checks that we can only refer a file if no more access could be
349+
* inherited.
350+
*/
351+
NMA_TRUE(&x0, &x0, false, &rx0, NULL, false);
352+
NMA_TRUE(&rx0, &rx0, false, &rx0, NULL, false);
353+
NMA_FALSE(&rx0, &rx0, false, &x0, NULL, false);
354+
NMA_FALSE(&rx0, &rx0, false, &x1, NULL, false);
355+
356+
/* Checks allowed referring with different nested domains. */
357+
NMA_TRUE(&x0, &x1, false, &x0, NULL, false);
358+
NMA_TRUE(&x1, &x0, false, &x0, NULL, false);
359+
NMA_TRUE(&x0, &x01, false, &x0, NULL, false);
360+
NMA_TRUE(&x0, &x01, false, &rx0, NULL, false);
361+
NMA_TRUE(&x01, &x0, false, &x0, NULL, false);
362+
NMA_TRUE(&x01, &x0, false, &rx0, NULL, false);
363+
NMA_FALSE(&x01, &x01, false, &x0, NULL, false);
364+
365+
/* Checks that file access rights are also enforced for a directory. */
366+
NMA_FALSE(&rx0, &rx0, true, &x0, NULL, false);
367+
368+
/* Checks that directory access rights don't impact file referring... */
369+
NMA_TRUE(&mx0, &mx0, false, &x0, NULL, false);
370+
/* ...but only directory referring. */
371+
NMA_FALSE(&mx0, &mx0, true, &x0, NULL, false);
372+
373+
/* Checks directory exchange. */
374+
NMA_TRUE(&mx0, &mx0, true, &mx0, &mx0, true);
375+
NMA_TRUE(&mx0, &mx0, true, &mx0, &x0, true);
376+
NMA_FALSE(&mx0, &mx0, true, &x0, &mx0, true);
377+
NMA_FALSE(&mx0, &mx0, true, &x0, &x0, true);
378+
NMA_FALSE(&mx0, &mx0, true, &x1, &x1, true);
379+
380+
/* Checks file exchange with directory access rights... */
381+
NMA_TRUE(&mx0, &mx0, false, &mx0, &mx0, false);
382+
NMA_TRUE(&mx0, &mx0, false, &mx0, &x0, false);
383+
NMA_TRUE(&mx0, &mx0, false, &x0, &mx0, false);
384+
NMA_TRUE(&mx0, &mx0, false, &x0, &x0, false);
385+
/* ...and with file access rights. */
386+
NMA_TRUE(&rx0, &rx0, false, &rx0, &rx0, false);
387+
NMA_TRUE(&rx0, &rx0, false, &rx0, &x0, false);
388+
NMA_FALSE(&rx0, &rx0, false, &x0, &rx0, false);
389+
NMA_FALSE(&rx0, &rx0, false, &x0, &x0, false);
390+
NMA_FALSE(&rx0, &rx0, false, &x1, &x1, false);
391+
392+
/*
393+
* Allowing the following requests should not be a security risk
394+
* because domain 0 denies execute access, and domain 1 is always
395+
* nested with domain 0. However, adding an exception for this case
396+
* would mean to check all nested domains to make sure none can get
397+
* more privileges (e.g. processes only sandboxed by domain 0).
398+
* Moreover, this behavior (i.e. composition of N domains) could then
399+
* be inconsistent compared to domain 1's ruleset alone (e.g. it might
400+
* be denied to link/rename with domain 1's ruleset, whereas it would
401+
* be allowed if nested on top of domain 0). Another drawback would be
402+
* to create a cover channel that could enable sandboxed processes to
403+
* infer most of the filesystem restrictions from their domain. To
404+
* make it simple, efficient, safe, and more consistent, this case is
405+
* always denied.
406+
*/
407+
NMA_FALSE(&x1, &x1, false, &x0, NULL, false);
408+
NMA_FALSE(&x1, &x1, false, &rx0, NULL, false);
409+
NMA_FALSE(&x1, &x1, true, &x0, NULL, false);
410+
NMA_FALSE(&x1, &x1, true, &rx0, NULL, false);
411+
412+
/* Checks the same case of exclusive domains with a file... */
413+
NMA_TRUE(&x1, &x1, false, &x01, NULL, false);
414+
NMA_FALSE(&x1, &x1, false, &x01, &x0, false);
415+
NMA_FALSE(&x1, &x1, false, &x01, &x01, false);
416+
NMA_FALSE(&x1, &x1, false, &x0, &x0, false);
417+
/* ...and with a directory. */
418+
NMA_FALSE(&x1, &x1, false, &x0, &x0, true);
419+
NMA_FALSE(&x1, &x1, true, &x0, &x0, false);
420+
NMA_FALSE(&x1, &x1, true, &x0, &x0, true);
421+
}
422+
423+
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
424+
425+
#undef NMA_TRUE
426+
#undef NMA_FALSE
427+
314428
/*
315429
* Removes @layer_masks accesses that are not requested.
316430
*
@@ -331,6 +445,57 @@ scope_to_request(const access_mask_t access_request,
331445
return !memchr_inv(layer_masks, 0, sizeof(*layer_masks));
332446
}
333447

448+
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
449+
450+
static void test_scope_to_request_with_exec_none(struct kunit *const test)
451+
{
452+
/* Allows everything. */
453+
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
454+
455+
/* Checks and scopes with execute. */
456+
KUNIT_EXPECT_TRUE(test, scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE,
457+
&layer_masks));
458+
KUNIT_EXPECT_EQ(test, 0,
459+
layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)]);
460+
KUNIT_EXPECT_EQ(test, 0,
461+
layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)]);
462+
}
463+
464+
static void test_scope_to_request_with_exec_some(struct kunit *const test)
465+
{
466+
/* Denies execute and write. */
467+
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
468+
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0),
469+
[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = BIT_ULL(1),
470+
};
471+
472+
/* Checks and scopes with execute. */
473+
KUNIT_EXPECT_FALSE(test, scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE,
474+
&layer_masks));
475+
KUNIT_EXPECT_EQ(test, BIT_ULL(0),
476+
layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)]);
477+
KUNIT_EXPECT_EQ(test, 0,
478+
layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)]);
479+
}
480+
481+
static void test_scope_to_request_without_access(struct kunit *const test)
482+
{
483+
/* Denies execute and write. */
484+
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
485+
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0),
486+
[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = BIT_ULL(1),
487+
};
488+
489+
/* Checks and scopes without access request. */
490+
KUNIT_EXPECT_TRUE(test, scope_to_request(0, &layer_masks));
491+
KUNIT_EXPECT_EQ(test, 0,
492+
layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)]);
493+
KUNIT_EXPECT_EQ(test, 0,
494+
layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)]);
495+
}
496+
497+
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
498+
334499
/*
335500
* Returns true if there is at least one access right different than
336501
* LANDLOCK_ACCESS_FS_REFER.
@@ -354,6 +519,51 @@ is_eacces(const layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS],
354519
return false;
355520
}
356521

522+
#define IE_TRUE(...) KUNIT_EXPECT_TRUE(test, is_eacces(__VA_ARGS__))
523+
#define IE_FALSE(...) KUNIT_EXPECT_FALSE(test, is_eacces(__VA_ARGS__))
524+
525+
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
526+
527+
static void test_is_eacces_with_none(struct kunit *const test)
528+
{
529+
const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
530+
531+
IE_FALSE(&layer_masks, 0);
532+
IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_REFER);
533+
IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_EXECUTE);
534+
IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_WRITE_FILE);
535+
}
536+
537+
static void test_is_eacces_with_refer(struct kunit *const test)
538+
{
539+
const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
540+
[BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = BIT_ULL(0),
541+
};
542+
543+
IE_FALSE(&layer_masks, 0);
544+
IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_REFER);
545+
IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_EXECUTE);
546+
IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_WRITE_FILE);
547+
}
548+
549+
static void test_is_eacces_with_write(struct kunit *const test)
550+
{
551+
const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
552+
[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = BIT_ULL(0),
553+
};
554+
555+
IE_FALSE(&layer_masks, 0);
556+
IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_REFER);
557+
IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_EXECUTE);
558+
559+
IE_TRUE(&layer_masks, LANDLOCK_ACCESS_FS_WRITE_FILE);
560+
}
561+
562+
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
563+
564+
#undef IE_TRUE
565+
#undef IE_FALSE
566+
357567
/**
358568
* is_access_to_paths_allowed - Check accesses for requests with a common path
359569
*
@@ -1225,3 +1435,27 @@ __init void landlock_add_fs_hooks(void)
12251435
security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
12261436
&landlock_lsmid);
12271437
}
1438+
1439+
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
1440+
1441+
/* clang-format off */
1442+
static struct kunit_case test_cases[] = {
1443+
KUNIT_CASE(test_no_more_access),
1444+
KUNIT_CASE(test_scope_to_request_with_exec_none),
1445+
KUNIT_CASE(test_scope_to_request_with_exec_some),
1446+
KUNIT_CASE(test_scope_to_request_without_access),
1447+
KUNIT_CASE(test_is_eacces_with_none),
1448+
KUNIT_CASE(test_is_eacces_with_refer),
1449+
KUNIT_CASE(test_is_eacces_with_write),
1450+
{}
1451+
};
1452+
/* clang-format on */
1453+
1454+
static struct kunit_suite test_suite = {
1455+
.name = "landlock_fs",
1456+
.test_cases = test_cases,
1457+
};
1458+
1459+
kunit_test_suite(test_suite);
1460+
1461+
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */

tools/testing/kunit/configs/all_tests.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ CONFIG_REGMAP_BUILD=y
3737

3838
CONFIG_SECURITY=y
3939
CONFIG_SECURITY_APPARMOR=y
40+
CONFIG_SECURITY_LANDLOCK=y
4041

4142
CONFIG_SOUND=y
4243
CONFIG_SND=y

0 commit comments

Comments
 (0)