From 1fd22ada2d5ec6a5bf5191d493e7bbb50fc19899 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 13 Mar 2023 13:39:33 +0100 Subject: [PATCH] drop landlock patches from 5.10 --- .../landlock-add-object-management.patch | 318 -- ...landlock-add-clang-format-exceptions.patch | 226 - ...ftests-landlock-add-user-space-tests.patch | 3694 ----------------- ...ck-skip-overlayfs-tests-when-not-sup.patch | 123 - ...ck-test-ptrace-as-much-as-possible-w.patch | 220 - queue-5.10/series | 5 - 6 files changed, 4586 deletions(-) delete mode 100644 queue-5.10/landlock-add-object-management.patch delete mode 100644 queue-5.10/selftests-landlock-add-clang-format-exceptions.patch delete mode 100644 queue-5.10/selftests-landlock-add-user-space-tests.patch delete mode 100644 queue-5.10/selftests-landlock-skip-overlayfs-tests-when-not-sup.patch delete mode 100644 queue-5.10/selftests-landlock-test-ptrace-as-much-as-possible-w.patch diff --git a/queue-5.10/landlock-add-object-management.patch b/queue-5.10/landlock-add-object-management.patch deleted file mode 100644 index 3fa753b3eac..00000000000 --- a/queue-5.10/landlock-add-object-management.patch +++ /dev/null @@ -1,318 +0,0 @@ -From ab07529d96375f4117929020ea88db6ed07d8abf Mon Sep 17 00:00:00 2001 -From: Sasha Levin -Date: Thu, 22 Apr 2021 17:41:11 +0200 -Subject: landlock: Add object management -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -From: Mickaël Salaün - -[ Upstream commit 90945448e9830aa1b39d7acaa4e0724a001e2ff8 ] - -A Landlock object enables to identify a kernel object (e.g. an inode). -A Landlock rule is a set of access rights allowed on an object. Rules -are grouped in rulesets that may be tied to a set of processes (i.e. -subjects) to enforce a scoped access-control (i.e. a domain). - -Because Landlock's goal is to empower any process (especially -unprivileged ones) to sandbox themselves, we cannot rely on a -system-wide object identification such as file extended attributes. -Indeed, we need innocuous, composable and modular access-controls. - -The main challenge with these constraints is to identify kernel objects -while this identification is useful (i.e. when a security policy makes -use of this object). But this identification data should be freed once -no policy is using it. This ephemeral tagging should not and may not be -written in the filesystem. We then need to manage the lifetime of a -rule according to the lifetime of its objects. To avoid a global lock, -this implementation make use of RCU and counters to safely reference -objects. - -A following commit uses this generic object management for inodes. - -Cc: James Morris -Signed-off-by: Mickaël Salaün -Reviewed-by: Jann Horn -Acked-by: Serge Hallyn -Reviewed-by: Kees Cook -Link: https://lore.kernel.org/r/20210422154123.13086-2-mic@digikod.net -Signed-off-by: James Morris -Stable-dep-of: 366617a69e60 ("selftests/landlock: Skip overlayfs tests when not supported") -Signed-off-by: Sasha Levin ---- - MAINTAINERS | 10 +++++ - security/Kconfig | 1 + - security/Makefile | 2 + - security/landlock/Kconfig | 21 +++++++++ - security/landlock/Makefile | 3 ++ - security/landlock/object.c | 67 ++++++++++++++++++++++++++++ - security/landlock/object.h | 91 ++++++++++++++++++++++++++++++++++++++ - 7 files changed, 195 insertions(+) - create mode 100644 security/landlock/Kconfig - create mode 100644 security/landlock/Makefile - create mode 100644 security/landlock/object.c - create mode 100644 security/landlock/object.h - -diff --git a/MAINTAINERS b/MAINTAINERS -index 6c5efc4013ab5..72815c1a325eb 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -9836,6 +9836,16 @@ F: net/core/sock_map.c - F: net/ipv4/tcp_bpf.c - F: net/ipv4/udp_bpf.c - -+LANDLOCK SECURITY MODULE -+M: Mickaël Salaün -+L: linux-security-module@vger.kernel.org -+S: Supported -+W: https://landlock.io -+T: git https://github.com/landlock-lsm/linux.git -+F: security/landlock/ -+K: landlock -+K: LANDLOCK -+ - LANTIQ / INTEL Ethernet drivers - M: Hauke Mehrtens - L: netdev@vger.kernel.org -diff --git a/security/Kconfig b/security/Kconfig -index 9893c316da897..7cb5476306676 100644 ---- a/security/Kconfig -+++ b/security/Kconfig -@@ -230,6 +230,7 @@ source "security/loadpin/Kconfig" - source "security/yama/Kconfig" - source "security/safesetid/Kconfig" - source "security/lockdown/Kconfig" -+source "security/landlock/Kconfig" - - source "security/integrity/Kconfig" - -diff --git a/security/Makefile b/security/Makefile -index 3baf435de5411..47e432900e242 100644 ---- a/security/Makefile -+++ b/security/Makefile -@@ -13,6 +13,7 @@ subdir-$(CONFIG_SECURITY_LOADPIN) += loadpin - subdir-$(CONFIG_SECURITY_SAFESETID) += safesetid - subdir-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown - subdir-$(CONFIG_BPF_LSM) += bpf -+subdir-$(CONFIG_SECURITY_LANDLOCK) += landlock - - # always enable default capabilities - obj-y += commoncap.o -@@ -32,6 +33,7 @@ obj-$(CONFIG_SECURITY_SAFESETID) += safesetid/ - obj-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown/ - obj-$(CONFIG_CGROUPS) += device_cgroup.o - obj-$(CONFIG_BPF_LSM) += bpf/ -+obj-$(CONFIG_SECURITY_LANDLOCK) += landlock/ - - # Object integrity file lists - subdir-$(CONFIG_INTEGRITY) += integrity -diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig -new file mode 100644 -index 0000000000000..c1e862a384107 ---- /dev/null -+++ b/security/landlock/Kconfig -@@ -0,0 +1,21 @@ -+# SPDX-License-Identifier: GPL-2.0-only -+ -+config SECURITY_LANDLOCK -+ bool "Landlock support" -+ depends on SECURITY -+ 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. -diff --git a/security/landlock/Makefile b/security/landlock/Makefile -new file mode 100644 -index 0000000000000..cb6deefbf4c09 ---- /dev/null -+++ b/security/landlock/Makefile -@@ -0,0 +1,3 @@ -+obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o -+ -+landlock-y := object.o -diff --git a/security/landlock/object.c b/security/landlock/object.c -new file mode 100644 -index 0000000000000..d674fdf9ff04f ---- /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 -+ * Copyright © 2018-2020 ANSSI -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#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 0000000000000..3f80674c6c8d3 ---- /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 -+ * Copyright © 2018-2020 ANSSI -+ */ -+ -+#ifndef _SECURITY_LANDLOCK_OBJECT_H -+#define _SECURITY_LANDLOCK_OBJECT_H -+ -+#include -+#include -+#include -+ -+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 */ --- -2.39.2 - diff --git a/queue-5.10/selftests-landlock-add-clang-format-exceptions.patch b/queue-5.10/selftests-landlock-add-clang-format-exceptions.patch deleted file mode 100644 index 5ee7009ebd0..00000000000 --- a/queue-5.10/selftests-landlock-add-clang-format-exceptions.patch +++ /dev/null @@ -1,226 +0,0 @@ -From 76f7cb6aeb2007c0d4febda804772cf6ae508604 Mon Sep 17 00:00:00 2001 -From: Sasha Levin -Date: Fri, 6 May 2022 18:05:09 +0200 -Subject: selftests/landlock: Add clang-format exceptions -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -From: Mickaël Salaün - -[ Upstream commit 4598d9abf4215e1e371a35683350d50122793c80 ] - -In preparation to a following commit, add clang-format on and -clang-format off stanzas around constant definitions and the TEST_F_FORK -macro. This enables to keep aligned values, which is much more readable -than packed definitions. - -Add other clang-format exceptions for FIXTURE() and -FIXTURE_VARIANT_ADD() declarations to force space before open brace, -which is reported by checkpatch.pl . - -Link: https://lore.kernel.org/r/20220506160513.523257-4-mic@digikod.net -Cc: stable@vger.kernel.org -Signed-off-by: Mickaël Salaün -Stable-dep-of: 8677e555f17f ("selftests/landlock: Test ptrace as much as possible with Yama") -Signed-off-by: Sasha Levin ---- - tools/testing/selftests/landlock/common.h | 2 ++ - tools/testing/selftests/landlock/fs_test.c | 23 ++++++++++++++----- - .../testing/selftests/landlock/ptrace_test.c | 20 +++++++++++++++- - 3 files changed, 38 insertions(+), 7 deletions(-) - -diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h -index 20e2a9286d710..61127fffbeb83 100644 ---- a/tools/testing/selftests/landlock/common.h -+++ b/tools/testing/selftests/landlock/common.h -@@ -29,6 +29,7 @@ - * this to be possible, we must not call abort() but instead exit smoothly - * (hence the step print). - */ -+/* clang-format off */ - #define TEST_F_FORK(fixture_name, test_name) \ - static void fixture_name##_##test_name##_child( \ - struct __test_metadata *_metadata, \ -@@ -75,6 +76,7 @@ - FIXTURE_DATA(fixture_name) __attribute__((unused)) *self, \ - const FIXTURE_VARIANT(fixture_name) \ - __attribute__((unused)) *variant) -+/* clang-format on */ - - #ifndef landlock_create_ruleset - static inline int landlock_create_ruleset( -diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c -index db153452b110a..036d55836b9ec 100644 ---- a/tools/testing/selftests/landlock/fs_test.c -+++ b/tools/testing/selftests/landlock/fs_test.c -@@ -256,8 +256,9 @@ static void remove_layout1(struct __test_metadata *const _metadata) - EXPECT_EQ(0, remove_path(dir_s3d2)); - } - --FIXTURE(layout1) { --}; -+/* clang-format off */ -+FIXTURE(layout1) {}; -+/* clang-format on */ - - FIXTURE_SETUP(layout1) - { -@@ -411,6 +412,8 @@ TEST_F_FORK(layout1, inval) - ASSERT_EQ(0, close(ruleset_fd)); - } - -+/* clang-format off */ -+ - #define ACCESS_FILE ( \ - LANDLOCK_ACCESS_FS_EXECUTE | \ - LANDLOCK_ACCESS_FS_WRITE_FILE | \ -@@ -431,6 +434,8 @@ TEST_F_FORK(layout1, inval) - LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ - ACCESS_LAST) - -+/* clang-format on */ -+ - TEST_F_FORK(layout1, file_access_rights) - { - __u64 access; -@@ -487,6 +492,8 @@ struct rule { - __u64 access; - }; - -+/* clang-format off */ -+ - #define ACCESS_RO ( \ - LANDLOCK_ACCESS_FS_READ_FILE | \ - LANDLOCK_ACCESS_FS_READ_DIR) -@@ -495,6 +502,8 @@ struct rule { - ACCESS_RO | \ - LANDLOCK_ACCESS_FS_WRITE_FILE) - -+/* clang-format on */ -+ - static int create_ruleset(struct __test_metadata *const _metadata, - const __u64 handled_access_fs, const struct rule rules[]) - { -@@ -2105,8 +2114,9 @@ TEST_F_FORK(layout1, proc_pipe) - ASSERT_EQ(0, close(pipe_fds[1])); - } - --FIXTURE(layout1_bind) { --}; -+/* clang-format off */ -+FIXTURE(layout1_bind) {}; -+/* clang-format on */ - - FIXTURE_SETUP(layout1_bind) - { -@@ -2446,8 +2456,9 @@ static const char (*merge_sub_files[])[] = { - * └── work - */ - --FIXTURE(layout2_overlay) { --}; -+/* clang-format off */ -+FIXTURE(layout2_overlay) {}; -+/* clang-format on */ - - FIXTURE_SETUP(layout2_overlay) - { -diff --git a/tools/testing/selftests/landlock/ptrace_test.c b/tools/testing/selftests/landlock/ptrace_test.c -index 15fbef9cc8496..090adadfe2dc3 100644 ---- a/tools/testing/selftests/landlock/ptrace_test.c -+++ b/tools/testing/selftests/landlock/ptrace_test.c -@@ -59,7 +59,9 @@ static int test_ptrace_read(const pid_t pid) - return 0; - } - --FIXTURE(hierarchy) { }; -+/* clang-format off */ -+FIXTURE(hierarchy) {}; -+/* clang-format on */ - - FIXTURE_VARIANT(hierarchy) { - const bool domain_both; -@@ -83,7 +85,9 @@ FIXTURE_VARIANT(hierarchy) { - * \ P2 -> P1 : allow - * 'P2 - */ -+/* clang-format off */ - FIXTURE_VARIANT_ADD(hierarchy, allow_without_domain) { -+ /* clang-format on */ - .domain_both = false, - .domain_parent = false, - .domain_child = false, -@@ -98,7 +102,9 @@ FIXTURE_VARIANT_ADD(hierarchy, allow_without_domain) { - * | P2 | - * '------' - */ -+/* clang-format off */ - FIXTURE_VARIANT_ADD(hierarchy, allow_with_one_domain) { -+ /* clang-format on */ - .domain_both = false, - .domain_parent = false, - .domain_child = true, -@@ -112,7 +118,9 @@ FIXTURE_VARIANT_ADD(hierarchy, allow_with_one_domain) { - * ' - * P2 - */ -+/* clang-format off */ - FIXTURE_VARIANT_ADD(hierarchy, deny_with_parent_domain) { -+ /* clang-format on */ - .domain_both = false, - .domain_parent = true, - .domain_child = false, -@@ -127,7 +135,9 @@ FIXTURE_VARIANT_ADD(hierarchy, deny_with_parent_domain) { - * | P2 | - * '------' - */ -+/* clang-format off */ - FIXTURE_VARIANT_ADD(hierarchy, deny_with_sibling_domain) { -+ /* clang-format on */ - .domain_both = false, - .domain_parent = true, - .domain_child = true, -@@ -142,7 +152,9 @@ FIXTURE_VARIANT_ADD(hierarchy, deny_with_sibling_domain) { - * | P2 | - * '-------------' - */ -+/* clang-format off */ - FIXTURE_VARIANT_ADD(hierarchy, allow_sibling_domain) { -+ /* clang-format on */ - .domain_both = true, - .domain_parent = false, - .domain_child = false, -@@ -158,7 +170,9 @@ FIXTURE_VARIANT_ADD(hierarchy, allow_sibling_domain) { - * | '------' | - * '-----------------' - */ -+/* clang-format off */ - FIXTURE_VARIANT_ADD(hierarchy, allow_with_nested_domain) { -+ /* clang-format on */ - .domain_both = true, - .domain_parent = false, - .domain_child = true, -@@ -174,7 +188,9 @@ FIXTURE_VARIANT_ADD(hierarchy, allow_with_nested_domain) { - * | P2 | - * '-----------------' - */ -+/* clang-format off */ - FIXTURE_VARIANT_ADD(hierarchy, deny_with_nested_and_parent_domain) { -+ /* clang-format on */ - .domain_both = true, - .domain_parent = true, - .domain_child = false, -@@ -192,7 +208,9 @@ FIXTURE_VARIANT_ADD(hierarchy, deny_with_nested_and_parent_domain) { - * | '------' | - * '-----------------' - */ -+/* clang-format off */ - FIXTURE_VARIANT_ADD(hierarchy, deny_with_forked_domain) { -+ /* clang-format on */ - .domain_both = true, - .domain_parent = true, - .domain_child = true, --- -2.39.2 - diff --git a/queue-5.10/selftests-landlock-add-user-space-tests.patch b/queue-5.10/selftests-landlock-add-user-space-tests.patch deleted file mode 100644 index 2332bfa504e..00000000000 --- a/queue-5.10/selftests-landlock-add-user-space-tests.patch +++ /dev/null @@ -1,3694 +0,0 @@ -From 9f43c1d77d1e7cac1c93a6f95a6d91ebc4089a73 Mon Sep 17 00:00:00 2001 -From: Sasha Levin -Date: Thu, 22 Apr 2021 17:41:20 +0200 -Subject: selftests/landlock: Add user space tests -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -From: Mickaël Salaün - -[ Upstream commit e1199815b47be83346c03e20a3de76f934e4bb34 ] - -Test all Landlock system calls, ptrace hooks semantic and filesystem -access-control with multiple layouts. - -Test coverage for security/landlock/ is 93.6% of lines. The code not -covered only deals with internal kernel errors (e.g. memory allocation) -and race conditions. - -Cc: James Morris -Cc: Jann Horn -Cc: Serge E. Hallyn -Cc: Shuah Khan -Signed-off-by: Mickaël Salaün -Reviewed-by: Vincent Dagonneau -Reviewed-by: Kees Cook -Link: https://lore.kernel.org/r/20210422154123.13086-11-mic@digikod.net -Signed-off-by: James Morris -Stable-dep-of: 366617a69e60 ("selftests/landlock: Skip overlayfs tests when not supported") -Signed-off-by: Sasha Levin ---- - MAINTAINERS | 1 + - tools/testing/selftests/Makefile | 1 + - tools/testing/selftests/landlock/.gitignore | 2 + - tools/testing/selftests/landlock/Makefile | 24 + - tools/testing/selftests/landlock/base_test.c | 219 ++ - tools/testing/selftests/landlock/common.h | 183 ++ - tools/testing/selftests/landlock/config | 7 + - tools/testing/selftests/landlock/fs_test.c | 2791 +++++++++++++++++ - .../testing/selftests/landlock/ptrace_test.c | 337 ++ - tools/testing/selftests/landlock/true.c | 5 + - 10 files changed, 3570 insertions(+) - create mode 100644 tools/testing/selftests/landlock/.gitignore - create mode 100644 tools/testing/selftests/landlock/Makefile - create mode 100644 tools/testing/selftests/landlock/base_test.c - create mode 100644 tools/testing/selftests/landlock/common.h - create mode 100644 tools/testing/selftests/landlock/config - create mode 100644 tools/testing/selftests/landlock/fs_test.c - create mode 100644 tools/testing/selftests/landlock/ptrace_test.c - create mode 100644 tools/testing/selftests/landlock/true.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 72815c1a325eb..5bc6a028236e3 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -9843,6 +9843,7 @@ S: Supported - W: https://landlock.io - T: git https://github.com/landlock-lsm/linux.git - F: security/landlock/ -+F: tools/testing/selftests/landlock/ - K: landlock - K: LANDLOCK - -diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile -index db1e24d7155fa..ca96973dca44d 100644 ---- a/tools/testing/selftests/Makefile -+++ b/tools/testing/selftests/Makefile -@@ -26,6 +26,7 @@ TARGETS += ir - TARGETS += kcmp - TARGETS += kexec - TARGETS += kvm -+TARGETS += landlock - TARGETS += lib - TARGETS += livepatch - TARGETS += lkdtm -diff --git a/tools/testing/selftests/landlock/.gitignore b/tools/testing/selftests/landlock/.gitignore -new file mode 100644 -index 0000000000000..470203a7cd737 ---- /dev/null -+++ b/tools/testing/selftests/landlock/.gitignore -@@ -0,0 +1,2 @@ -+/*_test -+/true -diff --git a/tools/testing/selftests/landlock/Makefile b/tools/testing/selftests/landlock/Makefile -new file mode 100644 -index 0000000000000..a99596ca9882b ---- /dev/null -+++ b/tools/testing/selftests/landlock/Makefile -@@ -0,0 +1,24 @@ -+# SPDX-License-Identifier: GPL-2.0 -+ -+CFLAGS += -Wall -O2 -+ -+src_test := $(wildcard *_test.c) -+ -+TEST_GEN_PROGS := $(src_test:.c=) -+ -+TEST_GEN_PROGS_EXTENDED := true -+ -+KSFT_KHDR_INSTALL := 1 -+OVERRIDE_TARGETS := 1 -+include ../lib.mk -+ -+khdr_dir = $(top_srcdir)/usr/include -+ -+$(khdr_dir)/linux/landlock.h: khdr -+ @: -+ -+$(OUTPUT)/true: true.c -+ $(LINK.c) $< $(LDLIBS) -o $@ -static -+ -+$(OUTPUT)/%_test: %_test.c $(khdr_dir)/linux/landlock.h ../kselftest_harness.h common.h -+ $(LINK.c) $< $(LDLIBS) -o $@ -lcap -I$(khdr_dir) -diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c -new file mode 100644 -index 0000000000000..262c3c8d953ad ---- /dev/null -+++ b/tools/testing/selftests/landlock/base_test.c -@@ -0,0 +1,219 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Landlock tests - Common user space base -+ * -+ * Copyright © 2017-2020 Mickaël Salaün -+ * Copyright © 2019-2020 ANSSI -+ */ -+ -+#define _GNU_SOURCE -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "common.h" -+ -+#ifndef O_PATH -+#define O_PATH 010000000 -+#endif -+ -+TEST(inconsistent_attr) { -+ const long page_size = sysconf(_SC_PAGESIZE); -+ char *const buf = malloc(page_size + 1); -+ struct landlock_ruleset_attr *const ruleset_attr = (void *)buf; -+ -+ ASSERT_NE(NULL, buf); -+ -+ /* Checks copy_from_user(). */ -+ ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, 0, 0)); -+ /* The size if less than sizeof(struct landlock_attr_enforce). */ -+ ASSERT_EQ(EINVAL, errno); -+ ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, 1, 0)); -+ ASSERT_EQ(EINVAL, errno); -+ -+ ASSERT_EQ(-1, landlock_create_ruleset(NULL, 1, 0)); -+ /* The size if less than sizeof(struct landlock_attr_enforce). */ -+ ASSERT_EQ(EFAULT, errno); -+ -+ ASSERT_EQ(-1, landlock_create_ruleset(NULL, -+ sizeof(struct landlock_ruleset_attr), 0)); -+ ASSERT_EQ(EFAULT, errno); -+ -+ ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size + 1, 0)); -+ ASSERT_EQ(E2BIG, errno); -+ -+ ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, -+ sizeof(struct landlock_ruleset_attr), 0)); -+ ASSERT_EQ(ENOMSG, errno); -+ ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size, 0)); -+ ASSERT_EQ(ENOMSG, errno); -+ -+ /* Checks non-zero value. */ -+ buf[page_size - 2] = '.'; -+ ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size, 0)); -+ ASSERT_EQ(E2BIG, errno); -+ -+ ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size + 1, 0)); -+ ASSERT_EQ(E2BIG, errno); -+ -+ free(buf); -+} -+ -+TEST(empty_path_beneath_attr) { -+ const struct landlock_ruleset_attr ruleset_attr = { -+ .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE, -+ }; -+ const int ruleset_fd = landlock_create_ruleset(&ruleset_attr, -+ sizeof(ruleset_attr), 0); -+ -+ ASSERT_LE(0, ruleset_fd); -+ -+ /* Similar to struct landlock_path_beneath_attr.parent_fd = 0 */ -+ ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, -+ NULL, 0)); -+ ASSERT_EQ(EFAULT, errno); -+ ASSERT_EQ(0, close(ruleset_fd)); -+} -+ -+TEST(inval_fd_enforce) { -+ ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); -+ -+ ASSERT_EQ(-1, landlock_restrict_self(-1, 0)); -+ ASSERT_EQ(EBADF, errno); -+} -+ -+TEST(unpriv_enforce_without_no_new_privs) { -+ int err; -+ -+ drop_caps(_metadata); -+ err = landlock_restrict_self(-1, 0); -+ ASSERT_EQ(EPERM, errno); -+ ASSERT_EQ(err, -1); -+} -+ -+TEST(ruleset_fd_io) -+{ -+ struct landlock_ruleset_attr ruleset_attr = { -+ .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, -+ }; -+ int ruleset_fd; -+ char buf; -+ -+ drop_caps(_metadata); -+ ruleset_fd = landlock_create_ruleset(&ruleset_attr, -+ sizeof(ruleset_attr), 0); -+ ASSERT_LE(0, ruleset_fd); -+ -+ ASSERT_EQ(-1, write(ruleset_fd, ".", 1)); -+ ASSERT_EQ(EINVAL, errno); -+ ASSERT_EQ(-1, read(ruleset_fd, &buf, 1)); -+ ASSERT_EQ(EINVAL, errno); -+ -+ ASSERT_EQ(0, close(ruleset_fd)); -+} -+ -+/* Tests enforcement of a ruleset FD transferred through a UNIX socket. */ -+TEST(ruleset_fd_transfer) -+{ -+ struct landlock_ruleset_attr ruleset_attr = { -+ .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, -+ }; -+ struct landlock_path_beneath_attr path_beneath_attr = { -+ .allowed_access = LANDLOCK_ACCESS_FS_READ_DIR, -+ }; -+ int ruleset_fd_tx, dir_fd; -+ union { -+ /* Aligned ancillary data buffer. */ -+ char buf[CMSG_SPACE(sizeof(ruleset_fd_tx))]; -+ struct cmsghdr _align; -+ } cmsg_tx = {}; -+ char data_tx = '.'; -+ struct iovec io = { -+ .iov_base = &data_tx, -+ .iov_len = sizeof(data_tx), -+ }; -+ struct msghdr msg = { -+ .msg_iov = &io, -+ .msg_iovlen = 1, -+ .msg_control = &cmsg_tx.buf, -+ .msg_controllen = sizeof(cmsg_tx.buf), -+ }; -+ struct cmsghdr *cmsg; -+ int socket_fds[2]; -+ pid_t child; -+ int status; -+ -+ drop_caps(_metadata); -+ -+ /* Creates a test ruleset with a simple rule. */ -+ ruleset_fd_tx = landlock_create_ruleset(&ruleset_attr, -+ sizeof(ruleset_attr), 0); -+ ASSERT_LE(0, ruleset_fd_tx); -+ path_beneath_attr.parent_fd = open("/tmp", O_PATH | O_NOFOLLOW | -+ O_DIRECTORY | O_CLOEXEC); -+ ASSERT_LE(0, path_beneath_attr.parent_fd); -+ ASSERT_EQ(0, landlock_add_rule(ruleset_fd_tx, LANDLOCK_RULE_PATH_BENEATH, -+ &path_beneath_attr, 0)); -+ ASSERT_EQ(0, close(path_beneath_attr.parent_fd)); -+ -+ cmsg = CMSG_FIRSTHDR(&msg); -+ ASSERT_NE(NULL, cmsg); -+ cmsg->cmsg_len = CMSG_LEN(sizeof(ruleset_fd_tx)); -+ cmsg->cmsg_level = SOL_SOCKET; -+ cmsg->cmsg_type = SCM_RIGHTS; -+ memcpy(CMSG_DATA(cmsg), &ruleset_fd_tx, sizeof(ruleset_fd_tx)); -+ -+ /* Sends the ruleset FD over a socketpair and then close it. */ -+ ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, socket_fds)); -+ ASSERT_EQ(sizeof(data_tx), sendmsg(socket_fds[0], &msg, 0)); -+ ASSERT_EQ(0, close(socket_fds[0])); -+ ASSERT_EQ(0, close(ruleset_fd_tx)); -+ -+ child = fork(); -+ ASSERT_LE(0, child); -+ if (child == 0) { -+ int ruleset_fd_rx; -+ -+ *(char *)msg.msg_iov->iov_base = '\0'; -+ ASSERT_EQ(sizeof(data_tx), recvmsg(socket_fds[1], &msg, MSG_CMSG_CLOEXEC)); -+ ASSERT_EQ('.', *(char *)msg.msg_iov->iov_base); -+ ASSERT_EQ(0, close(socket_fds[1])); -+ cmsg = CMSG_FIRSTHDR(&msg); -+ ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(ruleset_fd_tx))); -+ memcpy(&ruleset_fd_rx, CMSG_DATA(cmsg), sizeof(ruleset_fd_tx)); -+ -+ /* Enforces the received ruleset on the child. */ -+ ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); -+ ASSERT_EQ(0, landlock_restrict_self(ruleset_fd_rx, 0)); -+ ASSERT_EQ(0, close(ruleset_fd_rx)); -+ -+ /* Checks that the ruleset enforcement. */ -+ ASSERT_EQ(-1, open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC)); -+ ASSERT_EQ(EACCES, errno); -+ dir_fd = open("/tmp", O_RDONLY | O_DIRECTORY | O_CLOEXEC); -+ ASSERT_LE(0, dir_fd); -+ ASSERT_EQ(0, close(dir_fd)); -+ _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); -+ return; -+ } -+ -+ ASSERT_EQ(0, close(socket_fds[1])); -+ -+ /* Checks that the parent is unrestricted. */ -+ dir_fd = open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC); -+ ASSERT_LE(0, dir_fd); -+ ASSERT_EQ(0, close(dir_fd)); -+ dir_fd = open("/tmp", O_RDONLY | O_DIRECTORY | O_CLOEXEC); -+ ASSERT_LE(0, dir_fd); -+ ASSERT_EQ(0, close(dir_fd)); -+ -+ ASSERT_EQ(child, waitpid(child, &status, 0)); -+ ASSERT_EQ(1, WIFEXITED(status)); -+ ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); -+} -+ -+TEST_HARNESS_MAIN -diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h -new file mode 100644 -index 0000000000000..20e2a9286d710 ---- /dev/null -+++ b/tools/testing/selftests/landlock/common.h -@@ -0,0 +1,183 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* -+ * Landlock test helpers -+ * -+ * Copyright © 2017-2020 Mickaël Salaün -+ * Copyright © 2019-2020 ANSSI -+ * Copyright © 2021 Microsoft Corporation -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "../kselftest_harness.h" -+ -+#ifndef ARRAY_SIZE -+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) -+#endif -+ -+/* -+ * TEST_F_FORK() is useful when a test drop privileges but the corresponding -+ * FIXTURE_TEARDOWN() requires them (e.g. to remove files from a directory -+ * where write actions are denied). For convenience, FIXTURE_TEARDOWN() is -+ * also called when the test failed, but not when FIXTURE_SETUP() failed. For -+ * this to be possible, we must not call abort() but instead exit smoothly -+ * (hence the step print). -+ */ -+#define TEST_F_FORK(fixture_name, test_name) \ -+ static void fixture_name##_##test_name##_child( \ -+ struct __test_metadata *_metadata, \ -+ FIXTURE_DATA(fixture_name) *self, \ -+ const FIXTURE_VARIANT(fixture_name) *variant); \ -+ TEST_F(fixture_name, test_name) \ -+ { \ -+ int status; \ -+ const pid_t child = fork(); \ -+ if (child < 0) \ -+ abort(); \ -+ if (child == 0) { \ -+ _metadata->no_print = 1; \ -+ fixture_name##_##test_name##_child(_metadata, self, variant); \ -+ if (_metadata->skip) \ -+ _exit(255); \ -+ if (_metadata->passed) \ -+ _exit(0); \ -+ _exit(_metadata->step); \ -+ } \ -+ if (child != waitpid(child, &status, 0)) \ -+ abort(); \ -+ if (WIFSIGNALED(status) || !WIFEXITED(status)) { \ -+ _metadata->passed = 0; \ -+ _metadata->step = 1; \ -+ return; \ -+ } \ -+ switch (WEXITSTATUS(status)) { \ -+ case 0: \ -+ _metadata->passed = 1; \ -+ break; \ -+ case 255: \ -+ _metadata->passed = 1; \ -+ _metadata->skip = 1; \ -+ break; \ -+ default: \ -+ _metadata->passed = 0; \ -+ _metadata->step = WEXITSTATUS(status); \ -+ break; \ -+ } \ -+ } \ -+ static void fixture_name##_##test_name##_child( \ -+ struct __test_metadata __attribute__((unused)) *_metadata, \ -+ FIXTURE_DATA(fixture_name) __attribute__((unused)) *self, \ -+ const FIXTURE_VARIANT(fixture_name) \ -+ __attribute__((unused)) *variant) -+ -+#ifndef landlock_create_ruleset -+static inline int landlock_create_ruleset( -+ const struct landlock_ruleset_attr *const attr, -+ const size_t size, const __u32 flags) -+{ -+ return syscall(__NR_landlock_create_ruleset, attr, size, flags); -+} -+#endif -+ -+#ifndef landlock_add_rule -+static inline int landlock_add_rule(const int ruleset_fd, -+ const enum landlock_rule_type rule_type, -+ const void *const rule_attr, const __u32 flags) -+{ -+ return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, -+ rule_attr, flags); -+} -+#endif -+ -+#ifndef landlock_restrict_self -+static inline int landlock_restrict_self(const int ruleset_fd, -+ const __u32 flags) -+{ -+ return syscall(__NR_landlock_restrict_self, ruleset_fd, flags); -+} -+#endif -+ -+static void _init_caps(struct __test_metadata *const _metadata, bool drop_all) -+{ -+ cap_t cap_p; -+ /* Only these three capabilities are useful for the tests. */ -+ const cap_value_t caps[] = { -+ CAP_DAC_OVERRIDE, -+ CAP_MKNOD, -+ CAP_SYS_ADMIN, -+ CAP_SYS_CHROOT, -+ }; -+ -+ cap_p = cap_get_proc(); -+ EXPECT_NE(NULL, cap_p) { -+ TH_LOG("Failed to cap_get_proc: %s", strerror(errno)); -+ } -+ EXPECT_NE(-1, cap_clear(cap_p)) { -+ TH_LOG("Failed to cap_clear: %s", strerror(errno)); -+ } -+ if (!drop_all) { -+ EXPECT_NE(-1, cap_set_flag(cap_p, CAP_PERMITTED, -+ ARRAY_SIZE(caps), caps, CAP_SET)) { -+ TH_LOG("Failed to cap_set_flag: %s", strerror(errno)); -+ } -+ } -+ EXPECT_NE(-1, cap_set_proc(cap_p)) { -+ TH_LOG("Failed to cap_set_proc: %s", strerror(errno)); -+ } -+ EXPECT_NE(-1, cap_free(cap_p)) { -+ TH_LOG("Failed to cap_free: %s", strerror(errno)); -+ } -+} -+ -+/* We cannot put such helpers in a library because of kselftest_harness.h . */ -+__attribute__((__unused__)) -+static void disable_caps(struct __test_metadata *const _metadata) -+{ -+ _init_caps(_metadata, false); -+} -+ -+__attribute__((__unused__)) -+static void drop_caps(struct __test_metadata *const _metadata) -+{ -+ _init_caps(_metadata, true); -+} -+ -+static void _effective_cap(struct __test_metadata *const _metadata, -+ const cap_value_t caps, const cap_flag_value_t value) -+{ -+ cap_t cap_p; -+ -+ cap_p = cap_get_proc(); -+ EXPECT_NE(NULL, cap_p) { -+ TH_LOG("Failed to cap_get_proc: %s", strerror(errno)); -+ } -+ EXPECT_NE(-1, cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &caps, value)) { -+ TH_LOG("Failed to cap_set_flag: %s", strerror(errno)); -+ } -+ EXPECT_NE(-1, cap_set_proc(cap_p)) { -+ TH_LOG("Failed to cap_set_proc: %s", strerror(errno)); -+ } -+ EXPECT_NE(-1, cap_free(cap_p)) { -+ TH_LOG("Failed to cap_free: %s", strerror(errno)); -+ } -+} -+ -+__attribute__((__unused__)) -+static void set_cap(struct __test_metadata *const _metadata, -+ const cap_value_t caps) -+{ -+ _effective_cap(_metadata, caps, CAP_SET); -+} -+ -+__attribute__((__unused__)) -+static void clear_cap(struct __test_metadata *const _metadata, -+ const cap_value_t caps) -+{ -+ _effective_cap(_metadata, caps, CAP_CLEAR); -+} -diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config -new file mode 100644 -index 0000000000000..0f0a65287bacf ---- /dev/null -+++ b/tools/testing/selftests/landlock/config -@@ -0,0 +1,7 @@ -+CONFIG_OVERLAY_FS=y -+CONFIG_SECURITY_LANDLOCK=y -+CONFIG_SECURITY_PATH=y -+CONFIG_SECURITY=y -+CONFIG_SHMEM=y -+CONFIG_TMPFS_XATTR=y -+CONFIG_TMPFS=y -diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c -new file mode 100644 -index 0000000000000..10c9a1e4ebd9b ---- /dev/null -+++ b/tools/testing/selftests/landlock/fs_test.c -@@ -0,0 +1,2791 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Landlock tests - Filesystem -+ * -+ * Copyright © 2017-2020 Mickaël Salaün -+ * Copyright © 2020 ANSSI -+ * Copyright © 2020-2021 Microsoft Corporation -+ */ -+ -+#define _GNU_SOURCE -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "common.h" -+ -+#define TMP_DIR "tmp" -+#define BINARY_PATH "./true" -+ -+/* Paths (sibling number and depth) */ -+static const char dir_s1d1[] = TMP_DIR "/s1d1"; -+static const char file1_s1d1[] = TMP_DIR "/s1d1/f1"; -+static const char file2_s1d1[] = TMP_DIR "/s1d1/f2"; -+static const char dir_s1d2[] = TMP_DIR "/s1d1/s1d2"; -+static const char file1_s1d2[] = TMP_DIR "/s1d1/s1d2/f1"; -+static const char file2_s1d2[] = TMP_DIR "/s1d1/s1d2/f2"; -+static const char dir_s1d3[] = TMP_DIR "/s1d1/s1d2/s1d3"; -+static const char file1_s1d3[] = TMP_DIR "/s1d1/s1d2/s1d3/f1"; -+static const char file2_s1d3[] = TMP_DIR "/s1d1/s1d2/s1d3/f2"; -+ -+static const char dir_s2d1[] = TMP_DIR "/s2d1"; -+static const char file1_s2d1[] = TMP_DIR "/s2d1/f1"; -+static const char dir_s2d2[] = TMP_DIR "/s2d1/s2d2"; -+static const char file1_s2d2[] = TMP_DIR "/s2d1/s2d2/f1"; -+static const char dir_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3"; -+static const char file1_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3/f1"; -+static const char file2_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3/f2"; -+ -+static const char dir_s3d1[] = TMP_DIR "/s3d1"; -+/* dir_s3d2 is a mount point. */ -+static const char dir_s3d2[] = TMP_DIR "/s3d1/s3d2"; -+static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3"; -+ -+/* -+ * layout1 hierarchy: -+ * -+ * tmp -+ * ├── s1d1 -+ * │   ├── f1 -+ * │   ├── f2 -+ * │   └── s1d2 -+ * │   ├── f1 -+ * │   ├── f2 -+ * │   └── s1d3 -+ * │   ├── f1 -+ * │   └── f2 -+ * ├── s2d1 -+ * │   ├── f1 -+ * │   └── s2d2 -+ * │   ├── f1 -+ * │   └── s2d3 -+ * │   ├── f1 -+ * │   └── f2 -+ * └── s3d1 -+ * └── s3d2 -+ * └── s3d3 -+ */ -+ -+static void mkdir_parents(struct __test_metadata *const _metadata, -+ const char *const path) -+{ -+ char *walker; -+ const char *parent; -+ int i, err; -+ -+ ASSERT_NE(path[0], '\0'); -+ walker = strdup(path); -+ ASSERT_NE(NULL, walker); -+ parent = walker; -+ for (i = 1; walker[i]; i++) { -+ if (walker[i] != '/') -+ continue; -+ walker[i] = '\0'; -+ err = mkdir(parent, 0700); -+ ASSERT_FALSE(err && errno != EEXIST) { -+ TH_LOG("Failed to create directory \"%s\": %s", -+ parent, strerror(errno)); -+ } -+ walker[i] = '/'; -+ } -+ free(walker); -+} -+ -+static void create_directory(struct __test_metadata *const _metadata, -+ const char *const path) -+{ -+ mkdir_parents(_metadata, path); -+ ASSERT_EQ(0, mkdir(path, 0700)) { -+ TH_LOG("Failed to create directory \"%s\": %s", path, -+ strerror(errno)); -+ } -+} -+ -+static void create_file(struct __test_metadata *const _metadata, -+ const char *const path) -+{ -+ mkdir_parents(_metadata, path); -+ ASSERT_EQ(0, mknod(path, S_IFREG | 0700, 0)) { -+ TH_LOG("Failed to create file \"%s\": %s", path, -+ strerror(errno)); -+ } -+} -+ -+static int remove_path(const char *const path) -+{ -+ char *walker; -+ int i, ret, err = 0; -+ -+ walker = strdup(path); -+ if (!walker) { -+ err = ENOMEM; -+ goto out; -+ } -+ if (unlink(path) && rmdir(path)) { -+ if (errno != ENOENT) -+ err = errno; -+ goto out; -+ } -+ for (i = strlen(walker); i > 0; i--) { -+ if (walker[i] != '/') -+ continue; -+ walker[i] = '\0'; -+ ret = rmdir(walker); -+ if (ret) { -+ if (errno != ENOTEMPTY && errno != EBUSY) -+ err = errno; -+ goto out; -+ } -+ if (strcmp(walker, TMP_DIR) == 0) -+ goto out; -+ } -+ -+out: -+ free(walker); -+ return err; -+} -+ -+static void prepare_layout(struct __test_metadata *const _metadata) -+{ -+ disable_caps(_metadata); -+ umask(0077); -+ create_directory(_metadata, TMP_DIR); -+ -+ /* -+ * Do not pollute the rest of the system: creates a private mount point -+ * for tests relying on pivot_root(2) and move_mount(2). -+ */ -+ set_cap(_metadata, CAP_SYS_ADMIN); -+ ASSERT_EQ(0, unshare(CLONE_NEWNS)); -+ ASSERT_EQ(0, mount("tmp", TMP_DIR, "tmpfs", 0, "size=4m,mode=700")); -+ ASSERT_EQ(0, mount(NULL, TMP_DIR, NULL, MS_PRIVATE | MS_REC, NULL)); -+ clear_cap(_metadata, CAP_SYS_ADMIN); -+} -+ -+static void cleanup_layout(struct __test_metadata *const _metadata) -+{ -+ set_cap(_metadata, CAP_SYS_ADMIN); -+ EXPECT_EQ(0, umount(TMP_DIR)); -+ clear_cap(_metadata, CAP_SYS_ADMIN); -+ EXPECT_EQ(0, remove_path(TMP_DIR)); -+} -+ -+static void create_layout1(struct __test_metadata *const _metadata) -+{ -+ create_file(_metadata, file1_s1d1); -+ create_file(_metadata, file1_s1d2); -+ create_file(_metadata, file1_s1d3); -+ create_file(_metadata, file2_s1d1); -+ create_file(_metadata, file2_s1d2); -+ create_file(_metadata, file2_s1d3); -+ -+ create_file(_metadata, file1_s2d1); -+ create_file(_metadata, file1_s2d2); -+ create_file(_metadata, file1_s2d3); -+ create_file(_metadata, file2_s2d3); -+ -+ create_directory(_metadata, dir_s3d2); -+ set_cap(_metadata, CAP_SYS_ADMIN); -+ ASSERT_EQ(0, mount("tmp", dir_s3d2, "tmpfs", 0, "size=4m,mode=700")); -+ clear_cap(_metadata, CAP_SYS_ADMIN); -+ -+ ASSERT_EQ(0, mkdir(dir_s3d3, 0700)); -+} -+ -+static void remove_layout1(struct __test_metadata *const _metadata) -+{ -+ EXPECT_EQ(0, remove_path(file2_s1d3)); -+ EXPECT_EQ(0, remove_path(file2_s1d2)); -+ EXPECT_EQ(0, remove_path(file2_s1d1)); -+ EXPECT_EQ(0, remove_path(file1_s1d3)); -+ EXPECT_EQ(0, remove_path(file1_s1d2)); -+ EXPECT_EQ(0, remove_path(file1_s1d1)); -+ -+ EXPECT_EQ(0, remove_path(file2_s2d3)); -+ EXPECT_EQ(0, remove_path(file1_s2d3)); -+ EXPECT_EQ(0, remove_path(file1_s2d2)); -+ EXPECT_EQ(0, remove_path(file1_s2d1)); -+ -+ EXPECT_EQ(0, remove_path(dir_s3d3)); -+ set_cap(_metadata, CAP_SYS_ADMIN); -+ umount(dir_s3d2); -+ clear_cap(_metadata, CAP_SYS_ADMIN); -+ EXPECT_EQ(0, remove_path(dir_s3d2)); -+} -+ -+FIXTURE(layout1) { -+}; -+ -+FIXTURE_SETUP(layout1) -+{ -+ prepare_layout(_metadata); -+ -+ create_layout1(_metadata); -+} -+ -+FIXTURE_TEARDOWN(layout1) -+{ -+ remove_layout1(_metadata); -+ -+ cleanup_layout(_metadata); -+} -+ -+/* -+ * This helper enables to use the ASSERT_* macros and print the line number -+ * pointing to the test caller. -+ */ -+static int test_open_rel(const int dirfd, const char *const path, const int flags) -+{ -+ int fd; -+ -+ /* Works with file and directories. */ -+ fd = openat(dirfd, path, flags | O_CLOEXEC); -+ if (fd < 0) -+ return errno; -+ /* -+ * Mixing error codes from close(2) and open(2) should not lead to any -+ * (access type) confusion for this test. -+ */ -+ if (close(fd) != 0) -+ return errno; -+ return 0; -+} -+ -+static int test_open(const char *const path, const int flags) -+{ -+ return test_open_rel(AT_FDCWD, path, flags); -+} -+ -+TEST_F_FORK(layout1, no_restriction) -+{ -+ ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file2_s1d1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file2_s1d2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); -+ -+ ASSERT_EQ(0, test_open(dir_s2d1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s2d1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s2d2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(dir_s2d3, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s2d3, O_RDONLY)); -+ -+ ASSERT_EQ(0, test_open(dir_s3d1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(dir_s3d2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(dir_s3d3, O_RDONLY)); -+} -+ -+TEST_F_FORK(layout1, inval) -+{ -+ struct landlock_path_beneath_attr path_beneath = { -+ .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, -+ .parent_fd = -1, -+ }; -+ struct landlock_ruleset_attr ruleset_attr = { -+ .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, -+ }; -+ int ruleset_fd; -+ -+ path_beneath.parent_fd = open(dir_s1d2, O_PATH | O_DIRECTORY | -+ O_CLOEXEC); -+ ASSERT_LE(0, path_beneath.parent_fd); -+ -+ ruleset_fd = open(dir_s1d1, O_PATH | O_DIRECTORY | O_CLOEXEC); -+ ASSERT_LE(0, ruleset_fd); -+ ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, -+ &path_beneath, 0)); -+ /* Returns EBADF because ruleset_fd is not a landlock-ruleset FD. */ -+ ASSERT_EQ(EBADF, errno); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ ruleset_fd = open(dir_s1d1, O_DIRECTORY | O_CLOEXEC); -+ ASSERT_LE(0, ruleset_fd); -+ ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, -+ &path_beneath, 0)); -+ /* Returns EBADFD because ruleset_fd is not a valid ruleset. */ -+ ASSERT_EQ(EBADFD, errno); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Gets a real ruleset. */ -+ ruleset_fd = landlock_create_ruleset(&ruleset_attr, -+ sizeof(ruleset_attr), 0); -+ ASSERT_LE(0, ruleset_fd); -+ ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, -+ &path_beneath, 0)); -+ ASSERT_EQ(0, close(path_beneath.parent_fd)); -+ -+ /* Tests without O_PATH. */ -+ path_beneath.parent_fd = open(dir_s1d2, O_DIRECTORY | O_CLOEXEC); -+ ASSERT_LE(0, path_beneath.parent_fd); -+ ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, -+ &path_beneath, 0)); -+ ASSERT_EQ(0, close(path_beneath.parent_fd)); -+ -+ /* Tests with a ruleset FD. */ -+ path_beneath.parent_fd = ruleset_fd; -+ ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, -+ &path_beneath, 0)); -+ ASSERT_EQ(EBADFD, errno); -+ -+ /* Checks unhandled allowed_access. */ -+ path_beneath.parent_fd = open(dir_s1d2, O_PATH | O_DIRECTORY | -+ O_CLOEXEC); -+ ASSERT_LE(0, path_beneath.parent_fd); -+ -+ /* Test with legitimate values. */ -+ path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_EXECUTE; -+ ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, -+ &path_beneath, 0)); -+ ASSERT_EQ(EINVAL, errno); -+ path_beneath.allowed_access &= ~LANDLOCK_ACCESS_FS_EXECUTE; -+ -+ /* Test with unknown (64-bits) value. */ -+ path_beneath.allowed_access |= (1ULL << 60); -+ ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, -+ &path_beneath, 0)); -+ ASSERT_EQ(EINVAL, errno); -+ path_beneath.allowed_access &= ~(1ULL << 60); -+ -+ /* Test with no access. */ -+ path_beneath.allowed_access = 0; -+ ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, -+ &path_beneath, 0)); -+ ASSERT_EQ(ENOMSG, errno); -+ path_beneath.allowed_access &= ~(1ULL << 60); -+ -+ ASSERT_EQ(0, close(path_beneath.parent_fd)); -+ -+ /* Enforces the ruleset. */ -+ ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); -+ ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0)); -+ -+ ASSERT_EQ(0, close(ruleset_fd)); -+} -+ -+#define ACCESS_FILE ( \ -+ LANDLOCK_ACCESS_FS_EXECUTE | \ -+ LANDLOCK_ACCESS_FS_WRITE_FILE | \ -+ LANDLOCK_ACCESS_FS_READ_FILE) -+ -+#define ACCESS_LAST LANDLOCK_ACCESS_FS_MAKE_SYM -+ -+#define ACCESS_ALL ( \ -+ ACCESS_FILE | \ -+ LANDLOCK_ACCESS_FS_READ_DIR | \ -+ LANDLOCK_ACCESS_FS_REMOVE_DIR | \ -+ LANDLOCK_ACCESS_FS_REMOVE_FILE | \ -+ LANDLOCK_ACCESS_FS_MAKE_CHAR | \ -+ LANDLOCK_ACCESS_FS_MAKE_DIR | \ -+ LANDLOCK_ACCESS_FS_MAKE_REG | \ -+ LANDLOCK_ACCESS_FS_MAKE_SOCK | \ -+ LANDLOCK_ACCESS_FS_MAKE_FIFO | \ -+ LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ -+ ACCESS_LAST) -+ -+TEST_F_FORK(layout1, file_access_rights) -+{ -+ __u64 access; -+ int err; -+ struct landlock_path_beneath_attr path_beneath = {}; -+ struct landlock_ruleset_attr ruleset_attr = { -+ .handled_access_fs = ACCESS_ALL, -+ }; -+ const int ruleset_fd = landlock_create_ruleset(&ruleset_attr, -+ sizeof(ruleset_attr), 0); -+ -+ ASSERT_LE(0, ruleset_fd); -+ -+ /* Tests access rights for files. */ -+ path_beneath.parent_fd = open(file1_s1d2, O_PATH | O_CLOEXEC); -+ ASSERT_LE(0, path_beneath.parent_fd); -+ for (access = 1; access <= ACCESS_LAST; access <<= 1) { -+ path_beneath.allowed_access = access; -+ err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, -+ &path_beneath, 0); -+ if ((access | ACCESS_FILE) == ACCESS_FILE) { -+ ASSERT_EQ(0, err); -+ } else { -+ ASSERT_EQ(-1, err); -+ ASSERT_EQ(EINVAL, errno); -+ } -+ } -+ ASSERT_EQ(0, close(path_beneath.parent_fd)); -+} -+ -+static void add_path_beneath(struct __test_metadata *const _metadata, -+ const int ruleset_fd, const __u64 allowed_access, -+ const char *const path) -+{ -+ struct landlock_path_beneath_attr path_beneath = { -+ .allowed_access = allowed_access, -+ }; -+ -+ path_beneath.parent_fd = open(path, O_PATH | O_CLOEXEC); -+ ASSERT_LE(0, path_beneath.parent_fd) { -+ TH_LOG("Failed to open directory \"%s\": %s", path, -+ strerror(errno)); -+ } -+ ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, -+ &path_beneath, 0)) { -+ TH_LOG("Failed to update the ruleset with \"%s\": %s", path, -+ strerror(errno)); -+ } -+ ASSERT_EQ(0, close(path_beneath.parent_fd)); -+} -+ -+struct rule { -+ const char *path; -+ __u64 access; -+}; -+ -+#define ACCESS_RO ( \ -+ LANDLOCK_ACCESS_FS_READ_FILE | \ -+ LANDLOCK_ACCESS_FS_READ_DIR) -+ -+#define ACCESS_RW ( \ -+ ACCESS_RO | \ -+ LANDLOCK_ACCESS_FS_WRITE_FILE) -+ -+static int create_ruleset(struct __test_metadata *const _metadata, -+ const __u64 handled_access_fs, const struct rule rules[]) -+{ -+ int ruleset_fd, i; -+ struct landlock_ruleset_attr ruleset_attr = { -+ .handled_access_fs = handled_access_fs, -+ }; -+ -+ ASSERT_NE(NULL, rules) { -+ TH_LOG("No rule list"); -+ } -+ ASSERT_NE(NULL, rules[0].path) { -+ TH_LOG("Empty rule list"); -+ } -+ -+ ruleset_fd = landlock_create_ruleset(&ruleset_attr, -+ sizeof(ruleset_attr), 0); -+ ASSERT_LE(0, ruleset_fd) { -+ TH_LOG("Failed to create a ruleset: %s", strerror(errno)); -+ } -+ -+ for (i = 0; rules[i].path; i++) { -+ add_path_beneath(_metadata, ruleset_fd, rules[i].access, -+ rules[i].path); -+ } -+ return ruleset_fd; -+} -+ -+static void enforce_ruleset(struct __test_metadata *const _metadata, -+ const int ruleset_fd) -+{ -+ ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); -+ ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0)) { -+ TH_LOG("Failed to enforce ruleset: %s", strerror(errno)); -+ } -+} -+ -+TEST_F_FORK(layout1, proc_nsfs) -+{ -+ const struct rule rules[] = { -+ { -+ .path = "/dev/null", -+ .access = LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, -+ }, -+ {} -+ }; -+ struct landlock_path_beneath_attr path_beneath; -+ const int ruleset_fd = create_ruleset(_metadata, rules[0].access | -+ LANDLOCK_ACCESS_FS_READ_DIR, rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ ASSERT_EQ(0, test_open("/proc/self/ns/mnt", O_RDONLY)); -+ -+ enforce_ruleset(_metadata, ruleset_fd); -+ -+ ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open("/dev", O_RDONLY)); -+ ASSERT_EQ(0, test_open("/dev/null", O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open("/dev/full", O_RDONLY)); -+ -+ ASSERT_EQ(EACCES, test_open("/proc", O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open("/proc/self", O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open("/proc/self/ns", O_RDONLY)); -+ /* -+ * Because nsfs is an internal filesystem, /proc/self/ns/mnt is a -+ * disconnected path. Such path cannot be identified and must then be -+ * allowed. -+ */ -+ ASSERT_EQ(0, test_open("/proc/self/ns/mnt", O_RDONLY)); -+ -+ /* -+ * Checks that it is not possible to add nsfs-like filesystem -+ * references to a ruleset. -+ */ -+ path_beneath.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, -+ path_beneath.parent_fd = open("/proc/self/ns/mnt", O_PATH | O_CLOEXEC); -+ ASSERT_LE(0, path_beneath.parent_fd); -+ ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, -+ &path_beneath, 0)); -+ ASSERT_EQ(EBADFD, errno); -+ ASSERT_EQ(0, close(path_beneath.parent_fd)); -+} -+ -+TEST_F_FORK(layout1, unpriv) { -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d2, -+ .access = ACCESS_RO, -+ }, -+ {} -+ }; -+ int ruleset_fd; -+ -+ drop_caps(_metadata); -+ -+ ruleset_fd = create_ruleset(_metadata, ACCESS_RO, rules); -+ ASSERT_LE(0, ruleset_fd); -+ ASSERT_EQ(-1, landlock_restrict_self(ruleset_fd, 0)); -+ ASSERT_EQ(EPERM, errno); -+ -+ /* enforce_ruleset() calls prctl(no_new_privs). */ -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+} -+ -+TEST_F_FORK(layout1, effective_access) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d2, -+ .access = ACCESS_RO, -+ }, -+ { -+ .path = file1_s2d2, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); -+ char buf; -+ int reg_fd; -+ -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Tests on a directory. */ -+ ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); -+ -+ /* Tests on a file. */ -+ ASSERT_EQ(EACCES, test_open(dir_s2d2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s2d2, O_RDONLY)); -+ -+ /* Checks effective read and write actions. */ -+ reg_fd = open(file1_s2d2, O_RDWR | O_CLOEXEC); -+ ASSERT_LE(0, reg_fd); -+ ASSERT_EQ(1, write(reg_fd, ".", 1)); -+ ASSERT_LE(0, lseek(reg_fd, 0, SEEK_SET)); -+ ASSERT_EQ(1, read(reg_fd, &buf, 1)); -+ ASSERT_EQ('.', buf); -+ ASSERT_EQ(0, close(reg_fd)); -+ -+ /* Just in case, double-checks effective actions. */ -+ reg_fd = open(file1_s2d2, O_RDONLY | O_CLOEXEC); -+ ASSERT_LE(0, reg_fd); -+ ASSERT_EQ(-1, write(reg_fd, &buf, 1)); -+ ASSERT_EQ(EBADF, errno); -+ ASSERT_EQ(0, close(reg_fd)); -+} -+ -+TEST_F_FORK(layout1, unhandled_access) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d2, -+ .access = ACCESS_RO, -+ }, -+ {} -+ }; -+ /* Here, we only handle read accesses, not write accesses. */ -+ const int ruleset_fd = create_ruleset(_metadata, ACCESS_RO, rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* -+ * Because the policy does not handle LANDLOCK_ACCESS_FS_WRITE_FILE, -+ * opening for write-only should be allowed, but not read-write. -+ */ -+ ASSERT_EQ(0, test_open(file1_s1d1, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR)); -+ -+ ASSERT_EQ(0, test_open(file1_s1d2, O_WRONLY)); -+ ASSERT_EQ(0, test_open(file1_s1d2, O_RDWR)); -+} -+ -+TEST_F_FORK(layout1, ruleset_overlap) -+{ -+ const struct rule rules[] = { -+ /* These rules should be ORed among them. */ -+ { -+ .path = dir_s1d2, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, -+ }, -+ { -+ .path = dir_s1d2, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_READ_DIR, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks s1d1 hierarchy. */ -+ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR)); -+ ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); -+ -+ /* Checks s1d2 hierarchy. */ -+ ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s1d2, O_WRONLY)); -+ ASSERT_EQ(0, test_open(file1_s1d2, O_RDWR)); -+ ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); -+ -+ /* Checks s1d3 hierarchy. */ -+ ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s1d3, O_WRONLY)); -+ ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); -+ ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); -+} -+ -+TEST_F_FORK(layout1, non_overlapping_accesses) -+{ -+ const struct rule layer1[] = { -+ { -+ .path = dir_s1d2, -+ .access = LANDLOCK_ACCESS_FS_MAKE_REG, -+ }, -+ {} -+ }; -+ const struct rule layer2[] = { -+ { -+ .path = dir_s1d3, -+ .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, -+ }, -+ {} -+ }; -+ int ruleset_fd; -+ -+ ASSERT_EQ(0, unlink(file1_s1d1)); -+ ASSERT_EQ(0, unlink(file1_s1d2)); -+ -+ ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, -+ layer1); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ ASSERT_EQ(-1, mknod(file1_s1d1, S_IFREG | 0700, 0)); -+ ASSERT_EQ(EACCES, errno); -+ ASSERT_EQ(0, mknod(file1_s1d2, S_IFREG | 0700, 0)); -+ ASSERT_EQ(0, unlink(file1_s1d2)); -+ -+ ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REMOVE_FILE, -+ layer2); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Unchanged accesses for file creation. */ -+ ASSERT_EQ(-1, mknod(file1_s1d1, S_IFREG | 0700, 0)); -+ ASSERT_EQ(EACCES, errno); -+ ASSERT_EQ(0, mknod(file1_s1d2, S_IFREG | 0700, 0)); -+ -+ /* Checks file removing. */ -+ ASSERT_EQ(-1, unlink(file1_s1d2)); -+ ASSERT_EQ(EACCES, errno); -+ ASSERT_EQ(0, unlink(file1_s1d3)); -+} -+ -+TEST_F_FORK(layout1, interleaved_masked_accesses) -+{ -+ /* -+ * Checks overly restrictive rules: -+ * layer 1: allows R s1d1/s1d2/s1d3/file1 -+ * layer 2: allows RW s1d1/s1d2/s1d3 -+ * allows W s1d1/s1d2 -+ * denies R s1d1/s1d2 -+ * layer 3: allows R s1d1 -+ * layer 4: allows R s1d1/s1d2 -+ * denies W s1d1/s1d2 -+ * layer 5: allows R s1d1/s1d2 -+ * layer 6: allows X ---- -+ * layer 7: allows W s1d1/s1d2 -+ * denies R s1d1/s1d2 -+ */ -+ const struct rule layer1_read[] = { -+ /* Allows read access to file1_s1d3 with the first layer. */ -+ { -+ .path = file1_s1d3, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ {} -+ }; -+ /* First rule with write restrictions. */ -+ const struct rule layer2_read_write[] = { -+ /* Start by granting read-write access via its parent directory... */ -+ { -+ .path = dir_s1d3, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, -+ }, -+ /* ...but also denies read access via its grandparent directory. */ -+ { -+ .path = dir_s1d2, -+ .access = LANDLOCK_ACCESS_FS_WRITE_FILE, -+ }, -+ {} -+ }; -+ const struct rule layer3_read[] = { -+ /* Allows read access via its great-grandparent directory. */ -+ { -+ .path = dir_s1d1, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ {} -+ }; -+ const struct rule layer4_read_write[] = { -+ /* -+ * Try to confuse the deny access by denying write (but not -+ * read) access via its grandparent directory. -+ */ -+ { -+ .path = dir_s1d2, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ {} -+ }; -+ const struct rule layer5_read[] = { -+ /* -+ * Try to override layer2's deny read access by explicitly -+ * allowing read access via file1_s1d3's grandparent. -+ */ -+ { -+ .path = dir_s1d2, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ {} -+ }; -+ const struct rule layer6_execute[] = { -+ /* -+ * Restricts an unrelated file hierarchy with a new access -+ * (non-overlapping) type. -+ */ -+ { -+ .path = dir_s2d1, -+ .access = LANDLOCK_ACCESS_FS_EXECUTE, -+ }, -+ {} -+ }; -+ const struct rule layer7_read_write[] = { -+ /* -+ * Finally, denies read access to file1_s1d3 via its -+ * grandparent. -+ */ -+ { -+ .path = dir_s1d2, -+ .access = LANDLOCK_ACCESS_FS_WRITE_FILE, -+ }, -+ {} -+ }; -+ int ruleset_fd; -+ -+ ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, -+ layer1_read); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks that read access is granted for file1_s1d3 with layer 1. */ -+ ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); -+ ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY)); -+ -+ ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, layer2_read_write); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks that previous access rights are unchanged with layer 2. */ -+ ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); -+ ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY)); -+ -+ ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, -+ layer3_read); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks that previous access rights are unchanged with layer 3. */ -+ ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); -+ ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY)); -+ -+ /* This time, denies write access for the file hierarchy. */ -+ ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, layer4_read_write); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* -+ * Checks that the only change with layer 4 is that write access is -+ * denied. -+ */ -+ ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY)); -+ -+ ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, -+ layer5_read); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks that previous access rights are unchanged with layer 5. */ -+ ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); -+ -+ ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_EXECUTE, -+ layer6_execute); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks that previous access rights are unchanged with layer 6. */ -+ ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); -+ -+ ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, layer7_read_write); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks read access is now denied with layer 7. */ -+ ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); -+} -+ -+TEST_F_FORK(layout1, inherit_subset) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d2, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_READ_DIR, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ -+ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); -+ -+ /* Write access is forbidden. */ -+ ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); -+ /* Readdir access is allowed. */ -+ ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); -+ -+ /* Write access is forbidden. */ -+ ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); -+ /* Readdir access is allowed. */ -+ ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); -+ -+ /* -+ * Tests shared rule extension: the following rules should not grant -+ * any new access, only remove some. Once enforced, these rules are -+ * ANDed with the previous ones. -+ */ -+ add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE, -+ dir_s1d2); -+ /* -+ * According to ruleset_fd, dir_s1d2 should now have the -+ * LANDLOCK_ACCESS_FS_READ_FILE and LANDLOCK_ACCESS_FS_WRITE_FILE -+ * access rights (even if this directory is opened a second time). -+ * However, when enforcing this updated ruleset, the ruleset tied to -+ * the current process (i.e. its domain) will still only have the -+ * dir_s1d2 with LANDLOCK_ACCESS_FS_READ_FILE and -+ * LANDLOCK_ACCESS_FS_READ_DIR accesses, but -+ * LANDLOCK_ACCESS_FS_WRITE_FILE must not be allowed because it would -+ * be a privilege escalation. -+ */ -+ enforce_ruleset(_metadata, ruleset_fd); -+ -+ /* Same tests and results as above. */ -+ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); -+ -+ /* It is still forbidden to write in file1_s1d2. */ -+ ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); -+ /* Readdir access is still allowed. */ -+ ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); -+ -+ /* It is still forbidden to write in file1_s1d3. */ -+ ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); -+ /* Readdir access is still allowed. */ -+ ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); -+ -+ /* -+ * Try to get more privileges by adding new access rights to the parent -+ * directory: dir_s1d1. -+ */ -+ add_path_beneath(_metadata, ruleset_fd, ACCESS_RW, dir_s1d1); -+ enforce_ruleset(_metadata, ruleset_fd); -+ -+ /* Same tests and results as above. */ -+ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); -+ -+ /* It is still forbidden to write in file1_s1d2. */ -+ ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); -+ /* Readdir access is still allowed. */ -+ ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); -+ -+ /* It is still forbidden to write in file1_s1d3. */ -+ ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); -+ /* Readdir access is still allowed. */ -+ ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); -+ -+ /* -+ * Now, dir_s1d3 get a new rule tied to it, only allowing -+ * LANDLOCK_ACCESS_FS_WRITE_FILE. The (kernel internal) difference is -+ * that there was no rule tied to it before. -+ */ -+ add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE, -+ dir_s1d3); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* -+ * Same tests and results as above, except for open(dir_s1d3) which is -+ * now denied because the new rule mask the rule previously inherited -+ * from dir_s1d2. -+ */ -+ -+ /* Same tests and results as above. */ -+ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); -+ -+ /* It is still forbidden to write in file1_s1d2. */ -+ ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); -+ /* Readdir access is still allowed. */ -+ ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); -+ -+ /* It is still forbidden to write in file1_s1d3. */ -+ ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); -+ /* -+ * Readdir of dir_s1d3 is still allowed because of the OR policy inside -+ * the same layer. -+ */ -+ ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); -+} -+ -+TEST_F_FORK(layout1, inherit_superset) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d3, -+ .access = ACCESS_RO, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ -+ /* Readdir access is denied for dir_s1d2. */ -+ ASSERT_EQ(EACCES, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); -+ /* Readdir access is allowed for dir_s1d3. */ -+ ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); -+ /* File access is allowed for file1_s1d3. */ -+ ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); -+ -+ /* Now dir_s1d2, parent of dir_s1d3, gets a new rule tied to it. */ -+ add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_READ_DIR, dir_s1d2); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Readdir access is still denied for dir_s1d2. */ -+ ASSERT_EQ(EACCES, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); -+ /* Readdir access is still allowed for dir_s1d3. */ -+ ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); -+ /* File access is still allowed for file1_s1d3. */ -+ ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); -+} -+ -+TEST_F_FORK(layout1, max_layers) -+{ -+ int i, err; -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d2, -+ .access = ACCESS_RO, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ for (i = 0; i < 64; i++) -+ enforce_ruleset(_metadata, ruleset_fd); -+ -+ for (i = 0; i < 2; i++) { -+ err = landlock_restrict_self(ruleset_fd, 0); -+ ASSERT_EQ(-1, err); -+ ASSERT_EQ(E2BIG, errno); -+ } -+ ASSERT_EQ(0, close(ruleset_fd)); -+} -+ -+TEST_F_FORK(layout1, empty_or_same_ruleset) -+{ -+ struct landlock_ruleset_attr ruleset_attr = {}; -+ int ruleset_fd; -+ -+ /* Tests empty handled_access_fs. */ -+ ruleset_fd = landlock_create_ruleset(&ruleset_attr, -+ sizeof(ruleset_attr), 0); -+ ASSERT_LE(-1, ruleset_fd); -+ ASSERT_EQ(ENOMSG, errno); -+ -+ /* Enforces policy which deny read access to all files. */ -+ ruleset_attr.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE; -+ ruleset_fd = landlock_create_ruleset(&ruleset_attr, -+ sizeof(ruleset_attr), 0); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); -+ -+ /* Nests a policy which deny read access to all directories. */ -+ ruleset_attr.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR; -+ ruleset_fd = landlock_create_ruleset(&ruleset_attr, -+ sizeof(ruleset_attr), 0); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY)); -+ -+ /* Enforces a second time with the same ruleset. */ -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+} -+ -+TEST_F_FORK(layout1, rule_on_mountpoint) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d1, -+ .access = ACCESS_RO, -+ }, -+ { -+ /* dir_s3d2 is a mount point. */ -+ .path = dir_s3d2, -+ .access = ACCESS_RO, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); -+ -+ ASSERT_EQ(EACCES, test_open(dir_s2d1, O_RDONLY)); -+ -+ ASSERT_EQ(EACCES, test_open(dir_s3d1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(dir_s3d2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(dir_s3d3, O_RDONLY)); -+} -+ -+TEST_F_FORK(layout1, rule_over_mountpoint) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d1, -+ .access = ACCESS_RO, -+ }, -+ { -+ /* dir_s3d2 is a mount point. */ -+ .path = dir_s3d1, -+ .access = ACCESS_RO, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); -+ -+ ASSERT_EQ(EACCES, test_open(dir_s2d1, O_RDONLY)); -+ -+ ASSERT_EQ(0, test_open(dir_s3d1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(dir_s3d2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(dir_s3d3, O_RDONLY)); -+} -+ -+/* -+ * This test verifies that we can apply a landlock rule on the root directory -+ * (which might require special handling). -+ */ -+TEST_F_FORK(layout1, rule_over_root_allow_then_deny) -+{ -+ struct rule rules[] = { -+ { -+ .path = "/", -+ .access = ACCESS_RO, -+ }, -+ {} -+ }; -+ int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks allowed access. */ -+ ASSERT_EQ(0, test_open("/", O_RDONLY)); -+ ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); -+ -+ rules[0].access = LANDLOCK_ACCESS_FS_READ_FILE; -+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks denied access (on a directory). */ -+ ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY)); -+} -+ -+TEST_F_FORK(layout1, rule_over_root_deny) -+{ -+ const struct rule rules[] = { -+ { -+ .path = "/", -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks denied access (on a directory). */ -+ ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY)); -+} -+ -+TEST_F_FORK(layout1, rule_inside_mount_ns) -+{ -+ const struct rule rules[] = { -+ { -+ .path = "s3d3", -+ .access = ACCESS_RO, -+ }, -+ {} -+ }; -+ int ruleset_fd; -+ -+ set_cap(_metadata, CAP_SYS_ADMIN); -+ ASSERT_EQ(0, syscall(SYS_pivot_root, dir_s3d2, dir_s3d3)) { -+ TH_LOG("Failed to pivot root: %s", strerror(errno)); -+ }; -+ ASSERT_EQ(0, chdir("/")); -+ clear_cap(_metadata, CAP_SYS_ADMIN); -+ -+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ ASSERT_EQ(0, test_open("s3d3", O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); -+} -+ -+TEST_F_FORK(layout1, mount_and_pivot) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s3d2, -+ .access = ACCESS_RO, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ set_cap(_metadata, CAP_SYS_ADMIN); -+ ASSERT_EQ(-1, mount(NULL, dir_s3d2, NULL, MS_RDONLY, NULL)); -+ ASSERT_EQ(EPERM, errno); -+ ASSERT_EQ(-1, syscall(SYS_pivot_root, dir_s3d2, dir_s3d3)); -+ ASSERT_EQ(EPERM, errno); -+ clear_cap(_metadata, CAP_SYS_ADMIN); -+} -+ -+TEST_F_FORK(layout1, move_mount) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s3d2, -+ .access = ACCESS_RO, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ -+ set_cap(_metadata, CAP_SYS_ADMIN); -+ ASSERT_EQ(0, syscall(SYS_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD, -+ dir_s1d2, 0)) { -+ TH_LOG("Failed to move mount: %s", strerror(errno)); -+ } -+ -+ ASSERT_EQ(0, syscall(SYS_move_mount, AT_FDCWD, dir_s1d2, AT_FDCWD, -+ dir_s3d2, 0)); -+ clear_cap(_metadata, CAP_SYS_ADMIN); -+ -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ set_cap(_metadata, CAP_SYS_ADMIN); -+ ASSERT_EQ(-1, syscall(SYS_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD, -+ dir_s1d2, 0)); -+ ASSERT_EQ(EPERM, errno); -+ clear_cap(_metadata, CAP_SYS_ADMIN); -+} -+ -+TEST_F_FORK(layout1, release_inodes) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d1, -+ .access = ACCESS_RO, -+ }, -+ { -+ .path = dir_s3d2, -+ .access = ACCESS_RO, -+ }, -+ { -+ .path = dir_s3d3, -+ .access = ACCESS_RO, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ /* Unmount a file hierarchy while it is being used by a ruleset. */ -+ set_cap(_metadata, CAP_SYS_ADMIN); -+ ASSERT_EQ(0, umount(dir_s3d2)); -+ clear_cap(_metadata, CAP_SYS_ADMIN); -+ -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(dir_s3d2, O_RDONLY)); -+ /* This dir_s3d3 would not be allowed and does not exist anyway. */ -+ ASSERT_EQ(ENOENT, test_open(dir_s3d3, O_RDONLY)); -+} -+ -+enum relative_access { -+ REL_OPEN, -+ REL_CHDIR, -+ REL_CHROOT_ONLY, -+ REL_CHROOT_CHDIR, -+}; -+ -+static void test_relative_path(struct __test_metadata *const _metadata, -+ const enum relative_access rel) -+{ -+ /* -+ * Common layer to check that chroot doesn't ignore it (i.e. a chroot -+ * is not a disconnected root directory). -+ */ -+ const struct rule layer1_base[] = { -+ { -+ .path = TMP_DIR, -+ .access = ACCESS_RO, -+ }, -+ {} -+ }; -+ const struct rule layer2_subs[] = { -+ { -+ .path = dir_s1d2, -+ .access = ACCESS_RO, -+ }, -+ { -+ .path = dir_s2d2, -+ .access = ACCESS_RO, -+ }, -+ {} -+ }; -+ int dirfd, ruleset_fd; -+ -+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_base); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_subs); -+ -+ ASSERT_LE(0, ruleset_fd); -+ switch (rel) { -+ case REL_OPEN: -+ case REL_CHDIR: -+ break; -+ case REL_CHROOT_ONLY: -+ ASSERT_EQ(0, chdir(dir_s2d2)); -+ break; -+ case REL_CHROOT_CHDIR: -+ ASSERT_EQ(0, chdir(dir_s1d2)); -+ break; -+ default: -+ ASSERT_TRUE(false); -+ return; -+ } -+ -+ set_cap(_metadata, CAP_SYS_CHROOT); -+ enforce_ruleset(_metadata, ruleset_fd); -+ -+ switch (rel) { -+ case REL_OPEN: -+ dirfd = open(dir_s1d2, O_DIRECTORY); -+ ASSERT_LE(0, dirfd); -+ break; -+ case REL_CHDIR: -+ ASSERT_EQ(0, chdir(dir_s1d2)); -+ dirfd = AT_FDCWD; -+ break; -+ case REL_CHROOT_ONLY: -+ /* Do chroot into dir_s1d2 (relative to dir_s2d2). */ -+ ASSERT_EQ(0, chroot("../../s1d1/s1d2")) { -+ TH_LOG("Failed to chroot: %s", strerror(errno)); -+ } -+ dirfd = AT_FDCWD; -+ break; -+ case REL_CHROOT_CHDIR: -+ /* Do chroot into dir_s1d2. */ -+ ASSERT_EQ(0, chroot(".")) { -+ TH_LOG("Failed to chroot: %s", strerror(errno)); -+ } -+ dirfd = AT_FDCWD; -+ break; -+ } -+ -+ ASSERT_EQ((rel == REL_CHROOT_CHDIR) ? 0 : EACCES, -+ test_open_rel(dirfd, "..", O_RDONLY)); -+ ASSERT_EQ(0, test_open_rel(dirfd, ".", O_RDONLY)); -+ -+ if (rel == REL_CHROOT_ONLY) { -+ /* The current directory is dir_s2d2. */ -+ ASSERT_EQ(0, test_open_rel(dirfd, "./s2d3", O_RDONLY)); -+ } else { -+ /* The current directory is dir_s1d2. */ -+ ASSERT_EQ(0, test_open_rel(dirfd, "./s1d3", O_RDONLY)); -+ } -+ -+ if (rel == REL_CHROOT_ONLY || rel == REL_CHROOT_CHDIR) { -+ /* Checks the root dir_s1d2. */ -+ ASSERT_EQ(0, test_open_rel(dirfd, "/..", O_RDONLY)); -+ ASSERT_EQ(0, test_open_rel(dirfd, "/", O_RDONLY)); -+ ASSERT_EQ(0, test_open_rel(dirfd, "/f1", O_RDONLY)); -+ ASSERT_EQ(0, test_open_rel(dirfd, "/s1d3", O_RDONLY)); -+ } -+ -+ if (rel != REL_CHROOT_CHDIR) { -+ ASSERT_EQ(EACCES, test_open_rel(dirfd, "../../s1d1", O_RDONLY)); -+ ASSERT_EQ(0, test_open_rel(dirfd, "../../s1d1/s1d2", O_RDONLY)); -+ ASSERT_EQ(0, test_open_rel(dirfd, "../../s1d1/s1d2/s1d3", O_RDONLY)); -+ -+ ASSERT_EQ(EACCES, test_open_rel(dirfd, "../../s2d1", O_RDONLY)); -+ ASSERT_EQ(0, test_open_rel(dirfd, "../../s2d1/s2d2", O_RDONLY)); -+ ASSERT_EQ(0, test_open_rel(dirfd, "../../s2d1/s2d2/s2d3", O_RDONLY)); -+ } -+ -+ if (rel == REL_OPEN) -+ ASSERT_EQ(0, close(dirfd)); -+ ASSERT_EQ(0, close(ruleset_fd)); -+} -+ -+TEST_F_FORK(layout1, relative_open) -+{ -+ test_relative_path(_metadata, REL_OPEN); -+} -+ -+TEST_F_FORK(layout1, relative_chdir) -+{ -+ test_relative_path(_metadata, REL_CHDIR); -+} -+ -+TEST_F_FORK(layout1, relative_chroot_only) -+{ -+ test_relative_path(_metadata, REL_CHROOT_ONLY); -+} -+ -+TEST_F_FORK(layout1, relative_chroot_chdir) -+{ -+ test_relative_path(_metadata, REL_CHROOT_CHDIR); -+} -+ -+static void copy_binary(struct __test_metadata *const _metadata, -+ const char *const dst_path) -+{ -+ int dst_fd, src_fd; -+ struct stat statbuf; -+ -+ dst_fd = open(dst_path, O_WRONLY | O_TRUNC | O_CLOEXEC); -+ ASSERT_LE(0, dst_fd) { -+ TH_LOG("Failed to open \"%s\": %s", dst_path, -+ strerror(errno)); -+ } -+ src_fd = open(BINARY_PATH, O_RDONLY | O_CLOEXEC); -+ ASSERT_LE(0, src_fd) { -+ TH_LOG("Failed to open \"" BINARY_PATH "\": %s", -+ strerror(errno)); -+ } -+ ASSERT_EQ(0, fstat(src_fd, &statbuf)); -+ ASSERT_EQ(statbuf.st_size, sendfile(dst_fd, src_fd, 0, -+ statbuf.st_size)); -+ ASSERT_EQ(0, close(src_fd)); -+ ASSERT_EQ(0, close(dst_fd)); -+} -+ -+static void test_execute(struct __test_metadata *const _metadata, -+ const int err, const char *const path) -+{ -+ int status; -+ char *const argv[] = {(char *)path, NULL}; -+ const pid_t child = fork(); -+ -+ ASSERT_LE(0, child); -+ if (child == 0) { -+ ASSERT_EQ(err ? -1 : 0, execve(path, argv, NULL)) { -+ TH_LOG("Failed to execute \"%s\": %s", path, -+ strerror(errno)); -+ }; -+ ASSERT_EQ(err, errno); -+ _exit(_metadata->passed ? 2 : 1); -+ return; -+ } -+ ASSERT_EQ(child, waitpid(child, &status, 0)); -+ ASSERT_EQ(1, WIFEXITED(status)); -+ ASSERT_EQ(err ? 2 : 0, WEXITSTATUS(status)) { -+ TH_LOG("Unexpected return code for \"%s\": %s", path, -+ strerror(errno)); -+ }; -+} -+ -+TEST_F_FORK(layout1, execute) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d2, -+ .access = LANDLOCK_ACCESS_FS_EXECUTE, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, rules[0].access, -+ rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ copy_binary(_metadata, file1_s1d1); -+ copy_binary(_metadata, file1_s1d2); -+ copy_binary(_metadata, file1_s1d3); -+ -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); -+ test_execute(_metadata, EACCES, file1_s1d1); -+ -+ ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); -+ test_execute(_metadata, 0, file1_s1d2); -+ -+ ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); -+ test_execute(_metadata, 0, file1_s1d3); -+} -+ -+TEST_F_FORK(layout1, link) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d2, -+ .access = LANDLOCK_ACCESS_FS_MAKE_REG, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, rules[0].access, -+ rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ -+ ASSERT_EQ(0, unlink(file1_s1d1)); -+ ASSERT_EQ(0, unlink(file1_s1d2)); -+ ASSERT_EQ(0, unlink(file1_s1d3)); -+ -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1)); -+ ASSERT_EQ(EACCES, errno); -+ /* Denies linking because of reparenting. */ -+ ASSERT_EQ(-1, link(file1_s2d1, file1_s1d2)); -+ ASSERT_EQ(EXDEV, errno); -+ ASSERT_EQ(-1, link(file2_s1d2, file1_s1d3)); -+ ASSERT_EQ(EXDEV, errno); -+ -+ ASSERT_EQ(0, link(file2_s1d2, file1_s1d2)); -+ ASSERT_EQ(0, link(file2_s1d3, file1_s1d3)); -+} -+ -+TEST_F_FORK(layout1, rename_file) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d3, -+ .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, -+ }, -+ { -+ .path = dir_s2d2, -+ .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, rules[0].access, -+ rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ -+ ASSERT_EQ(0, unlink(file1_s1d1)); -+ ASSERT_EQ(0, unlink(file1_s1d2)); -+ -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* -+ * Tries to replace a file, from a directory that allows file removal, -+ * but to a different directory (which also allows file removal). -+ */ -+ ASSERT_EQ(-1, rename(file1_s2d3, file1_s1d3)); -+ ASSERT_EQ(EXDEV, errno); -+ ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, file1_s1d3, -+ RENAME_EXCHANGE)); -+ ASSERT_EQ(EXDEV, errno); -+ ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, dir_s1d3, -+ RENAME_EXCHANGE)); -+ ASSERT_EQ(EXDEV, errno); -+ -+ /* -+ * Tries to replace a file, from a directory that denies file removal, -+ * to a different directory (which allows file removal). -+ */ -+ ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d3)); -+ ASSERT_EQ(EXDEV, errno); -+ ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, file1_s1d3, -+ RENAME_EXCHANGE)); -+ ASSERT_EQ(EXDEV, errno); -+ ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d2, AT_FDCWD, file1_s1d3, -+ RENAME_EXCHANGE)); -+ ASSERT_EQ(EXDEV, errno); -+ -+ /* Exchanges files and directories that partially allow removal. */ -+ ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d2, AT_FDCWD, file1_s2d1, -+ RENAME_EXCHANGE)); -+ ASSERT_EQ(EACCES, errno); -+ ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, dir_s2d2, -+ RENAME_EXCHANGE)); -+ ASSERT_EQ(EACCES, errno); -+ -+ /* Renames files with different parents. */ -+ ASSERT_EQ(-1, rename(file1_s2d2, file1_s1d2)); -+ ASSERT_EQ(EXDEV, errno); -+ ASSERT_EQ(0, unlink(file1_s1d3)); -+ ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d3)); -+ ASSERT_EQ(EXDEV, errno); -+ -+ /* Exchanges and renames files with same parent. */ -+ ASSERT_EQ(0, renameat2(AT_FDCWD, file2_s2d3, AT_FDCWD, file1_s2d3, -+ RENAME_EXCHANGE)); -+ ASSERT_EQ(0, rename(file2_s2d3, file1_s2d3)); -+ -+ /* Exchanges files and directories with same parent, twice. */ -+ ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s2d3, -+ RENAME_EXCHANGE)); -+ ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s2d3, -+ RENAME_EXCHANGE)); -+} -+ -+TEST_F_FORK(layout1, rename_dir) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d2, -+ .access = LANDLOCK_ACCESS_FS_REMOVE_DIR, -+ }, -+ { -+ .path = dir_s2d1, -+ .access = LANDLOCK_ACCESS_FS_REMOVE_DIR, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, rules[0].access, -+ rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ -+ /* Empties dir_s1d3 to allow renaming. */ -+ ASSERT_EQ(0, unlink(file1_s1d3)); -+ ASSERT_EQ(0, unlink(file2_s1d3)); -+ -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Exchanges and renames directory to a different parent. */ -+ ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_s1d3, -+ RENAME_EXCHANGE)); -+ ASSERT_EQ(EXDEV, errno); -+ ASSERT_EQ(-1, rename(dir_s2d3, dir_s1d3)); -+ ASSERT_EQ(EXDEV, errno); -+ ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s1d3, -+ RENAME_EXCHANGE)); -+ ASSERT_EQ(EXDEV, errno); -+ -+ /* -+ * Exchanges directory to the same parent, which doesn't allow -+ * directory removal. -+ */ -+ ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s1d1, AT_FDCWD, dir_s2d1, -+ RENAME_EXCHANGE)); -+ ASSERT_EQ(EACCES, errno); -+ ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, dir_s1d2, -+ RENAME_EXCHANGE)); -+ ASSERT_EQ(EACCES, errno); -+ -+ /* -+ * Exchanges and renames directory to the same parent, which allows -+ * directory removal. -+ */ -+ ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, file1_s1d2, -+ RENAME_EXCHANGE)); -+ ASSERT_EQ(0, unlink(dir_s1d3)); -+ ASSERT_EQ(0, mkdir(dir_s1d3, 0700)); -+ ASSERT_EQ(0, rename(file1_s1d2, dir_s1d3)); -+ ASSERT_EQ(0, rmdir(dir_s1d3)); -+} -+ -+TEST_F_FORK(layout1, remove_dir) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d2, -+ .access = LANDLOCK_ACCESS_FS_REMOVE_DIR, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, rules[0].access, -+ rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ -+ ASSERT_EQ(0, unlink(file1_s1d1)); -+ ASSERT_EQ(0, unlink(file1_s1d2)); -+ ASSERT_EQ(0, unlink(file1_s1d3)); -+ ASSERT_EQ(0, unlink(file2_s1d3)); -+ -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ ASSERT_EQ(0, rmdir(dir_s1d3)); -+ ASSERT_EQ(0, mkdir(dir_s1d3, 0700)); -+ ASSERT_EQ(0, unlinkat(AT_FDCWD, dir_s1d3, AT_REMOVEDIR)); -+ -+ /* dir_s1d2 itself cannot be removed. */ -+ ASSERT_EQ(-1, rmdir(dir_s1d2)); -+ ASSERT_EQ(EACCES, errno); -+ ASSERT_EQ(-1, unlinkat(AT_FDCWD, dir_s1d2, AT_REMOVEDIR)); -+ ASSERT_EQ(EACCES, errno); -+ ASSERT_EQ(-1, rmdir(dir_s1d1)); -+ ASSERT_EQ(EACCES, errno); -+ ASSERT_EQ(-1, unlinkat(AT_FDCWD, dir_s1d1, AT_REMOVEDIR)); -+ ASSERT_EQ(EACCES, errno); -+} -+ -+TEST_F_FORK(layout1, remove_file) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d2, -+ .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, rules[0].access, -+ rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ ASSERT_EQ(-1, unlink(file1_s1d1)); -+ ASSERT_EQ(EACCES, errno); -+ ASSERT_EQ(-1, unlinkat(AT_FDCWD, file1_s1d1, 0)); -+ ASSERT_EQ(EACCES, errno); -+ ASSERT_EQ(0, unlink(file1_s1d2)); -+ ASSERT_EQ(0, unlinkat(AT_FDCWD, file1_s1d3, 0)); -+} -+ -+static void test_make_file(struct __test_metadata *const _metadata, -+ const __u64 access, const mode_t mode, const dev_t dev) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d2, -+ .access = access, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, access, rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ -+ ASSERT_EQ(0, unlink(file1_s1d1)); -+ ASSERT_EQ(0, unlink(file2_s1d1)); -+ ASSERT_EQ(0, mknod(file2_s1d1, mode | 0400, dev)) { -+ TH_LOG("Failed to make file \"%s\": %s", -+ file2_s1d1, strerror(errno)); -+ }; -+ -+ ASSERT_EQ(0, unlink(file1_s1d2)); -+ ASSERT_EQ(0, unlink(file2_s1d2)); -+ -+ ASSERT_EQ(0, unlink(file1_s1d3)); -+ ASSERT_EQ(0, unlink(file2_s1d3)); -+ -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ ASSERT_EQ(-1, mknod(file1_s1d1, mode | 0400, dev)); -+ ASSERT_EQ(EACCES, errno); -+ ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1)); -+ ASSERT_EQ(EACCES, errno); -+ ASSERT_EQ(-1, rename(file2_s1d1, file1_s1d1)); -+ ASSERT_EQ(EACCES, errno); -+ -+ ASSERT_EQ(0, mknod(file1_s1d2, mode | 0400, dev)) { -+ TH_LOG("Failed to make file \"%s\": %s", -+ file1_s1d2, strerror(errno)); -+ }; -+ ASSERT_EQ(0, link(file1_s1d2, file2_s1d2)); -+ ASSERT_EQ(0, unlink(file2_s1d2)); -+ ASSERT_EQ(0, rename(file1_s1d2, file2_s1d2)); -+ -+ ASSERT_EQ(0, mknod(file1_s1d3, mode | 0400, dev)); -+ ASSERT_EQ(0, link(file1_s1d3, file2_s1d3)); -+ ASSERT_EQ(0, unlink(file2_s1d3)); -+ ASSERT_EQ(0, rename(file1_s1d3, file2_s1d3)); -+} -+ -+TEST_F_FORK(layout1, make_char) -+{ -+ /* Creates a /dev/null device. */ -+ set_cap(_metadata, CAP_MKNOD); -+ test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_CHAR, S_IFCHR, -+ makedev(1, 3)); -+} -+ -+TEST_F_FORK(layout1, make_block) -+{ -+ /* Creates a /dev/loop0 device. */ -+ set_cap(_metadata, CAP_MKNOD); -+ test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_BLOCK, S_IFBLK, -+ makedev(7, 0)); -+} -+ -+TEST_F_FORK(layout1, make_reg_1) -+{ -+ test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, S_IFREG, 0); -+} -+ -+TEST_F_FORK(layout1, make_reg_2) -+{ -+ test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, 0, 0); -+} -+ -+TEST_F_FORK(layout1, make_sock) -+{ -+ test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_SOCK, S_IFSOCK, 0); -+} -+ -+TEST_F_FORK(layout1, make_fifo) -+{ -+ test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_FIFO, S_IFIFO, 0); -+} -+ -+TEST_F_FORK(layout1, make_sym) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d2, -+ .access = LANDLOCK_ACCESS_FS_MAKE_SYM, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, rules[0].access, -+ rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ -+ ASSERT_EQ(0, unlink(file1_s1d1)); -+ ASSERT_EQ(0, unlink(file2_s1d1)); -+ ASSERT_EQ(0, symlink("none", file2_s1d1)); -+ -+ ASSERT_EQ(0, unlink(file1_s1d2)); -+ ASSERT_EQ(0, unlink(file2_s1d2)); -+ -+ ASSERT_EQ(0, unlink(file1_s1d3)); -+ ASSERT_EQ(0, unlink(file2_s1d3)); -+ -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ ASSERT_EQ(-1, symlink("none", file1_s1d1)); -+ ASSERT_EQ(EACCES, errno); -+ ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1)); -+ ASSERT_EQ(EACCES, errno); -+ ASSERT_EQ(-1, rename(file2_s1d1, file1_s1d1)); -+ ASSERT_EQ(EACCES, errno); -+ -+ ASSERT_EQ(0, symlink("none", file1_s1d2)); -+ ASSERT_EQ(0, link(file1_s1d2, file2_s1d2)); -+ ASSERT_EQ(0, unlink(file2_s1d2)); -+ ASSERT_EQ(0, rename(file1_s1d2, file2_s1d2)); -+ -+ ASSERT_EQ(0, symlink("none", file1_s1d3)); -+ ASSERT_EQ(0, link(file1_s1d3, file2_s1d3)); -+ ASSERT_EQ(0, unlink(file2_s1d3)); -+ ASSERT_EQ(0, rename(file1_s1d3, file2_s1d3)); -+} -+ -+TEST_F_FORK(layout1, make_dir) -+{ -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d2, -+ .access = LANDLOCK_ACCESS_FS_MAKE_DIR, -+ }, -+ {} -+ }; -+ const int ruleset_fd = create_ruleset(_metadata, rules[0].access, -+ rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ -+ ASSERT_EQ(0, unlink(file1_s1d1)); -+ ASSERT_EQ(0, unlink(file1_s1d2)); -+ ASSERT_EQ(0, unlink(file1_s1d3)); -+ -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Uses file_* as directory names. */ -+ ASSERT_EQ(-1, mkdir(file1_s1d1, 0700)); -+ ASSERT_EQ(EACCES, errno); -+ ASSERT_EQ(0, mkdir(file1_s1d2, 0700)); -+ ASSERT_EQ(0, mkdir(file1_s1d3, 0700)); -+} -+ -+static int open_proc_fd(struct __test_metadata *const _metadata, const int fd, -+ const int open_flags) -+{ -+ static const char path_template[] = "/proc/self/fd/%d"; -+ char procfd_path[sizeof(path_template) + 10]; -+ const int procfd_path_size = snprintf(procfd_path, sizeof(procfd_path), -+ path_template, fd); -+ -+ ASSERT_LT(procfd_path_size, sizeof(procfd_path)); -+ return open(procfd_path, open_flags); -+} -+ -+TEST_F_FORK(layout1, proc_unlinked_file) -+{ -+ const struct rule rules[] = { -+ { -+ .path = file1_s1d2, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ {} -+ }; -+ int reg_fd, proc_fd; -+ const int ruleset_fd = create_ruleset(_metadata, -+ LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR)); -+ ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); -+ reg_fd = open(file1_s1d2, O_RDONLY | O_CLOEXEC); -+ ASSERT_LE(0, reg_fd); -+ ASSERT_EQ(0, unlink(file1_s1d2)); -+ -+ proc_fd = open_proc_fd(_metadata, reg_fd, O_RDONLY | O_CLOEXEC); -+ ASSERT_LE(0, proc_fd); -+ ASSERT_EQ(0, close(proc_fd)); -+ -+ proc_fd = open_proc_fd(_metadata, reg_fd, O_RDWR | O_CLOEXEC); -+ ASSERT_EQ(-1, proc_fd) { -+ TH_LOG("Successfully opened /proc/self/fd/%d: %s", -+ reg_fd, strerror(errno)); -+ } -+ ASSERT_EQ(EACCES, errno); -+ -+ ASSERT_EQ(0, close(reg_fd)); -+} -+ -+TEST_F_FORK(layout1, proc_pipe) -+{ -+ int proc_fd; -+ int pipe_fds[2]; -+ char buf = '\0'; -+ const struct rule rules[] = { -+ { -+ .path = dir_s1d2, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, -+ }, -+ {} -+ }; -+ /* Limits read and write access to files tied to the filesystem. */ -+ const int ruleset_fd = create_ruleset(_metadata, rules[0].access, -+ rules); -+ -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks enforcement for normal files. */ -+ ASSERT_EQ(0, test_open(file1_s1d2, O_RDWR)); -+ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR)); -+ -+ /* Checks access to pipes through FD. */ -+ ASSERT_EQ(0, pipe2(pipe_fds, O_CLOEXEC)); -+ ASSERT_EQ(1, write(pipe_fds[1], ".", 1)) { -+ TH_LOG("Failed to write in pipe: %s", strerror(errno)); -+ } -+ ASSERT_EQ(1, read(pipe_fds[0], &buf, 1)); -+ ASSERT_EQ('.', buf); -+ -+ /* Checks write access to pipe through /proc/self/fd . */ -+ proc_fd = open_proc_fd(_metadata, pipe_fds[1], O_WRONLY | O_CLOEXEC); -+ ASSERT_LE(0, proc_fd); -+ ASSERT_EQ(1, write(proc_fd, ".", 1)) { -+ TH_LOG("Failed to write through /proc/self/fd/%d: %s", -+ pipe_fds[1], strerror(errno)); -+ } -+ ASSERT_EQ(0, close(proc_fd)); -+ -+ /* Checks read access to pipe through /proc/self/fd . */ -+ proc_fd = open_proc_fd(_metadata, pipe_fds[0], O_RDONLY | O_CLOEXEC); -+ ASSERT_LE(0, proc_fd); -+ buf = '\0'; -+ ASSERT_EQ(1, read(proc_fd, &buf, 1)) { -+ TH_LOG("Failed to read through /proc/self/fd/%d: %s", -+ pipe_fds[1], strerror(errno)); -+ } -+ ASSERT_EQ(0, close(proc_fd)); -+ -+ ASSERT_EQ(0, close(pipe_fds[0])); -+ ASSERT_EQ(0, close(pipe_fds[1])); -+} -+ -+FIXTURE(layout1_bind) { -+}; -+ -+FIXTURE_SETUP(layout1_bind) -+{ -+ prepare_layout(_metadata); -+ -+ create_layout1(_metadata); -+ -+ set_cap(_metadata, CAP_SYS_ADMIN); -+ ASSERT_EQ(0, mount(dir_s1d2, dir_s2d2, NULL, MS_BIND, NULL)); -+ clear_cap(_metadata, CAP_SYS_ADMIN); -+} -+ -+FIXTURE_TEARDOWN(layout1_bind) -+{ -+ set_cap(_metadata, CAP_SYS_ADMIN); -+ EXPECT_EQ(0, umount(dir_s2d2)); -+ clear_cap(_metadata, CAP_SYS_ADMIN); -+ -+ remove_layout1(_metadata); -+ -+ cleanup_layout(_metadata); -+} -+ -+static const char bind_dir_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3"; -+static const char bind_file1_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3/f1"; -+ -+/* -+ * layout1_bind hierarchy: -+ * -+ * tmp -+ * ├── s1d1 -+ * │   ├── f1 -+ * │   ├── f2 -+ * │   └── s1d2 -+ * │   ├── f1 -+ * │   ├── f2 -+ * │   └── s1d3 -+ * │   ├── f1 -+ * │   └── f2 -+ * ├── s2d1 -+ * │   ├── f1 -+ * │   └── s2d2 -+ * │   ├── f1 -+ * │   ├── f2 -+ * │   └── s1d3 -+ * │   ├── f1 -+ * │   └── f2 -+ * └── s3d1 -+ * └── s3d2 -+ * └── s3d3 -+ */ -+ -+TEST_F_FORK(layout1_bind, no_restriction) -+{ -+ ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); -+ -+ ASSERT_EQ(0, test_open(dir_s2d1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s2d1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(file1_s2d2, O_RDONLY)); -+ ASSERT_EQ(ENOENT, test_open(dir_s2d3, O_RDONLY)); -+ ASSERT_EQ(ENOENT, test_open(file1_s2d3, O_RDONLY)); -+ -+ ASSERT_EQ(0, test_open(bind_dir_s1d3, O_RDONLY)); -+ ASSERT_EQ(0, test_open(bind_file1_s1d3, O_RDONLY)); -+ -+ ASSERT_EQ(0, test_open(dir_s3d1, O_RDONLY)); -+} -+ -+TEST_F_FORK(layout1_bind, same_content_same_file) -+{ -+ /* -+ * Sets access right on parent directories of both source and -+ * destination mount points. -+ */ -+ const struct rule layer1_parent[] = { -+ { -+ .path = dir_s1d1, -+ .access = ACCESS_RO, -+ }, -+ { -+ .path = dir_s2d1, -+ .access = ACCESS_RW, -+ }, -+ {} -+ }; -+ /* -+ * Sets access rights on the same bind-mounted directories. The result -+ * should be ACCESS_RW for both directories, but not both hierarchies -+ * because of the first layer. -+ */ -+ const struct rule layer2_mount_point[] = { -+ { -+ .path = dir_s1d2, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ { -+ .path = dir_s2d2, -+ .access = ACCESS_RW, -+ }, -+ {} -+ }; -+ /* Only allow read-access to the s1d3 hierarchies. */ -+ const struct rule layer3_source[] = { -+ { -+ .path = dir_s1d3, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ {} -+ }; -+ /* Removes all access rights. */ -+ const struct rule layer4_destination[] = { -+ { -+ .path = bind_file1_s1d3, -+ .access = LANDLOCK_ACCESS_FS_WRITE_FILE, -+ }, -+ {} -+ }; -+ int ruleset_fd; -+ -+ /* Sets rules for the parent directories. */ -+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_parent); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks source hierarchy. */ -+ ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); -+ ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); -+ -+ ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); -+ ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); -+ -+ /* Checks destination hierarchy. */ -+ ASSERT_EQ(0, test_open(file1_s2d1, O_RDWR)); -+ ASSERT_EQ(0, test_open(dir_s2d1, O_RDONLY | O_DIRECTORY)); -+ -+ ASSERT_EQ(0, test_open(file1_s2d2, O_RDWR)); -+ ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY | O_DIRECTORY)); -+ -+ /* Sets rules for the mount points. */ -+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_mount_point); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks source hierarchy. */ -+ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); -+ -+ ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); -+ ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); -+ -+ /* Checks destination hierarchy. */ -+ ASSERT_EQ(EACCES, test_open(file1_s2d1, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(file1_s2d1, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(dir_s2d1, O_RDONLY | O_DIRECTORY)); -+ -+ ASSERT_EQ(0, test_open(file1_s2d2, O_RDWR)); -+ ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY | O_DIRECTORY)); -+ ASSERT_EQ(0, test_open(bind_dir_s1d3, O_RDONLY | O_DIRECTORY)); -+ -+ /* Sets a (shared) rule only on the source. */ -+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3_source); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks source hierarchy. */ -+ ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); -+ -+ ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); -+ -+ /* Checks destination hierarchy. */ -+ ASSERT_EQ(EACCES, test_open(file1_s2d2, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(file1_s2d2, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(dir_s2d2, O_RDONLY | O_DIRECTORY)); -+ -+ ASSERT_EQ(0, test_open(bind_file1_s1d3, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(bind_file1_s1d3, O_WRONLY)); -+ ASSERT_EQ(EACCES, test_open(bind_dir_s1d3, O_RDONLY | O_DIRECTORY)); -+ -+ /* Sets a (shared) rule only on the destination. */ -+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer4_destination); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks source hierarchy. */ -+ ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); -+ -+ /* Checks destination hierarchy. */ -+ ASSERT_EQ(EACCES, test_open(bind_file1_s1d3, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(bind_file1_s1d3, O_WRONLY)); -+} -+ -+#define LOWER_BASE TMP_DIR "/lower" -+#define LOWER_DATA LOWER_BASE "/data" -+static const char lower_fl1[] = LOWER_DATA "/fl1"; -+static const char lower_dl1[] = LOWER_DATA "/dl1"; -+static const char lower_dl1_fl2[] = LOWER_DATA "/dl1/fl2"; -+static const char lower_fo1[] = LOWER_DATA "/fo1"; -+static const char lower_do1[] = LOWER_DATA "/do1"; -+static const char lower_do1_fo2[] = LOWER_DATA "/do1/fo2"; -+static const char lower_do1_fl3[] = LOWER_DATA "/do1/fl3"; -+ -+static const char (*lower_base_files[])[] = { -+ &lower_fl1, -+ &lower_fo1, -+ NULL -+}; -+static const char (*lower_base_directories[])[] = { -+ &lower_dl1, -+ &lower_do1, -+ NULL -+}; -+static const char (*lower_sub_files[])[] = { -+ &lower_dl1_fl2, -+ &lower_do1_fo2, -+ &lower_do1_fl3, -+ NULL -+}; -+ -+#define UPPER_BASE TMP_DIR "/upper" -+#define UPPER_DATA UPPER_BASE "/data" -+#define UPPER_WORK UPPER_BASE "/work" -+static const char upper_fu1[] = UPPER_DATA "/fu1"; -+static const char upper_du1[] = UPPER_DATA "/du1"; -+static const char upper_du1_fu2[] = UPPER_DATA "/du1/fu2"; -+static const char upper_fo1[] = UPPER_DATA "/fo1"; -+static const char upper_do1[] = UPPER_DATA "/do1"; -+static const char upper_do1_fo2[] = UPPER_DATA "/do1/fo2"; -+static const char upper_do1_fu3[] = UPPER_DATA "/do1/fu3"; -+ -+static const char (*upper_base_files[])[] = { -+ &upper_fu1, -+ &upper_fo1, -+ NULL -+}; -+static const char (*upper_base_directories[])[] = { -+ &upper_du1, -+ &upper_do1, -+ NULL -+}; -+static const char (*upper_sub_files[])[] = { -+ &upper_du1_fu2, -+ &upper_do1_fo2, -+ &upper_do1_fu3, -+ NULL -+}; -+ -+#define MERGE_BASE TMP_DIR "/merge" -+#define MERGE_DATA MERGE_BASE "/data" -+static const char merge_fl1[] = MERGE_DATA "/fl1"; -+static const char merge_dl1[] = MERGE_DATA "/dl1"; -+static const char merge_dl1_fl2[] = MERGE_DATA "/dl1/fl2"; -+static const char merge_fu1[] = MERGE_DATA "/fu1"; -+static const char merge_du1[] = MERGE_DATA "/du1"; -+static const char merge_du1_fu2[] = MERGE_DATA "/du1/fu2"; -+static const char merge_fo1[] = MERGE_DATA "/fo1"; -+static const char merge_do1[] = MERGE_DATA "/do1"; -+static const char merge_do1_fo2[] = MERGE_DATA "/do1/fo2"; -+static const char merge_do1_fl3[] = MERGE_DATA "/do1/fl3"; -+static const char merge_do1_fu3[] = MERGE_DATA "/do1/fu3"; -+ -+static const char (*merge_base_files[])[] = { -+ &merge_fl1, -+ &merge_fu1, -+ &merge_fo1, -+ NULL -+}; -+static const char (*merge_base_directories[])[] = { -+ &merge_dl1, -+ &merge_du1, -+ &merge_do1, -+ NULL -+}; -+static const char (*merge_sub_files[])[] = { -+ &merge_dl1_fl2, -+ &merge_du1_fu2, -+ &merge_do1_fo2, -+ &merge_do1_fl3, -+ &merge_do1_fu3, -+ NULL -+}; -+ -+/* -+ * layout2_overlay hierarchy: -+ * -+ * tmp -+ * ├── lower -+ * │   └── data -+ * │   ├── dl1 -+ * │   │   └── fl2 -+ * │   ├── do1 -+ * │   │   ├── fl3 -+ * │   │   └── fo2 -+ * │   ├── fl1 -+ * │   └── fo1 -+ * ├── merge -+ * │   └── data -+ * │   ├── dl1 -+ * │   │   └── fl2 -+ * │   ├── do1 -+ * │   │   ├── fl3 -+ * │   │   ├── fo2 -+ * │   │   └── fu3 -+ * │   ├── du1 -+ * │   │   └── fu2 -+ * │   ├── fl1 -+ * │   ├── fo1 -+ * │   └── fu1 -+ * └── upper -+ * ├── data -+ * │   ├── do1 -+ * │   │   ├── fo2 -+ * │   │   └── fu3 -+ * │   ├── du1 -+ * │   │   └── fu2 -+ * │   ├── fo1 -+ * │   └── fu1 -+ * └── work -+ * └── work -+ */ -+ -+FIXTURE(layout2_overlay) { -+}; -+ -+FIXTURE_SETUP(layout2_overlay) -+{ -+ prepare_layout(_metadata); -+ -+ create_directory(_metadata, LOWER_BASE); -+ set_cap(_metadata, CAP_SYS_ADMIN); -+ /* Creates tmpfs mount points to get deterministic overlayfs. */ -+ ASSERT_EQ(0, mount("tmp", LOWER_BASE, "tmpfs", 0, "size=4m,mode=700")); -+ clear_cap(_metadata, CAP_SYS_ADMIN); -+ create_file(_metadata, lower_fl1); -+ create_file(_metadata, lower_dl1_fl2); -+ create_file(_metadata, lower_fo1); -+ create_file(_metadata, lower_do1_fo2); -+ create_file(_metadata, lower_do1_fl3); -+ -+ create_directory(_metadata, UPPER_BASE); -+ set_cap(_metadata, CAP_SYS_ADMIN); -+ ASSERT_EQ(0, mount("tmp", UPPER_BASE, "tmpfs", 0, "size=4m,mode=700")); -+ clear_cap(_metadata, CAP_SYS_ADMIN); -+ create_file(_metadata, upper_fu1); -+ create_file(_metadata, upper_du1_fu2); -+ create_file(_metadata, upper_fo1); -+ create_file(_metadata, upper_do1_fo2); -+ create_file(_metadata, upper_do1_fu3); -+ ASSERT_EQ(0, mkdir(UPPER_WORK, 0700)); -+ -+ create_directory(_metadata, MERGE_DATA); -+ set_cap(_metadata, CAP_SYS_ADMIN); -+ set_cap(_metadata, CAP_DAC_OVERRIDE); -+ ASSERT_EQ(0, mount("overlay", MERGE_DATA, "overlay", 0, -+ "lowerdir=" LOWER_DATA -+ ",upperdir=" UPPER_DATA -+ ",workdir=" UPPER_WORK)); -+ clear_cap(_metadata, CAP_DAC_OVERRIDE); -+ clear_cap(_metadata, CAP_SYS_ADMIN); -+} -+ -+FIXTURE_TEARDOWN(layout2_overlay) -+{ -+ EXPECT_EQ(0, remove_path(lower_do1_fl3)); -+ EXPECT_EQ(0, remove_path(lower_dl1_fl2)); -+ EXPECT_EQ(0, remove_path(lower_fl1)); -+ EXPECT_EQ(0, remove_path(lower_do1_fo2)); -+ EXPECT_EQ(0, remove_path(lower_fo1)); -+ set_cap(_metadata, CAP_SYS_ADMIN); -+ EXPECT_EQ(0, umount(LOWER_BASE)); -+ clear_cap(_metadata, CAP_SYS_ADMIN); -+ EXPECT_EQ(0, remove_path(LOWER_BASE)); -+ -+ EXPECT_EQ(0, remove_path(upper_do1_fu3)); -+ EXPECT_EQ(0, remove_path(upper_du1_fu2)); -+ EXPECT_EQ(0, remove_path(upper_fu1)); -+ EXPECT_EQ(0, remove_path(upper_do1_fo2)); -+ EXPECT_EQ(0, remove_path(upper_fo1)); -+ EXPECT_EQ(0, remove_path(UPPER_WORK "/work")); -+ set_cap(_metadata, CAP_SYS_ADMIN); -+ EXPECT_EQ(0, umount(UPPER_BASE)); -+ clear_cap(_metadata, CAP_SYS_ADMIN); -+ EXPECT_EQ(0, remove_path(UPPER_BASE)); -+ -+ set_cap(_metadata, CAP_SYS_ADMIN); -+ EXPECT_EQ(0, umount(MERGE_DATA)); -+ clear_cap(_metadata, CAP_SYS_ADMIN); -+ EXPECT_EQ(0, remove_path(MERGE_DATA)); -+ -+ cleanup_layout(_metadata); -+} -+ -+TEST_F_FORK(layout2_overlay, no_restriction) -+{ -+ ASSERT_EQ(0, test_open(lower_fl1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(lower_dl1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(lower_dl1_fl2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(lower_fo1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(lower_do1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(lower_do1_fo2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(lower_do1_fl3, O_RDONLY)); -+ -+ ASSERT_EQ(0, test_open(upper_fu1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(upper_du1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(upper_du1_fu2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(upper_fo1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(upper_do1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(upper_do1_fo2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(upper_do1_fu3, O_RDONLY)); -+ -+ ASSERT_EQ(0, test_open(merge_fl1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(merge_dl1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(merge_dl1_fl2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(merge_fu1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(merge_du1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(merge_du1_fu2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(merge_fo1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(merge_do1, O_RDONLY)); -+ ASSERT_EQ(0, test_open(merge_do1_fo2, O_RDONLY)); -+ ASSERT_EQ(0, test_open(merge_do1_fl3, O_RDONLY)); -+ ASSERT_EQ(0, test_open(merge_do1_fu3, O_RDONLY)); -+} -+ -+#define for_each_path(path_list, path_entry, i) \ -+ for (i = 0, path_entry = *path_list[i]; path_list[i]; \ -+ path_entry = *path_list[++i]) -+ -+TEST_F_FORK(layout2_overlay, same_content_different_file) -+{ -+ /* Sets access right on parent directories of both layers. */ -+ const struct rule layer1_base[] = { -+ { -+ .path = LOWER_BASE, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ { -+ .path = UPPER_BASE, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ { -+ .path = MERGE_BASE, -+ .access = ACCESS_RW, -+ }, -+ {} -+ }; -+ const struct rule layer2_data[] = { -+ { -+ .path = LOWER_DATA, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ { -+ .path = UPPER_DATA, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ { -+ .path = MERGE_DATA, -+ .access = ACCESS_RW, -+ }, -+ {} -+ }; -+ /* Sets access right on directories inside both layers. */ -+ const struct rule layer3_subdirs[] = { -+ { -+ .path = lower_dl1, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ { -+ .path = lower_do1, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ { -+ .path = upper_du1, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ { -+ .path = upper_do1, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ { -+ .path = merge_dl1, -+ .access = ACCESS_RW, -+ }, -+ { -+ .path = merge_du1, -+ .access = ACCESS_RW, -+ }, -+ { -+ .path = merge_do1, -+ .access = ACCESS_RW, -+ }, -+ {} -+ }; -+ /* Tighten access rights to the files. */ -+ const struct rule layer4_files[] = { -+ { -+ .path = lower_dl1_fl2, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ { -+ .path = lower_do1_fo2, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ { -+ .path = lower_do1_fl3, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ { -+ .path = upper_du1_fu2, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ { -+ .path = upper_do1_fo2, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ { -+ .path = upper_do1_fu3, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE, -+ }, -+ { -+ .path = merge_dl1_fl2, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, -+ }, -+ { -+ .path = merge_du1_fu2, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, -+ }, -+ { -+ .path = merge_do1_fo2, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, -+ }, -+ { -+ .path = merge_do1_fl3, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, -+ }, -+ { -+ .path = merge_do1_fu3, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, -+ }, -+ {} -+ }; -+ const struct rule layer5_merge_only[] = { -+ { -+ .path = MERGE_DATA, -+ .access = LANDLOCK_ACCESS_FS_READ_FILE | -+ LANDLOCK_ACCESS_FS_WRITE_FILE, -+ }, -+ {} -+ }; -+ int ruleset_fd; -+ size_t i; -+ const char *path_entry; -+ -+ /* Sets rules on base directories (i.e. outside overlay scope). */ -+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_base); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks lower layer. */ -+ for_each_path(lower_base_files, path_entry, i) { -+ ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); -+ } -+ for_each_path(lower_base_directories, path_entry, i) { -+ ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY | O_DIRECTORY)); -+ } -+ for_each_path(lower_sub_files, path_entry, i) { -+ ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); -+ } -+ /* Checks upper layer. */ -+ for_each_path(upper_base_files, path_entry, i) { -+ ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); -+ } -+ for_each_path(upper_base_directories, path_entry, i) { -+ ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY | O_DIRECTORY)); -+ } -+ for_each_path(upper_sub_files, path_entry, i) { -+ ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); -+ } -+ /* -+ * Checks that access rights are independent from the lower and upper -+ * layers: write access to upper files viewed through the merge point -+ * is still allowed, and write access to lower file viewed (and copied) -+ * through the merge point is still allowed. -+ */ -+ for_each_path(merge_base_files, path_entry, i) { -+ ASSERT_EQ(0, test_open(path_entry, O_RDWR)); -+ } -+ for_each_path(merge_base_directories, path_entry, i) { -+ ASSERT_EQ(0, test_open(path_entry, O_RDONLY | O_DIRECTORY)); -+ } -+ for_each_path(merge_sub_files, path_entry, i) { -+ ASSERT_EQ(0, test_open(path_entry, O_RDWR)); -+ } -+ -+ /* Sets rules on data directories (i.e. inside overlay scope). */ -+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_data); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks merge. */ -+ for_each_path(merge_base_files, path_entry, i) { -+ ASSERT_EQ(0, test_open(path_entry, O_RDWR)); -+ } -+ for_each_path(merge_base_directories, path_entry, i) { -+ ASSERT_EQ(0, test_open(path_entry, O_RDONLY | O_DIRECTORY)); -+ } -+ for_each_path(merge_sub_files, path_entry, i) { -+ ASSERT_EQ(0, test_open(path_entry, O_RDWR)); -+ } -+ -+ /* Same checks with tighter rules. */ -+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3_subdirs); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks changes for lower layer. */ -+ for_each_path(lower_base_files, path_entry, i) { -+ ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY)); -+ } -+ /* Checks changes for upper layer. */ -+ for_each_path(upper_base_files, path_entry, i) { -+ ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY)); -+ } -+ /* Checks all merge accesses. */ -+ for_each_path(merge_base_files, path_entry, i) { -+ ASSERT_EQ(EACCES, test_open(path_entry, O_RDWR)); -+ } -+ for_each_path(merge_base_directories, path_entry, i) { -+ ASSERT_EQ(0, test_open(path_entry, O_RDONLY | O_DIRECTORY)); -+ } -+ for_each_path(merge_sub_files, path_entry, i) { -+ ASSERT_EQ(0, test_open(path_entry, O_RDWR)); -+ } -+ -+ /* Sets rules directly on overlayed files. */ -+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer4_files); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks unchanged accesses on lower layer. */ -+ for_each_path(lower_sub_files, path_entry, i) { -+ ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); -+ } -+ /* Checks unchanged accesses on upper layer. */ -+ for_each_path(upper_sub_files, path_entry, i) { -+ ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); -+ ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); -+ } -+ /* Checks all merge accesses. */ -+ for_each_path(merge_base_files, path_entry, i) { -+ ASSERT_EQ(EACCES, test_open(path_entry, O_RDWR)); -+ } -+ for_each_path(merge_base_directories, path_entry, i) { -+ ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY | O_DIRECTORY)); -+ } -+ for_each_path(merge_sub_files, path_entry, i) { -+ ASSERT_EQ(0, test_open(path_entry, O_RDWR)); -+ } -+ -+ /* Only allowes access to the merge hierarchy. */ -+ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer5_merge_only); -+ ASSERT_LE(0, ruleset_fd); -+ enforce_ruleset(_metadata, ruleset_fd); -+ ASSERT_EQ(0, close(ruleset_fd)); -+ -+ /* Checks new accesses on lower layer. */ -+ for_each_path(lower_sub_files, path_entry, i) { -+ ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY)); -+ } -+ /* Checks new accesses on upper layer. */ -+ for_each_path(upper_sub_files, path_entry, i) { -+ ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY)); -+ } -+ /* Checks all merge accesses. */ -+ for_each_path(merge_base_files, path_entry, i) { -+ ASSERT_EQ(EACCES, test_open(path_entry, O_RDWR)); -+ } -+ for_each_path(merge_base_directories, path_entry, i) { -+ ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY | O_DIRECTORY)); -+ } -+ for_each_path(merge_sub_files, path_entry, i) { -+ ASSERT_EQ(0, test_open(path_entry, O_RDWR)); -+ } -+} -+ -+TEST_HARNESS_MAIN -diff --git a/tools/testing/selftests/landlock/ptrace_test.c b/tools/testing/selftests/landlock/ptrace_test.c -new file mode 100644 -index 0000000000000..15fbef9cc8496 ---- /dev/null -+++ b/tools/testing/selftests/landlock/ptrace_test.c -@@ -0,0 +1,337 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Landlock tests - Ptrace -+ * -+ * Copyright © 2017-2020 Mickaël Salaün -+ * Copyright © 2019-2020 ANSSI -+ */ -+ -+#define _GNU_SOURCE -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "common.h" -+ -+static void create_domain(struct __test_metadata *const _metadata) -+{ -+ int ruleset_fd; -+ struct landlock_ruleset_attr ruleset_attr = { -+ .handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_BLOCK, -+ }; -+ -+ ruleset_fd = landlock_create_ruleset(&ruleset_attr, -+ sizeof(ruleset_attr), 0); -+ EXPECT_LE(0, ruleset_fd) { -+ TH_LOG("Failed to create a ruleset: %s", strerror(errno)); -+ } -+ EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); -+ EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0)); -+ EXPECT_EQ(0, close(ruleset_fd)); -+} -+ -+static int test_ptrace_read(const pid_t pid) -+{ -+ static const char path_template[] = "/proc/%d/environ"; -+ char procenv_path[sizeof(path_template) + 10]; -+ int procenv_path_size, fd; -+ -+ procenv_path_size = snprintf(procenv_path, sizeof(procenv_path), -+ path_template, pid); -+ if (procenv_path_size >= sizeof(procenv_path)) -+ return E2BIG; -+ -+ fd = open(procenv_path, O_RDONLY | O_CLOEXEC); -+ if (fd < 0) -+ return errno; -+ /* -+ * Mixing error codes from close(2) and open(2) should not lead to any -+ * (access type) confusion for this test. -+ */ -+ if (close(fd) != 0) -+ return errno; -+ return 0; -+} -+ -+FIXTURE(hierarchy) { }; -+ -+FIXTURE_VARIANT(hierarchy) { -+ const bool domain_both; -+ const bool domain_parent; -+ const bool domain_child; -+}; -+ -+/* -+ * Test multiple tracing combinations between a parent process P1 and a child -+ * process P2. -+ * -+ * Yama's scoped ptrace is presumed disabled. If enabled, this optional -+ * restriction is enforced in addition to any Landlock check, which means that -+ * all P2 requests to trace P1 would be denied. -+ */ -+ -+/* -+ * No domain -+ * -+ * P1-. P1 -> P2 : allow -+ * \ P2 -> P1 : allow -+ * 'P2 -+ */ -+FIXTURE_VARIANT_ADD(hierarchy, allow_without_domain) { -+ .domain_both = false, -+ .domain_parent = false, -+ .domain_child = false, -+}; -+ -+/* -+ * Child domain -+ * -+ * P1--. P1 -> P2 : allow -+ * \ P2 -> P1 : deny -+ * .'-----. -+ * | P2 | -+ * '------' -+ */ -+FIXTURE_VARIANT_ADD(hierarchy, allow_with_one_domain) { -+ .domain_both = false, -+ .domain_parent = false, -+ .domain_child = true, -+}; -+ -+/* -+ * Parent domain -+ * .------. -+ * | P1 --. P1 -> P2 : deny -+ * '------' \ P2 -> P1 : allow -+ * ' -+ * P2 -+ */ -+FIXTURE_VARIANT_ADD(hierarchy, deny_with_parent_domain) { -+ .domain_both = false, -+ .domain_parent = true, -+ .domain_child = false, -+}; -+ -+/* -+ * Parent + child domain (siblings) -+ * .------. -+ * | P1 ---. P1 -> P2 : deny -+ * '------' \ P2 -> P1 : deny -+ * .---'--. -+ * | P2 | -+ * '------' -+ */ -+FIXTURE_VARIANT_ADD(hierarchy, deny_with_sibling_domain) { -+ .domain_both = false, -+ .domain_parent = true, -+ .domain_child = true, -+}; -+ -+/* -+ * Same domain (inherited) -+ * .-------------. -+ * | P1----. | P1 -> P2 : allow -+ * | \ | P2 -> P1 : allow -+ * | ' | -+ * | P2 | -+ * '-------------' -+ */ -+FIXTURE_VARIANT_ADD(hierarchy, allow_sibling_domain) { -+ .domain_both = true, -+ .domain_parent = false, -+ .domain_child = false, -+}; -+ -+/* -+ * Inherited + child domain -+ * .-----------------. -+ * | P1----. | P1 -> P2 : allow -+ * | \ | P2 -> P1 : deny -+ * | .-'----. | -+ * | | P2 | | -+ * | '------' | -+ * '-----------------' -+ */ -+FIXTURE_VARIANT_ADD(hierarchy, allow_with_nested_domain) { -+ .domain_both = true, -+ .domain_parent = false, -+ .domain_child = true, -+}; -+ -+/* -+ * Inherited + parent domain -+ * .-----------------. -+ * |.------. | P1 -> P2 : deny -+ * || P1 ----. | P2 -> P1 : allow -+ * |'------' \ | -+ * | ' | -+ * | P2 | -+ * '-----------------' -+ */ -+FIXTURE_VARIANT_ADD(hierarchy, deny_with_nested_and_parent_domain) { -+ .domain_both = true, -+ .domain_parent = true, -+ .domain_child = false, -+}; -+ -+/* -+ * Inherited + parent and child domain (siblings) -+ * .-----------------. -+ * | .------. | P1 -> P2 : deny -+ * | | P1 . | P2 -> P1 : deny -+ * | '------'\ | -+ * | \ | -+ * | .--'---. | -+ * | | P2 | | -+ * | '------' | -+ * '-----------------' -+ */ -+FIXTURE_VARIANT_ADD(hierarchy, deny_with_forked_domain) { -+ .domain_both = true, -+ .domain_parent = true, -+ .domain_child = true, -+}; -+ -+FIXTURE_SETUP(hierarchy) -+{ } -+ -+FIXTURE_TEARDOWN(hierarchy) -+{ } -+ -+/* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */ -+TEST_F(hierarchy, trace) -+{ -+ pid_t child, parent; -+ int status, err_proc_read; -+ int pipe_child[2], pipe_parent[2]; -+ char buf_parent; -+ long ret; -+ -+ /* -+ * Removes all effective and permitted capabilities to not interfere -+ * with cap_ptrace_access_check() in case of PTRACE_MODE_FSCREDS. -+ */ -+ drop_caps(_metadata); -+ -+ parent = getpid(); -+ ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); -+ ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); -+ if (variant->domain_both) { -+ create_domain(_metadata); -+ if (!_metadata->passed) -+ /* Aborts before forking. */ -+ return; -+ } -+ -+ child = fork(); -+ ASSERT_LE(0, child); -+ if (child == 0) { -+ char buf_child; -+ -+ ASSERT_EQ(0, close(pipe_parent[1])); -+ ASSERT_EQ(0, close(pipe_child[0])); -+ if (variant->domain_child) -+ create_domain(_metadata); -+ -+ /* Waits for the parent to be in a domain, if any. */ -+ ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)); -+ -+ /* Tests PTRACE_ATTACH and PTRACE_MODE_READ on the parent. */ -+ err_proc_read = test_ptrace_read(parent); -+ ret = ptrace(PTRACE_ATTACH, parent, NULL, 0); -+ if (variant->domain_child) { -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EPERM, errno); -+ EXPECT_EQ(EACCES, err_proc_read); -+ } else { -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, err_proc_read); -+ } -+ if (ret == 0) { -+ ASSERT_EQ(parent, waitpid(parent, &status, 0)); -+ ASSERT_EQ(1, WIFSTOPPED(status)); -+ ASSERT_EQ(0, ptrace(PTRACE_DETACH, parent, NULL, 0)); -+ } -+ -+ /* Tests child PTRACE_TRACEME. */ -+ ret = ptrace(PTRACE_TRACEME); -+ if (variant->domain_parent) { -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EPERM, errno); -+ } else { -+ EXPECT_EQ(0, ret); -+ } -+ -+ /* -+ * Signals that the PTRACE_ATTACH test is done and the -+ * PTRACE_TRACEME test is ongoing. -+ */ -+ ASSERT_EQ(1, write(pipe_child[1], ".", 1)); -+ -+ if (!variant->domain_parent) { -+ ASSERT_EQ(0, raise(SIGSTOP)); -+ } -+ -+ /* Waits for the parent PTRACE_ATTACH test. */ -+ ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)); -+ _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); -+ return; -+ } -+ -+ ASSERT_EQ(0, close(pipe_child[1])); -+ ASSERT_EQ(0, close(pipe_parent[0])); -+ if (variant->domain_parent) -+ create_domain(_metadata); -+ -+ /* Signals that the parent is in a domain, if any. */ -+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); -+ -+ /* -+ * Waits for the child to test PTRACE_ATTACH on the parent and start -+ * testing PTRACE_TRACEME. -+ */ -+ ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1)); -+ -+ /* Tests child PTRACE_TRACEME. */ -+ if (!variant->domain_parent) { -+ ASSERT_EQ(child, waitpid(child, &status, 0)); -+ ASSERT_EQ(1, WIFSTOPPED(status)); -+ ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0)); -+ } else { -+ /* The child should not be traced by the parent. */ -+ EXPECT_EQ(-1, ptrace(PTRACE_DETACH, child, NULL, 0)); -+ EXPECT_EQ(ESRCH, errno); -+ } -+ -+ /* Tests PTRACE_ATTACH and PTRACE_MODE_READ on the child. */ -+ err_proc_read = test_ptrace_read(child); -+ ret = ptrace(PTRACE_ATTACH, child, NULL, 0); -+ if (variant->domain_parent) { -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EPERM, errno); -+ EXPECT_EQ(EACCES, err_proc_read); -+ } else { -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, err_proc_read); -+ } -+ if (ret == 0) { -+ ASSERT_EQ(child, waitpid(child, &status, 0)); -+ ASSERT_EQ(1, WIFSTOPPED(status)); -+ ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0)); -+ } -+ -+ /* Signals that the parent PTRACE_ATTACH test is done. */ -+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); -+ ASSERT_EQ(child, waitpid(child, &status, 0)); -+ if (WIFSIGNALED(status) || !WIFEXITED(status) || -+ WEXITSTATUS(status) != EXIT_SUCCESS) -+ _metadata->passed = 0; -+} -+ -+TEST_HARNESS_MAIN -diff --git a/tools/testing/selftests/landlock/true.c b/tools/testing/selftests/landlock/true.c -new file mode 100644 -index 0000000000000..3f9ccbf52783a ---- /dev/null -+++ b/tools/testing/selftests/landlock/true.c -@@ -0,0 +1,5 @@ -+// SPDX-License-Identifier: GPL-2.0 -+int main(void) -+{ -+ return 0; -+} --- -2.39.2 - diff --git a/queue-5.10/selftests-landlock-skip-overlayfs-tests-when-not-sup.patch b/queue-5.10/selftests-landlock-skip-overlayfs-tests-when-not-sup.patch deleted file mode 100644 index ffdf1daa347..00000000000 --- a/queue-5.10/selftests-landlock-skip-overlayfs-tests-when-not-sup.patch +++ /dev/null @@ -1,123 +0,0 @@ -From 54bbaa4518388e88b866567125ccc67d87b9a423 Mon Sep 17 00:00:00 2001 -From: Sasha Levin -Date: Fri, 13 Jan 2023 05:32:29 +0000 -Subject: selftests/landlock: Skip overlayfs tests when not supported -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -From: Jeff Xu - -[ Upstream commit 366617a69e60610912836570546f118006ebc7cb ] - -overlayfs may be disabled in the kernel configuration, causing related -tests to fail. Check that overlayfs is supported at runtime, so we can -skip layout2_overlay.* accordingly. - -Signed-off-by: Jeff Xu -Reviewed-by: Guenter Roeck -Cc: stable@vger.kernel.org -Link: https://lore.kernel.org/r/20230113053229.1281774-2-jeffxu@google.com -[mic: Reword comments and constify variables] -Signed-off-by: Mickaël Salaün -Signed-off-by: Sasha Levin ---- - tools/testing/selftests/landlock/fs_test.c | 47 ++++++++++++++++++++++ - 1 file changed, 47 insertions(+) - -diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c -index 10c9a1e4ebd9b..db153452b110a 100644 ---- a/tools/testing/selftests/landlock/fs_test.c -+++ b/tools/testing/selftests/landlock/fs_test.c -@@ -11,6 +11,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -74,6 +75,40 @@ static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3"; - * └── s3d3 - */ - -+static bool fgrep(FILE *const inf, const char *const str) -+{ -+ char line[32]; -+ const int slen = strlen(str); -+ -+ while (!feof(inf)) { -+ if (!fgets(line, sizeof(line), inf)) -+ break; -+ if (strncmp(line, str, slen)) -+ continue; -+ -+ return true; -+ } -+ -+ return false; -+} -+ -+static bool supports_overlayfs(void) -+{ -+ bool res; -+ FILE *const inf = fopen("/proc/filesystems", "r"); -+ -+ /* -+ * Consider that the filesystem is supported if we cannot get the -+ * supported ones. -+ */ -+ if (!inf) -+ return true; -+ -+ res = fgrep(inf, "nodev\toverlay\n"); -+ fclose(inf); -+ return res; -+} -+ - static void mkdir_parents(struct __test_metadata *const _metadata, - const char *const path) - { -@@ -2416,6 +2451,9 @@ FIXTURE(layout2_overlay) { - - FIXTURE_SETUP(layout2_overlay) - { -+ if (!supports_overlayfs()) -+ SKIP(return, "overlayfs is not supported"); -+ - prepare_layout(_metadata); - - create_directory(_metadata, LOWER_BASE); -@@ -2453,6 +2491,9 @@ FIXTURE_SETUP(layout2_overlay) - - FIXTURE_TEARDOWN(layout2_overlay) - { -+ if (!supports_overlayfs()) -+ SKIP(return, "overlayfs is not supported"); -+ - EXPECT_EQ(0, remove_path(lower_do1_fl3)); - EXPECT_EQ(0, remove_path(lower_dl1_fl2)); - EXPECT_EQ(0, remove_path(lower_fl1)); -@@ -2484,6 +2525,9 @@ FIXTURE_TEARDOWN(layout2_overlay) - - TEST_F_FORK(layout2_overlay, no_restriction) - { -+ if (!supports_overlayfs()) -+ SKIP(return, "overlayfs is not supported"); -+ - ASSERT_EQ(0, test_open(lower_fl1, O_RDONLY)); - ASSERT_EQ(0, test_open(lower_dl1, O_RDONLY)); - ASSERT_EQ(0, test_open(lower_dl1_fl2, O_RDONLY)); -@@ -2647,6 +2691,9 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) - size_t i; - const char *path_entry; - -+ if (!supports_overlayfs()) -+ SKIP(return, "overlayfs is not supported"); -+ - /* Sets rules on base directories (i.e. outside overlay scope). */ - ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_base); - ASSERT_LE(0, ruleset_fd); --- -2.39.2 - diff --git a/queue-5.10/selftests-landlock-test-ptrace-as-much-as-possible-w.patch b/queue-5.10/selftests-landlock-test-ptrace-as-much-as-possible-w.patch deleted file mode 100644 index bc08571f063..00000000000 --- a/queue-5.10/selftests-landlock-test-ptrace-as-much-as-possible-w.patch +++ /dev/null @@ -1,220 +0,0 @@ -From 16905b2ec61fefd3ae8a5ed6e13c12ce98bd579e Mon Sep 17 00:00:00 2001 -From: Sasha Levin -Date: Sat, 14 Jan 2023 02:03:06 +0000 -Subject: selftests/landlock: Test ptrace as much as possible with Yama -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -From: Jeff Xu - -[ Upstream commit 8677e555f17f51321d0730b945aeb7d4b95f998f ] - -Update ptrace tests according to all potential Yama security policies. -This is required to make such tests pass even if Yama is enabled. - -Tests are not skipped but they now check both Landlock and Yama boundary -restrictions at run time to keep a maximum test coverage (i.e. positive -and negative testing). - -Signed-off-by: Jeff Xu -Link: https://lore.kernel.org/r/20230114020306.1407195-2-jeffxu@google.com -Cc: stable@vger.kernel.org -[mic: Add curly braces around EXPECT_EQ() to make it build, and improve -commit message] -Co-developed-by: Mickaël Salaün -Signed-off-by: Mickaël Salaün -Signed-off-by: Sasha Levin ---- - .../testing/selftests/landlock/ptrace_test.c | 113 +++++++++++++++--- - 1 file changed, 96 insertions(+), 17 deletions(-) - -diff --git a/tools/testing/selftests/landlock/ptrace_test.c b/tools/testing/selftests/landlock/ptrace_test.c -index 090adadfe2dc3..14745cceb059a 100644 ---- a/tools/testing/selftests/landlock/ptrace_test.c -+++ b/tools/testing/selftests/landlock/ptrace_test.c -@@ -19,6 +19,12 @@ - - #include "common.h" - -+/* Copied from security/yama/yama_lsm.c */ -+#define YAMA_SCOPE_DISABLED 0 -+#define YAMA_SCOPE_RELATIONAL 1 -+#define YAMA_SCOPE_CAPABILITY 2 -+#define YAMA_SCOPE_NO_ATTACH 3 -+ - static void create_domain(struct __test_metadata *const _metadata) - { - int ruleset_fd; -@@ -59,6 +65,25 @@ static int test_ptrace_read(const pid_t pid) - return 0; - } - -+static int get_yama_ptrace_scope(void) -+{ -+ int ret; -+ char buf[2] = {}; -+ const int fd = open("/proc/sys/kernel/yama/ptrace_scope", O_RDONLY); -+ -+ if (fd < 0) -+ return 0; -+ -+ if (read(fd, buf, 1) < 0) { -+ close(fd); -+ return -1; -+ } -+ -+ ret = atoi(buf); -+ close(fd); -+ return ret; -+} -+ - /* clang-format off */ - FIXTURE(hierarchy) {}; - /* clang-format on */ -@@ -228,8 +253,51 @@ TEST_F(hierarchy, trace) - pid_t child, parent; - int status, err_proc_read; - int pipe_child[2], pipe_parent[2]; -+ int yama_ptrace_scope; - char buf_parent; - long ret; -+ bool can_read_child, can_trace_child, can_read_parent, can_trace_parent; -+ -+ yama_ptrace_scope = get_yama_ptrace_scope(); -+ ASSERT_LE(0, yama_ptrace_scope); -+ -+ if (yama_ptrace_scope > YAMA_SCOPE_DISABLED) -+ TH_LOG("Incomplete tests due to Yama restrictions (scope %d)", -+ yama_ptrace_scope); -+ -+ /* -+ * can_read_child is true if a parent process can read its child -+ * process, which is only the case when the parent process is not -+ * isolated from the child with a dedicated Landlock domain. -+ */ -+ can_read_child = !variant->domain_parent; -+ -+ /* -+ * can_trace_child is true if a parent process can trace its child -+ * process. This depends on two conditions: -+ * - The parent process is not isolated from the child with a dedicated -+ * Landlock domain. -+ * - Yama allows tracing children (up to YAMA_SCOPE_RELATIONAL). -+ */ -+ can_trace_child = can_read_child && -+ yama_ptrace_scope <= YAMA_SCOPE_RELATIONAL; -+ -+ /* -+ * can_read_parent is true if a child process can read its parent -+ * process, which is only the case when the child process is not -+ * isolated from the parent with a dedicated Landlock domain. -+ */ -+ can_read_parent = !variant->domain_child; -+ -+ /* -+ * can_trace_parent is true if a child process can trace its parent -+ * process. This depends on two conditions: -+ * - The child process is not isolated from the parent with a dedicated -+ * Landlock domain. -+ * - Yama is disabled (YAMA_SCOPE_DISABLED). -+ */ -+ can_trace_parent = can_read_parent && -+ yama_ptrace_scope <= YAMA_SCOPE_DISABLED; - - /* - * Removes all effective and permitted capabilities to not interfere -@@ -260,16 +328,21 @@ TEST_F(hierarchy, trace) - /* Waits for the parent to be in a domain, if any. */ - ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)); - -- /* Tests PTRACE_ATTACH and PTRACE_MODE_READ on the parent. */ -+ /* Tests PTRACE_MODE_READ on the parent. */ - err_proc_read = test_ptrace_read(parent); -+ if (can_read_parent) { -+ EXPECT_EQ(0, err_proc_read); -+ } else { -+ EXPECT_EQ(EACCES, err_proc_read); -+ } -+ -+ /* Tests PTRACE_ATTACH on the parent. */ - ret = ptrace(PTRACE_ATTACH, parent, NULL, 0); -- if (variant->domain_child) { -+ if (can_trace_parent) { -+ EXPECT_EQ(0, ret); -+ } else { - EXPECT_EQ(-1, ret); - EXPECT_EQ(EPERM, errno); -- EXPECT_EQ(EACCES, err_proc_read); -- } else { -- EXPECT_EQ(0, ret); -- EXPECT_EQ(0, err_proc_read); - } - if (ret == 0) { - ASSERT_EQ(parent, waitpid(parent, &status, 0)); -@@ -279,11 +352,11 @@ TEST_F(hierarchy, trace) - - /* Tests child PTRACE_TRACEME. */ - ret = ptrace(PTRACE_TRACEME); -- if (variant->domain_parent) { -+ if (can_trace_child) { -+ EXPECT_EQ(0, ret); -+ } else { - EXPECT_EQ(-1, ret); - EXPECT_EQ(EPERM, errno); -- } else { -- EXPECT_EQ(0, ret); - } - - /* -@@ -292,7 +365,7 @@ TEST_F(hierarchy, trace) - */ - ASSERT_EQ(1, write(pipe_child[1], ".", 1)); - -- if (!variant->domain_parent) { -+ if (can_trace_child) { - ASSERT_EQ(0, raise(SIGSTOP)); - } - -@@ -317,7 +390,7 @@ TEST_F(hierarchy, trace) - ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1)); - - /* Tests child PTRACE_TRACEME. */ -- if (!variant->domain_parent) { -+ if (can_trace_child) { - ASSERT_EQ(child, waitpid(child, &status, 0)); - ASSERT_EQ(1, WIFSTOPPED(status)); - ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0)); -@@ -327,17 +400,23 @@ TEST_F(hierarchy, trace) - EXPECT_EQ(ESRCH, errno); - } - -- /* Tests PTRACE_ATTACH and PTRACE_MODE_READ on the child. */ -+ /* Tests PTRACE_MODE_READ on the child. */ - err_proc_read = test_ptrace_read(child); -+ if (can_read_child) { -+ EXPECT_EQ(0, err_proc_read); -+ } else { -+ EXPECT_EQ(EACCES, err_proc_read); -+ } -+ -+ /* Tests PTRACE_ATTACH on the child. */ - ret = ptrace(PTRACE_ATTACH, child, NULL, 0); -- if (variant->domain_parent) { -+ if (can_trace_child) { -+ EXPECT_EQ(0, ret); -+ } else { - EXPECT_EQ(-1, ret); - EXPECT_EQ(EPERM, errno); -- EXPECT_EQ(EACCES, err_proc_read); -- } else { -- EXPECT_EQ(0, ret); -- EXPECT_EQ(0, err_proc_read); - } -+ - if (ret == 0) { - ASSERT_EQ(child, waitpid(child, &status, 0)); - ASSERT_EQ(1, WIFSTOPPED(status)); --- -2.39.2 - diff --git a/queue-5.10/series b/queue-5.10/series index 92235b40fff..6f8b8d919f9 100644 --- a/queue-5.10/series +++ b/queue-5.10/series @@ -10,12 +10,7 @@ ext4-move-where-set-the-may_inline_data-flag-is-set.patch ext4-fix-warning-in-ext4_update_inline_data.patch ext4-zero-i_disksize-when-initializing-the-bootloader-inode.patch nfc-change-order-inside-nfc_se_io-error-path.patch -landlock-add-object-management.patch -selftests-landlock-add-user-space-tests.patch -selftests-landlock-skip-overlayfs-tests-when-not-sup.patch udf-fix-off-by-one-error-when-discarding-preallocati.patch -selftests-landlock-add-clang-format-exceptions.patch -selftests-landlock-test-ptrace-as-much-as-possible-w.patch irq-fix-typos-in-comments.patch irqdomain-look-for-existing-mapping-only-once.patch irqdomain-refactor-__irq_domain_alloc_irqs.patch -- 2.47.3