summaryrefslogtreecommitdiff
path: root/security/landlock
diff options
context:
space:
mode:
Diffstat (limited to 'security/landlock')
-rw-r--r--security/landlock/.kunitconfig6
-rw-r--r--security/landlock/Kconfig37
-rw-r--r--security/landlock/Makefile11
-rw-r--r--security/landlock/access.h100
-rw-r--r--security/landlock/audit.c522
-rw-r--r--security/landlock/audit.h76
-rw-r--r--security/landlock/common.h22
-rw-r--r--security/landlock/cred.c71
-rw-r--r--security/landlock/cred.h146
-rw-r--r--security/landlock/domain.c264
-rw-r--r--security/landlock/domain.h174
-rw-r--r--security/landlock/errata.h99
-rw-r--r--security/landlock/errata/abi-1.h16
-rw-r--r--security/landlock/errata/abi-4.h15
-rw-r--r--security/landlock/errata/abi-6.h19
-rw-r--r--security/landlock/fs.c1905
-rw-r--r--security/landlock/fs.h131
-rw-r--r--security/landlock/id.c295
-rw-r--r--security/landlock/id.h25
-rw-r--r--security/landlock/limits.h39
-rw-r--r--security/landlock/net.c222
-rw-r--r--security/landlock/net.h33
-rw-r--r--security/landlock/object.c67
-rw-r--r--security/landlock/object.h91
-rw-r--r--security/landlock/ruleset.c740
-rw-r--r--security/landlock/ruleset.h315
-rw-r--r--security/landlock/setup.c81
-rw-r--r--security/landlock/setup.h22
-rw-r--r--security/landlock/syscalls.c570
-rw-r--r--security/landlock/task.c448
-rw-r--r--security/landlock/task.h14
31 files changed, 6576 insertions, 0 deletions
diff --git a/security/landlock/.kunitconfig b/security/landlock/.kunitconfig
new file mode 100644
index 000000000000..f9423f01ac5b
--- /dev/null
+++ b/security/landlock/.kunitconfig
@@ -0,0 +1,6 @@
+CONFIG_AUDIT=y
+CONFIG_KUNIT=y
+CONFIG_NET=y
+CONFIG_SECURITY=y
+CONFIG_SECURITY_LANDLOCK=y
+CONFIG_SECURITY_LANDLOCK_KUNIT_TEST=y
diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
new file mode 100644
index 000000000000..3f1493402052
--- /dev/null
+++ b/security/landlock/Kconfig
@@ -0,0 +1,37 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config SECURITY_LANDLOCK
+ bool "Landlock support"
+ depends on SECURITY
+ select SECURITY_NETWORK
+ select SECURITY_PATH
+ help
+ Landlock is a sandboxing mechanism that enables processes to restrict
+ themselves (and their future children) by gradually enforcing
+ tailored access control policies. A Landlock security policy is a
+ set of access rights (e.g. open a file in read-only, make a
+ directory, etc.) tied to a file hierarchy. Such policy can be
+ configured and enforced by any processes for themselves using the
+ dedicated system calls: landlock_create_ruleset(),
+ landlock_add_rule(), and landlock_restrict_self().
+
+ See Documentation/userspace-api/landlock.rst for further information.
+
+ 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
new file mode 100644
index 000000000000..3160c2bdac1d
--- /dev/null
+++ b/security/landlock/Makefile
@@ -0,0 +1,11 @@
+obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
+
+landlock-y := setup.o syscalls.o object.o ruleset.o \
+ cred.o task.o fs.o
+
+landlock-$(CONFIG_INET) += net.o
+
+landlock-$(CONFIG_AUDIT) += \
+ id.o \
+ audit.o \
+ domain.o
diff --git a/security/landlock/access.h b/security/landlock/access.h
new file mode 100644
index 000000000000..7961c6630a2d
--- /dev/null
+++ b/security/landlock/access.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock - Access types and helpers
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+#ifndef _SECURITY_LANDLOCK_ACCESS_H
+#define _SECURITY_LANDLOCK_ACCESS_H
+
+#include <linux/bitops.h>
+#include <linux/build_bug.h>
+#include <linux/kernel.h>
+#include <uapi/linux/landlock.h>
+
+#include "limits.h"
+
+/*
+ * All access rights that are denied by default whether they are handled or not
+ * by a ruleset/layer. This must be ORed with all ruleset->access_masks[]
+ * entries when we need to get the absolute handled access masks, see
+ * landlock_upgrade_handled_access_masks().
+ */
+/* clang-format off */
+#define _LANDLOCK_ACCESS_FS_INITIALLY_DENIED ( \
+ LANDLOCK_ACCESS_FS_REFER)
+/* clang-format on */
+
+/* clang-format off */
+#define _LANDLOCK_ACCESS_FS_OPTIONAL ( \
+ LANDLOCK_ACCESS_FS_TRUNCATE | \
+ LANDLOCK_ACCESS_FS_IOCTL_DEV)
+/* clang-format on */
+
+typedef u16 access_mask_t;
+
+/* Makes sure all filesystem access rights can be stored. */
+static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
+/* Makes sure all network access rights can be stored. */
+static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET);
+/* Makes sure all scoped rights can be stored. */
+static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
+/* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
+static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
+
+/* Ruleset access masks. */
+struct access_masks {
+ access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
+ access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
+ access_mask_t scope : LANDLOCK_NUM_SCOPE;
+};
+
+union access_masks_all {
+ struct access_masks masks;
+ u32 all;
+};
+
+/* Makes sure all fields are covered. */
+static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
+ sizeof(typeof_member(union access_masks_all, all)));
+
+typedef u16 layer_mask_t;
+
+/* Makes sure all layers can be checked. */
+static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
+
+/*
+ * Tracks domains responsible of a denied access. This is required to avoid
+ * storing in each object the full layer_masks[] required by update_request().
+ */
+typedef u8 deny_masks_t;
+
+/*
+ * Makes sure all optional access rights can be tied to a layer index (cf.
+ * get_deny_mask).
+ */
+static_assert(BITS_PER_TYPE(deny_masks_t) >=
+ (HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1) *
+ HWEIGHT(_LANDLOCK_ACCESS_FS_OPTIONAL)));
+
+/* LANDLOCK_MAX_NUM_LAYERS must be a power of two (cf. deny_masks_t assert). */
+static_assert(HWEIGHT(LANDLOCK_MAX_NUM_LAYERS) == 1);
+
+/* Upgrades with all initially denied by default access rights. */
+static inline struct access_masks
+landlock_upgrade_handled_access_masks(struct access_masks access_masks)
+{
+ /*
+ * All access rights that are denied by default whether they are
+ * explicitly handled or not.
+ */
+ if (access_masks.fs)
+ access_masks.fs |= _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
+
+ return access_masks;
+}
+
+#endif /* _SECURITY_LANDLOCK_ACCESS_H */
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
new file mode 100644
index 000000000000..c52d079cdb77
--- /dev/null
+++ b/security/landlock/audit.c
@@ -0,0 +1,522 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock - Audit helpers
+ *
+ * Copyright © 2023-2025 Microsoft Corporation
+ */
+
+#include <kunit/test.h>
+#include <linux/audit.h>
+#include <linux/bitops.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 "ruleset.h"
+
+static const char *const fs_access_strings[] = {
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = "fs.execute",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = "fs.write_file",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = "fs.read_file",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = "fs.read_dir",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = "fs.remove_dir",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_FILE)] = "fs.remove_file",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_CHAR)] = "fs.make_char",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_DIR)] = "fs.make_dir",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_REG)] = "fs.make_reg",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SOCK)] = "fs.make_sock",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_FIFO)] = "fs.make_fifo",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_BLOCK)] = "fs.make_block",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SYM)] = "fs.make_sym",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = "fs.refer",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = "fs.truncate",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = "fs.ioctl_dev",
+};
+
+static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS);
+
+static const char *const net_access_strings[] = {
+ [BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_TCP)] = "net.bind_tcp",
+ [BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_TCP)] = "net.connect_tcp",
+};
+
+static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET);
+
+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 "fs.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.abstract_unix_socket";
+
+ case LANDLOCK_REQUEST_SCOPE_SIGNAL:
+ WARN_ON_ONCE(access_bit != -1);
+ return "scope.signal";
+ }
+
+ WARN_ON_ONCE(1);
+ return "unknown";
+}
+
+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;
+ 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", is_first ? "" : ",",
+ get_blocker(type, access_bit));
+ is_first = false;
+ }
+ if (is_first)
+ audit_log_format(ab, "%s", 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);
+}
+
+static struct landlock_hierarchy *
+get_hierarchy(const struct landlock_ruleset *const domain, const size_t layer)
+{
+ struct landlock_hierarchy *hierarchy = domain->hierarchy;
+ ssize_t i;
+
+ if (WARN_ON_ONCE(layer >= domain->num_layers))
+ return hierarchy;
+
+ for (i = domain->num_layers - 1; i > layer; i--) {
+ if (WARN_ON_ONCE(!hierarchy->parent))
+ break;
+
+ hierarchy = hierarchy->parent;
+ }
+
+ return hierarchy;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_get_hierarchy(struct kunit *const test)
+{
+ struct landlock_hierarchy dom0_hierarchy = {
+ .id = 10,
+ };
+ struct landlock_hierarchy dom1_hierarchy = {
+ .parent = &dom0_hierarchy,
+ .id = 20,
+ };
+ struct landlock_hierarchy dom2_hierarchy = {
+ .parent = &dom1_hierarchy,
+ .id = 30,
+ };
+ struct landlock_ruleset dom2 = {
+ .hierarchy = &dom2_hierarchy,
+ .num_layers = 3,
+ };
+
+ KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id);
+ KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id);
+ KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id);
+ /* KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id); */
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+static size_t get_denied_layer(const struct landlock_ruleset *const domain,
+ access_mask_t *const access_request,
+ const layer_mask_t (*const layer_masks)[],
+ const size_t layer_masks_size)
+{
+ const unsigned long access_req = *access_request;
+ unsigned long access_bit;
+ access_mask_t missing = 0;
+ long youngest_layer = -1;
+
+ for_each_set_bit(access_bit, &access_req, layer_masks_size) {
+ const access_mask_t mask = (*layer_masks)[access_bit];
+ long layer;
+
+ if (!mask)
+ continue;
+
+ /* __fls(1) == 0 */
+ layer = __fls(mask);
+ if (layer > youngest_layer) {
+ youngest_layer = layer;
+ missing = BIT(access_bit);
+ } else if (layer == youngest_layer) {
+ missing |= BIT(access_bit);
+ }
+ }
+
+ *access_request = missing;
+ if (youngest_layer == -1)
+ return domain->num_layers - 1;
+
+ return youngest_layer;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_get_denied_layer(struct kunit *const test)
+{
+ const struct landlock_ruleset dom = {
+ .num_layers = 5,
+ };
+ const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT(0),
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = BIT(1),
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = BIT(1) | BIT(0),
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = BIT(2),
+ };
+ access_mask_t access;
+
+ access = LANDLOCK_ACCESS_FS_EXECUTE;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_denied_layer(&dom, &access, &layer_masks,
+ sizeof(layer_masks)));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_EXECUTE);
+
+ access = LANDLOCK_ACCESS_FS_READ_FILE;
+ KUNIT_EXPECT_EQ(test, 1,
+ get_denied_layer(&dom, &access, &layer_masks,
+ sizeof(layer_masks)));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_FILE);
+
+ access = LANDLOCK_ACCESS_FS_READ_DIR;
+ KUNIT_EXPECT_EQ(test, 1,
+ get_denied_layer(&dom, &access, &layer_masks,
+ sizeof(layer_masks)));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
+
+ access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
+ KUNIT_EXPECT_EQ(test, 1,
+ get_denied_layer(&dom, &access, &layer_masks,
+ sizeof(layer_masks)));
+ KUNIT_EXPECT_EQ(test, access,
+ LANDLOCK_ACCESS_FS_READ_FILE |
+ LANDLOCK_ACCESS_FS_READ_DIR);
+
+ access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_DIR;
+ KUNIT_EXPECT_EQ(test, 1,
+ get_denied_layer(&dom, &access, &layer_masks,
+ sizeof(layer_masks)));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
+
+ access = LANDLOCK_ACCESS_FS_WRITE_FILE;
+ KUNIT_EXPECT_EQ(test, 4,
+ get_denied_layer(&dom, &access, &layer_masks,
+ sizeof(layer_masks)));
+ KUNIT_EXPECT_EQ(test, access, 0);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+static size_t
+get_layer_from_deny_masks(access_mask_t *const access_request,
+ const access_mask_t all_existing_optional_access,
+ const deny_masks_t deny_masks)
+{
+ const unsigned long access_opt = all_existing_optional_access;
+ const unsigned long access_req = *access_request;
+ access_mask_t missing = 0;
+ size_t youngest_layer = 0;
+ size_t access_index = 0;
+ unsigned long access_bit;
+
+ /* This will require change with new object types. */
+ WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL);
+
+ for_each_set_bit(access_bit, &access_opt,
+ BITS_PER_TYPE(access_mask_t)) {
+ if (access_req & BIT(access_bit)) {
+ const size_t layer =
+ (deny_masks >> (access_index * 4)) &
+ (LANDLOCK_MAX_NUM_LAYERS - 1);
+
+ if (layer > youngest_layer) {
+ youngest_layer = layer;
+ missing = BIT(access_bit);
+ } else if (layer == youngest_layer) {
+ missing |= BIT(access_bit);
+ }
+ }
+ access_index++;
+ }
+
+ *access_request = missing;
+ return youngest_layer;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_get_layer_from_deny_masks(struct kunit *const test)
+{
+ deny_masks_t deny_mask;
+ access_mask_t access;
+
+ /* truncate:0 ioctl_dev:2 */
+ deny_mask = 0x20;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_from_deny_masks(&access,
+ _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(&access,
+ _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+
+ /* truncate:15 ioctl_dev:15 */
+ deny_mask = 0xff;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 15,
+ get_layer_from_deny_masks(&access,
+ _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 15,
+ get_layer_from_deny_masks(&access,
+ _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask));
+ KUNIT_EXPECT_EQ(test, access,
+ LANDLOCK_ACCESS_FS_TRUNCATE |
+ LANDLOCK_ACCESS_FS_IOCTL_DEV);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+static bool is_valid_request(const struct landlock_request *const request)
+{
+ if (WARN_ON_ONCE(request->layer_plus_one > LANDLOCK_MAX_NUM_LAYERS))
+ return false;
+
+ if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access)))
+ return false;
+
+ if (request->access) {
+ if (WARN_ON_ONCE(!(!!request->layer_masks ^
+ !!request->all_existing_optional_access)))
+ return false;
+ } else {
+ if (WARN_ON_ONCE(request->layer_masks ||
+ request->all_existing_optional_access))
+ return false;
+ }
+
+ if (WARN_ON_ONCE(!!request->layer_masks ^ !!request->layer_masks_size))
+ return false;
+
+ if (request->deny_masks) {
+ if (WARN_ON_ONCE(!request->all_existing_optional_access))
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * landlock_log_denial - Create audit records related to a denial
+ *
+ * @subject: The Landlock subject's credential denying an action.
+ * @request: Detail of the user space request.
+ */
+void landlock_log_denial(const struct landlock_cred_security *const subject,
+ const struct landlock_request *const request)
+{
+ struct audit_buffer *ab;
+ struct landlock_hierarchy *youngest_denied;
+ size_t youngest_layer;
+ access_mask_t missing;
+
+ if (WARN_ON_ONCE(!subject || !subject->domain ||
+ !subject->domain->hierarchy || !request))
+ return;
+
+ if (!is_valid_request(request))
+ return;
+
+ missing = request->access;
+ if (missing) {
+ /* Gets the nearest domain that denies the request. */
+ if (request->layer_masks) {
+ youngest_layer = get_denied_layer(
+ subject->domain, &missing, request->layer_masks,
+ request->layer_masks_size);
+ } else {
+ youngest_layer = get_layer_from_deny_masks(
+ &missing, request->all_existing_optional_access,
+ request->deny_masks);
+ }
+ youngest_denied =
+ get_hierarchy(subject->domain, youngest_layer);
+ } else {
+ youngest_layer = request->layer_plus_one - 1;
+ youngest_denied =
+ get_hierarchy(subject->domain, youngest_layer);
+ }
+
+ if (READ_ONCE(youngest_denied->log_status) == LANDLOCK_LOG_DISABLED)
+ return;
+
+ /*
+ * Consistently keeps track of the number of denied access requests
+ * even if audit is currently disabled, or if audit rules currently
+ * exclude this record type, or if landlock_restrict_self(2)'s flags
+ * quiet logs.
+ */
+ atomic64_inc(&youngest_denied->num_denials);
+
+ if (!audit_enabled)
+ return;
+
+ /* Checks if the current exec was restricting itself. */
+ if (subject->domain_exec & BIT(youngest_layer)) {
+ /* Ignores denials for the same execution. */
+ if (!youngest_denied->log_same_exec)
+ return;
+ } else {
+ /* Ignores denials after a new execution. */
+ if (!youngest_denied->log_new_exec)
+ 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_log_drop_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 in a work queue scheduled by landlock_put_ruleset_deferred() called
+ * by hook_cred_free().
+ */
+void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)
+{
+ struct audit_buffer *ab;
+
+ if (WARN_ON_ONCE(!hierarchy))
+ return;
+
+ 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);
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static struct kunit_case test_cases[] = {
+ /* clang-format off */
+ KUNIT_CASE(test_get_hierarchy),
+ KUNIT_CASE(test_get_denied_layer),
+ KUNIT_CASE(test_get_layer_from_deny_masks),
+ {}
+ /* clang-format on */
+};
+
+static struct kunit_suite test_suite = {
+ .name = "landlock_audit",
+ .test_cases = test_cases,
+};
+
+kunit_test_suite(test_suite);
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
new file mode 100644
index 000000000000..92428b7fc4d8
--- /dev/null
+++ b/security/landlock/audit.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock - Audit helpers
+ *
+ * Copyright © 2023-2025 Microsoft Corporation
+ */
+
+#ifndef _SECURITY_LANDLOCK_AUDIT_H
+#define _SECURITY_LANDLOCK_AUDIT_H
+
+#include <linux/audit.h>
+#include <linux/lsm_audit.h>
+
+#include "access.h"
+#include "cred.h"
+
+enum landlock_request_type {
+ LANDLOCK_REQUEST_PTRACE = 1,
+ LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY,
+ LANDLOCK_REQUEST_FS_ACCESS,
+ LANDLOCK_REQUEST_NET_ACCESS,
+ LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
+ LANDLOCK_REQUEST_SCOPE_SIGNAL,
+};
+
+/*
+ * We should be careful to only use a variable of this type for
+ * landlock_log_denial(). This way, the compiler can remove it entirely if
+ * CONFIG_AUDIT is not set.
+ */
+struct landlock_request {
+ /* Mandatory fields. */
+ enum landlock_request_type type;
+ struct common_audit_data audit;
+
+ /**
+ * layer_plus_one: First layer level that denies the request + 1. The
+ * extra one is useful to detect uninitialized field.
+ */
+ size_t layer_plus_one;
+
+ /* Required field for configurable access control. */
+ access_mask_t access;
+
+ /* Required fields for requests with layer masks. */
+ const layer_mask_t (*layer_masks)[];
+ size_t layer_masks_size;
+
+ /* Required fields for requests with deny masks. */
+ const access_mask_t all_existing_optional_access;
+ deny_masks_t deny_masks;
+};
+
+#ifdef CONFIG_AUDIT
+
+void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy);
+
+void landlock_log_denial(const struct landlock_cred_security *const subject,
+ const struct landlock_request *const request);
+
+#else /* CONFIG_AUDIT */
+
+static inline void
+landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)
+{
+}
+
+static inline void
+landlock_log_denial(const struct landlock_cred_security *const subject,
+ const struct landlock_request *const request)
+{
+}
+
+#endif /* CONFIG_AUDIT */
+
+#endif /* _SECURITY_LANDLOCK_AUDIT_H */
diff --git a/security/landlock/common.h b/security/landlock/common.h
new file mode 100644
index 000000000000..0eb1d34c2eae
--- /dev/null
+++ b/security/landlock/common.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Common constants and helpers
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#ifndef _SECURITY_LANDLOCK_COMMON_H
+#define _SECURITY_LANDLOCK_COMMON_H
+
+#define LANDLOCK_NAME "landlock"
+
+#ifdef pr_fmt
+#undef pr_fmt
+#endif
+
+#define pr_fmt(fmt) LANDLOCK_NAME ": " fmt
+
+#define BIT_INDEX(bit) HWEIGHT(bit - 1)
+
+#endif /* _SECURITY_LANDLOCK_COMMON_H */
diff --git a/security/landlock/cred.c b/security/landlock/cred.c
new file mode 100644
index 000000000000..0cb3edde4d18
--- /dev/null
+++ b/security/landlock/cred.c
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock - Credential hooks
+ *
+ * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+#include <linux/binfmts.h>
+#include <linux/cred.h>
+#include <linux/lsm_hooks.h>
+
+#include "common.h"
+#include "cred.h"
+#include "ruleset.h"
+#include "setup.h"
+
+static void hook_cred_transfer(struct cred *const new,
+ const struct cred *const old)
+{
+ const struct landlock_cred_security *const old_llcred =
+ landlock_cred(old);
+
+ if (old_llcred->domain) {
+ landlock_get_ruleset(old_llcred->domain);
+ *landlock_cred(new) = *old_llcred;
+ }
+}
+
+static int hook_cred_prepare(struct cred *const new,
+ const struct cred *const old, const gfp_t gfp)
+{
+ hook_cred_transfer(new, old);
+ return 0;
+}
+
+static void hook_cred_free(struct cred *const cred)
+{
+ struct landlock_ruleset *const dom = landlock_cred(cred)->domain;
+
+ if (dom)
+ landlock_put_ruleset_deferred(dom);
+}
+
+#ifdef CONFIG_AUDIT
+
+static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm)
+{
+ /* Resets for each execution. */
+ landlock_cred(bprm->cred)->domain_exec = 0;
+ return 0;
+}
+
+#endif /* CONFIG_AUDIT */
+
+static struct security_hook_list landlock_hooks[] __ro_after_init = {
+ LSM_HOOK_INIT(cred_prepare, hook_cred_prepare),
+ LSM_HOOK_INIT(cred_transfer, hook_cred_transfer),
+ LSM_HOOK_INIT(cred_free, hook_cred_free),
+
+#ifdef CONFIG_AUDIT
+ LSM_HOOK_INIT(bprm_creds_for_exec, hook_bprm_creds_for_exec),
+#endif /* CONFIG_AUDIT */
+};
+
+__init void landlock_add_cred_hooks(void)
+{
+ security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
+ &landlock_lsmid);
+}
diff --git a/security/landlock/cred.h b/security/landlock/cred.h
new file mode 100644
index 000000000000..c82fe63ec598
--- /dev/null
+++ b/security/landlock/cred.h
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock - Credential hooks
+ *
+ * Copyright © 2019-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2019-2020 ANSSI
+ * Copyright © 2021-2025 Microsoft Corporation
+ */
+
+#ifndef _SECURITY_LANDLOCK_CRED_H
+#define _SECURITY_LANDLOCK_CRED_H
+
+#include <linux/container_of.h>
+#include <linux/cred.h>
+#include <linux/init.h>
+#include <linux/rcupdate.h>
+
+#include "access.h"
+#include "limits.h"
+#include "ruleset.h"
+#include "setup.h"
+
+/**
+ * struct landlock_cred_security - Credential security blob
+ *
+ * This structure is packed to minimize the size of struct
+ * landlock_file_security. However, it is always aligned in the LSM cred blob,
+ * see lsm_set_blob_size().
+ */
+struct landlock_cred_security {
+ /**
+ * @domain: Immutable ruleset enforced on a task.
+ */
+ struct landlock_ruleset *domain;
+
+#ifdef CONFIG_AUDIT
+ /**
+ * @domain_exec: Bitmask identifying the domain layers that were enforced by
+ * the current task's executed file (i.e. no new execve(2) since
+ * landlock_restrict_self(2)).
+ */
+ u16 domain_exec;
+ /**
+ * @log_subdomains_off: Set if the domain descendants's log_status should be
+ * set to %LANDLOCK_LOG_DISABLED. This is not a landlock_hierarchy
+ * configuration because it applies to future descendant domains and it does
+ * not require a current domain.
+ */
+ u8 log_subdomains_off : 1;
+#endif /* CONFIG_AUDIT */
+} __packed;
+
+#ifdef CONFIG_AUDIT
+
+/* Makes sure all layer executions can be stored. */
+static_assert(BITS_PER_TYPE(typeof_member(struct landlock_cred_security,
+ domain_exec)) >=
+ LANDLOCK_MAX_NUM_LAYERS);
+
+#endif /* CONFIG_AUDIT */
+
+static inline struct landlock_cred_security *
+landlock_cred(const struct cred *cred)
+{
+ return cred->security + landlock_blob_sizes.lbs_cred;
+}
+
+static inline struct landlock_ruleset *landlock_get_current_domain(void)
+{
+ return landlock_cred(current_cred())->domain;
+}
+
+/*
+ * The call needs to come from an RCU read-side critical section.
+ */
+static inline const struct landlock_ruleset *
+landlock_get_task_domain(const struct task_struct *const task)
+{
+ return landlock_cred(__task_cred(task))->domain;
+}
+
+static inline bool landlocked(const struct task_struct *const task)
+{
+ bool has_dom;
+
+ if (task == current)
+ return !!landlock_get_current_domain();
+
+ rcu_read_lock();
+ has_dom = !!landlock_get_task_domain(task);
+ rcu_read_unlock();
+ return has_dom;
+}
+
+/**
+ * landlock_get_applicable_subject - Return the subject's Landlock credential
+ * if its enforced domain applies to (i.e.
+ * handles) at least one of the access rights
+ * specified in @masks
+ *
+ * @cred: credential
+ * @masks: access masks
+ * @handle_layer: returned youngest layer handling a subset of @masks. Not set
+ * if the function returns NULL.
+ *
+ * Returns: landlock_cred(@cred) if any access rights specified in @masks is
+ * handled, or NULL otherwise.
+ */
+static inline const struct landlock_cred_security *
+landlock_get_applicable_subject(const struct cred *const cred,
+ const struct access_masks masks,
+ size_t *const handle_layer)
+{
+ const union access_masks_all masks_all = {
+ .masks = masks,
+ };
+ const struct landlock_ruleset *domain;
+ ssize_t layer_level;
+
+ if (!cred)
+ return NULL;
+
+ domain = landlock_cred(cred)->domain;
+ if (!domain)
+ return NULL;
+
+ for (layer_level = domain->num_layers - 1; layer_level >= 0;
+ layer_level--) {
+ union access_masks_all layer = {
+ .masks = domain->access_masks[layer_level],
+ };
+
+ if (layer.all & masks_all.all) {
+ if (handle_layer)
+ *handle_layer = layer_level;
+
+ return landlock_cred(cred);
+ }
+ }
+
+ return NULL;
+}
+
+__init void landlock_add_cred_hooks(void);
+
+#endif /* _SECURITY_LANDLOCK_CRED_H */
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
new file mode 100644
index 000000000000..a647b68e8d06
--- /dev/null
+++ b/security/landlock/domain.c
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock - Domain management
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+#include <kunit/test.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/cred.h>
+#include <linux/file.h>
+#include <linux/mm.h>
+#include <linux/path.h>
+#include <linux/pid.h>
+#include <linux/sched.h>
+#include <linux/signal.h>
+#include <linux/uidgid.h>
+
+#include "access.h"
+#include "common.h"
+#include "domain.h"
+#include "id.h"
+
+#ifdef CONFIG_AUDIT
+
+/**
+ * get_current_exe - Get the current's executable path, if any
+ *
+ * @exe_str: Returned pointer to a path string with a lifetime tied to the
+ * returned buffer, if any.
+ * @exe_size: Returned size of @exe_str (including the trailing null
+ * character), if any.
+ *
+ * Returns: A pointer to an allocated buffer where @exe_str point to, %NULL if
+ * there is no executable path, or an error otherwise.
+ */
+static const void *get_current_exe(const char **const exe_str,
+ size_t *const exe_size)
+{
+ const size_t buffer_size = LANDLOCK_PATH_MAX_SIZE;
+ struct mm_struct *mm = current->mm;
+ struct file *file __free(fput) = NULL;
+ char *buffer __free(kfree) = NULL;
+ const char *exe;
+ ssize_t size;
+
+ if (!mm)
+ return NULL;
+
+ file = get_mm_exe_file(mm);
+ if (!file)
+ return NULL;
+
+ buffer = kmalloc(buffer_size, GFP_KERNEL);
+ if (!buffer)
+ return ERR_PTR(-ENOMEM);
+
+ exe = d_path(&file->f_path, buffer, buffer_size);
+ if (WARN_ON_ONCE(IS_ERR(exe)))
+ /* Should never happen according to LANDLOCK_PATH_MAX_SIZE. */
+ return ERR_CAST(exe);
+
+ size = buffer + buffer_size - exe;
+ if (WARN_ON_ONCE(size <= 0))
+ return ERR_PTR(-ENAMETOOLONG);
+
+ *exe_size = size;
+ *exe_str = exe;
+ return no_free_ptr(buffer);
+}
+
+/*
+ * Returns: A newly allocated object describing a domain, or an error
+ * otherwise.
+ */
+static struct landlock_details *get_current_details(void)
+{
+ /* Cf. audit_log_d_path_exe() */
+ static const char null_path[] = "(null)";
+ const char *path_str = null_path;
+ size_t path_size = sizeof(null_path);
+ const void *buffer __free(kfree) = NULL;
+ struct landlock_details *details;
+
+ buffer = get_current_exe(&path_str, &path_size);
+ if (IS_ERR(buffer))
+ return ERR_CAST(buffer);
+
+ /*
+ * Create the new details according to the path's length. Do not
+ * allocate with GFP_KERNEL_ACCOUNT because it is independent from the
+ * caller.
+ */
+ details =
+ kzalloc(struct_size(details, exe_path, path_size), GFP_KERNEL);
+ if (!details)
+ return ERR_PTR(-ENOMEM);
+
+ memcpy(details->exe_path, path_str, path_size);
+ details->pid = get_pid(task_tgid(current));
+ details->uid = from_kuid(&init_user_ns, current_uid());
+ get_task_comm(details->comm, current);
+ return details;
+}
+
+/**
+ * landlock_init_hierarchy_log - Partially initialize landlock_hierarchy
+ *
+ * @hierarchy: The hierarchy to initialize.
+ *
+ * The current task is referenced as the domain that is enforcing the
+ * restriction. The subjective credentials must not be in an overridden state.
+ *
+ * @hierarchy->parent and @hierarchy->usage should already be set.
+ */
+int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy)
+{
+ struct landlock_details *details;
+
+ details = get_current_details();
+ if (IS_ERR(details))
+ return PTR_ERR(details);
+
+ hierarchy->details = details;
+ hierarchy->id = landlock_get_id_range(1);
+ hierarchy->log_status = LANDLOCK_LOG_PENDING;
+ hierarchy->log_same_exec = true;
+ hierarchy->log_new_exec = false;
+ atomic64_set(&hierarchy->num_denials, 0);
+ return 0;
+}
+
+static deny_masks_t
+get_layer_deny_mask(const access_mask_t all_existing_optional_access,
+ const unsigned long access_bit, const size_t layer)
+{
+ unsigned long access_weight;
+
+ /* This may require change with new object types. */
+ WARN_ON_ONCE(all_existing_optional_access !=
+ _LANDLOCK_ACCESS_FS_OPTIONAL);
+
+ if (WARN_ON_ONCE(layer >= LANDLOCK_MAX_NUM_LAYERS))
+ return 0;
+
+ access_weight = hweight_long(all_existing_optional_access &
+ GENMASK(access_bit, 0));
+ if (WARN_ON_ONCE(access_weight < 1))
+ return 0;
+
+ return layer
+ << ((access_weight - 1) * HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1));
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_get_layer_deny_mask(struct kunit *const test)
+{
+ const unsigned long truncate = BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE);
+ const unsigned long ioctl_dev = BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV);
+
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL,
+ truncate, 0));
+ KUNIT_EXPECT_EQ(test, 0x3,
+ get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL,
+ truncate, 3));
+
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL,
+ ioctl_dev, 0));
+ KUNIT_EXPECT_EQ(test, 0xf0,
+ get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL,
+ ioctl_dev, 15));
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+deny_masks_t
+landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
+ const access_mask_t optional_access,
+ const layer_mask_t (*const layer_masks)[],
+ const size_t layer_masks_size)
+{
+ const unsigned long access_opt = optional_access;
+ unsigned long access_bit;
+ deny_masks_t deny_masks = 0;
+
+ /* This may require change with new object types. */
+ WARN_ON_ONCE(access_opt !=
+ (optional_access & all_existing_optional_access));
+
+ if (WARN_ON_ONCE(!layer_masks))
+ return 0;
+
+ if (WARN_ON_ONCE(!access_opt))
+ return 0;
+
+ for_each_set_bit(access_bit, &access_opt, layer_masks_size) {
+ const layer_mask_t mask = (*layer_masks)[access_bit];
+
+ if (!mask)
+ continue;
+
+ /* __fls(1) == 0 */
+ deny_masks |= get_layer_deny_mask(all_existing_optional_access,
+ access_bit, __fls(mask));
+ }
+ return deny_masks;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void test_landlock_get_deny_masks(struct kunit *const test)
+{
+ const layer_mask_t layers1[BITS_PER_TYPE(access_mask_t)] = {
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0) |
+ BIT_ULL(9),
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = BIT_ULL(1),
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = BIT_ULL(2) |
+ BIT_ULL(0),
+ };
+
+ KUNIT_EXPECT_EQ(test, 0x1,
+ landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL,
+ LANDLOCK_ACCESS_FS_TRUNCATE,
+ &layers1, ARRAY_SIZE(layers1)));
+ KUNIT_EXPECT_EQ(test, 0x20,
+ landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL,
+ LANDLOCK_ACCESS_FS_IOCTL_DEV,
+ &layers1, ARRAY_SIZE(layers1)));
+ KUNIT_EXPECT_EQ(
+ test, 0x21,
+ landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL,
+ LANDLOCK_ACCESS_FS_TRUNCATE |
+ LANDLOCK_ACCESS_FS_IOCTL_DEV,
+ &layers1, ARRAY_SIZE(layers1)));
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static struct kunit_case test_cases[] = {
+ /* clang-format off */
+ KUNIT_CASE(test_get_layer_deny_mask),
+ KUNIT_CASE(test_landlock_get_deny_masks),
+ {}
+ /* clang-format on */
+};
+
+static struct kunit_suite test_suite = {
+ .name = "landlock_domain",
+ .test_cases = test_cases,
+};
+
+kunit_test_suite(test_suite);
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+#endif /* CONFIG_AUDIT */
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
new file mode 100644
index 000000000000..7fb70b25f85a
--- /dev/null
+++ b/security/landlock/domain.h
@@ -0,0 +1,174 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock - Domain management
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+#ifndef _SECURITY_LANDLOCK_DOMAIN_H
+#define _SECURITY_LANDLOCK_DOMAIN_H
+
+#include <linux/limits.h>
+#include <linux/mm.h>
+#include <linux/path.h>
+#include <linux/pid.h>
+#include <linux/refcount.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include "access.h"
+#include "audit.h"
+
+enum landlock_log_status {
+ LANDLOCK_LOG_PENDING = 0,
+ LANDLOCK_LOG_RECORDED,
+ LANDLOCK_LOG_DISABLED,
+};
+
+/**
+ * struct landlock_details - Domain's creation information
+ *
+ * Rarely accessed, mainly when logging the first domain's denial.
+ *
+ * The contained pointers are initialized at the domain creation time and never
+ * changed again. Contrary to most other Landlock object types, this one is
+ * not allocated with GFP_KERNEL_ACCOUNT because its size may not be under the
+ * caller's control (e.g. unknown exe_path) and the data is not explicitly
+ * requested nor used by tasks.
+ */
+struct landlock_details {
+ /**
+ * @pid: PID of the task that initially restricted itself. It still
+ * identifies the same task. Keeping a reference to this PID ensures that
+ * it will not be recycled.
+ */
+ struct pid *pid;
+ /**
+ * @uid: UID of the task that initially restricted itself, at creation time.
+ */
+ uid_t uid;
+ /**
+ * @comm: Command line of the task that initially restricted itself, at
+ * creation time. Always NULL terminated.
+ */
+ char comm[TASK_COMM_LEN];
+ /**
+ * @exe_path: Executable path of the task that initially restricted
+ * itself, at creation time. Always NULL terminated, and never greater
+ * than LANDLOCK_PATH_MAX_SIZE.
+ */
+ char exe_path[];
+};
+
+/* Adds 11 extra characters for the potential " (deleted)" suffix. */
+#define LANDLOCK_PATH_MAX_SIZE (PATH_MAX + 11)
+
+/* Makes sure the greatest landlock_details can be allocated. */
+static_assert(struct_size_t(struct landlock_details, exe_path,
+ LANDLOCK_PATH_MAX_SIZE) <= KMALLOC_MAX_SIZE);
+
+/**
+ * struct landlock_hierarchy - Node in a domain hierarchy
+ */
+struct landlock_hierarchy {
+ /**
+ * @parent: Pointer to the parent node, or NULL if it is a root
+ * Landlock domain.
+ */
+ struct landlock_hierarchy *parent;
+ /**
+ * @usage: Number of potential children domains plus their parent
+ * domain.
+ */
+ refcount_t usage;
+
+#ifdef CONFIG_AUDIT
+ /**
+ * @log_status: Whether this domain should be logged or not. Because
+ * concurrent log entries may be created at the same time, it is still
+ * possible to have several domain records of the same domain.
+ */
+ enum landlock_log_status log_status;
+ /**
+ * @num_denials: Number of access requests denied by this domain.
+ * Masked (i.e. never logged) denials are still counted.
+ */
+ atomic64_t num_denials;
+ /**
+ * @id: Landlock domain ID, sets once at domain creation time.
+ */
+ u64 id;
+ /**
+ * @details: Information about the related domain.
+ */
+ const struct landlock_details *details;
+ /**
+ * @log_same_exec: Set if the domain is *not* configured with
+ * %LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF. Set to true by default.
+ */
+ u32 log_same_exec : 1,
+ /**
+ * @log_new_exec: Set if the domain is configured with
+ * %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON. Set to false by default.
+ */
+ log_new_exec : 1;
+#endif /* CONFIG_AUDIT */
+};
+
+#ifdef CONFIG_AUDIT
+
+deny_masks_t
+landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
+ const access_mask_t optional_access,
+ const layer_mask_t (*const layer_masks)[],
+ size_t layer_masks_size);
+
+int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy);
+
+static inline void
+landlock_free_hierarchy_details(struct landlock_hierarchy *const hierarchy)
+{
+ if (!hierarchy || !hierarchy->details)
+ return;
+
+ put_pid(hierarchy->details->pid);
+ kfree(hierarchy->details);
+}
+
+#else /* CONFIG_AUDIT */
+
+static inline int
+landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy)
+{
+ return 0;
+}
+
+static inline void
+landlock_free_hierarchy_details(struct landlock_hierarchy *const hierarchy)
+{
+}
+
+#endif /* CONFIG_AUDIT */
+
+static inline void
+landlock_get_hierarchy(struct landlock_hierarchy *const hierarchy)
+{
+ if (hierarchy)
+ refcount_inc(&hierarchy->usage);
+}
+
+static inline void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy)
+{
+ while (hierarchy && refcount_dec_and_test(&hierarchy->usage)) {
+ const struct landlock_hierarchy *const freeme = hierarchy;
+
+ landlock_log_drop_domain(hierarchy);
+ landlock_free_hierarchy_details(hierarchy);
+ hierarchy = hierarchy->parent;
+ kfree(freeme);
+ }
+}
+
+#endif /* _SECURITY_LANDLOCK_DOMAIN_H */
diff --git a/security/landlock/errata.h b/security/landlock/errata.h
new file mode 100644
index 000000000000..8e626accac10
--- /dev/null
+++ b/security/landlock/errata.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock - Errata information
+ *
+ * Copyright © 2025 Microsoft Corporation
+ */
+
+#ifndef _SECURITY_LANDLOCK_ERRATA_H
+#define _SECURITY_LANDLOCK_ERRATA_H
+
+#include <linux/init.h>
+
+struct landlock_erratum {
+ const int abi;
+ const u8 number;
+};
+
+/* clang-format off */
+#define LANDLOCK_ERRATUM(NUMBER) \
+ { \
+ .abi = LANDLOCK_ERRATA_ABI, \
+ .number = NUMBER, \
+ },
+/* clang-format on */
+
+/*
+ * Some fixes may require user space to check if they are applied on the running
+ * kernel before using a specific feature. For instance, this applies when a
+ * restriction was previously too restrictive and is now getting relaxed (for
+ * compatibility or semantic reasons). However, non-visible changes for
+ * legitimate use (e.g. security fixes) do not require an erratum.
+ */
+static const struct landlock_erratum landlock_errata_init[] __initconst = {
+
+/*
+ * Only Sparse may not implement __has_include. If a compiler does not
+ * implement __has_include, a warning will be printed at boot time (see
+ * setup.c).
+ */
+#ifdef __has_include
+
+#define LANDLOCK_ERRATA_ABI 1
+#if __has_include("errata/abi-1.h")
+#include "errata/abi-1.h"
+#endif
+#undef LANDLOCK_ERRATA_ABI
+
+#define LANDLOCK_ERRATA_ABI 2
+#if __has_include("errata/abi-2.h")
+#include "errata/abi-2.h"
+#endif
+#undef LANDLOCK_ERRATA_ABI
+
+#define LANDLOCK_ERRATA_ABI 3
+#if __has_include("errata/abi-3.h")
+#include "errata/abi-3.h"
+#endif
+#undef LANDLOCK_ERRATA_ABI
+
+#define LANDLOCK_ERRATA_ABI 4
+#if __has_include("errata/abi-4.h")
+#include "errata/abi-4.h"
+#endif
+#undef LANDLOCK_ERRATA_ABI
+
+#define LANDLOCK_ERRATA_ABI 5
+#if __has_include("errata/abi-5.h")
+#include "errata/abi-5.h"
+#endif
+#undef LANDLOCK_ERRATA_ABI
+
+#define LANDLOCK_ERRATA_ABI 6
+#if __has_include("errata/abi-6.h")
+#include "errata/abi-6.h"
+#endif
+#undef LANDLOCK_ERRATA_ABI
+
+/*
+ * For each new erratum, we need to include all the ABI files up to the impacted
+ * ABI to make all potential future intermediate errata easy to backport.
+ *
+ * If such change involves more than one ABI addition, then it must be in a
+ * dedicated commit with the same Fixes tag as used for the actual fix.
+ *
+ * Each commit creating a new security/landlock/errata/abi-*.h file must have a
+ * Depends-on tag to reference the commit that previously added the line to
+ * include this new file, except if the original Fixes tag is enough.
+ *
+ * Each erratum must be documented in its related ABI file, and a dedicated
+ * commit must update Documentation/userspace-api/landlock.rst to include this
+ * erratum. This commit will not be backported.
+ */
+
+#endif
+
+ {}
+};
+
+#endif /* _SECURITY_LANDLOCK_ERRATA_H */
diff --git a/security/landlock/errata/abi-1.h b/security/landlock/errata/abi-1.h
new file mode 100644
index 000000000000..e8a2bff2e5b6
--- /dev/null
+++ b/security/landlock/errata/abi-1.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+/**
+ * DOC: erratum_3
+ *
+ * Erratum 3: Disconnected directory handling
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This fix addresses an issue with disconnected directories that occur when a
+ * directory is moved outside the scope of a bind mount. The change ensures
+ * that evaluated access rights include both those from the disconnected file
+ * hierarchy down to its filesystem root and those from the related mount point
+ * hierarchy. This prevents access right widening through rename or link
+ * actions.
+ */
+LANDLOCK_ERRATUM(3)
diff --git a/security/landlock/errata/abi-4.h b/security/landlock/errata/abi-4.h
new file mode 100644
index 000000000000..c052ee54f89f
--- /dev/null
+++ b/security/landlock/errata/abi-4.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+/**
+ * DOC: erratum_1
+ *
+ * Erratum 1: TCP socket identification
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This fix addresses an issue where IPv4 and IPv6 stream sockets (e.g., SMC,
+ * MPTCP, or SCTP) were incorrectly restricted by TCP access rights during
+ * :manpage:`bind(2)` and :manpage:`connect(2)` operations. This change ensures
+ * that only TCP sockets are subject to TCP access rights, allowing other
+ * protocols to operate without unnecessary restrictions.
+ */
+LANDLOCK_ERRATUM(1)
diff --git a/security/landlock/errata/abi-6.h b/security/landlock/errata/abi-6.h
new file mode 100644
index 000000000000..df7bc0e1fdf4
--- /dev/null
+++ b/security/landlock/errata/abi-6.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+/**
+ * DOC: erratum_2
+ *
+ * Erratum 2: Scoped signal handling
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This fix addresses an issue where signal scoping was overly restrictive,
+ * preventing sandboxed threads from signaling other threads within the same
+ * process if they belonged to different domains. Because threads are not
+ * security boundaries, user space might assume that any thread within the same
+ * process can send signals between themselves (see :manpage:`nptl(7)` and
+ * :manpage:`libpsx(3)`). Consistent with :manpage:`ptrace(2)` behavior, direct
+ * interaction between threads of the same process should always be allowed.
+ * This change ensures that any thread is allowed to send signals to any other
+ * thread within the same process, regardless of their domain.
+ */
+LANDLOCK_ERRATUM(2)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
new file mode 100644
index 000000000000..fe794875ad46
--- /dev/null
+++ b/security/landlock/fs.c
@@ -0,0 +1,1905 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock - Filesystem management and hooks
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ * Copyright © 2021-2025 Microsoft Corporation
+ * Copyright © 2022 Günther Noack <gnoack3000@gmail.com>
+ * Copyright © 2023-2024 Google LLC
+ */
+
+#include <asm/ioctls.h>
+#include <kunit/test.h>
+#include <linux/atomic.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/compiler_types.h>
+#include <linux/dcache.h>
+#include <linux/err.h>
+#include <linux/falloc.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/list.h>
+#include <linux/lsm_audit.h>
+#include <linux/lsm_hooks.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/path.h>
+#include <linux/pid.h>
+#include <linux/rcupdate.h>
+#include <linux/sched/signal.h>
+#include <linux/spinlock.h>
+#include <linux/stat.h>
+#include <linux/types.h>
+#include <linux/wait_bit.h>
+#include <linux/workqueue.h>
+#include <uapi/linux/fiemap.h>
+#include <uapi/linux/landlock.h>
+
+#include "access.h"
+#include "audit.h"
+#include "common.h"
+#include "cred.h"
+#include "domain.h"
+#include "fs.h"
+#include "limits.h"
+#include "object.h"
+#include "ruleset.h"
+#include "setup.h"
+
+/* Underlying object management */
+
+static void release_inode(struct landlock_object *const object)
+ __releases(object->lock)
+{
+ struct inode *const inode = object->underobj;
+ struct super_block *sb;
+
+ if (!inode) {
+ spin_unlock(&object->lock);
+ return;
+ }
+
+ /*
+ * Protects against concurrent use by hook_sb_delete() of the reference
+ * to the underlying inode.
+ */
+ object->underobj = NULL;
+ /*
+ * Makes sure that if the filesystem is concurrently unmounted,
+ * hook_sb_delete() will wait for us to finish iput().
+ */
+ sb = inode->i_sb;
+ atomic_long_inc(&landlock_superblock(sb)->inode_refs);
+ spin_unlock(&object->lock);
+ /*
+ * Because object->underobj was not NULL, hook_sb_delete() and
+ * get_inode_object() guarantee that it is safe to reset
+ * landlock_inode(inode)->object while it is not NULL. It is therefore
+ * not necessary to lock inode->i_lock.
+ */
+ rcu_assign_pointer(landlock_inode(inode)->object, NULL);
+ /*
+ * Now, new rules can safely be tied to @inode with get_inode_object().
+ */
+
+ iput(inode);
+ if (atomic_long_dec_and_test(&landlock_superblock(sb)->inode_refs))
+ wake_up_var(&landlock_superblock(sb)->inode_refs);
+}
+
+static const struct landlock_object_underops landlock_fs_underops = {
+ .release = release_inode
+};
+
+/* IOCTL helpers */
+
+/**
+ * is_masked_device_ioctl - Determine whether an IOCTL command is always
+ * permitted with Landlock for device files. These commands can not be
+ * restricted on device files by enforcing a Landlock policy.
+ *
+ * @cmd: The IOCTL command that is supposed to be run.
+ *
+ * By default, any IOCTL on a device file requires the
+ * LANDLOCK_ACCESS_FS_IOCTL_DEV right. However, we blanket-permit some
+ * commands, if:
+ *
+ * 1. The command is implemented in fs/ioctl.c's do_vfs_ioctl(),
+ * not in f_ops->unlocked_ioctl() or f_ops->compat_ioctl().
+ *
+ * 2. The command is harmless when invoked on devices.
+ *
+ * We also permit commands that do not make sense for devices, but where the
+ * do_vfs_ioctl() implementation returns a more conventional error code.
+ *
+ * Any new IOCTL commands that are implemented in fs/ioctl.c's do_vfs_ioctl()
+ * should be considered for inclusion here.
+ *
+ * Returns: true if the IOCTL @cmd can not be restricted with Landlock for
+ * device files.
+ */
+static __attribute_const__ bool is_masked_device_ioctl(const unsigned int cmd)
+{
+ switch (cmd) {
+ /*
+ * FIOCLEX, FIONCLEX, FIONBIO and FIOASYNC manipulate the FD's
+ * close-on-exec and the file's buffered-IO and async flags. These
+ * operations are also available through fcntl(2), and are
+ * unconditionally permitted in Landlock.
+ */
+ case FIOCLEX:
+ case FIONCLEX:
+ case FIONBIO:
+ case FIOASYNC:
+ /*
+ * FIOQSIZE queries the size of a regular file, directory, or link.
+ *
+ * We still permit it, because it always returns -ENOTTY for
+ * other file types.
+ */
+ case FIOQSIZE:
+ /*
+ * FIFREEZE and FITHAW freeze and thaw the file system which the
+ * given file belongs to. Requires CAP_SYS_ADMIN.
+ *
+ * These commands operate on the file system's superblock rather
+ * than on the file itself. The same operations can also be
+ * done through any other file or directory on the same file
+ * system, so it is safe to permit these.
+ */
+ case FIFREEZE:
+ case FITHAW:
+ /*
+ * FS_IOC_FIEMAP queries information about the allocation of
+ * blocks within a file.
+ *
+ * This IOCTL command only makes sense for regular files and is
+ * not implemented by devices. It is harmless to permit.
+ */
+ case FS_IOC_FIEMAP:
+ /*
+ * FIGETBSZ queries the file system's block size for a file or
+ * directory.
+ *
+ * This command operates on the file system's superblock rather
+ * than on the file itself. The same operation can also be done
+ * through any other file or directory on the same file system,
+ * so it is safe to permit it.
+ */
+ case FIGETBSZ:
+ /*
+ * FICLONE, FICLONERANGE and FIDEDUPERANGE make files share
+ * their underlying storage ("reflink") between source and
+ * destination FDs, on file systems which support that.
+ *
+ * These IOCTL commands only apply to regular files
+ * and are harmless to permit for device files.
+ */
+ case FICLONE:
+ case FICLONERANGE:
+ case FIDEDUPERANGE:
+ /*
+ * FS_IOC_GETFSUUID and FS_IOC_GETFSSYSFSPATH both operate on
+ * the file system superblock, not on the specific file, so
+ * these operations are available through any other file on the
+ * same file system as well.
+ */
+ case FS_IOC_GETFSUUID:
+ case FS_IOC_GETFSSYSFSPATH:
+ return true;
+
+ /*
+ * FIONREAD, FS_IOC_GETFLAGS, FS_IOC_SETFLAGS, FS_IOC_FSGETXATTR and
+ * FS_IOC_FSSETXATTR are forwarded to device implementations.
+ */
+
+ /*
+ * file_ioctl() commands (FIBMAP, FS_IOC_RESVSP, FS_IOC_RESVSP64,
+ * FS_IOC_UNRESVSP, FS_IOC_UNRESVSP64 and FS_IOC_ZERO_RANGE) are
+ * forwarded to device implementations, so not permitted.
+ */
+
+ /* Other commands are guarded by the access right. */
+ default:
+ return false;
+ }
+}
+
+/*
+ * is_masked_device_ioctl_compat - same as the helper above, but checking the
+ * "compat" IOCTL commands.
+ *
+ * The IOCTL commands with special handling in compat-mode should behave the
+ * same as their non-compat counterparts.
+ */
+static __attribute_const__ bool
+is_masked_device_ioctl_compat(const unsigned int cmd)
+{
+ switch (cmd) {
+ /* FICLONE is permitted, same as in the non-compat variant. */
+ case FICLONE:
+ return true;
+
+#if defined(CONFIG_X86_64)
+ /*
+ * FS_IOC_RESVSP_32, FS_IOC_RESVSP64_32, FS_IOC_UNRESVSP_32,
+ * FS_IOC_UNRESVSP64_32, FS_IOC_ZERO_RANGE_32: not blanket-permitted,
+ * for consistency with their non-compat variants.
+ */
+ case FS_IOC_RESVSP_32:
+ case FS_IOC_RESVSP64_32:
+ case FS_IOC_UNRESVSP_32:
+ case FS_IOC_UNRESVSP64_32:
+ case FS_IOC_ZERO_RANGE_32:
+#endif
+
+ /*
+ * FS_IOC32_GETFLAGS, FS_IOC32_SETFLAGS are forwarded to their device
+ * implementations.
+ */
+ case FS_IOC32_GETFLAGS:
+ case FS_IOC32_SETFLAGS:
+ return false;
+ default:
+ return is_masked_device_ioctl(cmd);
+ }
+}
+
+/* Ruleset management */
+
+static struct landlock_object *get_inode_object(struct inode *const inode)
+{
+ struct landlock_object *object, *new_object;
+ struct landlock_inode_security *inode_sec = landlock_inode(inode);
+
+ rcu_read_lock();
+retry:
+ object = rcu_dereference(inode_sec->object);
+ if (object) {
+ if (likely(refcount_inc_not_zero(&object->usage))) {
+ rcu_read_unlock();
+ return object;
+ }
+ /*
+ * We are racing with release_inode(), the object is going
+ * away. Wait for release_inode(), then retry.
+ */
+ spin_lock(&object->lock);
+ spin_unlock(&object->lock);
+ goto retry;
+ }
+ rcu_read_unlock();
+
+ /*
+ * If there is no object tied to @inode, then create a new one (without
+ * holding any locks).
+ */
+ new_object = landlock_create_object(&landlock_fs_underops, inode);
+ if (IS_ERR(new_object))
+ return new_object;
+
+ /*
+ * Protects against concurrent calls to get_inode_object() or
+ * hook_sb_delete().
+ */
+ spin_lock(&inode->i_lock);
+ if (unlikely(rcu_access_pointer(inode_sec->object))) {
+ /* Someone else just created the object, bail out and retry. */
+ spin_unlock(&inode->i_lock);
+ kfree(new_object);
+
+ rcu_read_lock();
+ goto retry;
+ }
+
+ /*
+ * @inode will be released by hook_sb_delete() on its superblock
+ * shutdown, or by release_inode() when no more ruleset references the
+ * related object.
+ */
+ ihold(inode);
+ rcu_assign_pointer(inode_sec->object, new_object);
+ spin_unlock(&inode->i_lock);
+ return new_object;
+}
+
+/* All access rights that can be tied to files. */
+/* clang-format off */
+#define ACCESS_FILE ( \
+ LANDLOCK_ACCESS_FS_EXECUTE | \
+ LANDLOCK_ACCESS_FS_WRITE_FILE | \
+ LANDLOCK_ACCESS_FS_READ_FILE | \
+ LANDLOCK_ACCESS_FS_TRUNCATE | \
+ LANDLOCK_ACCESS_FS_IOCTL_DEV)
+/* clang-format on */
+
+/*
+ * @path: Should have been checked by get_path_from_fd().
+ */
+int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
+ const struct path *const path,
+ access_mask_t access_rights)
+{
+ int err;
+ struct landlock_id id = {
+ .type = LANDLOCK_KEY_INODE,
+ };
+
+ /* Files only get access rights that make sense. */
+ if (!d_is_dir(path->dentry) &&
+ (access_rights | ACCESS_FILE) != ACCESS_FILE)
+ return -EINVAL;
+ if (WARN_ON_ONCE(ruleset->num_layers != 1))
+ return -EINVAL;
+
+ /* Transforms relative access rights to absolute ones. */
+ access_rights |= LANDLOCK_MASK_ACCESS_FS &
+ ~landlock_get_fs_access_mask(ruleset, 0);
+ id.key.object = get_inode_object(d_backing_inode(path->dentry));
+ if (IS_ERR(id.key.object))
+ return PTR_ERR(id.key.object);
+ mutex_lock(&ruleset->lock);
+ err = landlock_insert_rule(ruleset, id, access_rights);
+ mutex_unlock(&ruleset->lock);
+ /*
+ * No need to check for an error because landlock_insert_rule()
+ * increments the refcount for the new object if needed.
+ */
+ landlock_put_object(id.key.object);
+ return err;
+}
+
+/* Access-control management */
+
+/*
+ * The lifetime of the returned rule is tied to @domain.
+ *
+ * Returns NULL if no rule is found or if @dentry is negative.
+ */
+static const struct landlock_rule *
+find_rule(const struct landlock_ruleset *const domain,
+ const struct dentry *const dentry)
+{
+ const struct landlock_rule *rule;
+ const struct inode *inode;
+ struct landlock_id id = {
+ .type = LANDLOCK_KEY_INODE,
+ };
+
+ /* Ignores nonexistent leafs. */
+ if (d_is_negative(dentry))
+ return NULL;
+
+ inode = d_backing_inode(dentry);
+ rcu_read_lock();
+ id.key.object = rcu_dereference(landlock_inode(inode)->object);
+ rule = landlock_find_rule(domain, id);
+ rcu_read_unlock();
+ return rule;
+}
+
+/*
+ * Allows access to pseudo filesystems that will never be mountable (e.g.
+ * sockfs, pipefs), but can still be reachable through
+ * /proc/<pid>/fd/<file-descriptor>
+ */
+static bool is_nouser_or_private(const struct dentry *dentry)
+{
+ return (dentry->d_sb->s_flags & SB_NOUSER) ||
+ (d_is_positive(dentry) &&
+ unlikely(IS_PRIVATE(d_backing_inode(dentry))));
+}
+
+static const struct access_masks any_fs = {
+ .fs = ~0,
+};
+
+/*
+ * Check that a destination file hierarchy has more restrictions than a source
+ * file hierarchy. This is only used for link and rename actions.
+ *
+ * @layer_masks_child2: Optional child masks.
+ */
+static bool no_more_access(
+ const layer_mask_t (*const layer_masks_parent1)[LANDLOCK_NUM_ACCESS_FS],
+ const layer_mask_t (*const layer_masks_child1)[LANDLOCK_NUM_ACCESS_FS],
+ const bool child1_is_directory,
+ const layer_mask_t (*const layer_masks_parent2)[LANDLOCK_NUM_ACCESS_FS],
+ const layer_mask_t (*const layer_masks_child2)[LANDLOCK_NUM_ACCESS_FS],
+ const bool child2_is_directory)
+{
+ unsigned long access_bit;
+
+ for (access_bit = 0; access_bit < ARRAY_SIZE(*layer_masks_parent2);
+ access_bit++) {
+ /* Ignores accesses that only make sense for directories. */
+ const bool is_file_access =
+ !!(BIT_ULL(access_bit) & ACCESS_FILE);
+
+ if (child1_is_directory || is_file_access) {
+ /*
+ * Checks if the destination restrictions are a
+ * superset of the source ones (i.e. inherited access
+ * rights without child exceptions):
+ * restrictions(parent2) >= restrictions(child1)
+ */
+ if ((((*layer_masks_parent1)[access_bit] &
+ (*layer_masks_child1)[access_bit]) |
+ (*layer_masks_parent2)[access_bit]) !=
+ (*layer_masks_parent2)[access_bit])
+ return false;
+ }
+
+ if (!layer_masks_child2)
+ continue;
+ if (child2_is_directory || is_file_access) {
+ /*
+ * Checks inverted restrictions for RENAME_EXCHANGE:
+ * restrictions(parent1) >= restrictions(child2)
+ */
+ if ((((*layer_masks_parent2)[access_bit] &
+ (*layer_masks_child2)[access_bit]) |
+ (*layer_masks_parent1)[access_bit]) !=
+ (*layer_masks_parent1)[access_bit])
+ return false;
+ }
+ }
+ 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
+
+static bool is_layer_masks_allowed(
+ layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
+{
+ return !memchr_inv(layer_masks, 0, sizeof(*layer_masks));
+}
+
+/*
+ * Removes @layer_masks accesses that are not requested.
+ *
+ * Returns true if the request is allowed, false otherwise.
+ */
+static bool
+scope_to_request(const access_mask_t access_request,
+ layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
+{
+ const unsigned long access_req = access_request;
+ unsigned long access_bit;
+
+ if (WARN_ON_ONCE(!layer_masks))
+ return true;
+
+ for_each_clear_bit(access_bit, &access_req, ARRAY_SIZE(*layer_masks))
+ (*layer_masks)[access_bit] = 0;
+
+ return is_layer_masks_allowed(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.
+ */
+static bool
+is_eacces(const layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS],
+ const access_mask_t access_request)
+{
+ unsigned long access_bit;
+ /* LANDLOCK_ACCESS_FS_REFER alone must return -EXDEV. */
+ const unsigned long access_check = access_request &
+ ~LANDLOCK_ACCESS_FS_REFER;
+
+ if (!layer_masks)
+ return false;
+
+ for_each_set_bit(access_bit, &access_check, ARRAY_SIZE(*layer_masks)) {
+ if ((*layer_masks)[access_bit])
+ return true;
+ }
+ 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
+ *
+ * @domain: Domain to check against.
+ * @path: File hierarchy to walk through. For refer checks, this would be
+ * the common mountpoint.
+ * @access_request_parent1: Accesses to check, once @layer_masks_parent1 is
+ * equal to @layer_masks_parent2 (if any). This is tied to the unique
+ * requested path for most actions, or the source in case of a refer action
+ * (i.e. rename or link), or the source and destination in case of
+ * RENAME_EXCHANGE.
+ * @layer_masks_parent1: Pointer to a matrix of layer masks per access
+ * masks, identifying the layers that forbid a specific access. Bits from
+ * this matrix can be unset according to the @path walk. An empty matrix
+ * means that @domain allows all possible Landlock accesses (i.e. not only
+ * those identified by @access_request_parent1). This matrix can
+ * initially refer to domain layer masks and, when the accesses for the
+ * destination and source are the same, to requested layer masks.
+ * @log_request_parent1: Audit request to fill if the related access is denied.
+ * @dentry_child1: Dentry to the initial child of the parent1 path. This
+ * pointer must be NULL for non-refer actions (i.e. not link nor rename).
+ * @access_request_parent2: Similar to @access_request_parent1 but for a
+ * request involving a source and a destination. This refers to the
+ * destination, except in case of RENAME_EXCHANGE where it also refers to
+ * the source. Must be set to 0 when using a simple path request.
+ * @layer_masks_parent2: Similar to @layer_masks_parent1 but for a refer
+ * action. This must be NULL otherwise.
+ * @log_request_parent2: Audit request to fill if the related access is denied.
+ * @dentry_child2: Dentry to the initial child of the parent2 path. This
+ * pointer is only set for RENAME_EXCHANGE actions and must be NULL
+ * otherwise.
+ *
+ * This helper first checks that the destination has a superset of restrictions
+ * compared to the source (if any) for a common path. Because of
+ * RENAME_EXCHANGE actions, source and destinations may be swapped. It then
+ * checks that the collected accesses and the remaining ones are enough to
+ * allow the request.
+ *
+ * Returns:
+ * - true if the access request is granted;
+ * - false otherwise.
+ */
+static bool is_access_to_paths_allowed(
+ const struct landlock_ruleset *const domain,
+ const struct path *const path,
+ const access_mask_t access_request_parent1,
+ layer_mask_t (*const layer_masks_parent1)[LANDLOCK_NUM_ACCESS_FS],
+ struct landlock_request *const log_request_parent1,
+ struct dentry *const dentry_child1,
+ const access_mask_t access_request_parent2,
+ layer_mask_t (*const layer_masks_parent2)[LANDLOCK_NUM_ACCESS_FS],
+ struct landlock_request *const log_request_parent2,
+ struct dentry *const dentry_child2)
+{
+ bool allowed_parent1 = false, allowed_parent2 = false, is_dom_check,
+ child1_is_directory = true, child2_is_directory = true;
+ struct path walker_path;
+ access_mask_t access_masked_parent1, access_masked_parent2;
+ layer_mask_t _layer_masks_child1[LANDLOCK_NUM_ACCESS_FS],
+ _layer_masks_child2[LANDLOCK_NUM_ACCESS_FS];
+ layer_mask_t(*layer_masks_child1)[LANDLOCK_NUM_ACCESS_FS] = NULL,
+ (*layer_masks_child2)[LANDLOCK_NUM_ACCESS_FS] = NULL;
+
+ if (!access_request_parent1 && !access_request_parent2)
+ return true;
+
+ if (WARN_ON_ONCE(!path))
+ return true;
+
+ if (is_nouser_or_private(path->dentry))
+ return true;
+
+ if (WARN_ON_ONCE(!layer_masks_parent1))
+ return false;
+
+ allowed_parent1 = is_layer_masks_allowed(layer_masks_parent1);
+
+ if (unlikely(layer_masks_parent2)) {
+ if (WARN_ON_ONCE(!dentry_child1))
+ return false;
+
+ allowed_parent2 = is_layer_masks_allowed(layer_masks_parent2);
+
+ /*
+ * For a double request, first check for potential privilege
+ * escalation by looking at domain handled accesses (which are
+ * a superset of the meaningful requested accesses).
+ */
+ access_masked_parent1 = access_masked_parent2 =
+ landlock_union_access_masks(domain).fs;
+ is_dom_check = true;
+ } else {
+ if (WARN_ON_ONCE(dentry_child1 || dentry_child2))
+ return false;
+ /* For a simple request, only check for requested accesses. */
+ access_masked_parent1 = access_request_parent1;
+ access_masked_parent2 = access_request_parent2;
+ is_dom_check = false;
+ }
+
+ if (unlikely(dentry_child1)) {
+ landlock_unmask_layers(
+ find_rule(domain, dentry_child1),
+ landlock_init_layer_masks(
+ domain, LANDLOCK_MASK_ACCESS_FS,
+ &_layer_masks_child1, LANDLOCK_KEY_INODE),
+ &_layer_masks_child1, ARRAY_SIZE(_layer_masks_child1));
+ layer_masks_child1 = &_layer_masks_child1;
+ child1_is_directory = d_is_dir(dentry_child1);
+ }
+ if (unlikely(dentry_child2)) {
+ landlock_unmask_layers(
+ find_rule(domain, dentry_child2),
+ landlock_init_layer_masks(
+ domain, LANDLOCK_MASK_ACCESS_FS,
+ &_layer_masks_child2, LANDLOCK_KEY_INODE),
+ &_layer_masks_child2, ARRAY_SIZE(_layer_masks_child2));
+ layer_masks_child2 = &_layer_masks_child2;
+ child2_is_directory = d_is_dir(dentry_child2);
+ }
+
+ walker_path = *path;
+ path_get(&walker_path);
+ /*
+ * We need to walk through all the hierarchy to not miss any relevant
+ * restriction.
+ */
+ while (true) {
+ const struct landlock_rule *rule;
+
+ /*
+ * If at least all accesses allowed on the destination are
+ * already allowed on the source, respectively if there is at
+ * least as much as restrictions on the destination than on the
+ * source, then we can safely refer files from the source to
+ * the destination without risking a privilege escalation.
+ * This also applies in the case of RENAME_EXCHANGE, which
+ * implies checks on both direction. This is crucial for
+ * standalone multilayered security policies. Furthermore,
+ * this helps avoid policy writers to shoot themselves in the
+ * foot.
+ */
+ if (unlikely(is_dom_check &&
+ no_more_access(
+ layer_masks_parent1, layer_masks_child1,
+ child1_is_directory, layer_masks_parent2,
+ layer_masks_child2,
+ child2_is_directory))) {
+ /*
+ * Now, downgrades the remaining checks from domain
+ * handled accesses to requested accesses.
+ */
+ is_dom_check = false;
+ access_masked_parent1 = access_request_parent1;
+ access_masked_parent2 = access_request_parent2;
+
+ allowed_parent1 =
+ allowed_parent1 ||
+ scope_to_request(access_masked_parent1,
+ layer_masks_parent1);
+ allowed_parent2 =
+ allowed_parent2 ||
+ scope_to_request(access_masked_parent2,
+ layer_masks_parent2);
+
+ /* Stops when all accesses are granted. */
+ if (allowed_parent1 && allowed_parent2)
+ break;
+ }
+
+ rule = find_rule(domain, walker_path.dentry);
+ allowed_parent1 = allowed_parent1 ||
+ landlock_unmask_layers(
+ rule, access_masked_parent1,
+ layer_masks_parent1,
+ ARRAY_SIZE(*layer_masks_parent1));
+ allowed_parent2 = allowed_parent2 ||
+ landlock_unmask_layers(
+ rule, access_masked_parent2,
+ layer_masks_parent2,
+ ARRAY_SIZE(*layer_masks_parent2));
+
+ /* Stops when a rule from each layer grants access. */
+ if (allowed_parent1 && allowed_parent2)
+ break;
+
+jump_up:
+ if (walker_path.dentry == walker_path.mnt->mnt_root) {
+ if (follow_up(&walker_path)) {
+ /* Ignores hidden mount points. */
+ goto jump_up;
+ } else {
+ /*
+ * Stops at the real root. Denies access
+ * because not all layers have granted access.
+ */
+ break;
+ }
+ }
+
+ if (unlikely(IS_ROOT(walker_path.dentry))) {
+ if (likely(walker_path.mnt->mnt_flags & MNT_INTERNAL)) {
+ /*
+ * Stops and allows access when reaching disconnected root
+ * directories that are part of internal filesystems (e.g. nsfs,
+ * which is reachable through /proc/<pid>/ns/<namespace>).
+ */
+ allowed_parent1 = true;
+ allowed_parent2 = true;
+ break;
+ }
+
+ /*
+ * We reached a disconnected root directory from a bind mount.
+ * Let's continue the walk with the mount point we missed.
+ */
+ dput(walker_path.dentry);
+ walker_path.dentry = walker_path.mnt->mnt_root;
+ dget(walker_path.dentry);
+ } else {
+ struct dentry *const parent_dentry =
+ dget_parent(walker_path.dentry);
+
+ dput(walker_path.dentry);
+ walker_path.dentry = parent_dentry;
+ }
+ }
+ path_put(&walker_path);
+
+ if (!allowed_parent1) {
+ log_request_parent1->type = LANDLOCK_REQUEST_FS_ACCESS;
+ log_request_parent1->audit.type = LSM_AUDIT_DATA_PATH;
+ log_request_parent1->audit.u.path = *path;
+ log_request_parent1->access = access_masked_parent1;
+ log_request_parent1->layer_masks = layer_masks_parent1;
+ log_request_parent1->layer_masks_size =
+ ARRAY_SIZE(*layer_masks_parent1);
+ }
+
+ if (!allowed_parent2) {
+ log_request_parent2->type = LANDLOCK_REQUEST_FS_ACCESS;
+ log_request_parent2->audit.type = LSM_AUDIT_DATA_PATH;
+ log_request_parent2->audit.u.path = *path;
+ log_request_parent2->access = access_masked_parent2;
+ log_request_parent2->layer_masks = layer_masks_parent2;
+ log_request_parent2->layer_masks_size =
+ ARRAY_SIZE(*layer_masks_parent2);
+ }
+ return allowed_parent1 && allowed_parent2;
+}
+
+static int current_check_access_path(const struct path *const path,
+ access_mask_t access_request)
+{
+ const struct access_masks masks = {
+ .fs = access_request,
+ };
+ const struct landlock_cred_security *const subject =
+ landlock_get_applicable_subject(current_cred(), masks, NULL);
+ layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
+ struct landlock_request request = {};
+
+ if (!subject)
+ return 0;
+
+ access_request = landlock_init_layer_masks(subject->domain,
+ access_request, &layer_masks,
+ LANDLOCK_KEY_INODE);
+ if (is_access_to_paths_allowed(subject->domain, path, access_request,
+ &layer_masks, &request, NULL, 0, NULL,
+ NULL, NULL))
+ return 0;
+
+ landlock_log_denial(subject, &request);
+ return -EACCES;
+}
+
+static __attribute_const__ access_mask_t get_mode_access(const umode_t mode)
+{
+ switch (mode & S_IFMT) {
+ case S_IFLNK:
+ return LANDLOCK_ACCESS_FS_MAKE_SYM;
+ case S_IFDIR:
+ return LANDLOCK_ACCESS_FS_MAKE_DIR;
+ case S_IFCHR:
+ return LANDLOCK_ACCESS_FS_MAKE_CHAR;
+ case S_IFBLK:
+ return LANDLOCK_ACCESS_FS_MAKE_BLOCK;
+ case S_IFIFO:
+ return LANDLOCK_ACCESS_FS_MAKE_FIFO;
+ case S_IFSOCK:
+ return LANDLOCK_ACCESS_FS_MAKE_SOCK;
+ case S_IFREG:
+ case 0:
+ /* A zero mode translates to S_IFREG. */
+ default:
+ /* Treats weird files as regular files. */
+ return LANDLOCK_ACCESS_FS_MAKE_REG;
+ }
+}
+
+static access_mask_t maybe_remove(const struct dentry *const dentry)
+{
+ if (d_is_negative(dentry))
+ return 0;
+ return d_is_dir(dentry) ? LANDLOCK_ACCESS_FS_REMOVE_DIR :
+ LANDLOCK_ACCESS_FS_REMOVE_FILE;
+}
+
+/**
+ * collect_domain_accesses - Walk through a file path and collect accesses
+ *
+ * @domain: Domain to check against.
+ * @mnt_root: Last directory to check.
+ * @dir: Directory to start the walk from.
+ * @layer_masks_dom: Where to store the collected accesses.
+ *
+ * This helper is useful to begin a path walk from the @dir directory to a
+ * @mnt_root directory used as a mount point. This mount point is the common
+ * ancestor between the source and the destination of a renamed and linked
+ * file. While walking from @dir to @mnt_root, we record all the domain's
+ * allowed accesses in @layer_masks_dom.
+ *
+ * Because of disconnected directories, this walk may not reach @mnt_dir. In
+ * this case, the walk will continue to @mnt_dir after this call.
+ *
+ * This is similar to is_access_to_paths_allowed() but much simpler because it
+ * only handles walking on the same mount point and only checks one set of
+ * accesses.
+ *
+ * Returns:
+ * - true if all the domain access rights are allowed for @dir;
+ * - false if the walk reached @mnt_root.
+ */
+static bool collect_domain_accesses(
+ const struct landlock_ruleset *const domain,
+ const struct dentry *const mnt_root, struct dentry *dir,
+ layer_mask_t (*const layer_masks_dom)[LANDLOCK_NUM_ACCESS_FS])
+{
+ unsigned long access_dom;
+ bool ret = false;
+
+ if (WARN_ON_ONCE(!domain || !mnt_root || !dir || !layer_masks_dom))
+ return true;
+ if (is_nouser_or_private(dir))
+ return true;
+
+ access_dom = landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
+ layer_masks_dom,
+ LANDLOCK_KEY_INODE);
+
+ dget(dir);
+ while (true) {
+ struct dentry *parent_dentry;
+
+ /* Gets all layers allowing all domain accesses. */
+ if (landlock_unmask_layers(find_rule(domain, dir), access_dom,
+ layer_masks_dom,
+ ARRAY_SIZE(*layer_masks_dom))) {
+ /*
+ * Stops when all handled accesses are allowed by at
+ * least one rule in each layer.
+ */
+ ret = true;
+ break;
+ }
+
+ /*
+ * Stops at the mount point or the filesystem root for a disconnected
+ * directory.
+ */
+ if (dir == mnt_root || unlikely(IS_ROOT(dir)))
+ break;
+
+ parent_dentry = dget_parent(dir);
+ dput(dir);
+ dir = parent_dentry;
+ }
+ dput(dir);
+ return ret;
+}
+
+/**
+ * current_check_refer_path - Check if a rename or link action is allowed
+ *
+ * @old_dentry: File or directory requested to be moved or linked.
+ * @new_dir: Destination parent directory.
+ * @new_dentry: Destination file or directory.
+ * @removable: Sets to true if it is a rename operation.
+ * @exchange: Sets to true if it is a rename operation with RENAME_EXCHANGE.
+ *
+ * Because of its unprivileged constraints, Landlock relies on file hierarchies
+ * (and not only inodes) to tie access rights to files. Being able to link or
+ * rename a file hierarchy brings some challenges. Indeed, moving or linking a
+ * file (i.e. creating a new reference to an inode) can have an impact on the
+ * actions allowed for a set of files if it would change its parent directory
+ * (i.e. reparenting).
+ *
+ * To avoid trivial access right bypasses, Landlock first checks if the file or
+ * directory requested to be moved would gain new access rights inherited from
+ * its new hierarchy. Before returning any error, Landlock then checks that
+ * the parent source hierarchy and the destination hierarchy would allow the
+ * link or rename action. If it is not the case, an error with EACCES is
+ * returned to inform user space that there is no way to remove or create the
+ * requested source file type. If it should be allowed but the new inherited
+ * access rights would be greater than the source access rights, then the
+ * kernel returns an error with EXDEV. Prioritizing EACCES over EXDEV enables
+ * user space to abort the whole operation if there is no way to do it, or to
+ * manually copy the source to the destination if this remains allowed, e.g.
+ * because file creation is allowed on the destination directory but not direct
+ * linking.
+ *
+ * To achieve this goal, the kernel needs to compare two file hierarchies: the
+ * one identifying the source file or directory (including itself), and the
+ * destination one. This can be seen as a multilayer partial ordering problem.
+ * The kernel walks through these paths and collects in a matrix the access
+ * rights that are denied per layer. These matrices are then compared to see
+ * if the destination one has more (or the same) restrictions as the source
+ * one. If this is the case, the requested action will not return EXDEV, which
+ * doesn't mean the action is allowed. The parent hierarchy of the source
+ * (i.e. parent directory), and the destination hierarchy must also be checked
+ * to verify that they explicitly allow such action (i.e. referencing,
+ * creation and potentially removal rights). The kernel implementation is then
+ * required to rely on potentially four matrices of access rights: one for the
+ * source file or directory (i.e. the child), a potentially other one for the
+ * other source/destination (in case of RENAME_EXCHANGE), one for the source
+ * parent hierarchy and a last one for the destination hierarchy. These
+ * ephemeral matrices take some space on the stack, which limits the number of
+ * layers to a deemed reasonable number: 16.
+ *
+ * Returns:
+ * - 0 if access is allowed;
+ * - -EXDEV if @old_dentry would inherit new access rights from @new_dir;
+ * - -EACCES if file removal or creation is denied.
+ */
+static int current_check_refer_path(struct dentry *const old_dentry,
+ const struct path *const new_dir,
+ struct dentry *const new_dentry,
+ const bool removable, const bool exchange)
+{
+ const struct landlock_cred_security *const subject =
+ landlock_get_applicable_subject(current_cred(), any_fs, NULL);
+ bool allow_parent1, allow_parent2;
+ access_mask_t access_request_parent1, access_request_parent2;
+ struct path mnt_dir;
+ struct dentry *old_parent;
+ layer_mask_t layer_masks_parent1[LANDLOCK_NUM_ACCESS_FS] = {},
+ layer_masks_parent2[LANDLOCK_NUM_ACCESS_FS] = {};
+ struct landlock_request request1 = {}, request2 = {};
+
+ if (!subject)
+ return 0;
+
+ if (unlikely(d_is_negative(old_dentry)))
+ return -ENOENT;
+ if (exchange) {
+ if (unlikely(d_is_negative(new_dentry)))
+ return -ENOENT;
+ access_request_parent1 =
+ get_mode_access(d_backing_inode(new_dentry)->i_mode);
+ } else {
+ access_request_parent1 = 0;
+ }
+ access_request_parent2 =
+ get_mode_access(d_backing_inode(old_dentry)->i_mode);
+ if (removable) {
+ access_request_parent1 |= maybe_remove(old_dentry);
+ access_request_parent2 |= maybe_remove(new_dentry);
+ }
+
+ /* The mount points are the same for old and new paths, cf. EXDEV. */
+ if (old_dentry->d_parent == new_dir->dentry) {
+ /*
+ * The LANDLOCK_ACCESS_FS_REFER access right is not required
+ * for same-directory referer (i.e. no reparenting).
+ */
+ access_request_parent1 = landlock_init_layer_masks(
+ subject->domain,
+ access_request_parent1 | access_request_parent2,
+ &layer_masks_parent1, LANDLOCK_KEY_INODE);
+ if (is_access_to_paths_allowed(subject->domain, new_dir,
+ access_request_parent1,
+ &layer_masks_parent1, &request1,
+ NULL, 0, NULL, NULL, NULL))
+ return 0;
+
+ landlock_log_denial(subject, &request1);
+ return -EACCES;
+ }
+
+ access_request_parent1 |= LANDLOCK_ACCESS_FS_REFER;
+ access_request_parent2 |= LANDLOCK_ACCESS_FS_REFER;
+
+ /* Saves the common mount point. */
+ mnt_dir.mnt = new_dir->mnt;
+ mnt_dir.dentry = new_dir->mnt->mnt_root;
+
+ /*
+ * old_dentry may be the root of the common mount point and
+ * !IS_ROOT(old_dentry) at the same time (e.g. with open_tree() and
+ * OPEN_TREE_CLONE). We do not need to call dget(old_parent) because
+ * we keep a reference to old_dentry.
+ */
+ old_parent = (old_dentry == mnt_dir.dentry) ? old_dentry :
+ old_dentry->d_parent;
+
+ /* new_dir->dentry is equal to new_dentry->d_parent */
+ allow_parent1 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
+ old_parent,
+ &layer_masks_parent1);
+ allow_parent2 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
+ new_dir->dentry,
+ &layer_masks_parent2);
+
+ if (allow_parent1 && allow_parent2)
+ return 0;
+
+ /*
+ * To be able to compare source and destination domain access rights,
+ * take into account the @old_dentry access rights aggregated with its
+ * parent access rights. This will be useful to compare with the
+ * destination parent access rights.
+ */
+ if (is_access_to_paths_allowed(
+ subject->domain, &mnt_dir, access_request_parent1,
+ &layer_masks_parent1, &request1, old_dentry,
+ access_request_parent2, &layer_masks_parent2, &request2,
+ exchange ? new_dentry : NULL))
+ return 0;
+
+ if (request1.access) {
+ request1.audit.u.path.dentry = old_parent;
+ landlock_log_denial(subject, &request1);
+ }
+ if (request2.access) {
+ request2.audit.u.path.dentry = new_dir->dentry;
+ landlock_log_denial(subject, &request2);
+ }
+
+ /*
+ * This prioritizes EACCES over EXDEV for all actions, including
+ * renames with RENAME_EXCHANGE.
+ */
+ if (likely(is_eacces(&layer_masks_parent1, access_request_parent1) ||
+ is_eacces(&layer_masks_parent2, access_request_parent2)))
+ return -EACCES;
+
+ /*
+ * Gracefully forbids reparenting if the destination directory
+ * hierarchy is not a superset of restrictions of the source directory
+ * hierarchy, or if LANDLOCK_ACCESS_FS_REFER is not allowed by the
+ * source or the destination.
+ */
+ return -EXDEV;
+}
+
+/* Inode hooks */
+
+static void hook_inode_free_security_rcu(void *inode_security)
+{
+ struct landlock_inode_security *inode_sec;
+
+ /*
+ * All inodes must already have been untied from their object by
+ * release_inode() or hook_sb_delete().
+ */
+ inode_sec = inode_security + landlock_blob_sizes.lbs_inode;
+ WARN_ON_ONCE(inode_sec->object);
+}
+
+/* Super-block hooks */
+
+/*
+ * Release the inodes used in a security policy.
+ *
+ * Cf. fsnotify_unmount_inodes() and evict_inodes()
+ */
+static void hook_sb_delete(struct super_block *const sb)
+{
+ struct inode *inode, *prev_inode = NULL;
+
+ if (!landlock_initialized)
+ return;
+
+ spin_lock(&sb->s_inode_list_lock);
+ list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
+ struct landlock_object *object;
+
+ /* Only handles referenced inodes. */
+ if (!icount_read(inode))
+ continue;
+
+ /*
+ * Protects against concurrent modification of inode (e.g.
+ * from get_inode_object()).
+ */
+ spin_lock(&inode->i_lock);
+ /*
+ * Checks I_FREEING and I_WILL_FREE to protect against a race
+ * condition when release_inode() just called iput(), which
+ * could lead to a NULL dereference of inode->security or a
+ * second call to iput() for the same Landlock object. Also
+ * checks I_NEW because such inode cannot be tied to an object.
+ */
+ if (inode_state_read(inode) & (I_FREEING | I_WILL_FREE | I_NEW)) {
+ spin_unlock(&inode->i_lock);
+ continue;
+ }
+
+ rcu_read_lock();
+ object = rcu_dereference(landlock_inode(inode)->object);
+ if (!object) {
+ rcu_read_unlock();
+ spin_unlock(&inode->i_lock);
+ continue;
+ }
+ /* Keeps a reference to this inode until the next loop walk. */
+ __iget(inode);
+ spin_unlock(&inode->i_lock);
+
+ /*
+ * If there is no concurrent release_inode() ongoing, then we
+ * are in charge of calling iput() on this inode, otherwise we
+ * will just wait for it to finish.
+ */
+ spin_lock(&object->lock);
+ if (object->underobj == inode) {
+ object->underobj = NULL;
+ spin_unlock(&object->lock);
+ rcu_read_unlock();
+
+ /*
+ * Because object->underobj was not NULL,
+ * release_inode() and get_inode_object() guarantee
+ * that it is safe to reset
+ * landlock_inode(inode)->object while it is not NULL.
+ * It is therefore not necessary to lock inode->i_lock.
+ */
+ rcu_assign_pointer(landlock_inode(inode)->object, NULL);
+ /*
+ * At this point, we own the ihold() reference that was
+ * originally set up by get_inode_object() and the
+ * __iget() reference that we just set in this loop
+ * walk. Therefore there are at least two references
+ * on the inode.
+ */
+ iput_not_last(inode);
+ } else {
+ spin_unlock(&object->lock);
+ rcu_read_unlock();
+ }
+
+ if (prev_inode) {
+ /*
+ * At this point, we still own the __iget() reference
+ * that we just set in this loop walk. Therefore we
+ * can drop the list lock and know that the inode won't
+ * disappear from under us until the next loop walk.
+ */
+ spin_unlock(&sb->s_inode_list_lock);
+ /*
+ * We can now actually put the inode reference from the
+ * previous loop walk, which is not needed anymore.
+ */
+ iput(prev_inode);
+ cond_resched();
+ spin_lock(&sb->s_inode_list_lock);
+ }
+ prev_inode = inode;
+ }
+ spin_unlock(&sb->s_inode_list_lock);
+
+ /* Puts the inode reference from the last loop walk, if any. */
+ if (prev_inode)
+ iput(prev_inode);
+ /* Waits for pending iput() in release_inode(). */
+ wait_var_event(&landlock_superblock(sb)->inode_refs,
+ !atomic_long_read(&landlock_superblock(sb)->inode_refs));
+}
+
+static void
+log_fs_change_topology_path(const struct landlock_cred_security *const subject,
+ size_t handle_layer, const struct path *const path)
+{
+ landlock_log_denial(subject, &(struct landlock_request) {
+ .type = LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY,
+ .audit = {
+ .type = LSM_AUDIT_DATA_PATH,
+ .u.path = *path,
+ },
+ .layer_plus_one = handle_layer + 1,
+ });
+}
+
+static void log_fs_change_topology_dentry(
+ const struct landlock_cred_security *const subject, size_t handle_layer,
+ struct dentry *const dentry)
+{
+ landlock_log_denial(subject, &(struct landlock_request) {
+ .type = LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY,
+ .audit = {
+ .type = LSM_AUDIT_DATA_DENTRY,
+ .u.dentry = dentry,
+ },
+ .layer_plus_one = handle_layer + 1,
+ });
+}
+
+/*
+ * Because a Landlock security policy is defined according to the filesystem
+ * topology (i.e. the mount namespace), changing it may grant access to files
+ * not previously allowed.
+ *
+ * To make it simple, deny any filesystem topology modification by landlocked
+ * processes. Non-landlocked processes may still change the namespace of a
+ * landlocked process, but this kind of threat must be handled by a system-wide
+ * access-control security policy.
+ *
+ * This could be lifted in the future if Landlock can safely handle mount
+ * namespace updates requested by a landlocked process. Indeed, we could
+ * update the current domain (which is currently read-only) by taking into
+ * account the accesses of the source and the destination of a new mount point.
+ * However, it would also require to make all the child domains dynamically
+ * inherit these new constraints. Anyway, for backward compatibility reasons,
+ * a dedicated user space option would be required (e.g. as a ruleset flag).
+ */
+static int hook_sb_mount(const char *const dev_name,
+ const struct path *const path, const char *const type,
+ const unsigned long flags, void *const data)
+{
+ size_t handle_layer;
+ const struct landlock_cred_security *const subject =
+ landlock_get_applicable_subject(current_cred(), any_fs,
+ &handle_layer);
+
+ if (!subject)
+ return 0;
+
+ log_fs_change_topology_path(subject, handle_layer, path);
+ return -EPERM;
+}
+
+static int hook_move_mount(const struct path *const from_path,
+ const struct path *const to_path)
+{
+ size_t handle_layer;
+ const struct landlock_cred_security *const subject =
+ landlock_get_applicable_subject(current_cred(), any_fs,
+ &handle_layer);
+
+ if (!subject)
+ return 0;
+
+ log_fs_change_topology_path(subject, handle_layer, to_path);
+ return -EPERM;
+}
+
+/*
+ * Removing a mount point may reveal a previously hidden file hierarchy, which
+ * may then grant access to files, which may have previously been forbidden.
+ */
+static int hook_sb_umount(struct vfsmount *const mnt, const int flags)
+{
+ size_t handle_layer;
+ const struct landlock_cred_security *const subject =
+ landlock_get_applicable_subject(current_cred(), any_fs,
+ &handle_layer);
+
+ if (!subject)
+ return 0;
+
+ log_fs_change_topology_dentry(subject, handle_layer, mnt->mnt_root);
+ return -EPERM;
+}
+
+static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts)
+{
+ size_t handle_layer;
+ const struct landlock_cred_security *const subject =
+ landlock_get_applicable_subject(current_cred(), any_fs,
+ &handle_layer);
+
+ if (!subject)
+ return 0;
+
+ log_fs_change_topology_dentry(subject, handle_layer, sb->s_root);
+ return -EPERM;
+}
+
+/*
+ * pivot_root(2), like mount(2), changes the current mount namespace. It must
+ * then be forbidden for a landlocked process.
+ *
+ * However, chroot(2) may be allowed because it only changes the relative root
+ * directory of the current process. Moreover, it can be used to restrict the
+ * view of the filesystem.
+ */
+static int hook_sb_pivotroot(const struct path *const old_path,
+ const struct path *const new_path)
+{
+ size_t handle_layer;
+ const struct landlock_cred_security *const subject =
+ landlock_get_applicable_subject(current_cred(), any_fs,
+ &handle_layer);
+
+ if (!subject)
+ return 0;
+
+ log_fs_change_topology_path(subject, handle_layer, new_path);
+ return -EPERM;
+}
+
+/* Path hooks */
+
+static int hook_path_link(struct dentry *const old_dentry,
+ const struct path *const new_dir,
+ struct dentry *const new_dentry)
+{
+ return current_check_refer_path(old_dentry, new_dir, new_dentry, false,
+ false);
+}
+
+static int hook_path_rename(const struct path *const old_dir,
+ struct dentry *const old_dentry,
+ const struct path *const new_dir,
+ struct dentry *const new_dentry,
+ const unsigned int flags)
+{
+ /* old_dir refers to old_dentry->d_parent and new_dir->mnt */
+ return current_check_refer_path(old_dentry, new_dir, new_dentry, true,
+ !!(flags & RENAME_EXCHANGE));
+}
+
+static int hook_path_mkdir(const struct path *const dir,
+ struct dentry *const dentry, const umode_t mode)
+{
+ return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_DIR);
+}
+
+static int hook_path_mknod(const struct path *const dir,
+ struct dentry *const dentry, const umode_t mode,
+ const unsigned int dev)
+{
+ return current_check_access_path(dir, get_mode_access(mode));
+}
+
+static int hook_path_symlink(const struct path *const dir,
+ struct dentry *const dentry,
+ const char *const old_name)
+{
+ return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_SYM);
+}
+
+static int hook_path_unlink(const struct path *const dir,
+ struct dentry *const dentry)
+{
+ return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_FILE);
+}
+
+static int hook_path_rmdir(const struct path *const dir,
+ struct dentry *const dentry)
+{
+ return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_DIR);
+}
+
+static int hook_path_truncate(const struct path *const path)
+{
+ return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE);
+}
+
+/* File hooks */
+
+/**
+ * get_required_file_open_access - Get access needed to open a file
+ *
+ * @file: File being opened.
+ *
+ * Returns the access rights that are required for opening the given file,
+ * depending on the file type and open mode.
+ */
+static access_mask_t
+get_required_file_open_access(const struct file *const file)
+{
+ access_mask_t access = 0;
+
+ if (file->f_mode & FMODE_READ) {
+ /* A directory can only be opened in read mode. */
+ if (S_ISDIR(file_inode(file)->i_mode))
+ return LANDLOCK_ACCESS_FS_READ_DIR;
+ access = LANDLOCK_ACCESS_FS_READ_FILE;
+ }
+ if (file->f_mode & FMODE_WRITE)
+ access |= LANDLOCK_ACCESS_FS_WRITE_FILE;
+ /* __FMODE_EXEC is indeed part of f_flags, not f_mode. */
+ if (file->f_flags & __FMODE_EXEC)
+ access |= LANDLOCK_ACCESS_FS_EXECUTE;
+ return access;
+}
+
+static int hook_file_alloc_security(struct file *const file)
+{
+ /*
+ * Grants all access rights, even if most of them are not checked later
+ * on. It is more consistent.
+ *
+ * Notably, file descriptors for regular files can also be acquired
+ * without going through the file_open hook, for example when using
+ * memfd_create(2).
+ */
+ landlock_file(file)->allowed_access = LANDLOCK_MASK_ACCESS_FS;
+ return 0;
+}
+
+static bool is_device(const struct file *const file)
+{
+ const struct inode *inode = file_inode(file);
+
+ return S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode);
+}
+
+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,
+ optional_access;
+ const struct landlock_cred_security *const subject =
+ landlock_get_applicable_subject(file->f_cred, any_fs, NULL);
+ struct landlock_request request = {};
+
+ if (!subject)
+ return 0;
+
+ /*
+ * Because a file may be opened with O_PATH, get_required_file_open_access()
+ * may return 0. This case will be handled with a future Landlock
+ * evolution.
+ */
+ open_access_request = get_required_file_open_access(file);
+
+ /*
+ * We look up more access than what we immediately need for open(), so
+ * that we can later authorize operations on opened files.
+ */
+ optional_access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ if (is_device(file))
+ optional_access |= LANDLOCK_ACCESS_FS_IOCTL_DEV;
+
+ full_access_request = open_access_request | optional_access;
+
+ if (is_access_to_paths_allowed(
+ subject->domain, &file->f_path,
+ landlock_init_layer_masks(subject->domain,
+ full_access_request, &layer_masks,
+ LANDLOCK_KEY_INODE),
+ &layer_masks, &request, NULL, 0, NULL, NULL, NULL)) {
+ allowed_access = full_access_request;
+ } else {
+ unsigned long access_bit;
+ const unsigned long access_req = full_access_request;
+
+ /*
+ * Calculate the actual allowed access rights from layer_masks.
+ * Add each access right to allowed_access which has not been
+ * vetoed by any layer.
+ */
+ allowed_access = 0;
+ for_each_set_bit(access_bit, &access_req,
+ ARRAY_SIZE(layer_masks)) {
+ if (!layer_masks[access_bit])
+ allowed_access |= BIT_ULL(access_bit);
+ }
+ }
+
+ /*
+ * For operations on already opened files (i.e. ftruncate()), it is the
+ * access rights at the time of open() which decide whether the
+ * operation is permitted. Therefore, we record the relevant subset of
+ * file access rights in the opened struct file.
+ */
+ landlock_file(file)->allowed_access = allowed_access;
+#ifdef CONFIG_AUDIT
+ landlock_file(file)->deny_masks = landlock_get_deny_masks(
+ _LANDLOCK_ACCESS_FS_OPTIONAL, optional_access, &layer_masks,
+ ARRAY_SIZE(layer_masks));
+#endif /* CONFIG_AUDIT */
+
+ if ((open_access_request & allowed_access) == open_access_request)
+ return 0;
+
+ /* Sets access to reflect the actual request. */
+ request.access = open_access_request;
+ landlock_log_denial(subject, &request);
+ return -EACCES;
+}
+
+static int hook_file_truncate(struct file *const file)
+{
+ /*
+ * Allows truncation if the truncate right was available at the time of
+ * opening the file, to get a consistent access check as for read, write
+ * and execute operations.
+ *
+ * Note: For checks done based on the file's Landlock allowed access, we
+ * enforce them independently of whether the current thread is in a
+ * Landlock domain, so that open files passed between independent
+ * processes retain their behaviour.
+ */
+ if (landlock_file(file)->allowed_access & LANDLOCK_ACCESS_FS_TRUNCATE)
+ return 0;
+
+ landlock_log_denial(landlock_cred(file->f_cred), &(struct landlock_request) {
+ .type = LANDLOCK_REQUEST_FS_ACCESS,
+ .audit = {
+ .type = LSM_AUDIT_DATA_FILE,
+ .u.file = file,
+ },
+ .all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
+ .access = LANDLOCK_ACCESS_FS_TRUNCATE,
+#ifdef CONFIG_AUDIT
+ .deny_masks = landlock_file(file)->deny_masks,
+#endif /* CONFIG_AUDIT */
+ });
+ return -EACCES;
+}
+
+static int hook_file_ioctl_common(const struct file *const file,
+ const unsigned int cmd, const bool is_compat)
+{
+ access_mask_t allowed_access = landlock_file(file)->allowed_access;
+
+ /*
+ * It is the access rights at the time of opening the file which
+ * determine whether IOCTL can be used on the opened file later.
+ *
+ * The access right is attached to the opened file in hook_file_open().
+ */
+ if (allowed_access & LANDLOCK_ACCESS_FS_IOCTL_DEV)
+ return 0;
+
+ if (!is_device(file))
+ return 0;
+
+ if (unlikely(is_compat) ? is_masked_device_ioctl_compat(cmd) :
+ is_masked_device_ioctl(cmd))
+ return 0;
+
+ landlock_log_denial(landlock_cred(file->f_cred), &(struct landlock_request) {
+ .type = LANDLOCK_REQUEST_FS_ACCESS,
+ .audit = {
+ .type = LSM_AUDIT_DATA_IOCTL_OP,
+ .u.op = &(struct lsm_ioctlop_audit) {
+ .path = file->f_path,
+ .cmd = cmd,
+ },
+ },
+ .all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
+ .access = LANDLOCK_ACCESS_FS_IOCTL_DEV,
+#ifdef CONFIG_AUDIT
+ .deny_masks = landlock_file(file)->deny_masks,
+#endif /* CONFIG_AUDIT */
+ });
+ return -EACCES;
+}
+
+static int hook_file_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ return hook_file_ioctl_common(file, cmd, false);
+}
+
+static int hook_file_ioctl_compat(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ return hook_file_ioctl_common(file, cmd, true);
+}
+
+/*
+ * Always allow sending signals between threads of the same process. This
+ * ensures consistency with hook_task_kill().
+ */
+static bool control_current_fowner(struct fown_struct *const fown)
+{
+ struct task_struct *p;
+
+ /*
+ * Lock already held by __f_setown(), see commit 26f204380a3c ("fs: Fix
+ * file_set_fowner LSM hook inconsistencies").
+ */
+ lockdep_assert_held(&fown->lock);
+
+ /*
+ * Some callers (e.g. fcntl_dirnotify) may not be in an RCU read-side
+ * critical section.
+ */
+ guard(rcu)();
+ p = pid_task(fown->pid, fown->pid_type);
+ if (!p)
+ return true;
+
+ return !same_thread_group(p, current);
+}
+
+static void hook_file_set_fowner(struct file *file)
+{
+ struct landlock_ruleset *prev_dom;
+ struct landlock_cred_security fown_subject = {};
+ size_t fown_layer = 0;
+
+ if (control_current_fowner(file_f_owner(file))) {
+ static const struct access_masks signal_scope = {
+ .scope = LANDLOCK_SCOPE_SIGNAL,
+ };
+ const struct landlock_cred_security *new_subject =
+ landlock_get_applicable_subject(
+ current_cred(), signal_scope, &fown_layer);
+ if (new_subject) {
+ landlock_get_ruleset(new_subject->domain);
+ fown_subject = *new_subject;
+ }
+ }
+
+ prev_dom = landlock_file(file)->fown_subject.domain;
+ landlock_file(file)->fown_subject = fown_subject;
+#ifdef CONFIG_AUDIT
+ landlock_file(file)->fown_layer = fown_layer;
+#endif /* CONFIG_AUDIT*/
+
+ /* May be called in an RCU read-side critical section. */
+ landlock_put_ruleset_deferred(prev_dom);
+}
+
+static void hook_file_free_security(struct file *file)
+{
+ landlock_put_ruleset_deferred(landlock_file(file)->fown_subject.domain);
+}
+
+static struct security_hook_list landlock_hooks[] __ro_after_init = {
+ LSM_HOOK_INIT(inode_free_security_rcu, hook_inode_free_security_rcu),
+
+ LSM_HOOK_INIT(sb_delete, hook_sb_delete),
+ LSM_HOOK_INIT(sb_mount, hook_sb_mount),
+ LSM_HOOK_INIT(move_mount, hook_move_mount),
+ LSM_HOOK_INIT(sb_umount, hook_sb_umount),
+ LSM_HOOK_INIT(sb_remount, hook_sb_remount),
+ LSM_HOOK_INIT(sb_pivotroot, hook_sb_pivotroot),
+
+ LSM_HOOK_INIT(path_link, hook_path_link),
+ LSM_HOOK_INIT(path_rename, hook_path_rename),
+ LSM_HOOK_INIT(path_mkdir, hook_path_mkdir),
+ LSM_HOOK_INIT(path_mknod, hook_path_mknod),
+ LSM_HOOK_INIT(path_symlink, hook_path_symlink),
+ LSM_HOOK_INIT(path_unlink, hook_path_unlink),
+ LSM_HOOK_INIT(path_rmdir, hook_path_rmdir),
+ LSM_HOOK_INIT(path_truncate, hook_path_truncate),
+
+ LSM_HOOK_INIT(file_alloc_security, hook_file_alloc_security),
+ LSM_HOOK_INIT(file_open, hook_file_open),
+ LSM_HOOK_INIT(file_truncate, hook_file_truncate),
+ LSM_HOOK_INIT(file_ioctl, hook_file_ioctl),
+ LSM_HOOK_INIT(file_ioctl_compat, hook_file_ioctl_compat),
+ LSM_HOOK_INIT(file_set_fowner, hook_file_set_fowner),
+ LSM_HOOK_INIT(file_free_security, hook_file_free_security),
+};
+
+__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/fs.h b/security/landlock/fs.h
new file mode 100644
index 000000000000..bf9948941f2f
--- /dev/null
+++ b/security/landlock/fs.h
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock - Filesystem management and hooks
+ *
+ * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+#ifndef _SECURITY_LANDLOCK_FS_H
+#define _SECURITY_LANDLOCK_FS_H
+
+#include <linux/build_bug.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/rcupdate.h>
+
+#include "access.h"
+#include "cred.h"
+#include "ruleset.h"
+#include "setup.h"
+
+/**
+ * struct landlock_inode_security - Inode security blob
+ *
+ * Enable to reference a &struct landlock_object tied to an inode (i.e.
+ * underlying object).
+ */
+struct landlock_inode_security {
+ /**
+ * @object: Weak pointer to an allocated object. All assignments of a
+ * new object are protected by the underlying inode->i_lock. However,
+ * atomically disassociating @object from the inode is only protected
+ * by @object->lock, from the time @object's usage refcount drops to
+ * zero to the time this pointer is nulled out (cf. release_inode() and
+ * hook_sb_delete()). Indeed, such disassociation doesn't require
+ * inode->i_lock thanks to the careful rcu_access_pointer() check
+ * performed by get_inode_object().
+ */
+ struct landlock_object __rcu *object;
+};
+
+/**
+ * struct landlock_file_security - File security blob
+ *
+ * This information is populated when opening a file in hook_file_open, and
+ * tracks the relevant Landlock access rights that were available at the time
+ * of opening the file. Other LSM hooks use these rights in order to authorize
+ * operations on already opened files.
+ */
+struct landlock_file_security {
+ /**
+ * @allowed_access: Access rights that were available at the time of
+ * opening the file. This is not necessarily the full set of access
+ * rights available at that time, but it's the necessary subset as
+ * needed to authorize later operations on the open file.
+ */
+ access_mask_t allowed_access;
+
+#ifdef CONFIG_AUDIT
+ /**
+ * @deny_masks: Domain layer levels that deny an optional access (see
+ * _LANDLOCK_ACCESS_FS_OPTIONAL).
+ */
+ deny_masks_t deny_masks;
+ /**
+ * @fown_layer: Layer level of @fown_subject->domain with
+ * LANDLOCK_SCOPE_SIGNAL.
+ */
+ u8 fown_layer;
+#endif /* CONFIG_AUDIT */
+
+ /**
+ * @fown_subject: Landlock credential of the task that set the PID that
+ * may receive a signal e.g., SIGURG when writing MSG_OOB to the
+ * related socket. This pointer is protected by the related
+ * file->f_owner->lock, as for fown_struct's members: pid, uid, and
+ * euid.
+ */
+ struct landlock_cred_security fown_subject;
+};
+
+#ifdef CONFIG_AUDIT
+
+/* Makes sure all layers can be identified. */
+/* clang-format off */
+static_assert((typeof_member(struct landlock_file_security, fown_layer))~0 >=
+ LANDLOCK_MAX_NUM_LAYERS);
+/* clang-format off */
+
+#endif /* CONFIG_AUDIT */
+
+/**
+ * struct landlock_superblock_security - Superblock security blob
+ *
+ * Enable hook_sb_delete() to wait for concurrent calls to release_inode().
+ */
+struct landlock_superblock_security {
+ /**
+ * @inode_refs: Number of pending inodes (from this superblock) that
+ * are being released by release_inode().
+ * Cf. struct super_block->s_fsnotify_inode_refs .
+ */
+ atomic_long_t inode_refs;
+};
+
+static inline struct landlock_file_security *
+landlock_file(const struct file *const file)
+{
+ return file->f_security + landlock_blob_sizes.lbs_file;
+}
+
+static inline struct landlock_inode_security *
+landlock_inode(const struct inode *const inode)
+{
+ return inode->i_security + landlock_blob_sizes.lbs_inode;
+}
+
+static inline struct landlock_superblock_security *
+landlock_superblock(const struct super_block *const superblock)
+{
+ return superblock->s_security + landlock_blob_sizes.lbs_superblock;
+}
+
+__init void landlock_add_fs_hooks(void);
+
+int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
+ const struct path *const path,
+ access_mask_t access_hierarchy);
+
+#endif /* _SECURITY_LANDLOCK_FS_H */
diff --git a/security/landlock/id.c b/security/landlock/id.c
new file mode 100644
index 000000000000..838c3ed7bb82
--- /dev/null
+++ b/security/landlock/id.c
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock - Unique identification number generator
+ *
+ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+#include <kunit/test.h>
+#include <linux/atomic.h>
+#include <linux/bitops.h>
+#include <linux/random.h>
+#include <linux/spinlock.h>
+
+#include "common.h"
+#include "id.h"
+
+#define COUNTER_PRE_INIT 0
+
+static atomic64_t next_id = ATOMIC64_INIT(COUNTER_PRE_INIT);
+
+static void __init init_id(atomic64_t *const counter, const u32 random_32bits)
+{
+ u64 init;
+
+ /*
+ * Ensures sure 64-bit values are always used by user space (or may
+ * fail with -EOVERFLOW), and makes this testable.
+ */
+ init = BIT_ULL(32);
+
+ /*
+ * Makes a large (2^32) boot-time value to limit ID collision in logs
+ * from different boots, and to limit info leak about the number of
+ * initially (relative to the reader) created elements (e.g. domains).
+ */
+ init += random_32bits;
+
+ /* Sets first or ignores. This will be the first ID. */
+ atomic64_cmpxchg(counter, COUNTER_PRE_INIT, init);
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static void __init test_init_min(struct kunit *const test)
+{
+ atomic64_t counter = ATOMIC64_INIT(COUNTER_PRE_INIT);
+
+ init_id(&counter, 0);
+ KUNIT_EXPECT_EQ(test, atomic64_read(&counter), 1ULL + U32_MAX);
+}
+
+static void __init test_init_max(struct kunit *const test)
+{
+ atomic64_t counter = ATOMIC64_INIT(COUNTER_PRE_INIT);
+
+ init_id(&counter, ~0);
+ KUNIT_EXPECT_EQ(test, atomic64_read(&counter), 1 + (2ULL * U32_MAX));
+}
+
+static void __init test_init_once(struct kunit *const test)
+{
+ const u64 first_init = 1ULL + U32_MAX;
+ atomic64_t counter = ATOMIC64_INIT(COUNTER_PRE_INIT);
+
+ init_id(&counter, 0);
+ KUNIT_EXPECT_EQ(test, atomic64_read(&counter), first_init);
+
+ init_id(&counter, ~0);
+ KUNIT_EXPECT_EQ_MSG(
+ test, atomic64_read(&counter), first_init,
+ "Should still have the same value after the subsequent init_id()");
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+void __init landlock_init_id(void)
+{
+ return init_id(&next_id, get_random_u32());
+}
+
+/*
+ * It's not worth it to try to hide the monotonic counter because it can still
+ * be inferred (with N counter ranges), and if we are allowed to read the inode
+ * number we should also be allowed to read the time creation anyway, and it
+ * can be handy to store and sort domain IDs for user space.
+ *
+ * Returns the value of next_id and increment it to let some space for the next
+ * one.
+ */
+static u64 get_id_range(size_t number_of_ids, atomic64_t *const counter,
+ u8 random_4bits)
+{
+ u64 id, step;
+
+ /*
+ * We should return at least 1 ID, and we may need a set of consecutive
+ * ones (e.g. to generate a set of inodes).
+ */
+ if (WARN_ON_ONCE(number_of_ids <= 0))
+ number_of_ids = 1;
+
+ /*
+ * Blurs the next ID guess with 1/16 ratio. We get 2^(64 - 4) -
+ * (2 * 2^32), so a bit less than 2^60 available IDs, which should be
+ * much more than enough considering the number of CPU cycles required
+ * to get a new ID (e.g. a full landlock_restrict_self() call), and the
+ * cost of draining all available IDs during the system's uptime.
+ */
+ random_4bits &= 0b1111;
+ step = number_of_ids + random_4bits;
+
+ /* It is safe to cast a signed atomic to an unsigned value. */
+ id = atomic64_fetch_add(step, counter);
+
+ /* Warns if landlock_init_id() was not called. */
+ WARN_ON_ONCE(id == COUNTER_PRE_INIT);
+ return id;
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static u8 get_random_u8_positive(void)
+{
+ /* max() evaluates its arguments once. */
+ return max(1, get_random_u8());
+}
+
+static void test_range1_rand0(struct kunit *const test)
+{
+ atomic64_t counter;
+ u64 init;
+
+ init = get_random_u32();
+ atomic64_set(&counter, init);
+ KUNIT_EXPECT_EQ(test, get_id_range(1, &counter, 0), init);
+ KUNIT_EXPECT_EQ(test,
+ get_id_range(get_random_u8_positive(), &counter,
+ get_random_u8()),
+ init + 1);
+}
+
+static void test_range1_rand1(struct kunit *const test)
+{
+ atomic64_t counter;
+ u64 init;
+
+ init = get_random_u32();
+ atomic64_set(&counter, init);
+ KUNIT_EXPECT_EQ(test, get_id_range(1, &counter, 1), init);
+ KUNIT_EXPECT_EQ(test,
+ get_id_range(get_random_u8_positive(), &counter,
+ get_random_u8()),
+ init + 2);
+}
+
+static void test_range1_rand15(struct kunit *const test)
+{
+ atomic64_t counter;
+ u64 init;
+
+ init = get_random_u32();
+ atomic64_set(&counter, init);
+ KUNIT_EXPECT_EQ(test, get_id_range(1, &counter, 15), init);
+ KUNIT_EXPECT_EQ(test,
+ get_id_range(get_random_u8_positive(), &counter,
+ get_random_u8()),
+ init + 16);
+}
+
+static void test_range1_rand16(struct kunit *const test)
+{
+ atomic64_t counter;
+ u64 init;
+
+ init = get_random_u32();
+ atomic64_set(&counter, init);
+ KUNIT_EXPECT_EQ(test, get_id_range(1, &counter, 16), init);
+ KUNIT_EXPECT_EQ(test,
+ get_id_range(get_random_u8_positive(), &counter,
+ get_random_u8()),
+ init + 1);
+}
+
+static void test_range2_rand0(struct kunit *const test)
+{
+ atomic64_t counter;
+ u64 init;
+
+ init = get_random_u32();
+ atomic64_set(&counter, init);
+ KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 0), init);
+ KUNIT_EXPECT_EQ(test,
+ get_id_range(get_random_u8_positive(), &counter,
+ get_random_u8()),
+ init + 2);
+}
+
+static void test_range2_rand1(struct kunit *const test)
+{
+ atomic64_t counter;
+ u64 init;
+
+ init = get_random_u32();
+ atomic64_set(&counter, init);
+ KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 1), init);
+ KUNIT_EXPECT_EQ(test,
+ get_id_range(get_random_u8_positive(), &counter,
+ get_random_u8()),
+ init + 3);
+}
+
+static void test_range2_rand2(struct kunit *const test)
+{
+ atomic64_t counter;
+ u64 init;
+
+ init = get_random_u32();
+ atomic64_set(&counter, init);
+ KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 2), init);
+ KUNIT_EXPECT_EQ(test,
+ get_id_range(get_random_u8_positive(), &counter,
+ get_random_u8()),
+ init + 4);
+}
+
+static void test_range2_rand15(struct kunit *const test)
+{
+ atomic64_t counter;
+ u64 init;
+
+ init = get_random_u32();
+ atomic64_set(&counter, init);
+ KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 15), init);
+ KUNIT_EXPECT_EQ(test,
+ get_id_range(get_random_u8_positive(), &counter,
+ get_random_u8()),
+ init + 17);
+}
+
+static void test_range2_rand16(struct kunit *const test)
+{
+ atomic64_t counter;
+ u64 init;
+
+ init = get_random_u32();
+ atomic64_set(&counter, init);
+ KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 16), init);
+ KUNIT_EXPECT_EQ(test,
+ get_id_range(get_random_u8_positive(), &counter,
+ get_random_u8()),
+ init + 2);
+}
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
+
+/**
+ * landlock_get_id_range - Get a range of unique IDs
+ *
+ * @number_of_ids: Number of IDs to hold. Must be greater than one.
+ *
+ * Returns: The first ID in the range.
+ */
+u64 landlock_get_id_range(size_t number_of_ids)
+{
+ return get_id_range(number_of_ids, &next_id, get_random_u8());
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+static struct kunit_case __refdata test_cases[] = {
+ /* clang-format off */
+ KUNIT_CASE(test_init_min),
+ KUNIT_CASE(test_init_max),
+ KUNIT_CASE(test_init_once),
+ KUNIT_CASE(test_range1_rand0),
+ KUNIT_CASE(test_range1_rand1),
+ KUNIT_CASE(test_range1_rand15),
+ KUNIT_CASE(test_range1_rand16),
+ KUNIT_CASE(test_range2_rand0),
+ KUNIT_CASE(test_range2_rand1),
+ KUNIT_CASE(test_range2_rand2),
+ KUNIT_CASE(test_range2_rand15),
+ KUNIT_CASE(test_range2_rand16),
+ {}
+ /* clang-format on */
+};
+
+static struct kunit_suite test_suite = {
+ .name = "landlock_id",
+ .test_cases = test_cases,
+};
+
+kunit_test_init_section_suite(test_suite);
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
diff --git a/security/landlock/id.h b/security/landlock/id.h
new file mode 100644
index 000000000000..45dcfb9e9a8b
--- /dev/null
+++ b/security/landlock/id.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock - Unique identification number generator
+ *
+ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+#ifndef _SECURITY_LANDLOCK_ID_H
+#define _SECURITY_LANDLOCK_ID_H
+
+#ifdef CONFIG_AUDIT
+
+void __init landlock_init_id(void);
+
+u64 landlock_get_id_range(size_t number_of_ids);
+
+#else /* CONFIG_AUDIT */
+
+static inline void __init landlock_init_id(void)
+{
+}
+
+#endif /* CONFIG_AUDIT */
+
+#endif /* _SECURITY_LANDLOCK_ID_H */
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
new file mode 100644
index 000000000000..65b5ff051674
--- /dev/null
+++ b/security/landlock/limits.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock - Limits for different components
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ * Copyright © 2021-2025 Microsoft Corporation
+ */
+
+#ifndef _SECURITY_LANDLOCK_LIMITS_H
+#define _SECURITY_LANDLOCK_LIMITS_H
+
+#include <linux/bitops.h>
+#include <linux/limits.h>
+#include <uapi/linux/landlock.h>
+
+/* clang-format off */
+
+#define LANDLOCK_MAX_NUM_LAYERS 16
+#define LANDLOCK_MAX_NUM_RULES U32_MAX
+
+#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_IOCTL_DEV
+#define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
+#define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS)
+
+#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_TCP
+#define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
+#define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET)
+
+#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_SIGNAL
+#define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1)
+#define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE)
+
+#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF
+#define LANDLOCK_MASK_RESTRICT_SELF ((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1)
+
+/* clang-format on */
+
+#endif /* _SECURITY_LANDLOCK_LIMITS_H */
diff --git a/security/landlock/net.c b/security/landlock/net.c
new file mode 100644
index 000000000000..1f3915a90a80
--- /dev/null
+++ b/security/landlock/net.c
@@ -0,0 +1,222 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock - Network management and hooks
+ *
+ * Copyright © 2022-2023 Huawei Tech. Co., Ltd.
+ * Copyright © 2022-2025 Microsoft Corporation
+ */
+
+#include <linux/in.h>
+#include <linux/lsm_audit.h>
+#include <linux/net.h>
+#include <linux/socket.h>
+#include <net/ipv6.h>
+
+#include "audit.h"
+#include "common.h"
+#include "cred.h"
+#include "limits.h"
+#include "net.h"
+#include "ruleset.h"
+
+int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
+ const u16 port, access_mask_t access_rights)
+{
+ int err;
+ const struct landlock_id id = {
+ .key.data = (__force uintptr_t)htons(port),
+ .type = LANDLOCK_KEY_NET_PORT,
+ };
+
+ BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
+
+ /* Transforms relative access rights to absolute ones. */
+ access_rights |= LANDLOCK_MASK_ACCESS_NET &
+ ~landlock_get_net_access_mask(ruleset, 0);
+
+ mutex_lock(&ruleset->lock);
+ err = landlock_insert_rule(ruleset, id, access_rights);
+ mutex_unlock(&ruleset->lock);
+
+ return err;
+}
+
+static int current_check_access_socket(struct socket *const sock,
+ struct sockaddr *const address,
+ const int addrlen,
+ access_mask_t access_request)
+{
+ __be16 port;
+ layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
+ const struct landlock_rule *rule;
+ struct landlock_id id = {
+ .type = LANDLOCK_KEY_NET_PORT,
+ };
+ const struct access_masks masks = {
+ .net = access_request,
+ };
+ const struct landlock_cred_security *const subject =
+ landlock_get_applicable_subject(current_cred(), masks, NULL);
+ struct lsm_network_audit audit_net = {};
+
+ if (!subject)
+ return 0;
+
+ if (!sk_is_tcp(sock->sk))
+ return 0;
+
+ /* Checks for minimal header length to safely read sa_family. */
+ if (addrlen < offsetofend(typeof(*address), sa_family))
+ return -EINVAL;
+
+ switch (address->sa_family) {
+ case AF_UNSPEC:
+ case AF_INET: {
+ const struct sockaddr_in *addr4;
+
+ if (addrlen < sizeof(struct sockaddr_in))
+ return -EINVAL;
+
+ addr4 = (struct sockaddr_in *)address;
+ port = addr4->sin_port;
+
+ if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
+ audit_net.dport = port;
+ audit_net.v4info.daddr = addr4->sin_addr.s_addr;
+ } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
+ audit_net.sport = port;
+ audit_net.v4info.saddr = addr4->sin_addr.s_addr;
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ break;
+ }
+
+#if IS_ENABLED(CONFIG_IPV6)
+ case AF_INET6: {
+ const struct sockaddr_in6 *addr6;
+
+ if (addrlen < SIN6_LEN_RFC2133)
+ return -EINVAL;
+
+ addr6 = (struct sockaddr_in6 *)address;
+ port = addr6->sin6_port;
+
+ if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
+ audit_net.dport = port;
+ audit_net.v6info.daddr = addr6->sin6_addr;
+ } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
+ audit_net.sport = port;
+ audit_net.v6info.saddr = addr6->sin6_addr;
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ break;
+ }
+#endif /* IS_ENABLED(CONFIG_IPV6) */
+
+ default:
+ return 0;
+ }
+
+ /* Specific AF_UNSPEC handling. */
+ if (address->sa_family == AF_UNSPEC) {
+ /*
+ * Connecting to an address with AF_UNSPEC dissolves the TCP
+ * association, which have the same effect as closing the
+ * connection while retaining the socket object (i.e., the file
+ * descriptor). As for dropping privileges, closing
+ * connections is always allowed.
+ *
+ * For a TCP access control system, this request is legitimate.
+ * Let the network stack handle potential inconsistencies and
+ * return -EINVAL if needed.
+ */
+ if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP)
+ return 0;
+
+ /*
+ * For compatibility reason, accept AF_UNSPEC for bind
+ * accesses (mapped to AF_INET) only if the address is
+ * INADDR_ANY (cf. __inet_bind). Checking the address is
+ * required to not wrongfully return -EACCES instead of
+ * -EAFNOSUPPORT.
+ *
+ * We could return 0 and let the network stack handle these
+ * checks, but it is safer to return a proper error and test
+ * consistency thanks to kselftest.
+ */
+ if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
+ /* addrlen has already been checked for AF_UNSPEC. */
+ const struct sockaddr_in *const sockaddr =
+ (struct sockaddr_in *)address;
+
+ if (sock->sk->__sk_common.skc_family != AF_INET)
+ return -EINVAL;
+
+ if (sockaddr->sin_addr.s_addr != htonl(INADDR_ANY))
+ return -EAFNOSUPPORT;
+ }
+ } else {
+ /*
+ * Checks sa_family consistency to not wrongfully return
+ * -EACCES instead of -EINVAL. Valid sa_family changes are
+ * only (from AF_INET or AF_INET6) to AF_UNSPEC.
+ *
+ * We could return 0 and let the network stack handle this
+ * check, but it is safer to return a proper error and test
+ * consistency thanks to kselftest.
+ */
+ if (address->sa_family != sock->sk->__sk_common.skc_family)
+ return -EINVAL;
+ }
+
+ id.key.data = (__force uintptr_t)port;
+ BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
+
+ rule = landlock_find_rule(subject->domain, id);
+ access_request = landlock_init_layer_masks(subject->domain,
+ access_request, &layer_masks,
+ LANDLOCK_KEY_NET_PORT);
+ if (landlock_unmask_layers(rule, access_request, &layer_masks,
+ ARRAY_SIZE(layer_masks)))
+ return 0;
+
+ audit_net.family = address->sa_family;
+ landlock_log_denial(subject,
+ &(struct landlock_request){
+ .type = LANDLOCK_REQUEST_NET_ACCESS,
+ .audit.type = LSM_AUDIT_DATA_NET,
+ .audit.u.net = &audit_net,
+ .access = access_request,
+ .layer_masks = &layer_masks,
+ .layer_masks_size = ARRAY_SIZE(layer_masks),
+ });
+ return -EACCES;
+}
+
+static int hook_socket_bind(struct socket *const sock,
+ struct sockaddr *const address, const int addrlen)
+{
+ return current_check_access_socket(sock, address, addrlen,
+ LANDLOCK_ACCESS_NET_BIND_TCP);
+}
+
+static int hook_socket_connect(struct socket *const sock,
+ struct sockaddr *const address,
+ const int addrlen)
+{
+ return current_check_access_socket(sock, address, addrlen,
+ LANDLOCK_ACCESS_NET_CONNECT_TCP);
+}
+
+static struct security_hook_list landlock_hooks[] __ro_after_init = {
+ LSM_HOOK_INIT(socket_bind, hook_socket_bind),
+ LSM_HOOK_INIT(socket_connect, hook_socket_connect),
+};
+
+__init void landlock_add_net_hooks(void)
+{
+ security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
+ &landlock_lsmid);
+}
diff --git a/security/landlock/net.h b/security/landlock/net.h
new file mode 100644
index 000000000000..09960c237a13
--- /dev/null
+++ b/security/landlock/net.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Network management and hooks
+ *
+ * Copyright © 2022-2023 Huawei Tech. Co., Ltd.
+ */
+
+#ifndef _SECURITY_LANDLOCK_NET_H
+#define _SECURITY_LANDLOCK_NET_H
+
+#include "common.h"
+#include "ruleset.h"
+#include "setup.h"
+
+#if IS_ENABLED(CONFIG_INET)
+__init void landlock_add_net_hooks(void);
+
+int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
+ const u16 port, access_mask_t access_rights);
+#else /* IS_ENABLED(CONFIG_INET) */
+static inline void landlock_add_net_hooks(void)
+{
+}
+
+static inline int
+landlock_append_net_rule(struct landlock_ruleset *const ruleset, const u16 port,
+ access_mask_t access_rights)
+{
+ return -EAFNOSUPPORT;
+}
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+#endif /* _SECURITY_LANDLOCK_NET_H */
diff --git a/security/landlock/object.c b/security/landlock/object.c
new file mode 100644
index 000000000000..1f50612f0185
--- /dev/null
+++ b/security/landlock/object.c
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Object management
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#include <linux/bug.h>
+#include <linux/compiler_types.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/rcupdate.h>
+#include <linux/refcount.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include "object.h"
+
+struct landlock_object *
+landlock_create_object(const struct landlock_object_underops *const underops,
+ void *const underobj)
+{
+ struct landlock_object *new_object;
+
+ if (WARN_ON_ONCE(!underops || !underobj))
+ return ERR_PTR(-ENOENT);
+ new_object = kzalloc(sizeof(*new_object), GFP_KERNEL_ACCOUNT);
+ if (!new_object)
+ return ERR_PTR(-ENOMEM);
+ refcount_set(&new_object->usage, 1);
+ spin_lock_init(&new_object->lock);
+ new_object->underops = underops;
+ new_object->underobj = underobj;
+ return new_object;
+}
+
+/*
+ * The caller must own the object (i.e. thanks to object->usage) to safely put
+ * it.
+ */
+void landlock_put_object(struct landlock_object *const object)
+{
+ /*
+ * The call to @object->underops->release(object) might sleep, e.g.
+ * because of iput().
+ */
+ might_sleep();
+ if (!object)
+ return;
+
+ /*
+ * If the @object's refcount cannot drop to zero, we can just decrement
+ * the refcount without holding a lock. Otherwise, the decrement must
+ * happen under @object->lock for synchronization with things like
+ * get_inode_object().
+ */
+ if (refcount_dec_and_lock(&object->usage, &object->lock)) {
+ __acquire(&object->lock);
+ /*
+ * With @object->lock initially held, remove the reference from
+ * @object->underobj to @object (if it still exists).
+ */
+ object->underops->release(object);
+ kfree_rcu(object, rcu_free);
+ }
+}
diff --git a/security/landlock/object.h b/security/landlock/object.h
new file mode 100644
index 000000000000..5f28c35e8aa8
--- /dev/null
+++ b/security/landlock/object.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Object management
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#ifndef _SECURITY_LANDLOCK_OBJECT_H
+#define _SECURITY_LANDLOCK_OBJECT_H
+
+#include <linux/compiler_types.h>
+#include <linux/refcount.h>
+#include <linux/spinlock.h>
+
+struct landlock_object;
+
+/**
+ * struct landlock_object_underops - Operations on an underlying object
+ */
+struct landlock_object_underops {
+ /**
+ * @release: Releases the underlying object (e.g. iput() for an inode).
+ */
+ void (*release)(struct landlock_object *const object)
+ __releases(object->lock);
+};
+
+/**
+ * struct landlock_object - Security blob tied to a kernel object
+ *
+ * The goal of this structure is to enable to tie a set of ephemeral access
+ * rights (pertaining to different domains) to a kernel object (e.g an inode)
+ * in a safe way. This implies to handle concurrent use and modification.
+ *
+ * The lifetime of a &struct landlock_object depends on the rules referring to
+ * it.
+ */
+struct landlock_object {
+ /**
+ * @usage: This counter is used to tie an object to the rules matching
+ * it or to keep it alive while adding a new rule. If this counter
+ * reaches zero, this struct must not be modified, but this counter can
+ * still be read from within an RCU read-side critical section. When
+ * adding a new rule to an object with a usage counter of zero, we must
+ * wait until the pointer to this object is set to NULL (or recycled).
+ */
+ refcount_t usage;
+ /**
+ * @lock: Protects against concurrent modifications. This lock must be
+ * held from the time @usage drops to zero until any weak references
+ * from @underobj to this object have been cleaned up.
+ *
+ * Lock ordering: inode->i_lock nests inside this.
+ */
+ spinlock_t lock;
+ /**
+ * @underobj: Used when cleaning up an object and to mark an object as
+ * tied to its underlying kernel structure. This pointer is protected
+ * by @lock. Cf. landlock_release_inodes() and release_inode().
+ */
+ void *underobj;
+ union {
+ /**
+ * @rcu_free: Enables lockless use of @usage, @lock and
+ * @underobj from within an RCU read-side critical section.
+ * @rcu_free and @underops are only used by
+ * landlock_put_object().
+ */
+ struct rcu_head rcu_free;
+ /**
+ * @underops: Enables landlock_put_object() to release the
+ * underlying object (e.g. inode).
+ */
+ const struct landlock_object_underops *underops;
+ };
+};
+
+struct landlock_object *
+landlock_create_object(const struct landlock_object_underops *const underops,
+ void *const underobj);
+
+void landlock_put_object(struct landlock_object *const object);
+
+static inline void landlock_get_object(struct landlock_object *const object)
+{
+ if (object)
+ refcount_inc(&object->usage);
+}
+
+#endif /* _SECURITY_LANDLOCK_OBJECT_H */
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
new file mode 100644
index 000000000000..dfcdc19ea268
--- /dev/null
+++ b/security/landlock/ruleset.c
@@ -0,0 +1,740 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Ruleset management
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#include <linux/bits.h>
+#include <linux/bug.h>
+#include <linux/cleanup.h>
+#include <linux/compiler_types.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/lockdep.h>
+#include <linux/mutex.h>
+#include <linux/overflow.h>
+#include <linux/rbtree.h>
+#include <linux/refcount.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+
+#include "access.h"
+#include "audit.h"
+#include "domain.h"
+#include "limits.h"
+#include "object.h"
+#include "ruleset.h"
+
+static struct landlock_ruleset *create_ruleset(const u32 num_layers)
+{
+ struct landlock_ruleset *new_ruleset;
+
+ new_ruleset =
+ kzalloc(struct_size(new_ruleset, access_masks, num_layers),
+ GFP_KERNEL_ACCOUNT);
+ if (!new_ruleset)
+ return ERR_PTR(-ENOMEM);
+ refcount_set(&new_ruleset->usage, 1);
+ mutex_init(&new_ruleset->lock);
+ new_ruleset->root_inode = RB_ROOT;
+
+#if IS_ENABLED(CONFIG_INET)
+ new_ruleset->root_net_port = RB_ROOT;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ new_ruleset->num_layers = num_layers;
+ /*
+ * hierarchy = NULL
+ * num_rules = 0
+ * access_masks[] = 0
+ */
+ return new_ruleset;
+}
+
+struct landlock_ruleset *
+landlock_create_ruleset(const access_mask_t fs_access_mask,
+ const access_mask_t net_access_mask,
+ const access_mask_t scope_mask)
+{
+ struct landlock_ruleset *new_ruleset;
+
+ /* Informs about useless ruleset. */
+ if (!fs_access_mask && !net_access_mask && !scope_mask)
+ return ERR_PTR(-ENOMSG);
+ new_ruleset = create_ruleset(1);
+ if (IS_ERR(new_ruleset))
+ return new_ruleset;
+ if (fs_access_mask)
+ landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0);
+ if (net_access_mask)
+ landlock_add_net_access_mask(new_ruleset, net_access_mask, 0);
+ if (scope_mask)
+ landlock_add_scope_mask(new_ruleset, scope_mask, 0);
+ return new_ruleset;
+}
+
+static void build_check_rule(void)
+{
+ const struct landlock_rule rule = {
+ .num_layers = ~0,
+ };
+
+ /*
+ * Checks that .num_layers is large enough for at least
+ * LANDLOCK_MAX_NUM_LAYERS layers.
+ */
+ BUILD_BUG_ON(rule.num_layers < LANDLOCK_MAX_NUM_LAYERS);
+}
+
+static bool is_object_pointer(const enum landlock_key_type key_type)
+{
+ switch (key_type) {
+ case LANDLOCK_KEY_INODE:
+ return true;
+
+#if IS_ENABLED(CONFIG_INET)
+ case LANDLOCK_KEY_NET_PORT:
+ return false;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ default:
+ WARN_ON_ONCE(1);
+ return false;
+ }
+}
+
+static struct landlock_rule *
+create_rule(const struct landlock_id id,
+ const struct landlock_layer (*const layers)[], const u32 num_layers,
+ const struct landlock_layer *const new_layer)
+{
+ struct landlock_rule *new_rule;
+ u32 new_num_layers;
+
+ build_check_rule();
+ if (new_layer) {
+ /* Should already be checked by landlock_merge_ruleset(). */
+ if (WARN_ON_ONCE(num_layers >= LANDLOCK_MAX_NUM_LAYERS))
+ return ERR_PTR(-E2BIG);
+ new_num_layers = num_layers + 1;
+ } else {
+ new_num_layers = num_layers;
+ }
+ new_rule = kzalloc(struct_size(new_rule, layers, new_num_layers),
+ GFP_KERNEL_ACCOUNT);
+ if (!new_rule)
+ return ERR_PTR(-ENOMEM);
+ RB_CLEAR_NODE(&new_rule->node);
+ if (is_object_pointer(id.type)) {
+ /* This should have been caught by insert_rule(). */
+ WARN_ON_ONCE(!id.key.object);
+ landlock_get_object(id.key.object);
+ }
+
+ new_rule->key = id.key;
+ new_rule->num_layers = new_num_layers;
+ /* Copies the original layer stack. */
+ memcpy(new_rule->layers, layers,
+ flex_array_size(new_rule, layers, num_layers));
+ if (new_layer)
+ /* Adds a copy of @new_layer on the layer stack. */
+ new_rule->layers[new_rule->num_layers - 1] = *new_layer;
+ return new_rule;
+}
+
+static struct rb_root *get_root(struct landlock_ruleset *const ruleset,
+ const enum landlock_key_type key_type)
+{
+ switch (key_type) {
+ case LANDLOCK_KEY_INODE:
+ return &ruleset->root_inode;
+
+#if IS_ENABLED(CONFIG_INET)
+ case LANDLOCK_KEY_NET_PORT:
+ return &ruleset->root_net_port;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ default:
+ WARN_ON_ONCE(1);
+ return ERR_PTR(-EINVAL);
+ }
+}
+
+static void free_rule(struct landlock_rule *const rule,
+ const enum landlock_key_type key_type)
+{
+ might_sleep();
+ if (!rule)
+ return;
+ if (is_object_pointer(key_type))
+ landlock_put_object(rule->key.object);
+ kfree(rule);
+}
+
+static void build_check_ruleset(void)
+{
+ const struct landlock_ruleset ruleset = {
+ .num_rules = ~0,
+ .num_layers = ~0,
+ };
+
+ BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
+ BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
+}
+
+/**
+ * insert_rule - Create and insert a rule in a ruleset
+ *
+ * @ruleset: The ruleset to be updated.
+ * @id: The ID to build the new rule with. The underlying kernel object, if
+ * any, must be held by the caller.
+ * @layers: One or multiple layers to be copied into the new rule.
+ * @num_layers: The number of @layers entries.
+ *
+ * When user space requests to add a new rule to a ruleset, @layers only
+ * contains one entry and this entry is not assigned to any level. In this
+ * case, the new rule will extend @ruleset, similarly to a boolean OR between
+ * access rights.
+ *
+ * When merging a ruleset in a domain, or copying a domain, @layers will be
+ * added to @ruleset as new constraints, similarly to a boolean AND between
+ * access rights.
+ */
+static int insert_rule(struct landlock_ruleset *const ruleset,
+ const struct landlock_id id,
+ const struct landlock_layer (*const layers)[],
+ const size_t num_layers)
+{
+ struct rb_node **walker_node;
+ struct rb_node *parent_node = NULL;
+ struct landlock_rule *new_rule;
+ struct rb_root *root;
+
+ might_sleep();
+ lockdep_assert_held(&ruleset->lock);
+ if (WARN_ON_ONCE(!layers))
+ return -ENOENT;
+
+ if (is_object_pointer(id.type) && WARN_ON_ONCE(!id.key.object))
+ return -ENOENT;
+
+ root = get_root(ruleset, id.type);
+ if (IS_ERR(root))
+ return PTR_ERR(root);
+
+ walker_node = &root->rb_node;
+ while (*walker_node) {
+ struct landlock_rule *const this =
+ rb_entry(*walker_node, struct landlock_rule, node);
+
+ if (this->key.data != id.key.data) {
+ parent_node = *walker_node;
+ if (this->key.data < id.key.data)
+ walker_node = &((*walker_node)->rb_right);
+ else
+ walker_node = &((*walker_node)->rb_left);
+ continue;
+ }
+
+ /* Only a single-level layer should match an existing rule. */
+ if (WARN_ON_ONCE(num_layers != 1))
+ return -EINVAL;
+
+ /* If there is a matching rule, updates it. */
+ if ((*layers)[0].level == 0) {
+ /*
+ * Extends access rights when the request comes from
+ * landlock_add_rule(2), i.e. @ruleset is not a domain.
+ */
+ if (WARN_ON_ONCE(this->num_layers != 1))
+ return -EINVAL;
+ if (WARN_ON_ONCE(this->layers[0].level != 0))
+ return -EINVAL;
+ this->layers[0].access |= (*layers)[0].access;
+ return 0;
+ }
+
+ if (WARN_ON_ONCE(this->layers[0].level == 0))
+ return -EINVAL;
+
+ /*
+ * Intersects access rights when it is a merge between a
+ * ruleset and a domain.
+ */
+ new_rule = create_rule(id, &this->layers, this->num_layers,
+ &(*layers)[0]);
+ if (IS_ERR(new_rule))
+ return PTR_ERR(new_rule);
+ rb_replace_node(&this->node, &new_rule->node, root);
+ free_rule(this, id.type);
+ return 0;
+ }
+
+ /* There is no match for @id. */
+ build_check_ruleset();
+ if (ruleset->num_rules >= LANDLOCK_MAX_NUM_RULES)
+ return -E2BIG;
+ new_rule = create_rule(id, layers, num_layers, NULL);
+ if (IS_ERR(new_rule))
+ return PTR_ERR(new_rule);
+ rb_link_node(&new_rule->node, parent_node, walker_node);
+ rb_insert_color(&new_rule->node, root);
+ ruleset->num_rules++;
+ return 0;
+}
+
+static void build_check_layer(void)
+{
+ const struct landlock_layer layer = {
+ .level = ~0,
+ .access = ~0,
+ };
+
+ /*
+ * Checks that .level and .access are large enough to contain their expected
+ * maximum values.
+ */
+ BUILD_BUG_ON(layer.level < LANDLOCK_MAX_NUM_LAYERS);
+ BUILD_BUG_ON(layer.access < LANDLOCK_MASK_ACCESS_FS);
+}
+
+/* @ruleset must be locked by the caller. */
+int landlock_insert_rule(struct landlock_ruleset *const ruleset,
+ const struct landlock_id id,
+ const access_mask_t access)
+{
+ struct landlock_layer layers[] = { {
+ .access = access,
+ /* When @level is zero, insert_rule() extends @ruleset. */
+ .level = 0,
+ } };
+
+ build_check_layer();
+ return insert_rule(ruleset, id, &layers, ARRAY_SIZE(layers));
+}
+
+static int merge_tree(struct landlock_ruleset *const dst,
+ struct landlock_ruleset *const src,
+ const enum landlock_key_type key_type)
+{
+ struct landlock_rule *walker_rule, *next_rule;
+ struct rb_root *src_root;
+ int err = 0;
+
+ might_sleep();
+ lockdep_assert_held(&dst->lock);
+ lockdep_assert_held(&src->lock);
+
+ src_root = get_root(src, key_type);
+ if (IS_ERR(src_root))
+ return PTR_ERR(src_root);
+
+ /* Merges the @src tree. */
+ rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root,
+ node) {
+ struct landlock_layer layers[] = { {
+ .level = dst->num_layers,
+ } };
+ const struct landlock_id id = {
+ .key = walker_rule->key,
+ .type = key_type,
+ };
+
+ if (WARN_ON_ONCE(walker_rule->num_layers != 1))
+ return -EINVAL;
+
+ if (WARN_ON_ONCE(walker_rule->layers[0].level != 0))
+ return -EINVAL;
+
+ layers[0].access = walker_rule->layers[0].access;
+
+ err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers));
+ if (err)
+ return err;
+ }
+ return err;
+}
+
+static int merge_ruleset(struct landlock_ruleset *const dst,
+ struct landlock_ruleset *const src)
+{
+ int err = 0;
+
+ might_sleep();
+ /* Should already be checked by landlock_merge_ruleset() */
+ if (WARN_ON_ONCE(!src))
+ return 0;
+ /* Only merge into a domain. */
+ if (WARN_ON_ONCE(!dst || !dst->hierarchy))
+ return -EINVAL;
+
+ /* Locks @dst first because we are its only owner. */
+ mutex_lock(&dst->lock);
+ mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING);
+
+ /* Stacks the new layer. */
+ if (WARN_ON_ONCE(src->num_layers != 1 || dst->num_layers < 1)) {
+ err = -EINVAL;
+ goto out_unlock;
+ }
+ dst->access_masks[dst->num_layers - 1] =
+ landlock_upgrade_handled_access_masks(src->access_masks[0]);
+
+ /* Merges the @src inode tree. */
+ err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
+ if (err)
+ goto out_unlock;
+
+#if IS_ENABLED(CONFIG_INET)
+ /* Merges the @src network port tree. */
+ err = merge_tree(dst, src, LANDLOCK_KEY_NET_PORT);
+ if (err)
+ goto out_unlock;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+out_unlock:
+ mutex_unlock(&src->lock);
+ mutex_unlock(&dst->lock);
+ return err;
+}
+
+static int inherit_tree(struct landlock_ruleset *const parent,
+ struct landlock_ruleset *const child,
+ const enum landlock_key_type key_type)
+{
+ struct landlock_rule *walker_rule, *next_rule;
+ struct rb_root *parent_root;
+ int err = 0;
+
+ might_sleep();
+ lockdep_assert_held(&parent->lock);
+ lockdep_assert_held(&child->lock);
+
+ parent_root = get_root(parent, key_type);
+ if (IS_ERR(parent_root))
+ return PTR_ERR(parent_root);
+
+ /* Copies the @parent inode or network tree. */
+ rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
+ parent_root, node) {
+ const struct landlock_id id = {
+ .key = walker_rule->key,
+ .type = key_type,
+ };
+
+ err = insert_rule(child, id, &walker_rule->layers,
+ walker_rule->num_layers);
+ if (err)
+ return err;
+ }
+ return err;
+}
+
+static int inherit_ruleset(struct landlock_ruleset *const parent,
+ struct landlock_ruleset *const child)
+{
+ int err = 0;
+
+ might_sleep();
+ if (!parent)
+ return 0;
+
+ /* Locks @child first because we are its only owner. */
+ mutex_lock(&child->lock);
+ mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING);
+
+ /* Copies the @parent inode tree. */
+ err = inherit_tree(parent, child, LANDLOCK_KEY_INODE);
+ if (err)
+ goto out_unlock;
+
+#if IS_ENABLED(CONFIG_INET)
+ /* Copies the @parent network port tree. */
+ err = inherit_tree(parent, child, LANDLOCK_KEY_NET_PORT);
+ if (err)
+ goto out_unlock;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) {
+ err = -EINVAL;
+ goto out_unlock;
+ }
+ /* Copies the parent layer stack and leaves a space for the new layer. */
+ memcpy(child->access_masks, parent->access_masks,
+ flex_array_size(parent, access_masks, parent->num_layers));
+
+ if (WARN_ON_ONCE(!parent->hierarchy)) {
+ err = -EINVAL;
+ goto out_unlock;
+ }
+ landlock_get_hierarchy(parent->hierarchy);
+ child->hierarchy->parent = parent->hierarchy;
+
+out_unlock:
+ mutex_unlock(&parent->lock);
+ mutex_unlock(&child->lock);
+ return err;
+}
+
+static void free_ruleset(struct landlock_ruleset *const ruleset)
+{
+ struct landlock_rule *freeme, *next;
+
+ might_sleep();
+ rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
+ node)
+ free_rule(freeme, LANDLOCK_KEY_INODE);
+
+#if IS_ENABLED(CONFIG_INET)
+ rbtree_postorder_for_each_entry_safe(freeme, next,
+ &ruleset->root_net_port, node)
+ free_rule(freeme, LANDLOCK_KEY_NET_PORT);
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ landlock_put_hierarchy(ruleset->hierarchy);
+ kfree(ruleset);
+}
+
+void landlock_put_ruleset(struct landlock_ruleset *const ruleset)
+{
+ might_sleep();
+ if (ruleset && refcount_dec_and_test(&ruleset->usage))
+ free_ruleset(ruleset);
+}
+
+static void free_ruleset_work(struct work_struct *const work)
+{
+ struct landlock_ruleset *ruleset;
+
+ ruleset = container_of(work, struct landlock_ruleset, work_free);
+ free_ruleset(ruleset);
+}
+
+/* Only called by hook_cred_free(). */
+void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset)
+{
+ if (ruleset && refcount_dec_and_test(&ruleset->usage)) {
+ INIT_WORK(&ruleset->work_free, free_ruleset_work);
+ schedule_work(&ruleset->work_free);
+ }
+}
+
+/**
+ * landlock_merge_ruleset - Merge a ruleset with a domain
+ *
+ * @parent: Parent domain.
+ * @ruleset: New ruleset to be merged.
+ *
+ * The current task is requesting to be restricted. The subjective credentials
+ * must not be in an overridden state. cf. landlock_init_hierarchy_log().
+ *
+ * Returns the intersection of @parent and @ruleset, or returns @parent if
+ * @ruleset is empty, or returns a duplicate of @ruleset if @parent is empty.
+ */
+struct landlock_ruleset *
+landlock_merge_ruleset(struct landlock_ruleset *const parent,
+ struct landlock_ruleset *const ruleset)
+{
+ struct landlock_ruleset *new_dom __free(landlock_put_ruleset) = NULL;
+ u32 num_layers;
+ int err;
+
+ might_sleep();
+ if (WARN_ON_ONCE(!ruleset || parent == ruleset))
+ return ERR_PTR(-EINVAL);
+
+ if (parent) {
+ if (parent->num_layers >= LANDLOCK_MAX_NUM_LAYERS)
+ return ERR_PTR(-E2BIG);
+ num_layers = parent->num_layers + 1;
+ } else {
+ num_layers = 1;
+ }
+
+ /* Creates a new domain... */
+ new_dom = create_ruleset(num_layers);
+ if (IS_ERR(new_dom))
+ return new_dom;
+
+ new_dom->hierarchy =
+ kzalloc(sizeof(*new_dom->hierarchy), GFP_KERNEL_ACCOUNT);
+ if (!new_dom->hierarchy)
+ return ERR_PTR(-ENOMEM);
+
+ refcount_set(&new_dom->hierarchy->usage, 1);
+
+ /* ...as a child of @parent... */
+ err = inherit_ruleset(parent, new_dom);
+ if (err)
+ return ERR_PTR(err);
+
+ /* ...and including @ruleset. */
+ err = merge_ruleset(new_dom, ruleset);
+ if (err)
+ return ERR_PTR(err);
+
+ err = landlock_init_hierarchy_log(new_dom->hierarchy);
+ if (err)
+ return ERR_PTR(err);
+
+ return no_free_ptr(new_dom);
+}
+
+/*
+ * The returned access has the same lifetime as @ruleset.
+ */
+const struct landlock_rule *
+landlock_find_rule(const struct landlock_ruleset *const ruleset,
+ const struct landlock_id id)
+{
+ const struct rb_root *root;
+ const struct rb_node *node;
+
+ root = get_root((struct landlock_ruleset *)ruleset, id.type);
+ if (IS_ERR(root))
+ return NULL;
+ node = root->rb_node;
+
+ while (node) {
+ struct landlock_rule *this =
+ rb_entry(node, struct landlock_rule, node);
+
+ if (this->key.data == id.key.data)
+ return this;
+ if (this->key.data < id.key.data)
+ node = node->rb_right;
+ else
+ node = node->rb_left;
+ }
+ return NULL;
+}
+
+/*
+ * @layer_masks is read and may be updated according to the access request and
+ * the matching rule.
+ * @masks_array_size must be equal to ARRAY_SIZE(*layer_masks).
+ *
+ * Returns true if the request is allowed (i.e. relevant layer masks for the
+ * request are empty).
+ */
+bool landlock_unmask_layers(const struct landlock_rule *const rule,
+ const access_mask_t access_request,
+ layer_mask_t (*const layer_masks)[],
+ const size_t masks_array_size)
+{
+ size_t layer_level;
+
+ if (!access_request || !layer_masks)
+ return true;
+ if (!rule)
+ return false;
+
+ /*
+ * An access is granted if, for each policy layer, at least one rule
+ * encountered on the pathwalk grants the requested access,
+ * regardless of its position in the layer stack. We must then check
+ * the remaining layers for each inode, from the first added layer to
+ * the last one. When there is multiple requested accesses, for each
+ * policy layer, the full set of requested accesses may not be granted
+ * by only one rule, but by the union (binary OR) of multiple rules.
+ * E.g. /a/b <execute> + /a <read> => /a/b <execute + read>
+ */
+ for (layer_level = 0; layer_level < rule->num_layers; layer_level++) {
+ const struct landlock_layer *const layer =
+ &rule->layers[layer_level];
+ const layer_mask_t layer_bit = BIT_ULL(layer->level - 1);
+ const unsigned long access_req = access_request;
+ unsigned long access_bit;
+ bool is_empty;
+
+ /*
+ * Records in @layer_masks which layer grants access to each requested
+ * access: bit cleared if the related layer grants access.
+ */
+ is_empty = true;
+ for_each_set_bit(access_bit, &access_req, masks_array_size) {
+ if (layer->access & BIT_ULL(access_bit))
+ (*layer_masks)[access_bit] &= ~layer_bit;
+ is_empty = is_empty && !(*layer_masks)[access_bit];
+ }
+ if (is_empty)
+ return true;
+ }
+ return false;
+}
+
+typedef access_mask_t
+get_access_mask_t(const struct landlock_ruleset *const ruleset,
+ const u16 layer_level);
+
+/**
+ * landlock_init_layer_masks - Initialize layer masks from an access request
+ *
+ * Populates @layer_masks such that for each access right in @access_request,
+ * the bits for all the layers are set where this access right is handled.
+ *
+ * @domain: The domain that defines the current restrictions.
+ * @access_request: The requested access rights to check.
+ * @layer_masks: It must contain %LANDLOCK_NUM_ACCESS_FS or
+ * %LANDLOCK_NUM_ACCESS_NET elements according to @key_type.
+ * @key_type: The key type to switch between access masks of different types.
+ *
+ * Returns: An access mask where each access right bit is set which is handled
+ * in any of the active layers in @domain.
+ */
+access_mask_t
+landlock_init_layer_masks(const struct landlock_ruleset *const domain,
+ const access_mask_t access_request,
+ layer_mask_t (*const layer_masks)[],
+ const enum landlock_key_type key_type)
+{
+ access_mask_t handled_accesses = 0;
+ size_t layer_level, num_access;
+ get_access_mask_t *get_access_mask;
+
+ switch (key_type) {
+ case LANDLOCK_KEY_INODE:
+ get_access_mask = landlock_get_fs_access_mask;
+ num_access = LANDLOCK_NUM_ACCESS_FS;
+ break;
+
+#if IS_ENABLED(CONFIG_INET)
+ case LANDLOCK_KEY_NET_PORT:
+ get_access_mask = landlock_get_net_access_mask;
+ num_access = LANDLOCK_NUM_ACCESS_NET;
+ break;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ default:
+ WARN_ON_ONCE(1);
+ return 0;
+ }
+
+ memset(layer_masks, 0,
+ array_size(sizeof((*layer_masks)[0]), num_access));
+
+ /* An empty access request can happen because of O_WRONLY | O_RDWR. */
+ if (!access_request)
+ return 0;
+
+ /* Saves all handled accesses per layer. */
+ for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
+ const unsigned long access_req = access_request;
+ const access_mask_t access_mask =
+ get_access_mask(domain, layer_level);
+ unsigned long access_bit;
+
+ for_each_set_bit(access_bit, &access_req, num_access) {
+ if (BIT_ULL(access_bit) & access_mask) {
+ (*layer_masks)[access_bit] |=
+ BIT_ULL(layer_level);
+ handled_accesses |= BIT_ULL(access_bit);
+ }
+ }
+ }
+ return handled_accesses;
+}
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
new file mode 100644
index 000000000000..1a78cba662b2
--- /dev/null
+++ b/security/landlock/ruleset.h
@@ -0,0 +1,315 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Ruleset management
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#ifndef _SECURITY_LANDLOCK_RULESET_H
+#define _SECURITY_LANDLOCK_RULESET_H
+
+#include <linux/cleanup.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/rbtree.h>
+#include <linux/refcount.h>
+#include <linux/workqueue.h>
+
+#include "access.h"
+#include "limits.h"
+#include "object.h"
+
+struct landlock_hierarchy;
+
+/**
+ * struct landlock_layer - Access rights for a given layer
+ */
+struct landlock_layer {
+ /**
+ * @level: Position of this layer in the layer stack. Starts from 1.
+ */
+ u16 level;
+ /**
+ * @access: Bitfield of allowed actions on the kernel object. They are
+ * relative to the object type (e.g. %LANDLOCK_ACTION_FS_READ).
+ */
+ access_mask_t access;
+};
+
+/**
+ * union landlock_key - Key of a ruleset's red-black tree
+ */
+union landlock_key {
+ /**
+ * @object: Pointer to identify a kernel object (e.g. an inode).
+ */
+ struct landlock_object *object;
+ /**
+ * @data: Raw data to identify an arbitrary 32-bit value
+ * (e.g. a TCP port).
+ */
+ uintptr_t data;
+};
+
+/**
+ * enum landlock_key_type - Type of &union landlock_key
+ */
+enum landlock_key_type {
+ /**
+ * @LANDLOCK_KEY_INODE: Type of &landlock_ruleset.root_inode's node
+ * keys.
+ */
+ LANDLOCK_KEY_INODE = 1,
+ /**
+ * @LANDLOCK_KEY_NET_PORT: Type of &landlock_ruleset.root_net_port's
+ * node keys.
+ */
+ LANDLOCK_KEY_NET_PORT,
+};
+
+/**
+ * struct landlock_id - Unique rule identifier for a ruleset
+ */
+struct landlock_id {
+ /**
+ * @key: Identifies either a kernel object (e.g. an inode) or
+ * a raw value (e.g. a TCP port).
+ */
+ union landlock_key key;
+ /**
+ * @type: Type of a landlock_ruleset's root tree.
+ */
+ const enum landlock_key_type type;
+};
+
+/**
+ * struct landlock_rule - Access rights tied to an object
+ */
+struct landlock_rule {
+ /**
+ * @node: Node in the ruleset's red-black tree.
+ */
+ struct rb_node node;
+ /**
+ * @key: A union to identify either a kernel object (e.g. an inode) or
+ * a raw data value (e.g. a network socket port). This is used as a key
+ * for this ruleset element. The pointer is set once and never
+ * modified. It always points to an allocated object because each rule
+ * increments the refcount of its object.
+ */
+ union landlock_key key;
+ /**
+ * @num_layers: Number of entries in @layers.
+ */
+ u32 num_layers;
+ /**
+ * @layers: Stack of layers, from the latest to the newest, implemented
+ * as a flexible array member (FAM).
+ */
+ struct landlock_layer layers[] __counted_by(num_layers);
+};
+
+/**
+ * struct landlock_ruleset - Landlock ruleset
+ *
+ * This data structure must contain unique entries, be updatable, and quick to
+ * match an object.
+ */
+struct landlock_ruleset {
+ /**
+ * @root_inode: Root of a red-black tree containing &struct
+ * landlock_rule nodes with inode object. Once a ruleset is tied to a
+ * process (i.e. as a domain), this tree is immutable until @usage
+ * reaches zero.
+ */
+ struct rb_root root_inode;
+
+#if IS_ENABLED(CONFIG_INET)
+ /**
+ * @root_net_port: Root of a red-black tree containing &struct
+ * landlock_rule nodes with network port. Once a ruleset is tied to a
+ * process (i.e. as a domain), this tree is immutable until @usage
+ * reaches zero.
+ */
+ struct rb_root root_net_port;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ /**
+ * @hierarchy: Enables hierarchy identification even when a parent
+ * domain vanishes. This is needed for the ptrace protection.
+ */
+ struct landlock_hierarchy *hierarchy;
+ union {
+ /**
+ * @work_free: Enables to free a ruleset within a lockless
+ * section. This is only used by
+ * landlock_put_ruleset_deferred() when @usage reaches zero.
+ * The fields @lock, @usage, @num_rules, @num_layers and
+ * @access_masks are then unused.
+ */
+ struct work_struct work_free;
+ struct {
+ /**
+ * @lock: Protects against concurrent modifications of
+ * @root, if @usage is greater than zero.
+ */
+ struct mutex lock;
+ /**
+ * @usage: Number of processes (i.e. domains) or file
+ * descriptors referencing this ruleset.
+ */
+ refcount_t usage;
+ /**
+ * @num_rules: Number of non-overlapping (i.e. not for
+ * the same object) rules in this ruleset.
+ */
+ u32 num_rules;
+ /**
+ * @num_layers: Number of layers that are used in this
+ * ruleset. This enables to check that all the layers
+ * allow an access request. A value of 0 identifies a
+ * non-merged ruleset (i.e. not a domain).
+ */
+ u32 num_layers;
+ /**
+ * @access_masks: Contains the subset of filesystem and
+ * network actions that are restricted by a ruleset.
+ * A domain saves all layers of merged rulesets in a
+ * stack (FAM), starting from the first layer to the
+ * last one. These layers are used when merging
+ * rulesets, for user space backward compatibility
+ * (i.e. future-proof), and to properly handle merged
+ * rulesets without overlapping access rights. These
+ * layers are set once and never changed for the
+ * lifetime of the ruleset.
+ */
+ struct access_masks access_masks[];
+ };
+ };
+};
+
+struct landlock_ruleset *
+landlock_create_ruleset(const access_mask_t access_mask_fs,
+ const access_mask_t access_mask_net,
+ const access_mask_t scope_mask);
+
+void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
+void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
+
+DEFINE_FREE(landlock_put_ruleset, struct landlock_ruleset *,
+ if (!IS_ERR_OR_NULL(_T)) landlock_put_ruleset(_T))
+
+int landlock_insert_rule(struct landlock_ruleset *const ruleset,
+ const struct landlock_id id,
+ const access_mask_t access);
+
+struct landlock_ruleset *
+landlock_merge_ruleset(struct landlock_ruleset *const parent,
+ struct landlock_ruleset *const ruleset);
+
+const struct landlock_rule *
+landlock_find_rule(const struct landlock_ruleset *const ruleset,
+ const struct landlock_id id);
+
+static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
+{
+ if (ruleset)
+ refcount_inc(&ruleset->usage);
+}
+
+/**
+ * landlock_union_access_masks - Return all access rights handled in the
+ * domain
+ *
+ * @domain: Landlock ruleset (used as a domain)
+ *
+ * Returns: an access_masks result of the OR of all the domain's access masks.
+ */
+static inline struct access_masks
+landlock_union_access_masks(const struct landlock_ruleset *const domain)
+{
+ union access_masks_all matches = {};
+ size_t layer_level;
+
+ for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
+ union access_masks_all layer = {
+ .masks = domain->access_masks[layer_level],
+ };
+
+ matches.all |= layer.all;
+ }
+
+ return matches.masks;
+}
+
+static inline void
+landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
+ const access_mask_t fs_access_mask,
+ const u16 layer_level)
+{
+ access_mask_t fs_mask = fs_access_mask & LANDLOCK_MASK_ACCESS_FS;
+
+ /* Should already be checked in sys_landlock_create_ruleset(). */
+ WARN_ON_ONCE(fs_access_mask != fs_mask);
+ ruleset->access_masks[layer_level].fs |= fs_mask;
+}
+
+static inline void
+landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
+ const access_mask_t net_access_mask,
+ const u16 layer_level)
+{
+ access_mask_t net_mask = net_access_mask & LANDLOCK_MASK_ACCESS_NET;
+
+ /* Should already be checked in sys_landlock_create_ruleset(). */
+ WARN_ON_ONCE(net_access_mask != net_mask);
+ ruleset->access_masks[layer_level].net |= net_mask;
+}
+
+static inline void
+landlock_add_scope_mask(struct landlock_ruleset *const ruleset,
+ const access_mask_t scope_mask, const u16 layer_level)
+{
+ access_mask_t mask = scope_mask & LANDLOCK_MASK_SCOPE;
+
+ /* Should already be checked in sys_landlock_create_ruleset(). */
+ WARN_ON_ONCE(scope_mask != mask);
+ ruleset->access_masks[layer_level].scope |= mask;
+}
+
+static inline access_mask_t
+landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
+ const u16 layer_level)
+{
+ /* Handles all initially denied by default access rights. */
+ return ruleset->access_masks[layer_level].fs |
+ _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
+}
+
+static inline access_mask_t
+landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset,
+ const u16 layer_level)
+{
+ return ruleset->access_masks[layer_level].net;
+}
+
+static inline access_mask_t
+landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
+ const u16 layer_level)
+{
+ return ruleset->access_masks[layer_level].scope;
+}
+
+bool landlock_unmask_layers(const struct landlock_rule *const rule,
+ const access_mask_t access_request,
+ layer_mask_t (*const layer_masks)[],
+ const size_t masks_array_size);
+
+access_mask_t
+landlock_init_layer_masks(const struct landlock_ruleset *const domain,
+ const access_mask_t access_request,
+ layer_mask_t (*const layer_masks)[],
+ const enum landlock_key_type key_type);
+
+#endif /* _SECURITY_LANDLOCK_RULESET_H */
diff --git a/security/landlock/setup.c b/security/landlock/setup.c
new file mode 100644
index 000000000000..47dac1736f10
--- /dev/null
+++ b/security/landlock/setup.c
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Security framework setup
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#include <linux/bits.h>
+#include <linux/init.h>
+#include <linux/lsm_hooks.h>
+#include <uapi/linux/lsm.h>
+
+#include "common.h"
+#include "cred.h"
+#include "errata.h"
+#include "fs.h"
+#include "id.h"
+#include "net.h"
+#include "setup.h"
+#include "task.h"
+
+bool landlock_initialized __ro_after_init = false;
+
+const struct lsm_id landlock_lsmid = {
+ .name = LANDLOCK_NAME,
+ .id = LSM_ID_LANDLOCK,
+};
+
+struct lsm_blob_sizes landlock_blob_sizes __ro_after_init = {
+ .lbs_cred = sizeof(struct landlock_cred_security),
+ .lbs_file = sizeof(struct landlock_file_security),
+ .lbs_inode = sizeof(struct landlock_inode_security),
+ .lbs_superblock = sizeof(struct landlock_superblock_security),
+};
+
+int landlock_errata __ro_after_init;
+
+static void __init compute_errata(void)
+{
+ size_t i;
+
+#ifndef __has_include
+ /*
+ * This is a safeguard to make sure the compiler implements
+ * __has_include (see errata.h).
+ */
+ WARN_ON_ONCE(1);
+ return;
+#endif
+
+ for (i = 0; landlock_errata_init[i].number; i++) {
+ const int prev_errata = landlock_errata;
+
+ if (WARN_ON_ONCE(landlock_errata_init[i].abi >
+ landlock_abi_version))
+ continue;
+
+ landlock_errata |= BIT(landlock_errata_init[i].number - 1);
+ WARN_ON_ONCE(prev_errata == landlock_errata);
+ }
+}
+
+static int __init landlock_init(void)
+{
+ compute_errata();
+ landlock_add_cred_hooks();
+ landlock_add_task_hooks();
+ landlock_add_fs_hooks();
+ landlock_add_net_hooks();
+ landlock_init_id();
+ landlock_initialized = true;
+ pr_info("Up and running.\n");
+ return 0;
+}
+
+DEFINE_LSM(LANDLOCK_NAME) = {
+ .id = &landlock_lsmid,
+ .init = landlock_init,
+ .blobs = &landlock_blob_sizes,
+};
diff --git a/security/landlock/setup.h b/security/landlock/setup.h
new file mode 100644
index 000000000000..fca307c35fee
--- /dev/null
+++ b/security/landlock/setup.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Security framework setup
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#ifndef _SECURITY_LANDLOCK_SETUP_H
+#define _SECURITY_LANDLOCK_SETUP_H
+
+#include <linux/lsm_hooks.h>
+
+extern const int landlock_abi_version;
+
+extern bool landlock_initialized;
+extern int landlock_errata;
+
+extern struct lsm_blob_sizes landlock_blob_sizes;
+extern const struct lsm_id landlock_lsmid;
+
+#endif /* _SECURITY_LANDLOCK_SETUP_H */
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
new file mode 100644
index 000000000000..0116e9f93ffe
--- /dev/null
+++ b/security/landlock/syscalls.c
@@ -0,0 +1,570 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock - System call implementations and user space interfaces
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ * Copyright © 2021-2025 Microsoft Corporation
+ */
+
+#include <asm/current.h>
+#include <linux/anon_inodes.h>
+#include <linux/bitops.h>
+#include <linux/build_bug.h>
+#include <linux/capability.h>
+#include <linux/cleanup.h>
+#include <linux/compiler_types.h>
+#include <linux/dcache.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/limits.h>
+#include <linux/mount.h>
+#include <linux/path.h>
+#include <linux/sched.h>
+#include <linux/security.h>
+#include <linux/stddef.h>
+#include <linux/syscalls.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <uapi/linux/landlock.h>
+
+#include "cred.h"
+#include "domain.h"
+#include "fs.h"
+#include "limits.h"
+#include "net.h"
+#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
+ *
+ * Extend copy_struct_from_user() to check for consistent user buffer.
+ *
+ * @dst: Kernel space pointer or NULL.
+ * @ksize: Actual size of the data pointed to by @dst.
+ * @ksize_min: Minimal required size to be copied.
+ * @src: User space pointer or NULL.
+ * @usize: (Alleged) size of the data pointed to by @src.
+ */
+static __always_inline int
+copy_min_struct_from_user(void *const dst, const size_t ksize,
+ const size_t ksize_min, const void __user *const src,
+ const size_t usize)
+{
+ /* Checks buffer inconsistencies. */
+ BUILD_BUG_ON(!dst);
+ if (!src)
+ return -EFAULT;
+
+ /* Checks size ranges. */
+ BUILD_BUG_ON(ksize <= 0);
+ BUILD_BUG_ON(ksize < ksize_min);
+ if (usize < ksize_min)
+ return -EINVAL;
+ if (usize > PAGE_SIZE)
+ return -E2BIG;
+
+ /* Copies user buffer and fills with zeros. */
+ return copy_struct_from_user(dst, ksize, src, usize);
+}
+
+/*
+ * This function only contains arithmetic operations with constants, leading to
+ * BUILD_BUG_ON(). The related code is evaluated and checked at build time,
+ * but it is then ignored thanks to compiler optimizations.
+ */
+static void build_check_abi(void)
+{
+ struct landlock_ruleset_attr ruleset_attr;
+ struct landlock_path_beneath_attr path_beneath_attr;
+ struct landlock_net_port_attr net_port_attr;
+ size_t ruleset_size, path_beneath_size, net_port_size;
+
+ /*
+ * For each user space ABI structures, first checks that there is no
+ * hole in them, then checks that all architectures have the same
+ * struct size.
+ */
+ ruleset_size = sizeof(ruleset_attr.handled_access_fs);
+ ruleset_size += sizeof(ruleset_attr.handled_access_net);
+ ruleset_size += sizeof(ruleset_attr.scoped);
+ BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size);
+ BUILD_BUG_ON(sizeof(ruleset_attr) != 24);
+
+ path_beneath_size = sizeof(path_beneath_attr.allowed_access);
+ path_beneath_size += sizeof(path_beneath_attr.parent_fd);
+ BUILD_BUG_ON(sizeof(path_beneath_attr) != path_beneath_size);
+ BUILD_BUG_ON(sizeof(path_beneath_attr) != 12);
+
+ net_port_size = sizeof(net_port_attr.allowed_access);
+ net_port_size += sizeof(net_port_attr.port);
+ BUILD_BUG_ON(sizeof(net_port_attr) != net_port_size);
+ BUILD_BUG_ON(sizeof(net_port_attr) != 16);
+}
+
+/* Ruleset handling */
+
+static int fop_ruleset_release(struct inode *const inode,
+ struct file *const filp)
+{
+ struct landlock_ruleset *ruleset = filp->private_data;
+
+ landlock_put_ruleset(ruleset);
+ return 0;
+}
+
+static ssize_t fop_dummy_read(struct file *const filp, char __user *const buf,
+ const size_t size, loff_t *const ppos)
+{
+ /* Dummy handler to enable FMODE_CAN_READ. */
+ return -EINVAL;
+}
+
+static ssize_t fop_dummy_write(struct file *const filp,
+ const char __user *const buf, const size_t size,
+ loff_t *const ppos)
+{
+ /* Dummy handler to enable FMODE_CAN_WRITE. */
+ return -EINVAL;
+}
+
+/*
+ * A ruleset file descriptor enables to build a ruleset by adding (i.e.
+ * writing) rule after rule, without relying on the task's context. This
+ * reentrant design is also used in a read way to enforce the ruleset on the
+ * current task.
+ */
+static const struct file_operations ruleset_fops = {
+ .release = fop_ruleset_release,
+ .read = fop_dummy_read,
+ .write = fop_dummy_write,
+};
+
+/*
+ * The Landlock ABI version should be incremented for each new Landlock-related
+ * user space visible change (e.g. Landlock syscalls). This version should
+ * only be incremented once per Linux release, and the date in
+ * Documentation/userspace-api/landlock.rst should be updated to reflect the
+ * UAPI change.
+ */
+const int landlock_abi_version = 7;
+
+/**
+ * sys_landlock_create_ruleset - Create a new ruleset
+ *
+ * @attr: Pointer to a &struct landlock_ruleset_attr identifying the scope of
+ * the new ruleset.
+ * @size: Size of the pointed &struct landlock_ruleset_attr (needed for
+ * backward and forward compatibility).
+ * @flags: Supported values:
+ *
+ * - %LANDLOCK_CREATE_RULESET_VERSION
+ * - %LANDLOCK_CREATE_RULESET_ERRATA
+ *
+ * This system call enables to create a new Landlock ruleset, and returns the
+ * related file descriptor on success.
+ *
+ * If %LANDLOCK_CREATE_RULESET_VERSION or %LANDLOCK_CREATE_RULESET_ERRATA is
+ * set, then @attr must be NULL and @size must be 0.
+ *
+ * Possible returned errors are:
+ *
+ * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
+ * - %EINVAL: unknown @flags, or unknown access, or unknown scope, or too small @size;
+ * - %E2BIG: @attr or @size inconsistencies;
+ * - %EFAULT: @attr or @size inconsistencies;
+ * - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs.
+ *
+ * .. kernel-doc:: include/uapi/linux/landlock.h
+ * :identifiers: landlock_create_ruleset_flags
+ */
+SYSCALL_DEFINE3(landlock_create_ruleset,
+ const struct landlock_ruleset_attr __user *const, attr,
+ const size_t, size, const __u32, flags)
+{
+ struct landlock_ruleset_attr ruleset_attr;
+ struct landlock_ruleset *ruleset;
+ int err, ruleset_fd;
+
+ /* Build-time checks. */
+ build_check_abi();
+
+ if (!is_initialized())
+ return -EOPNOTSUPP;
+
+ if (flags) {
+ if (attr || size)
+ return -EINVAL;
+
+ if (flags == LANDLOCK_CREATE_RULESET_VERSION)
+ return landlock_abi_version;
+
+ if (flags == LANDLOCK_CREATE_RULESET_ERRATA)
+ return landlock_errata;
+
+ return -EINVAL;
+ }
+
+ /* Copies raw user space buffer. */
+ err = copy_min_struct_from_user(&ruleset_attr, sizeof(ruleset_attr),
+ offsetofend(typeof(ruleset_attr),
+ handled_access_fs),
+ attr, size);
+ if (err)
+ return err;
+
+ /* Checks content (and 32-bits cast). */
+ if ((ruleset_attr.handled_access_fs | LANDLOCK_MASK_ACCESS_FS) !=
+ LANDLOCK_MASK_ACCESS_FS)
+ return -EINVAL;
+
+ /* Checks network content (and 32-bits cast). */
+ if ((ruleset_attr.handled_access_net | LANDLOCK_MASK_ACCESS_NET) !=
+ LANDLOCK_MASK_ACCESS_NET)
+ return -EINVAL;
+
+ /* Checks IPC scoping content (and 32-bits cast). */
+ if ((ruleset_attr.scoped | LANDLOCK_MASK_SCOPE) != LANDLOCK_MASK_SCOPE)
+ return -EINVAL;
+
+ /* Checks arguments and transforms to kernel struct. */
+ ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs,
+ ruleset_attr.handled_access_net,
+ ruleset_attr.scoped);
+ if (IS_ERR(ruleset))
+ return PTR_ERR(ruleset);
+
+ /* Creates anonymous FD referring to the ruleset. */
+ ruleset_fd = anon_inode_getfd("[landlock-ruleset]", &ruleset_fops,
+ ruleset, O_RDWR | O_CLOEXEC);
+ if (ruleset_fd < 0)
+ landlock_put_ruleset(ruleset);
+ return ruleset_fd;
+}
+
+/*
+ * Returns an owned ruleset from a FD. It is thus needed to call
+ * landlock_put_ruleset() on the return value.
+ */
+static struct landlock_ruleset *get_ruleset_from_fd(const int fd,
+ const fmode_t mode)
+{
+ CLASS(fd, ruleset_f)(fd);
+ struct landlock_ruleset *ruleset;
+
+ if (fd_empty(ruleset_f))
+ return ERR_PTR(-EBADF);
+
+ /* Checks FD type and access right. */
+ if (fd_file(ruleset_f)->f_op != &ruleset_fops)
+ return ERR_PTR(-EBADFD);
+ if (!(fd_file(ruleset_f)->f_mode & mode))
+ return ERR_PTR(-EPERM);
+ ruleset = fd_file(ruleset_f)->private_data;
+ if (WARN_ON_ONCE(ruleset->num_layers != 1))
+ return ERR_PTR(-EINVAL);
+ landlock_get_ruleset(ruleset);
+ return ruleset;
+}
+
+/* Path handling */
+
+/*
+ * @path: Must call put_path(@path) after the call if it succeeded.
+ */
+static int get_path_from_fd(const s32 fd, struct path *const path)
+{
+ CLASS(fd_raw, f)(fd);
+
+ BUILD_BUG_ON(!__same_type(
+ fd, ((struct landlock_path_beneath_attr *)NULL)->parent_fd));
+
+ if (fd_empty(f))
+ return -EBADF;
+ /*
+ * Forbids ruleset FDs, internal filesystems (e.g. nsfs), including
+ * pseudo filesystems that will never be mountable (e.g. sockfs,
+ * pipefs).
+ */
+ if ((fd_file(f)->f_op == &ruleset_fops) ||
+ (fd_file(f)->f_path.mnt->mnt_flags & MNT_INTERNAL) ||
+ (fd_file(f)->f_path.dentry->d_sb->s_flags & SB_NOUSER) ||
+ IS_PRIVATE(d_backing_inode(fd_file(f)->f_path.dentry)))
+ return -EBADFD;
+
+ *path = fd_file(f)->f_path;
+ path_get(path);
+ return 0;
+}
+
+static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
+ const void __user *const rule_attr)
+{
+ struct landlock_path_beneath_attr path_beneath_attr;
+ struct path path;
+ int res, err;
+ access_mask_t mask;
+
+ /* Copies raw user space buffer. */
+ res = copy_from_user(&path_beneath_attr, rule_attr,
+ sizeof(path_beneath_attr));
+ if (res)
+ return -EFAULT;
+
+ /*
+ * Informs about useless rule: empty allowed_access (i.e. deny rules)
+ * are ignored in path walks.
+ */
+ if (!path_beneath_attr.allowed_access)
+ return -ENOMSG;
+
+ /* Checks that allowed_access matches the @ruleset constraints. */
+ mask = ruleset->access_masks[0].fs;
+ if ((path_beneath_attr.allowed_access | mask) != mask)
+ return -EINVAL;
+
+ /* Gets and checks the new rule. */
+ err = get_path_from_fd(path_beneath_attr.parent_fd, &path);
+ if (err)
+ return err;
+
+ /* Imports the new rule. */
+ err = landlock_append_fs_rule(ruleset, &path,
+ path_beneath_attr.allowed_access);
+ path_put(&path);
+ return err;
+}
+
+static int add_rule_net_port(struct landlock_ruleset *ruleset,
+ const void __user *const rule_attr)
+{
+ struct landlock_net_port_attr net_port_attr;
+ int res;
+ access_mask_t mask;
+
+ /* Copies raw user space buffer. */
+ res = copy_from_user(&net_port_attr, rule_attr, sizeof(net_port_attr));
+ if (res)
+ return -EFAULT;
+
+ /*
+ * Informs about useless rule: empty allowed_access (i.e. deny rules)
+ * are ignored by network actions.
+ */
+ if (!net_port_attr.allowed_access)
+ return -ENOMSG;
+
+ /* Checks that allowed_access matches the @ruleset constraints. */
+ mask = landlock_get_net_access_mask(ruleset, 0);
+ if ((net_port_attr.allowed_access | mask) != mask)
+ return -EINVAL;
+
+ /* Denies inserting a rule with port greater than 65535. */
+ if (net_port_attr.port > U16_MAX)
+ return -EINVAL;
+
+ /* Imports the new rule. */
+ return landlock_append_net_rule(ruleset, net_port_attr.port,
+ net_port_attr.allowed_access);
+}
+
+/**
+ * sys_landlock_add_rule - Add a new rule to a ruleset
+ *
+ * @ruleset_fd: File descriptor tied to the ruleset that should be extended
+ * with the new rule.
+ * @rule_type: Identify the structure type pointed to by @rule_attr:
+ * %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_PORT.
+ * @rule_attr: Pointer to a rule (matching the @rule_type).
+ * @flags: Must be 0.
+ *
+ * This system call enables to define a new rule and add it to an existing
+ * ruleset.
+ *
+ * Possible returned errors are:
+ *
+ * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
+ * - %EAFNOSUPPORT: @rule_type is %LANDLOCK_RULE_NET_PORT but TCP/IP is not
+ * supported by the running kernel;
+ * - %EINVAL: @flags is not 0;
+ * - %EINVAL: The rule accesses are inconsistent (i.e.
+ * &landlock_path_beneath_attr.allowed_access or
+ * &landlock_net_port_attr.allowed_access is not a subset of the ruleset
+ * handled accesses)
+ * - %EINVAL: &landlock_net_port_attr.port is greater than 65535;
+ * - %ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access is
+ * 0);
+ * - %EBADF: @ruleset_fd is not a file descriptor for the current thread, or a
+ * member of @rule_attr is not a file descriptor as expected;
+ * - %EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of
+ * @rule_attr is not the expected file descriptor type;
+ * - %EPERM: @ruleset_fd has no write access to the underlying ruleset;
+ * - %EFAULT: @rule_attr was not a valid address.
+ */
+SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
+ const enum landlock_rule_type, rule_type,
+ const void __user *const, rule_attr, const __u32, flags)
+{
+ struct landlock_ruleset *ruleset __free(landlock_put_ruleset) = NULL;
+
+ if (!is_initialized())
+ return -EOPNOTSUPP;
+
+ /* No flag for now. */
+ if (flags)
+ return -EINVAL;
+
+ /* Gets and checks the ruleset. */
+ ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
+ if (IS_ERR(ruleset))
+ return PTR_ERR(ruleset);
+
+ switch (rule_type) {
+ case LANDLOCK_RULE_PATH_BENEATH:
+ return add_rule_path_beneath(ruleset, rule_attr);
+ case LANDLOCK_RULE_NET_PORT:
+ return add_rule_net_port(ruleset, rule_attr);
+ default:
+ return -EINVAL;
+ }
+}
+
+/* Enforcement */
+
+/**
+ * sys_landlock_restrict_self - Enforce a ruleset on the calling thread
+ *
+ * @ruleset_fd: File descriptor tied to the ruleset to merge with the target.
+ * @flags: Supported values:
+ *
+ * - %LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF
+ * - %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON
+ * - %LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF
+ *
+ * This system call enables to enforce a Landlock ruleset on the current
+ * thread. Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its
+ * namespace or is running with no_new_privs. This avoids scenarios where
+ * unprivileged tasks can affect the behavior of privileged children.
+ *
+ * Possible returned errors are:
+ *
+ * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
+ * - %EINVAL: @flags contains an unknown bit.
+ * - %EBADF: @ruleset_fd is not a file descriptor for the current thread;
+ * - %EBADFD: @ruleset_fd is not a ruleset file descriptor;
+ * - %EPERM: @ruleset_fd has no read access to the underlying ruleset, or the
+ * current thread is not running with no_new_privs, or it doesn't have
+ * %CAP_SYS_ADMIN in its namespace.
+ * - %E2BIG: The maximum number of stacked rulesets is reached for the current
+ * thread.
+ *
+ * .. kernel-doc:: include/uapi/linux/landlock.h
+ * :identifiers: landlock_restrict_self_flags
+ */
+SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
+ flags)
+{
+ struct landlock_ruleset *new_dom,
+ *ruleset __free(landlock_put_ruleset) = NULL;
+ struct cred *new_cred;
+ struct landlock_cred_security *new_llcred;
+ bool __maybe_unused log_same_exec, log_new_exec, log_subdomains,
+ prev_log_subdomains;
+
+ if (!is_initialized())
+ return -EOPNOTSUPP;
+
+ /*
+ * Similar checks as for seccomp(2), except that an -EPERM may be
+ * returned.
+ */
+ if (!task_no_new_privs(current) &&
+ !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if ((flags | LANDLOCK_MASK_RESTRICT_SELF) !=
+ LANDLOCK_MASK_RESTRICT_SELF)
+ return -EINVAL;
+
+ /* Translates "off" flag to boolean. */
+ log_same_exec = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF);
+ /* Translates "on" flag to boolean. */
+ log_new_exec = !!(flags & LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON);
+ /* Translates "off" flag to boolean. */
+ log_subdomains = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF);
+
+ /*
+ * It is allowed to set LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF with
+ * -1 as ruleset_fd, but no other flag must be set.
+ */
+ if (!(ruleset_fd == -1 &&
+ flags == LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) {
+ /* Gets and checks the ruleset. */
+ ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_READ);
+ if (IS_ERR(ruleset))
+ return PTR_ERR(ruleset);
+ }
+
+ /* Prepares new credentials. */
+ new_cred = prepare_creds();
+ if (!new_cred)
+ return -ENOMEM;
+
+ new_llcred = landlock_cred(new_cred);
+
+#ifdef CONFIG_AUDIT
+ prev_log_subdomains = !new_llcred->log_subdomains_off;
+ new_llcred->log_subdomains_off = !prev_log_subdomains ||
+ !log_subdomains;
+#endif /* CONFIG_AUDIT */
+
+ /*
+ * The only case when a ruleset may not be set is if
+ * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF is set and ruleset_fd is -1.
+ * We could optimize this case by not calling commit_creds() if this flag
+ * was already set, but it is not worth the complexity.
+ */
+ if (!ruleset)
+ return commit_creds(new_cred);
+
+ /*
+ * There is no possible race condition while copying and manipulating
+ * the current credentials because they are dedicated per thread.
+ */
+ new_dom = landlock_merge_ruleset(new_llcred->domain, ruleset);
+ if (IS_ERR(new_dom)) {
+ abort_creds(new_cred);
+ return PTR_ERR(new_dom);
+ }
+
+#ifdef CONFIG_AUDIT
+ new_dom->hierarchy->log_same_exec = log_same_exec;
+ new_dom->hierarchy->log_new_exec = log_new_exec;
+ if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains)
+ new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED;
+#endif /* CONFIG_AUDIT */
+
+ /* Replaces the old (prepared) domain. */
+ landlock_put_ruleset(new_llcred->domain);
+ new_llcred->domain = new_dom;
+
+#ifdef CONFIG_AUDIT
+ new_llcred->domain_exec |= BIT(new_dom->num_layers - 1);
+#endif /* CONFIG_AUDIT */
+
+ return commit_creds(new_cred);
+}
diff --git a/security/landlock/task.c b/security/landlock/task.c
new file mode 100644
index 000000000000..2385017418ca
--- /dev/null
+++ b/security/landlock/task.c
@@ -0,0 +1,448 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock - Ptrace and scope hooks
+ *
+ * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2019-2020 ANSSI
+ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+#include <asm/current.h>
+#include <linux/cleanup.h>
+#include <linux/cred.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/lsm_audit.h>
+#include <linux/lsm_hooks.h>
+#include <linux/rcupdate.h>
+#include <linux/sched.h>
+#include <linux/sched/signal.h>
+#include <net/af_unix.h>
+#include <net/sock.h>
+
+#include "audit.h"
+#include "common.h"
+#include "cred.h"
+#include "domain.h"
+#include "fs.h"
+#include "ruleset.h"
+#include "setup.h"
+#include "task.h"
+
+/**
+ * domain_scope_le - Checks domain ordering for scoped ptrace
+ *
+ * @parent: Parent domain.
+ * @child: Potential child of @parent.
+ *
+ * Checks if the @parent domain is less or equal to (i.e. an ancestor, which
+ * means a subset of) the @child domain.
+ */
+static bool domain_scope_le(const struct landlock_ruleset *const parent,
+ const struct landlock_ruleset *const child)
+{
+ const struct landlock_hierarchy *walker;
+
+ /* Quick return for non-landlocked tasks. */
+ if (!parent)
+ return true;
+
+ if (!child)
+ return false;
+
+ for (walker = child->hierarchy; walker; walker = walker->parent) {
+ if (walker == parent->hierarchy)
+ /* @parent is in the scoped hierarchy of @child. */
+ return true;
+ }
+
+ /* There is no relationship between @parent and @child. */
+ return false;
+}
+
+static int domain_ptrace(const struct landlock_ruleset *const parent,
+ const struct landlock_ruleset *const child)
+{
+ if (domain_scope_le(parent, child))
+ return 0;
+
+ return -EPERM;
+}
+
+/**
+ * hook_ptrace_access_check - Determines whether the current process may access
+ * another
+ *
+ * @child: Process to be accessed.
+ * @mode: Mode of attachment.
+ *
+ * If the current task has Landlock rules, then the child must have at least
+ * the same rules. Else denied.
+ *
+ * Determines whether a process may access another, returning 0 if permission
+ * granted, -errno if denied.
+ */
+static int hook_ptrace_access_check(struct task_struct *const child,
+ const unsigned int mode)
+{
+ const struct landlock_cred_security *parent_subject;
+ const struct landlock_ruleset *child_dom;
+ int err;
+
+ /* Quick return for non-landlocked tasks. */
+ parent_subject = landlock_cred(current_cred());
+ if (!parent_subject)
+ return 0;
+
+ scoped_guard(rcu)
+ {
+ child_dom = landlock_get_task_domain(child);
+ err = domain_ptrace(parent_subject->domain, child_dom);
+ }
+
+ if (!err)
+ return 0;
+
+ /*
+ * For the ptrace_access_check case, we log the current/parent domain
+ * and the child task.
+ */
+ if (!(mode & PTRACE_MODE_NOAUDIT))
+ landlock_log_denial(parent_subject, &(struct landlock_request) {
+ .type = LANDLOCK_REQUEST_PTRACE,
+ .audit = {
+ .type = LSM_AUDIT_DATA_TASK,
+ .u.tsk = child,
+ },
+ .layer_plus_one = parent_subject->domain->num_layers,
+ });
+
+ return err;
+}
+
+/**
+ * hook_ptrace_traceme - Determines whether another process may trace the
+ * current one
+ *
+ * @parent: Task proposed to be the tracer.
+ *
+ * If the parent has Landlock rules, then the current task must have the same
+ * or more rules. Else denied.
+ *
+ * Determines whether the nominated task is permitted to trace the current
+ * process, returning 0 if permission is granted, -errno if denied.
+ */
+static int hook_ptrace_traceme(struct task_struct *const parent)
+{
+ const struct landlock_cred_security *parent_subject;
+ const struct landlock_ruleset *child_dom;
+ int err;
+
+ child_dom = landlock_get_current_domain();
+
+ guard(rcu)();
+ parent_subject = landlock_cred(__task_cred(parent));
+ err = domain_ptrace(parent_subject->domain, child_dom);
+
+ if (!err)
+ return 0;
+
+ /*
+ * For the ptrace_traceme case, we log the domain which is the cause of
+ * the denial, which means the parent domain instead of the current
+ * domain. This may look unusual because the ptrace_traceme action is a
+ * request to be traced, but the semantic is consistent with
+ * hook_ptrace_access_check().
+ */
+ landlock_log_denial(parent_subject, &(struct landlock_request) {
+ .type = LANDLOCK_REQUEST_PTRACE,
+ .audit = {
+ .type = LSM_AUDIT_DATA_TASK,
+ .u.tsk = current,
+ },
+ .layer_plus_one = parent_subject->domain->num_layers,
+ });
+ return err;
+}
+
+/**
+ * domain_is_scoped - Checks if the client domain is scoped in the same
+ * domain as the server.
+ *
+ * @client: IPC sender domain.
+ * @server: IPC receiver domain.
+ * @scope: The scope restriction criteria.
+ *
+ * Returns: True if the @client domain is scoped to access the @server,
+ * unless the @server is also scoped in the same domain as @client.
+ */
+static bool domain_is_scoped(const struct landlock_ruleset *const client,
+ const struct landlock_ruleset *const server,
+ access_mask_t scope)
+{
+ int client_layer, server_layer;
+ const struct landlock_hierarchy *client_walker, *server_walker;
+
+ /* Quick return if client has no domain */
+ if (WARN_ON_ONCE(!client))
+ return false;
+
+ client_layer = client->num_layers - 1;
+ client_walker = client->hierarchy;
+ /*
+ * client_layer must be a signed integer with greater capacity
+ * than client->num_layers to ensure the following loop stops.
+ */
+ BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers));
+
+ server_layer = server ? (server->num_layers - 1) : -1;
+ server_walker = server ? server->hierarchy : NULL;
+
+ /*
+ * Walks client's parent domains down to the same hierarchy level
+ * as the server's domain, and checks that none of these client's
+ * parent domains are scoped.
+ */
+ for (; client_layer > server_layer; client_layer--) {
+ if (landlock_get_scope_mask(client, client_layer) & scope)
+ return true;
+
+ client_walker = client_walker->parent;
+ }
+ /*
+ * Walks server's parent domains down to the same hierarchy level as
+ * the client's domain.
+ */
+ for (; server_layer > client_layer; server_layer--)
+ server_walker = server_walker->parent;
+
+ for (; client_layer >= 0; client_layer--) {
+ if (landlock_get_scope_mask(client, client_layer) & scope) {
+ /*
+ * Client and server are at the same level in the
+ * hierarchy. If the client is scoped, the request is
+ * only allowed if this domain is also a server's
+ * ancestor.
+ */
+ return server_walker != client_walker;
+ }
+ client_walker = client_walker->parent;
+ server_walker = server_walker->parent;
+ }
+ return false;
+}
+
+static bool sock_is_scoped(struct sock *const other,
+ const struct landlock_ruleset *const domain)
+{
+ const struct landlock_ruleset *dom_other;
+
+ /* The credentials will not change. */
+ lockdep_assert_held(&unix_sk(other)->lock);
+ dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
+ return domain_is_scoped(domain, dom_other,
+ LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+}
+
+static bool is_abstract_socket(struct sock *const sock)
+{
+ struct unix_address *addr = unix_sk(sock)->addr;
+
+ if (!addr)
+ return false;
+
+ if (addr->len >= offsetof(struct sockaddr_un, sun_path) + 1 &&
+ addr->name->sun_path[0] == '\0')
+ return true;
+
+ return false;
+}
+
+static const struct access_masks unix_scope = {
+ .scope = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+};
+
+static int hook_unix_stream_connect(struct sock *const sock,
+ struct sock *const other,
+ struct sock *const newsk)
+{
+ size_t handle_layer;
+ const struct landlock_cred_security *const subject =
+ landlock_get_applicable_subject(current_cred(), unix_scope,
+ &handle_layer);
+
+ /* Quick return for non-landlocked tasks. */
+ if (!subject)
+ return 0;
+
+ if (!is_abstract_socket(other))
+ return 0;
+
+ if (!sock_is_scoped(other, subject->domain))
+ return 0;
+
+ landlock_log_denial(subject, &(struct landlock_request) {
+ .type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
+ .audit = {
+ .type = LSM_AUDIT_DATA_NET,
+ .u.net = &(struct lsm_network_audit) {
+ .sk = other,
+ },
+ },
+ .layer_plus_one = handle_layer + 1,
+ });
+ return -EPERM;
+}
+
+static int hook_unix_may_send(struct socket *const sock,
+ struct socket *const other)
+{
+ size_t handle_layer;
+ const struct landlock_cred_security *const subject =
+ landlock_get_applicable_subject(current_cred(), unix_scope,
+ &handle_layer);
+
+ if (!subject)
+ return 0;
+
+ /*
+ * Checks if this datagram socket was already allowed to be connected
+ * to other.
+ */
+ if (unix_peer(sock->sk) == other->sk)
+ return 0;
+
+ if (!is_abstract_socket(other->sk))
+ return 0;
+
+ if (!sock_is_scoped(other->sk, subject->domain))
+ return 0;
+
+ landlock_log_denial(subject, &(struct landlock_request) {
+ .type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
+ .audit = {
+ .type = LSM_AUDIT_DATA_NET,
+ .u.net = &(struct lsm_network_audit) {
+ .sk = other->sk,
+ },
+ },
+ .layer_plus_one = handle_layer + 1,
+ });
+ return -EPERM;
+}
+
+static const struct access_masks signal_scope = {
+ .scope = LANDLOCK_SCOPE_SIGNAL,
+};
+
+static int hook_task_kill(struct task_struct *const p,
+ struct kernel_siginfo *const info, const int sig,
+ const struct cred *cred)
+{
+ bool is_scoped;
+ size_t handle_layer;
+ const struct landlock_cred_security *subject;
+
+ if (!cred) {
+ /*
+ * Always allow sending signals between threads of the same process.
+ * This is required for process credential changes by the Native POSIX
+ * Threads Library and implemented by the set*id(2) wrappers and
+ * libcap(3) with tgkill(2). See nptl(7) and libpsx(3).
+ *
+ * This exception is similar to the __ptrace_may_access() one.
+ */
+ if (same_thread_group(p, current))
+ return 0;
+
+ /* Not dealing with USB IO. */
+ cred = current_cred();
+ }
+
+ subject = landlock_get_applicable_subject(cred, signal_scope,
+ &handle_layer);
+
+ /* Quick return for non-landlocked tasks. */
+ if (!subject)
+ return 0;
+
+ scoped_guard(rcu)
+ {
+ is_scoped = domain_is_scoped(subject->domain,
+ landlock_get_task_domain(p),
+ signal_scope.scope);
+ }
+
+ if (!is_scoped)
+ return 0;
+
+ landlock_log_denial(subject, &(struct landlock_request) {
+ .type = LANDLOCK_REQUEST_SCOPE_SIGNAL,
+ .audit = {
+ .type = LSM_AUDIT_DATA_TASK,
+ .u.tsk = p,
+ },
+ .layer_plus_one = handle_layer + 1,
+ });
+ return -EPERM;
+}
+
+static int hook_file_send_sigiotask(struct task_struct *tsk,
+ struct fown_struct *fown, int signum)
+{
+ const struct landlock_cred_security *subject;
+ bool is_scoped = false;
+
+ /* Lock already held by send_sigio() and send_sigurg(). */
+ lockdep_assert_held(&fown->lock);
+ subject = &landlock_file(fown->file)->fown_subject;
+
+ /*
+ * Quick return for unowned socket.
+ *
+ * subject->domain has already been filtered when saved by
+ * hook_file_set_fowner(), so there is no need to call
+ * landlock_get_applicable_subject() here.
+ */
+ if (!subject->domain)
+ return 0;
+
+ scoped_guard(rcu)
+ {
+ is_scoped = domain_is_scoped(subject->domain,
+ landlock_get_task_domain(tsk),
+ signal_scope.scope);
+ }
+
+ if (!is_scoped)
+ return 0;
+
+ landlock_log_denial(subject, &(struct landlock_request) {
+ .type = LANDLOCK_REQUEST_SCOPE_SIGNAL,
+ .audit = {
+ .type = LSM_AUDIT_DATA_TASK,
+ .u.tsk = tsk,
+ },
+#ifdef CONFIG_AUDIT
+ .layer_plus_one = landlock_file(fown->file)->fown_layer + 1,
+#endif /* CONFIG_AUDIT */
+ });
+ return -EPERM;
+}
+
+static struct security_hook_list landlock_hooks[] __ro_after_init = {
+ LSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check),
+ LSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme),
+
+ LSM_HOOK_INIT(unix_stream_connect, hook_unix_stream_connect),
+ LSM_HOOK_INIT(unix_may_send, hook_unix_may_send),
+
+ LSM_HOOK_INIT(task_kill, hook_task_kill),
+ LSM_HOOK_INIT(file_send_sigiotask, hook_file_send_sigiotask),
+};
+
+__init void landlock_add_task_hooks(void)
+{
+ security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
+ &landlock_lsmid);
+}
diff --git a/security/landlock/task.h b/security/landlock/task.h
new file mode 100644
index 000000000000..7c00360219a2
--- /dev/null
+++ b/security/landlock/task.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Ptrace hooks
+ *
+ * Copyright © 2017-2019 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2019 ANSSI
+ */
+
+#ifndef _SECURITY_LANDLOCK_TASK_H
+#define _SECURITY_LANDLOCK_TASK_H
+
+__init void landlock_add_task_hooks(void);
+
+#endif /* _SECURITY_LANDLOCK_TASK_H */