summaryrefslogtreecommitdiff
path: root/security/landlock
diff options
context:
space:
mode:
Diffstat (limited to 'security/landlock')
-rw-r--r--security/landlock/.kunitconfig4
-rw-r--r--security/landlock/Kconfig15
-rw-r--r--security/landlock/Makefile2
-rw-r--r--security/landlock/common.h2
-rw-r--r--security/landlock/fs.c252
-rw-r--r--security/landlock/net.c7
-rw-r--r--security/landlock/setup.c4
-rw-r--r--security/landlock/syscalls.c18
-rw-r--r--security/landlock/task.c (renamed from security/landlock/ptrace.c)4
-rw-r--r--security/landlock/task.h (renamed from security/landlock/ptrace.h)8
10 files changed, 293 insertions, 23 deletions
diff --git a/security/landlock/.kunitconfig b/security/landlock/.kunitconfig
new file mode 100644
index 000000000000..03e119466604
--- /dev/null
+++ b/security/landlock/.kunitconfig
@@ -0,0 +1,4 @@
+CONFIG_KUNIT=y
+CONFIG_SECURITY=y
+CONFIG_SECURITY_LANDLOCK=y
+CONFIG_SECURITY_LANDLOCK_KUNIT_TEST=y
diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
index c4bf0d5eff39..3f1493402052 100644
--- a/security/landlock/Kconfig
+++ b/security/landlock/Kconfig
@@ -20,3 +20,18 @@ config SECURITY_LANDLOCK
If you are unsure how to answer this question, answer N. Otherwise,
you should also prepend "landlock," to the content of CONFIG_LSM to
enable Landlock at boot time.
+
+config SECURITY_LANDLOCK_KUNIT_TEST
+ bool "KUnit tests for Landlock" if !KUNIT_ALL_TESTS
+ depends on KUNIT=y
+ depends on SECURITY_LANDLOCK
+ default KUNIT_ALL_TESTS
+ help
+ Build KUnit tests for Landlock.
+
+ See the KUnit documentation in Documentation/dev-tools/kunit
+
+ Run all KUnit tests for Landlock with:
+ ./tools/testing/kunit/kunit.py run --kunitconfig security/landlock
+
+ If you are unsure how to answer this question, answer N.
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index c2e116f2a299..b4538b7cf7d2 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -1,6 +1,6 @@
obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
landlock-y := setup.o syscalls.o object.o ruleset.o \
- cred.o ptrace.o fs.o
+ cred.o task.o fs.o
landlock-$(CONFIG_INET) += net.o
diff --git a/security/landlock/common.h b/security/landlock/common.h
index 5dc0fe15707d..0eb1d34c2eae 100644
--- a/security/landlock/common.h
+++ b/security/landlock/common.h
@@ -17,4 +17,6 @@
#define pr_fmt(fmt) LANDLOCK_NAME ": " fmt
+#define BIT_INDEX(bit) HWEIGHT(bit - 1)
+
#endif /* _SECURITY_LANDLOCK_COMMON_H */
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 0171f7eb6ee1..c15559432d3d 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -7,6 +7,7 @@
* Copyright © 2021-2022 Microsoft Corporation
*/
+#include <kunit/test.h>
#include <linux/atomic.h>
#include <linux/bitops.h>
#include <linux/bits.h>
@@ -247,15 +248,18 @@ get_handled_fs_accesses(const struct landlock_ruleset *const domain)
LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
}
-static const struct landlock_ruleset *get_current_fs_domain(void)
+static const struct landlock_ruleset *
+get_fs_domain(const struct landlock_ruleset *const domain)
{
- const struct landlock_ruleset *const dom =
- landlock_get_current_domain();
-
- if (!dom || !get_raw_handled_fs_accesses(dom))
+ if (!domain || !get_raw_handled_fs_accesses(domain))
return NULL;
- return dom;
+ return domain;
+}
+
+static const struct landlock_ruleset *get_current_fs_domain(void)
+{
+ return get_fs_domain(landlock_get_current_domain());
}
/*
@@ -311,6 +315,119 @@ static bool no_more_access(
return true;
}
+#define NMA_TRUE(...) KUNIT_EXPECT_TRUE(test, no_more_access(__VA_ARGS__))
+#define NMA_FALSE(...) KUNIT_EXPECT_FALSE(test, no_more_access(__VA_ARGS__))
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_no_more_access(struct kunit *const test)
+{
+ const layer_mask_t rx0[LANDLOCK_NUM_ACCESS_FS] = {
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0),
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = BIT_ULL(0),
+ };
+ const layer_mask_t mx0[LANDLOCK_NUM_ACCESS_FS] = {
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0),
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_REG)] = BIT_ULL(0),
+ };
+ const layer_mask_t x0[LANDLOCK_NUM_ACCESS_FS] = {
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0),
+ };
+ const layer_mask_t x1[LANDLOCK_NUM_ACCESS_FS] = {
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(1),
+ };
+ const layer_mask_t x01[LANDLOCK_NUM_ACCESS_FS] = {
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0) |
+ BIT_ULL(1),
+ };
+ const layer_mask_t allows_all[LANDLOCK_NUM_ACCESS_FS] = {};
+
+ /* Checks without restriction. */
+ NMA_TRUE(&x0, &allows_all, false, &allows_all, NULL, false);
+ NMA_TRUE(&allows_all, &x0, false, &allows_all, NULL, false);
+ NMA_FALSE(&x0, &x0, false, &allows_all, NULL, false);
+
+ /*
+ * Checks that we can only refer a file if no more access could be
+ * inherited.
+ */
+ NMA_TRUE(&x0, &x0, false, &rx0, NULL, false);
+ NMA_TRUE(&rx0, &rx0, false, &rx0, NULL, false);
+ NMA_FALSE(&rx0, &rx0, false, &x0, NULL, false);
+ NMA_FALSE(&rx0, &rx0, false, &x1, NULL, false);
+
+ /* Checks allowed referring with different nested domains. */
+ NMA_TRUE(&x0, &x1, false, &x0, NULL, false);
+ NMA_TRUE(&x1, &x0, false, &x0, NULL, false);
+ NMA_TRUE(&x0, &x01, false, &x0, NULL, false);
+ NMA_TRUE(&x0, &x01, false, &rx0, NULL, false);
+ NMA_TRUE(&x01, &x0, false, &x0, NULL, false);
+ NMA_TRUE(&x01, &x0, false, &rx0, NULL, false);
+ NMA_FALSE(&x01, &x01, false, &x0, NULL, false);
+
+ /* Checks that file access rights are also enforced for a directory. */
+ NMA_FALSE(&rx0, &rx0, true, &x0, NULL, false);
+
+ /* Checks that directory access rights don't impact file referring... */
+ NMA_TRUE(&mx0, &mx0, false, &x0, NULL, false);
+ /* ...but only directory referring. */
+ NMA_FALSE(&mx0, &mx0, true, &x0, NULL, false);
+
+ /* Checks directory exchange. */
+ NMA_TRUE(&mx0, &mx0, true, &mx0, &mx0, true);
+ NMA_TRUE(&mx0, &mx0, true, &mx0, &x0, true);
+ NMA_FALSE(&mx0, &mx0, true, &x0, &mx0, true);
+ NMA_FALSE(&mx0, &mx0, true, &x0, &x0, true);
+ NMA_FALSE(&mx0, &mx0, true, &x1, &x1, true);
+
+ /* Checks file exchange with directory access rights... */
+ NMA_TRUE(&mx0, &mx0, false, &mx0, &mx0, false);
+ NMA_TRUE(&mx0, &mx0, false, &mx0, &x0, false);
+ NMA_TRUE(&mx0, &mx0, false, &x0, &mx0, false);
+ NMA_TRUE(&mx0, &mx0, false, &x0, &x0, false);
+ /* ...and with file access rights. */
+ NMA_TRUE(&rx0, &rx0, false, &rx0, &rx0, false);
+ NMA_TRUE(&rx0, &rx0, false, &rx0, &x0, false);
+ NMA_FALSE(&rx0, &rx0, false, &x0, &rx0, false);
+ NMA_FALSE(&rx0, &rx0, false, &x0, &x0, false);
+ NMA_FALSE(&rx0, &rx0, false, &x1, &x1, false);
+
+ /*
+ * Allowing the following requests should not be a security risk
+ * because domain 0 denies execute access, and domain 1 is always
+ * nested with domain 0. However, adding an exception for this case
+ * would mean to check all nested domains to make sure none can get
+ * more privileges (e.g. processes only sandboxed by domain 0).
+ * Moreover, this behavior (i.e. composition of N domains) could then
+ * be inconsistent compared to domain 1's ruleset alone (e.g. it might
+ * be denied to link/rename with domain 1's ruleset, whereas it would
+ * be allowed if nested on top of domain 0). Another drawback would be
+ * to create a cover channel that could enable sandboxed processes to
+ * infer most of the filesystem restrictions from their domain. To
+ * make it simple, efficient, safe, and more consistent, this case is
+ * always denied.
+ */
+ NMA_FALSE(&x1, &x1, false, &x0, NULL, false);
+ NMA_FALSE(&x1, &x1, false, &rx0, NULL, false);
+ NMA_FALSE(&x1, &x1, true, &x0, NULL, false);
+ NMA_FALSE(&x1, &x1, true, &rx0, NULL, false);
+
+ /* Checks the same case of exclusive domains with a file... */
+ NMA_TRUE(&x1, &x1, false, &x01, NULL, false);
+ NMA_FALSE(&x1, &x1, false, &x01, &x0, false);
+ NMA_FALSE(&x1, &x1, false, &x01, &x01, false);
+ NMA_FALSE(&x1, &x1, false, &x0, &x0, false);
+ /* ...and with a directory. */
+ NMA_FALSE(&x1, &x1, false, &x0, &x0, true);
+ NMA_FALSE(&x1, &x1, true, &x0, &x0, false);
+ NMA_FALSE(&x1, &x1, true, &x0, &x0, true);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+#undef NMA_TRUE
+#undef NMA_FALSE
+
/*
* Removes @layer_masks accesses that are not requested.
*
@@ -331,6 +448,57 @@ scope_to_request(const access_mask_t access_request,
return !memchr_inv(layer_masks, 0, sizeof(*layer_masks));
}
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_scope_to_request_with_exec_none(struct kunit *const test)
+{
+ /* Allows everything. */
+ layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
+
+ /* Checks and scopes with execute. */
+ KUNIT_EXPECT_TRUE(test, scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE,
+ &layer_masks));
+ KUNIT_EXPECT_EQ(test, 0,
+ layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)]);
+ KUNIT_EXPECT_EQ(test, 0,
+ layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)]);
+}
+
+static void test_scope_to_request_with_exec_some(struct kunit *const test)
+{
+ /* Denies execute and write. */
+ layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0),
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = BIT_ULL(1),
+ };
+
+ /* Checks and scopes with execute. */
+ KUNIT_EXPECT_FALSE(test, scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE,
+ &layer_masks));
+ KUNIT_EXPECT_EQ(test, BIT_ULL(0),
+ layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)]);
+ KUNIT_EXPECT_EQ(test, 0,
+ layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)]);
+}
+
+static void test_scope_to_request_without_access(struct kunit *const test)
+{
+ /* Denies execute and write. */
+ layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0),
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = BIT_ULL(1),
+ };
+
+ /* Checks and scopes without access request. */
+ KUNIT_EXPECT_TRUE(test, scope_to_request(0, &layer_masks));
+ KUNIT_EXPECT_EQ(test, 0,
+ layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)]);
+ KUNIT_EXPECT_EQ(test, 0,
+ layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)]);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
/*
* Returns true if there is at least one access right different than
* LANDLOCK_ACCESS_FS_REFER.
@@ -354,6 +522,51 @@ is_eacces(const layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS],
return false;
}
+#define IE_TRUE(...) KUNIT_EXPECT_TRUE(test, is_eacces(__VA_ARGS__))
+#define IE_FALSE(...) KUNIT_EXPECT_FALSE(test, is_eacces(__VA_ARGS__))
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_is_eacces_with_none(struct kunit *const test)
+{
+ const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
+
+ IE_FALSE(&layer_masks, 0);
+ IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_REFER);
+ IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_EXECUTE);
+ IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_WRITE_FILE);
+}
+
+static void test_is_eacces_with_refer(struct kunit *const test)
+{
+ const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = BIT_ULL(0),
+ };
+
+ IE_FALSE(&layer_masks, 0);
+ IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_REFER);
+ IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_EXECUTE);
+ IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_WRITE_FILE);
+}
+
+static void test_is_eacces_with_write(struct kunit *const test)
+{
+ const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = BIT_ULL(0),
+ };
+
+ IE_FALSE(&layer_masks, 0);
+ IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_REFER);
+ IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_EXECUTE);
+
+ IE_TRUE(&layer_masks, LANDLOCK_ACCESS_FS_WRITE_FILE);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+#undef IE_TRUE
+#undef IE_FALSE
+
/**
* is_access_to_paths_allowed - Check accesses for requests with a common path
*
@@ -1124,7 +1337,8 @@ static int hook_file_open(struct file *const file)
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
access_mask_t open_access_request, full_access_request, allowed_access;
const access_mask_t optional_access = LANDLOCK_ACCESS_FS_TRUNCATE;
- const struct landlock_ruleset *const dom = get_current_fs_domain();
+ const struct landlock_ruleset *const dom =
+ get_fs_domain(landlock_cred(file->f_cred)->domain);
if (!dom)
return 0;
@@ -1225,3 +1439,27 @@ __init void landlock_add_fs_hooks(void)
security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
&landlock_lsmid);
}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+/* clang-format off */
+static struct kunit_case test_cases[] = {
+ KUNIT_CASE(test_no_more_access),
+ KUNIT_CASE(test_scope_to_request_with_exec_none),
+ KUNIT_CASE(test_scope_to_request_with_exec_some),
+ KUNIT_CASE(test_scope_to_request_without_access),
+ KUNIT_CASE(test_is_eacces_with_none),
+ KUNIT_CASE(test_is_eacces_with_refer),
+ KUNIT_CASE(test_is_eacces_with_write),
+ {}
+};
+/* clang-format on */
+
+static struct kunit_suite test_suite = {
+ .name = "landlock_fs",
+ .test_cases = test_cases,
+};
+
+kunit_test_suite(test_suite);
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
diff --git a/security/landlock/net.c b/security/landlock/net.c
index efa1b644a4af..c8bcd29bde09 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -64,12 +64,11 @@ static const struct landlock_ruleset *get_current_net_domain(void)
static int current_check_access_socket(struct socket *const sock,
struct sockaddr *const address,
const int addrlen,
- const access_mask_t access_request)
+ access_mask_t access_request)
{
__be16 port;
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
const struct landlock_rule *rule;
- access_mask_t handled_access;
struct landlock_id id = {
.type = LANDLOCK_KEY_NET_PORT,
};
@@ -164,9 +163,9 @@ static int current_check_access_socket(struct socket *const sock,
BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
rule = landlock_find_rule(dom, id);
- handled_access = landlock_init_layer_masks(
+ access_request = landlock_init_layer_masks(
dom, access_request, &layer_masks, LANDLOCK_KEY_NET_PORT);
- if (landlock_unmask_layers(rule, handled_access, &layer_masks,
+ if (landlock_unmask_layers(rule, access_request, &layer_masks,
ARRAY_SIZE(layer_masks)))
return 0;
diff --git a/security/landlock/setup.c b/security/landlock/setup.c
index f6dd33143b7f..28519a45b11f 100644
--- a/security/landlock/setup.c
+++ b/security/landlock/setup.c
@@ -14,8 +14,8 @@
#include "cred.h"
#include "fs.h"
#include "net.h"
-#include "ptrace.h"
#include "setup.h"
+#include "task.h"
bool landlock_initialized __ro_after_init = false;
@@ -34,7 +34,7 @@ const struct lsm_id landlock_lsmid = {
static int __init landlock_init(void)
{
landlock_add_cred_hooks();
- landlock_add_ptrace_hooks();
+ landlock_add_task_hooks();
landlock_add_fs_hooks();
landlock_add_net_hooks();
landlock_initialized = true;
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 898358f57fa0..6788e73b6681 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -33,6 +33,18 @@
#include "ruleset.h"
#include "setup.h"
+static bool is_initialized(void)
+{
+ if (likely(landlock_initialized))
+ return true;
+
+ pr_warn_once(
+ "Disabled but requested by user space. "
+ "You should enable Landlock at boot time: "
+ "https://docs.kernel.org/userspace-api/landlock.html#boot-time-configuration\n");
+ return false;
+}
+
/**
* copy_min_struct_from_user - Safe future-proof argument copying
*
@@ -173,7 +185,7 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
/* Build-time checks. */
build_check_abi();
- if (!landlock_initialized)
+ if (!is_initialized())
return -EOPNOTSUPP;
if (flags) {
@@ -398,7 +410,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
struct landlock_ruleset *ruleset;
int err;
- if (!landlock_initialized)
+ if (!is_initialized())
return -EOPNOTSUPP;
/* No flag for now. */
@@ -458,7 +470,7 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
struct landlock_cred_security *new_llcred;
int err;
- if (!landlock_initialized)
+ if (!is_initialized())
return -EOPNOTSUPP;
/*
diff --git a/security/landlock/ptrace.c b/security/landlock/task.c
index 2bfc533d36e4..849f5123610b 100644
--- a/security/landlock/ptrace.c
+++ b/security/landlock/task.c
@@ -16,9 +16,9 @@
#include "common.h"
#include "cred.h"
-#include "ptrace.h"
#include "ruleset.h"
#include "setup.h"
+#include "task.h"
/**
* domain_scope_le - Checks domain ordering for scoped ptrace
@@ -113,7 +113,7 @@ static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme),
};
-__init void landlock_add_ptrace_hooks(void)
+__init void landlock_add_task_hooks(void)
{
security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
&landlock_lsmid);
diff --git a/security/landlock/ptrace.h b/security/landlock/task.h
index 265b220ae3bf..7c00360219a2 100644
--- a/security/landlock/ptrace.h
+++ b/security/landlock/task.h
@@ -6,9 +6,9 @@
* Copyright © 2019 ANSSI
*/
-#ifndef _SECURITY_LANDLOCK_PTRACE_H
-#define _SECURITY_LANDLOCK_PTRACE_H
+#ifndef _SECURITY_LANDLOCK_TASK_H
+#define _SECURITY_LANDLOCK_TASK_H
-__init void landlock_add_ptrace_hooks(void);
+__init void landlock_add_task_hooks(void);
-#endif /* _SECURITY_LANDLOCK_PTRACE_H */
+#endif /* _SECURITY_LANDLOCK_TASK_H */