]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
drop landlock patches from 5.10
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 13 Mar 2023 12:39:33 +0000 (13:39 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 13 Mar 2023 12:39:33 +0000 (13:39 +0100)
queue-5.10/landlock-add-object-management.patch [deleted file]
queue-5.10/selftests-landlock-add-clang-format-exceptions.patch [deleted file]
queue-5.10/selftests-landlock-add-user-space-tests.patch [deleted file]
queue-5.10/selftests-landlock-skip-overlayfs-tests-when-not-sup.patch [deleted file]
queue-5.10/selftests-landlock-test-ptrace-as-much-as-possible-w.patch [deleted file]
queue-5.10/series

diff --git a/queue-5.10/landlock-add-object-management.patch b/queue-5.10/landlock-add-object-management.patch
deleted file mode 100644 (file)
index 3fa753b..0000000
+++ /dev/null
@@ -1,318 +0,0 @@
-From ab07529d96375f4117929020ea88db6ed07d8abf Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-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 <mic@linux.microsoft.com>
-
-[ 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 <jmorris@namei.org>
-Signed-off-by: Mickaël Salaün <mic@linux.microsoft.com>
-Reviewed-by: Jann Horn <jannh@google.com>
-Acked-by: Serge Hallyn <serge@hallyn.com>
-Reviewed-by: Kees Cook <keescook@chromium.org>
-Link: https://lore.kernel.org/r/20210422154123.13086-2-mic@digikod.net
-Signed-off-by: James Morris <jamorris@linux.microsoft.com>
-Stable-dep-of: 366617a69e60 ("selftests/landlock: Skip overlayfs tests when not supported")
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- 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 <mic@digikod.net>
-+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 <hauke@hauke-m.de>
- 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 <mic@digikod.net>
-+ * Copyright © 2018-2020 ANSSI
-+ */
-+
-+#include <linux/bug.h>
-+#include <linux/compiler_types.h>
-+#include <linux/err.h>
-+#include <linux/kernel.h>
-+#include <linux/rcupdate.h>
-+#include <linux/refcount.h>
-+#include <linux/slab.h>
-+#include <linux/spinlock.h>
-+
-+#include "object.h"
-+
-+struct landlock_object *landlock_create_object(
-+              const struct landlock_object_underops *const underops,
-+              void *const underobj)
-+{
-+      struct landlock_object *new_object;
-+
-+      if (WARN_ON_ONCE(!underops || !underobj))
-+              return ERR_PTR(-ENOENT);
-+      new_object = kzalloc(sizeof(*new_object), GFP_KERNEL_ACCOUNT);
-+      if (!new_object)
-+              return ERR_PTR(-ENOMEM);
-+      refcount_set(&new_object->usage, 1);
-+      spin_lock_init(&new_object->lock);
-+      new_object->underops = underops;
-+      new_object->underobj = underobj;
-+      return new_object;
-+}
-+
-+/*
-+ * The caller must own the object (i.e. thanks to object->usage) to safely put
-+ * it.
-+ */
-+void landlock_put_object(struct landlock_object *const object)
-+{
-+      /*
-+       * The call to @object->underops->release(object) might sleep, e.g.
-+       * because of iput().
-+       */
-+      might_sleep();
-+      if (!object)
-+              return;
-+
-+      /*
-+       * If the @object's refcount cannot drop to zero, we can just decrement
-+       * the refcount without holding a lock. Otherwise, the decrement must
-+       * happen under @object->lock for synchronization with things like
-+       * get_inode_object().
-+       */
-+      if (refcount_dec_and_lock(&object->usage, &object->lock)) {
-+              __acquire(&object->lock);
-+              /*
-+               * With @object->lock initially held, remove the reference from
-+               * @object->underobj to @object (if it still exists).
-+               */
-+              object->underops->release(object);
-+              kfree_rcu(object, rcu_free);
-+      }
-+}
-diff --git a/security/landlock/object.h b/security/landlock/object.h
-new file mode 100644
-index 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 <mic@digikod.net>
-+ * Copyright © 2018-2020 ANSSI
-+ */
-+
-+#ifndef _SECURITY_LANDLOCK_OBJECT_H
-+#define _SECURITY_LANDLOCK_OBJECT_H
-+
-+#include <linux/compiler_types.h>
-+#include <linux/refcount.h>
-+#include <linux/spinlock.h>
-+
-+struct landlock_object;
-+
-+/**
-+ * struct landlock_object_underops - Operations on an underlying object
-+ */
-+struct landlock_object_underops {
-+      /**
-+       * @release: Releases the underlying object (e.g. iput() for an inode).
-+       */
-+      void (*release)(struct landlock_object *const object)
-+              __releases(object->lock);
-+};
-+
-+/**
-+ * struct landlock_object - Security blob tied to a kernel object
-+ *
-+ * The goal of this structure is to enable to tie a set of ephemeral access
-+ * rights (pertaining to different domains) to a kernel object (e.g an inode)
-+ * in a safe way.  This implies to handle concurrent use and modification.
-+ *
-+ * The lifetime of a &struct landlock_object depends on the rules referring to
-+ * it.
-+ */
-+struct landlock_object {
-+      /**
-+       * @usage: This counter is used to tie an object to the rules matching
-+       * it or to keep it alive while adding a new rule.  If this counter
-+       * reaches zero, this struct must not be modified, but this counter can
-+       * still be read from within an RCU read-side critical section.  When
-+       * adding a new rule to an object with a usage counter of zero, we must
-+       * wait until the pointer to this object is set to NULL (or recycled).
-+       */
-+      refcount_t usage;
-+      /**
-+       * @lock: Protects against concurrent modifications.  This lock must be
-+       * held from the time @usage drops to zero until any weak references
-+       * from @underobj to this object have been cleaned up.
-+       *
-+       * Lock ordering: inode->i_lock nests inside this.
-+       */
-+      spinlock_t lock;
-+      /**
-+       * @underobj: Used when cleaning up an object and to mark an object as
-+       * tied to its underlying kernel structure.  This pointer is protected
-+       * by @lock.  Cf. landlock_release_inodes() and release_inode().
-+       */
-+      void *underobj;
-+      union {
-+              /**
-+               * @rcu_free: Enables lockless use of @usage, @lock and
-+               * @underobj from within an RCU read-side critical section.
-+               * @rcu_free and @underops are only used by
-+               * landlock_put_object().
-+               */
-+              struct rcu_head rcu_free;
-+              /**
-+               * @underops: Enables landlock_put_object() to release the
-+               * underlying object (e.g. inode).
-+               */
-+              const struct landlock_object_underops *underops;
-+      };
-+};
-+
-+struct landlock_object *landlock_create_object(
-+              const struct landlock_object_underops *const underops,
-+              void *const underobj);
-+
-+void landlock_put_object(struct landlock_object *const object);
-+
-+static inline void landlock_get_object(struct landlock_object *const object)
-+{
-+      if (object)
-+              refcount_inc(&object->usage);
-+}
-+
-+#endif /* _SECURITY_LANDLOCK_OBJECT_H */
--- 
-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 (file)
index 5ee7009..0000000
+++ /dev/null
@@ -1,226 +0,0 @@
-From 76f7cb6aeb2007c0d4febda804772cf6ae508604 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-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 <mic@digikod.net>
-
-[ 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 <mic@digikod.net>
-Stable-dep-of: 8677e555f17f ("selftests/landlock: Test ptrace as much as possible with Yama")
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- 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 (file)
index 2332bfa..0000000
+++ /dev/null
@@ -1,3694 +0,0 @@
-From 9f43c1d77d1e7cac1c93a6f95a6d91ebc4089a73 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-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 <mic@linux.microsoft.com>
-
-[ 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 <jmorris@namei.org>
-Cc: Jann Horn <jannh@google.com>
-Cc: Serge E. Hallyn <serge@hallyn.com>
-Cc: Shuah Khan <shuah@kernel.org>
-Signed-off-by: Mickaël Salaün <mic@linux.microsoft.com>
-Reviewed-by: Vincent Dagonneau <vincent.dagonneau@ssi.gouv.fr>
-Reviewed-by: Kees Cook <keescook@chromium.org>
-Link: https://lore.kernel.org/r/20210422154123.13086-11-mic@digikod.net
-Signed-off-by: James Morris <jamorris@linux.microsoft.com>
-Stable-dep-of: 366617a69e60 ("selftests/landlock: Skip overlayfs tests when not supported")
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- 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 <mic@digikod.net>
-+ * Copyright © 2019-2020 ANSSI
-+ */
-+
-+#define _GNU_SOURCE
-+#include <errno.h>
-+#include <fcntl.h>
-+#include <linux/landlock.h>
-+#include <string.h>
-+#include <sys/prctl.h>
-+#include <sys/socket.h>
-+#include <sys/types.h>
-+
-+#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 <mic@digikod.net>
-+ * Copyright © 2019-2020 ANSSI
-+ * Copyright © 2021 Microsoft Corporation
-+ */
-+
-+#include <errno.h>
-+#include <linux/landlock.h>
-+#include <sys/capability.h>
-+#include <sys/syscall.h>
-+#include <sys/types.h>
-+#include <sys/wait.h>
-+#include <unistd.h>
-+
-+#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 <mic@digikod.net>
-+ * Copyright © 2020 ANSSI
-+ * Copyright © 2020-2021 Microsoft Corporation
-+ */
-+
-+#define _GNU_SOURCE
-+#include <fcntl.h>
-+#include <linux/landlock.h>
-+#include <sched.h>
-+#include <string.h>
-+#include <sys/capability.h>
-+#include <sys/mount.h>
-+#include <sys/prctl.h>
-+#include <sys/sendfile.h>
-+#include <sys/stat.h>
-+#include <sys/sysmacros.h>
-+#include <unistd.h>
-+
-+#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 <mic@digikod.net>
-+ * Copyright © 2019-2020 ANSSI
-+ */
-+
-+#define _GNU_SOURCE
-+#include <errno.h>
-+#include <fcntl.h>
-+#include <linux/landlock.h>
-+#include <signal.h>
-+#include <sys/prctl.h>
-+#include <sys/ptrace.h>
-+#include <sys/types.h>
-+#include <sys/wait.h>
-+#include <unistd.h>
-+
-+#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 (file)
index ffdf1da..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-From 54bbaa4518388e88b866567125ccc67d87b9a423 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-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 <jeffxu@google.com>
-
-[ 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 <jeffxu@google.com>
-Reviewed-by: Guenter Roeck <groeck@chromium.org>
-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 <mic@digikod.net>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- 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 <fcntl.h>
- #include <linux/landlock.h>
- #include <sched.h>
-+#include <stdio.h>
- #include <string.h>
- #include <sys/capability.h>
- #include <sys/mount.h>
-@@ -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 (file)
index bc08571..0000000
+++ /dev/null
@@ -1,220 +0,0 @@
-From 16905b2ec61fefd3ae8a5ed6e13c12ce98bd579e Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-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 <jeffxu@google.com>
-
-[ 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 <jeffxu@google.com>
-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 <mic@digikod.net>
-Signed-off-by: Mickaël Salaün <mic@digikod.net>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- .../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
-
index 92235b40fffe175a51c8d01df186be300c9c4dba..6f8b8d919f94bc15b9f2e5842416dcb070f3d8c0 100644 (file)
@@ -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