lkml.org 
[lkml]   [2020]   [Mar]   [26]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH v15 08/10] selftests/landlock: Add initial tests
    Date
    Test landlock syscall, ptrace hooks semantic and filesystem
    access-control.

    Signed-off-by: Mickaël Salaün <mic@digikod.net>
    Reviewed-by: Vincent Dagonneau <vincent.dagonneau@ssi.gouv.fr>
    Cc: Andy Lutomirski <luto@amacapital.net>
    Cc: James Morris <jmorris@namei.org>
    Cc: Kees Cook <keescook@chromium.org>
    Cc: Serge E. Hallyn <serge@hallyn.com>
    Cc: Shuah Khan <shuah@kernel.org>
    ---

    Changes since v14:
    * Add new tests:
    - Compatibility: empty_attr_{ruleset,path_beneath,enforce} to check
    minimal attr size.
    - Access types: link_to, rename_from, rename_to, rmdir, unlink,
    make_char, make_block, make_reg, make_sock, make_fifo, make_sym,
    make_dir, chroot, execute.
    - Test privilege escalation prevention by enforcing a nested rule, on
    a parent directory, with less restrictions than one on a child
    directory.
    - Test for empty and more than 32-bits allowed_access
    * Merge the two test mount hierarchies.
    * Complete relative path tests by combining chdir and chroot.
    * Adjust tests:
    - Remove the layout1/extend_ruleset_with_denied_path test.
    - Extend layout1/whitelist test with checks on file.
    - Add and use create_dir_and_file().
    * Only use read/write checks but not stat(2) for tests.
    * Rename test.h to common.h and improve it.
    * Rename path name to make them more consistent, easy to understand and
    make them in a common directory.
    * Make create_ruleset() more generic.
    * Constify variables.
    * Re-add static global variables.
    * Remove useless openat(2).
    * Fix and complete kernel config.
    * Set umask and clean up file modes.
    * Clean up open flags.
    * Improve Makefile.
    * Fix spelling.
    * Improve comments and error messages.

    Changes since v13:
    * Add back the filesystem tests (from v10) and extend them.
    * Add tests for the new syscall.

    Previous version:
    https://lore.kernel.org/lkml/20191104172146.30797-7-mic@digikod.net/
    ---
    tools/testing/selftests/Makefile | 1 +
    tools/testing/selftests/landlock/.gitignore | 4 +
    tools/testing/selftests/landlock/Makefile | 26 +
    tools/testing/selftests/landlock/common.h | 42 +
    tools/testing/selftests/landlock/config | 5 +
    tools/testing/selftests/landlock/test_base.c | 113 ++
    tools/testing/selftests/landlock/test_fs.c | 1249 +++++++++++++++++
    .../testing/selftests/landlock/test_ptrace.c | 294 ++++
    tools/testing/selftests/landlock/true.c | 5 +
    9 files changed, 1739 insertions(+)
    create mode 100644 tools/testing/selftests/landlock/.gitignore
    create mode 100644 tools/testing/selftests/landlock/Makefile
    create mode 100644 tools/testing/selftests/landlock/common.h
    create mode 100644 tools/testing/selftests/landlock/config
    create mode 100644 tools/testing/selftests/landlock/test_base.c
    create mode 100644 tools/testing/selftests/landlock/test_fs.c
    create mode 100644 tools/testing/selftests/landlock/test_ptrace.c
    create mode 100644 tools/testing/selftests/landlock/true.c

    diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
    index 6ec503912bea..5183f269c244 100644
    --- a/tools/testing/selftests/Makefile
    +++ b/tools/testing/selftests/Makefile
    @@ -24,6 +24,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 000000000000..fd081d6ef7c0
    --- /dev/null
    +++ b/tools/testing/selftests/landlock/.gitignore
    @@ -0,0 +1,4 @@
    +/test_base
    +/test_fs
    +/test_ptrace
    +/true
    diff --git a/tools/testing/selftests/landlock/Makefile b/tools/testing/selftests/landlock/Makefile
    new file mode 100644
    index 000000000000..96ec5863c156
    --- /dev/null
    +++ b/tools/testing/selftests/landlock/Makefile
    @@ -0,0 +1,26 @@
    +# SPDX-License-Identifier: GPL-2.0
    +
    +test_src := $(wildcard test_*.c)
    +
    +TEST_GEN_PROGS := $(test_src:.c=)
    +
    +TEST_GEN_PROGS_EXTENDED := true
    +
    +KSFT_KHDR_INSTALL := 1
    +include ../lib.mk
    +
    +# Cf. tools/testing/selftests/arm64/Makefile
    +ifeq ($(KBUILD_OUTPUT),)
    +khdr_dir = $(top_srcdir)/usr/include
    +else
    +khdr_dir = $(KBUILD_OUTPUT)/kselftest/usr/include
    +endif
    +
    +CFLAGS += -Wall -O2 -I$(khdr_dir)
    +
    +$(khdr_dir)/linux/landlock.h: khdr
    +
    +$(TEST_GEN_PROGS): $(khdr_dir)/linux/landlock.h ../kselftest_harness.h common.h
    +
    +$(OUTPUT)/true: true.c
    + $(CC) -Os -static -o $@ $<
    diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
    new file mode 100644
    index 000000000000..3e0d56a4363a
    --- /dev/null
    +++ b/tools/testing/selftests/landlock/common.h
    @@ -0,0 +1,42 @@
    +/* SPDX-License-Identifier: GPL-2.0 */
    +/*
    + * Landlock test helpers
    + *
    + * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
    + * Copyright © 2019-2020 ANSSI
    + */
    +
    +#include <errno.h>
    +#include <linux/landlock.h>
    +#include <sys/syscall.h>
    +
    +#include "../kselftest_harness.h"
    +
    +#ifndef landlock
    +static inline int landlock(const unsigned int command,
    + const unsigned int options,
    + const size_t attr_size, void *const attr_ptr)
    +{
    + errno = 0;
    + return syscall(__NR_landlock, command, options, attr_size, attr_ptr, 0,
    + NULL);
    +}
    +#endif
    +
    +FIXTURE(ruleset_rw) {
    + struct landlock_attr_ruleset attr_ruleset;
    + int ruleset_fd;
    +};
    +
    +FIXTURE_SETUP(ruleset_rw) {
    + self->attr_ruleset.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE |
    + LANDLOCK_ACCESS_FS_WRITE_FILE;
    + self->ruleset_fd = landlock(LANDLOCK_CMD_CREATE_RULESET,
    + LANDLOCK_OPT_CREATE_RULESET,
    + sizeof(self->attr_ruleset), &self->attr_ruleset);
    + ASSERT_LE(0, self->ruleset_fd);
    +}
    +
    +FIXTURE_TEARDOWN(ruleset_rw) {
    + ASSERT_EQ(0, close(self->ruleset_fd));
    +}
    diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
    new file mode 100644
    index 000000000000..042298105821
    --- /dev/null
    +++ b/tools/testing/selftests/landlock/config
    @@ -0,0 +1,5 @@
    +CONFIG_SECURITY_LANDLOCK=y
    +CONFIG_SECURITY_PATH=y
    +CONFIG_SECURITY=y
    +CONFIG_SHMEM=y
    +CONFIG_TMPFS=y
    diff --git a/tools/testing/selftests/landlock/test_base.c b/tools/testing/selftests/landlock/test_base.c
    new file mode 100644
    index 000000000000..effc419593ce
    --- /dev/null
    +++ b/tools/testing/selftests/landlock/test_base.c
    @@ -0,0 +1,113 @@
    +// SPDX-License-Identifier: GPL-2.0
    +/*
    + * Landlock tests - common resources
    + *
    + * 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 <sys/prctl.h>
    +
    +#include "common.h"
    +
    +#define FDINFO_TEMPLATE "/proc/self/fdinfo/%d"
    +#define FDINFO_SIZE 128
    +
    +#ifndef O_PATH
    +#define O_PATH 010000000
    +#endif
    +
    +TEST_F(ruleset_rw, fdinfo)
    +{
    + int fdinfo_fd, fdinfo_path_size, fdinfo_buf_size;
    + char fdinfo_path[sizeof(FDINFO_TEMPLATE) + 2];
    + char fdinfo_buf[FDINFO_SIZE];
    +
    + fdinfo_path_size = snprintf(fdinfo_path, sizeof(fdinfo_path),
    + FDINFO_TEMPLATE, self->ruleset_fd);
    + ASSERT_LE(fdinfo_path_size, sizeof(fdinfo_path));
    +
    + fdinfo_fd = open(fdinfo_path, O_RDONLY | O_CLOEXEC);
    + ASSERT_GE(fdinfo_fd, 0);
    +
    + fdinfo_buf_size = read(fdinfo_fd, fdinfo_buf, sizeof(fdinfo_buf));
    + ASSERT_LE(fdinfo_buf_size, sizeof(fdinfo_buf) - 1);
    +
    + /*
    + * fdinfo_buf: pos: 0
    + * flags: 02000002
    + * mnt_id: 13
    + * handled_access_fs: 804000
    + */
    + EXPECT_EQ(0, close(fdinfo_fd));
    +}
    +
    +TEST(features)
    +{
    + struct landlock_attr_features attr_features = {
    + .options_get_features = ~0ULL,
    + .options_create_ruleset = ~0ULL,
    + .options_add_rule = ~0ULL,
    + .options_enforce_ruleset = ~0ULL,
    + .access_fs = ~0ULL,
    + .size_attr_ruleset = ~0ULL,
    + .size_attr_path_beneath = ~0ULL,
    + .size_attr_enforce = ~0ULL,
    + };
    +
    + ASSERT_EQ(0, landlock(LANDLOCK_CMD_GET_FEATURES,
    + LANDLOCK_OPT_GET_FEATURES,
    + sizeof(attr_features), &attr_features));
    + ASSERT_EQ(((LANDLOCK_OPT_GET_FEATURES << 1) - 1),
    + attr_features.options_get_features);
    + ASSERT_EQ(((LANDLOCK_OPT_CREATE_RULESET << 1) - 1),
    + attr_features.options_create_ruleset);
    + ASSERT_EQ(((LANDLOCK_OPT_ADD_RULE_PATH_BENEATH << 1) - 1),
    + attr_features.options_add_rule);
    + ASSERT_EQ(((LANDLOCK_OPT_ENFORCE_RULESET << 1) - 1),
    + attr_features.options_enforce_ruleset);
    + ASSERT_EQ(((LANDLOCK_ACCESS_FS_CHROOT << 1) - 1),
    + attr_features.access_fs);
    + ASSERT_EQ(sizeof(struct landlock_attr_ruleset),
    + attr_features.size_attr_ruleset);
    + ASSERT_EQ(sizeof(struct landlock_attr_path_beneath),
    + attr_features.size_attr_path_beneath);
    + ASSERT_EQ(sizeof(struct landlock_attr_enforce),
    + attr_features.size_attr_enforce);
    +}
    +
    +TEST(empty_attr_ruleset) {
    + int err;
    +
    + /* Similar to struct landlock_attr_create.handled_access_fs = 0 */
    + err = landlock(LANDLOCK_CMD_CREATE_RULESET,
    + LANDLOCK_OPT_CREATE_RULESET, 0, NULL);
    + ASSERT_EQ(errno, EINVAL);
    + ASSERT_EQ(err, -1);
    +}
    +
    +TEST(empty_attr_path_beneath) {
    + int err;
    +
    + /* Similar to struct landlock_attr_path_beneath.*_fd = 0 */
    + err = landlock(LANDLOCK_CMD_ADD_RULE,
    + LANDLOCK_OPT_ADD_RULE_PATH_BENEATH, 0, NULL);
    + ASSERT_EQ(errno, EINVAL);
    + ASSERT_EQ(err, -1);
    +}
    +
    +TEST(empty_attr_enforce) {
    + int err;
    +
    + /* Similar to struct landlock_attr_enforce.ruleset_fd = 0 */
    + err = landlock(LANDLOCK_CMD_ENFORCE_RULESET,
    + LANDLOCK_OPT_ENFORCE_RULESET, 0, NULL);
    + ASSERT_EQ(errno, EINVAL);
    + ASSERT_EQ(err, -1);
    +}
    +
    +TEST_HARNESS_MAIN
    diff --git a/tools/testing/selftests/landlock/test_fs.c b/tools/testing/selftests/landlock/test_fs.c
    new file mode 100644
    index 000000000000..747f2cf7a59f
    --- /dev/null
    +++ b/tools/testing/selftests/landlock/test_fs.c
    @@ -0,0 +1,1249 @@
    +// SPDX-License-Identifier: GPL-2.0
    +/*
    + * Landlock tests - filesystem
    + *
    + * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
    + * Copyright © 2020 ANSSI
    + */
    +
    +#define _GNU_SOURCE
    +#include <fcntl.h>
    +#include <linux/landlock.h>
    +#include <sched.h>
    +#include <string.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 FILE_NAME "file"
    +#define BINARY_PATH "./true"
    +
    +/* Paths (sibling number and depth) */
    +static const char dir_s1d1[] = TMP_DIR "s1d1";
    +static const char file_s1d1[] = TMP_DIR "s1d1/" FILE_NAME;
    +static const char dir_s1d2[] = TMP_DIR "s1d1/s1d2";
    +static const char file_s1d2[] = TMP_DIR "s1d1/s1d2/" FILE_NAME;
    +static const char dir_s1d3[] = TMP_DIR "s1d1/s1d2/s1d3";
    +static const char file_s1d3[] = TMP_DIR "s1d1/s1d2/s1d3/" FILE_NAME;
    +
    +static const char dir_s2d1[] = TMP_DIR "s2d1";
    +static const char file_s2d1[] = TMP_DIR "s2d1/" FILE_NAME;
    +static const char dir_s2d2[] = TMP_DIR "s2d1/s2d2";
    +static const char file_s2d2[] = TMP_DIR "s2d1/s2d2/" FILE_NAME;
    +static const char dir_s2d3[] = TMP_DIR "s2d1/s2d2/s2d3";
    +static const char file_s2d3[] = TMP_DIR "s2d1/s2d2/s2d3/" FILE_NAME;
    +
    +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";
    +
    +static void create_dir_and_file(struct __test_metadata *const _metadata,
    + const char *const dir_path)
    +{
    + int file_fd;
    + const size_t file_name_len = sizeof(FILE_NAME);
    + char *const file_path = alloca(strlen(dir_path) + file_name_len + 2);
    +
    + strcpy(file_path, dir_path);
    + strcat(file_path, "/");
    + strcat(file_path, FILE_NAME);
    +
    + ASSERT_EQ(0, mkdir(dir_path, 0700)) {
    + TH_LOG("Failed to create directory \"%s\": %s\n", dir_path,
    + strerror(errno));
    + }
    + file_fd = open(file_path, O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC,
    + 0700);
    + ASSERT_LE(0, file_fd);
    + ASSERT_EQ(0, close(file_fd));
    +}
    +
    +static void delete_dir_and_file(const char *const dir_path)
    +{
    + char *const file_path = alloca(strlen(dir_path) +
    + sizeof(FILE_NAME) + 2);
    +
    + strcpy(file_path, dir_path);
    + strcat(file_path, "/");
    + strcat(file_path, FILE_NAME);
    +
    + unlink(file_path);
    + /* file_path may be a directory, cf. layout1/make_directory. */
    + rmdir(file_path);
    + rmdir(dir_path);
    +}
    +
    +static void cleanup_layout1(void)
    +{
    + delete_dir_and_file(dir_s1d3);
    + delete_dir_and_file(dir_s1d2);
    + delete_dir_and_file(dir_s1d1);
    +
    + delete_dir_and_file(dir_s2d3);
    + delete_dir_and_file(dir_s2d2);
    + delete_dir_and_file(dir_s2d1);
    +
    + delete_dir_and_file(dir_s3d3);
    + umount(dir_s3d2);
    + delete_dir_and_file(dir_s3d2);
    + delete_dir_and_file(dir_s3d1);
    +
    + delete_dir_and_file(TMP_DIR);
    +}
    +
    +FIXTURE(layout1) {
    +};
    +
    +FIXTURE_SETUP(layout1)
    +{
    + cleanup_layout1();
    +
    + /* Do not pollute the rest of the system. */
    + ASSERT_NE(-1, unshare(CLONE_NEWNS));
    + umask(0077);
    + create_dir_and_file(_metadata, TMP_DIR);
    +
    + create_dir_and_file(_metadata, dir_s1d1);
    + create_dir_and_file(_metadata, dir_s1d2);
    + create_dir_and_file(_metadata, dir_s1d3);
    +
    + create_dir_and_file(_metadata, dir_s2d1);
    + create_dir_and_file(_metadata, dir_s2d2);
    + create_dir_and_file(_metadata, dir_s2d3);
    +
    + create_dir_and_file(_metadata, dir_s3d1);
    + create_dir_and_file(_metadata, dir_s3d2);
    + ASSERT_EQ(0, mount("tmp", dir_s3d2, "tmpfs", 0, "size=4m,mode=700"));
    + create_dir_and_file(_metadata, dir_s3d3);
    +}
    +
    +FIXTURE_TEARDOWN(layout1)
    +{
    + /*
    + * cleanup_layout1() would be denied here, use TEST(cleanup) instead.
    + */
    +}
    +
    +static void test_path_rel(struct __test_metadata *const _metadata,
    + const int dirfd, const char *const path, const int ret)
    +{
    + int fd;
    +
    + /* Works with file and directories. */
    + fd = openat(dirfd, path, O_RDONLY | O_CLOEXEC);
    + if (ret) {
    + ASSERT_EQ(-1, fd) {
    + TH_LOG("Successfully opened \"%s\": %s\n", path,
    + strerror(errno));
    + }
    + ASSERT_EQ(EACCES, errno) {
    + TH_LOG("Wrong error code to open \"%s\": %s\n", path,
    + strerror(errno));
    + }
    + } else {
    + ASSERT_NE(-1, fd) {
    + TH_LOG("Failed to open \"%s\": %s\n", path,
    + strerror(errno));
    + }
    + EXPECT_EQ(0, close(fd));
    + }
    +}
    +
    +static void test_path(struct __test_metadata *const _metadata,
    + const char *const path, const int ret)
    +{
    + return test_path_rel(_metadata, AT_FDCWD, path, ret);
    +}
    +
    +TEST_F(layout1, no_restriction)
    +{
    + test_path(_metadata, dir_s1d1, 0);
    + test_path(_metadata, file_s1d1, 0);
    + test_path(_metadata, dir_s1d2, 0);
    + test_path(_metadata, file_s1d2, 0);
    + test_path(_metadata, dir_s1d3, 0);
    + test_path(_metadata, file_s1d3, 0);
    +
    + test_path(_metadata, dir_s2d1, 0);
    + test_path(_metadata, file_s2d1, 0);
    + test_path(_metadata, dir_s2d2, 0);
    + test_path(_metadata, file_s2d2, 0);
    + test_path(_metadata, dir_s2d3, 0);
    + test_path(_metadata, file_s2d3, 0);
    +
    + test_path(_metadata, dir_s3d1, 0);
    + test_path(_metadata, dir_s3d2, 0);
    + test_path(_metadata, dir_s3d3, 0);
    +}
    +
    +TEST_F(ruleset_rw, inval)
    +{
    + int err;
    + struct landlock_attr_path_beneath path_beneath = {
    + .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE |
    + LANDLOCK_ACCESS_FS_WRITE_FILE,
    + .parent_fd = -1,
    + };
    + struct landlock_attr_enforce attr_enforce;
    +
    + path_beneath.ruleset_fd = self->ruleset_fd;
    + path_beneath.parent_fd = open(dir_s1d2, O_PATH | O_DIRECTORY |
    + O_CLOEXEC);
    + ASSERT_GE(path_beneath.parent_fd, 0);
    + err = landlock(LANDLOCK_CMD_ADD_RULE,
    + LANDLOCK_OPT_ADD_RULE_PATH_BENEATH,
    + sizeof(path_beneath), &path_beneath);
    + ASSERT_EQ(errno, 0);
    + ASSERT_EQ(err, 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_GE(path_beneath.parent_fd, 0);
    + err = landlock(LANDLOCK_CMD_ADD_RULE,
    + LANDLOCK_OPT_ADD_RULE_PATH_BENEATH,
    + sizeof(path_beneath), &path_beneath);
    + ASSERT_EQ(err, -1);
    + ASSERT_EQ(errno, EBADR);
    + errno = 0;
    + ASSERT_EQ(0, close(path_beneath.parent_fd));
    +
    + /* Checks unhandled allowed_access. */
    + path_beneath.parent_fd = open(dir_s1d2, O_PATH | O_DIRECTORY |
    + O_CLOEXEC);
    + ASSERT_GE(path_beneath.parent_fd, 0);
    +
    + /* Test with legitimate values. */
    + path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_EXECUTE;
    + err = landlock(LANDLOCK_CMD_ADD_RULE,
    + LANDLOCK_OPT_ADD_RULE_PATH_BENEATH,
    + sizeof(path_beneath), &path_beneath);
    + path_beneath.allowed_access &= ~LANDLOCK_ACCESS_FS_EXECUTE;
    + ASSERT_EQ(errno, EINVAL);
    + errno = 0;
    + ASSERT_EQ(err, -1);
    +
    + /* Test with unknown (64-bits) value. */
    + path_beneath.allowed_access |= (1ULL << 60);
    + err = landlock(LANDLOCK_CMD_ADD_RULE,
    + LANDLOCK_OPT_ADD_RULE_PATH_BENEATH,
    + sizeof(path_beneath), &path_beneath);
    + path_beneath.allowed_access &= ~(1ULL << 60);
    + ASSERT_EQ(errno, EINVAL);
    + errno = 0;
    + ASSERT_EQ(err, -1);
    +
    + /* Test with no access. */
    + path_beneath.allowed_access = 0;
    + err = landlock(LANDLOCK_CMD_ADD_RULE,
    + LANDLOCK_OPT_ADD_RULE_PATH_BENEATH,
    + sizeof(path_beneath), &path_beneath);
    + path_beneath.allowed_access &= ~(1ULL << 60);
    + ASSERT_EQ(err, 0);
    +
    + ASSERT_EQ(0, close(path_beneath.parent_fd));
    +
    + err = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
    + ASSERT_EQ(errno, 0);
    + ASSERT_EQ(err, 0);
    +
    + attr_enforce.ruleset_fd = self->ruleset_fd;
    + err = landlock(LANDLOCK_CMD_ENFORCE_RULESET,
    + LANDLOCK_OPT_ENFORCE_RULESET, sizeof(attr_enforce),
    + &attr_enforce);
    + ASSERT_EQ(errno, 0);
    + ASSERT_EQ(err, 0);
    +}
    +
    +TEST_F(ruleset_rw, nsfs)
    +{
    + struct landlock_attr_path_beneath path_beneath = {
    + .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE |
    + LANDLOCK_ACCESS_FS_WRITE_FILE,
    + .ruleset_fd = self->ruleset_fd,
    + };
    + int err;
    +
    + path_beneath.parent_fd = open("/proc/self/ns/mnt", O_PATH | O_CLOEXEC);
    + ASSERT_GE(path_beneath.parent_fd, 0);
    + err = landlock(LANDLOCK_CMD_ADD_RULE,
    + LANDLOCK_OPT_ADD_RULE_PATH_BENEATH,
    + sizeof(path_beneath), &path_beneath);
    + ASSERT_EQ(errno, 0);
    + ASSERT_EQ(err, 0);
    + 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)
    +{
    + int err;
    + struct landlock_attr_path_beneath path_beneath = {
    + .ruleset_fd = ruleset_fd,
    + .allowed_access = allowed_access,
    + };
    +
    + path_beneath.parent_fd = open(path, O_PATH | O_CLOEXEC);
    + ASSERT_GE(path_beneath.parent_fd, 0) {
    + TH_LOG("Failed to open directory \"%s\": %s\n", path,
    + strerror(errno));
    + }
    + err = landlock(LANDLOCK_CMD_ADD_RULE,
    + LANDLOCK_OPT_ADD_RULE_PATH_BENEATH,
    + sizeof(path_beneath), &path_beneath);
    + ASSERT_EQ(err, 0) {
    + TH_LOG("Failed to update the ruleset with \"%s\": %s\n", path,
    + strerror(errno));
    + }
    + ASSERT_EQ(errno, 0);
    + 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_attr_features attr_features;
    + struct landlock_attr_ruleset attr_ruleset = {
    + .handled_access_fs = handled_access_fs,
    + };
    +
    + ASSERT_NE(NULL, rules) {
    + TH_LOG("No rule list\n");
    + }
    + ASSERT_NE(NULL, rules[0].path) {
    + TH_LOG("Empty rule list\n");
    + }
    +
    + ASSERT_EQ(0, landlock(LANDLOCK_CMD_GET_FEATURES,
    + LANDLOCK_OPT_GET_FEATURES,
    + sizeof(attr_features), &attr_features));
    + /* Only for test, use a binary AND for real application instead. */
    + ASSERT_EQ(attr_ruleset.handled_access_fs,
    + attr_ruleset.handled_access_fs &
    + attr_features.access_fs);
    + ruleset_fd = landlock(LANDLOCK_CMD_CREATE_RULESET,
    + LANDLOCK_OPT_CREATE_RULESET, sizeof(attr_ruleset),
    + &attr_ruleset);
    + ASSERT_GE(ruleset_fd, 0) {
    + TH_LOG("Failed to create a ruleset: %s\n", strerror(errno));
    + }
    +
    + for (i = 0; rules[i].path; i++) {
    + ASSERT_EQ(rules[i].access, rules[i].access &
    + attr_features.access_fs);
    + 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)
    +{
    + struct landlock_attr_enforce attr_enforce = {
    + .ruleset_fd = ruleset_fd,
    + };
    + int err;
    +
    + err = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
    + ASSERT_EQ(errno, 0);
    + ASSERT_EQ(err, 0);
    +
    + err = landlock(LANDLOCK_CMD_ENFORCE_RULESET,
    + LANDLOCK_OPT_ENFORCE_RULESET, sizeof(attr_enforce),
    + &attr_enforce);
    + ASSERT_EQ(err, 0) {
    + TH_LOG("Failed to enforce ruleset: %s\n", strerror(errno));
    + }
    + ASSERT_EQ(errno, 0);
    +}
    +
    +TEST_F(layout1, whitelist)
    +{
    + const struct rule rules[] = {
    + {
    + .path = dir_s1d2,
    + .access = ACCESS_RO,
    + },
    + {
    + .path = file_s2d2,
    + .access = ACCESS_RO,
    + },
    + {},
    + };
    + const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
    +
    + ASSERT_NE(-1, ruleset_fd);
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + /* Tests on a directory. */
    + test_path(_metadata, "/", -1);
    + test_path(_metadata, dir_s1d1, -1);
    + test_path(_metadata, file_s1d1, -1);
    + test_path(_metadata, dir_s1d2, 0);
    + test_path(_metadata, file_s1d2, 0);
    + test_path(_metadata, dir_s1d3, 0);
    + test_path(_metadata, file_s1d3, 0);
    +
    + /* Tests on a file. */
    + test_path(_metadata, dir_s2d2, -1);
    + test_path(_metadata, file_s2d2, 0);
    +}
    +
    +TEST_F(layout1, unhandled_access)
    +{
    + const struct rule rules[] = {
    + {
    + .path = dir_s1d2,
    + .access = ACCESS_RO,
    + },
    + {},
    + };
    + const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
    +
    + ASSERT_NE(-1, ruleset_fd);
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + /*
    + * Because the policy does not handle LANDLOCK_ACCESS_FS_CHROOT,
    + * chroot(2) should be allowed.
    + */
    + ASSERT_EQ(0, chroot(dir_s1d1));
    + ASSERT_EQ(0, chroot(dir_s1d2));
    + ASSERT_EQ(0, chroot(dir_s1d3));
    +}
    +
    +TEST_F(layout1, ruleset_overlap)
    +{
    + const struct rule rules[] = {
    + /* These rules should be ORed among them. */
    + {
    + .path = dir_s1d2,
    + .access = LANDLOCK_ACCESS_FS_WRITE_FILE,
    + },
    + {
    + .path = dir_s1d2,
    + .access = LANDLOCK_ACCESS_FS_READ_DIR,
    + },
    + {},
    + };
    + int open_fd;
    + const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
    +
    + ASSERT_NE(-1, ruleset_fd);
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + ASSERT_EQ(-1, open(file_s1d1, O_WRONLY | O_CLOEXEC));
    + ASSERT_EQ(EACCES, errno);
    + ASSERT_EQ(-1, open(dir_s1d1, O_RDONLY | O_DIRECTORY | O_CLOEXEC));
    + ASSERT_EQ(EACCES, errno);
    +
    + open_fd = open(file_s1d2, O_WRONLY | O_CLOEXEC);
    + ASSERT_NE(-1, open_fd);
    + EXPECT_EQ(0, close(open_fd));
    + open_fd = open(dir_s1d2, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
    + ASSERT_NE(-1, open_fd);
    + EXPECT_EQ(0, close(open_fd));
    +
    + open_fd = open(file_s1d3, O_WRONLY | O_CLOEXEC);
    + ASSERT_NE(-1, open_fd);
    + EXPECT_EQ(0, close(open_fd));
    + open_fd = open(dir_s1d3, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
    + ASSERT_NE(-1, open_fd);
    + EXPECT_EQ(0, close(open_fd));
    +}
    +
    +TEST_F(layout1, inherit_superset)
    +{
    + const struct rule rules[] = {
    + {
    + .path = dir_s1d2,
    + .access = LANDLOCK_ACCESS_FS_READ_FILE |
    + LANDLOCK_ACCESS_FS_READ_DIR,
    + },
    + {},
    + };
    + int open_fd;
    + const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
    +
    + ASSERT_NE(-1, ruleset_fd);
    + enforce_ruleset(_metadata, ruleset_fd);
    +
    + ASSERT_EQ(-1, open(file_s1d1, O_WRONLY | O_CLOEXEC));
    + ASSERT_EQ(EACCES, errno);
    + ASSERT_EQ(-1, open(dir_s1d1, O_RDONLY | O_DIRECTORY | O_CLOEXEC));
    + ASSERT_EQ(EACCES, errno);
    +
    + /* Write access is forbidden. */
    + ASSERT_EQ(-1, open(file_s1d2, O_WRONLY | O_CLOEXEC));
    + ASSERT_EQ(EACCES, errno);
    + /* Readdir access is allowed. */
    + open_fd = open(dir_s1d2, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
    + ASSERT_NE(-1, open_fd);
    + ASSERT_EQ(0, close(open_fd));
    +
    + /* Write access is forbidden. */
    + ASSERT_EQ(-1, open(file_s1d3, O_WRONLY | O_CLOEXEC));
    + ASSERT_EQ(EACCES, errno);
    + /* Readdir access is allowed. */
    + open_fd = open(dir_s1d3, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
    + ASSERT_NE(-1, open_fd);
    + ASSERT_EQ(0, close(open_fd));
    +
    + /*
    + * 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(-1, open(file_s1d1, O_WRONLY | O_CLOEXEC));
    + ASSERT_EQ(EACCES, errno);
    + ASSERT_EQ(-1, open(dir_s1d1, O_RDONLY | O_DIRECTORY | O_CLOEXEC));
    + ASSERT_EQ(EACCES, errno);
    +
    + /* It is still forbidden to write in file_s1d2. */
    + ASSERT_EQ(-1, open(file_s1d2, O_WRONLY | O_CLOEXEC));
    + ASSERT_EQ(EACCES, errno);
    + /* Readdir access is still allowed. */
    + open_fd = open(dir_s1d2, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
    + ASSERT_NE(-1, open_fd);
    + ASSERT_EQ(0, close(open_fd));
    +
    + /* It is still forbidden to write in file_s1d3. */
    + ASSERT_EQ(-1, open(file_s1d3, O_WRONLY | O_CLOEXEC));
    + ASSERT_EQ(EACCES, errno);
    + /* Readdir access is still allowed. */
    + open_fd = open(dir_s1d3, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
    + ASSERT_NE(-1, open_fd);
    + ASSERT_EQ(0, close(open_fd));
    +
    + /*
    + * 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(-1, open(file_s1d1, O_WRONLY | O_CLOEXEC));
    + ASSERT_EQ(EACCES, errno);
    + ASSERT_EQ(-1, open(dir_s1d1, O_RDONLY | O_DIRECTORY | O_CLOEXEC));
    + ASSERT_EQ(EACCES, errno);
    +
    + /* It is still forbidden to write in file_s1d2. */
    + ASSERT_EQ(-1, open(file_s1d2, O_WRONLY | O_CLOEXEC));
    + ASSERT_EQ(EACCES, errno);
    + /* Readdir access is still allowed. */
    + open_fd = open(dir_s1d2, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
    + ASSERT_NE(-1, open_fd);
    + ASSERT_EQ(0, close(open_fd));
    +
    + /* It is still forbidden to write in file_s1d3. */
    + ASSERT_EQ(-1, open(file_s1d3, O_WRONLY | O_CLOEXEC));
    + ASSERT_EQ(EACCES, errno);
    + /* Readdir access is still allowed. */
    + open_fd = open(dir_s1d3, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
    + ASSERT_NE(-1, open_fd);
    + ASSERT_EQ(0, close(open_fd));
    +
    + /*
    + * 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);
    + EXPECT_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(-1, open(file_s1d1, O_WRONLY | O_CLOEXEC));
    + ASSERT_EQ(EACCES, errno);
    + ASSERT_EQ(-1, open(dir_s1d1, O_RDONLY | O_DIRECTORY | O_CLOEXEC));
    + ASSERT_EQ(EACCES, errno);
    +
    + /* It is still forbidden to write in file_s1d2. */
    + ASSERT_EQ(-1, open(file_s1d2, O_WRONLY | O_CLOEXEC));
    + /* Readdir access is still allowed. */
    + open_fd = open(dir_s1d2, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
    + ASSERT_NE(-1, open_fd);
    + ASSERT_EQ(0, close(open_fd));
    +
    + /* It is still forbidden to write in file_s1d3. */
    + ASSERT_EQ(-1, open(file_s1d3, O_WRONLY | O_CLOEXEC));
    + open_fd = open(dir_s1d3, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
    + /* Readdir of dir_s1d3 is now forbidden too. */
    + ASSERT_EQ(-1, open_fd);
    + ASSERT_EQ(EACCES, errno);
    +}
    +
    +TEST_F(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_NE(-1, ruleset_fd);
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + test_path(_metadata, dir_s1d1, 0);
    +
    + test_path(_metadata, dir_s2d1, -1);
    +
    + test_path(_metadata, dir_s3d1, -1);
    + test_path(_metadata, dir_s3d2, 0);
    + test_path(_metadata, dir_s3d3, 0);
    +}
    +
    +TEST_F(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_NE(-1, ruleset_fd);
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + test_path(_metadata, dir_s1d1, 0);
    +
    + test_path(_metadata, dir_s2d1, -1);
    +
    + test_path(_metadata, dir_s3d1, 0);
    + test_path(_metadata, dir_s3d2, 0);
    + test_path(_metadata, dir_s3d3, 0);
    +}
    +
    +/*
    + * This test verifies that we can apply a landlock rule on the root (/), it
    + * might require special handling.
    + */
    +TEST_F(layout1, rule_over_root)
    +{
    + const struct rule rules[] = {
    + {
    + .path = "/",
    + .access = ACCESS_RO,
    + },
    + {},
    + };
    + const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
    +
    + ASSERT_NE(-1, ruleset_fd);
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + test_path(_metadata, "/", 0);
    + test_path(_metadata, dir_s1d1, 0);
    +}
    +
    +TEST_F(layout1, rule_inside_mount_ns)
    +{
    + const struct rule rules[] = {
    + {
    + .path = "s3d3",
    + .access = ACCESS_RO,
    + },
    + {},
    + };
    + int ruleset_fd;
    +
    + ASSERT_NE(-1, mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL));
    + ASSERT_NE(-1, syscall(SYS_pivot_root, dir_s3d2, dir_s3d3)) {
    + TH_LOG("Failed to pivot_root into \"%s\": %s\n", dir_s3d2,
    + strerror(errno));
    + };
    + ASSERT_NE(-1, chdir("/"));
    +
    + ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
    + ASSERT_NE(-1, ruleset_fd);
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + test_path(_metadata, "s3d3", 0);
    + test_path(_metadata, "/", -1);
    +}
    +
    +TEST_F(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_NE(-1, ruleset_fd);
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + ASSERT_EQ(-1, mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL));
    + ASSERT_EQ(-1, syscall(SYS_pivot_root, dir_s3d2, dir_s3d3));
    +}
    +
    +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)
    +{
    + const struct rule rules[] = {
    + {
    + .path = dir_s1d2,
    + .access = ACCESS_RO,
    + },
    + {
    + .path = dir_s2d2,
    + .access = ACCESS_RO,
    + },
    + {},
    + };
    + int dirfd;
    + const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
    +
    + ASSERT_NE(-1, 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;
    + }
    + enforce_ruleset(_metadata, ruleset_fd);
    +
    + switch (rel) {
    + case REL_OPEN:
    + dirfd = open(dir_s1d2, O_DIRECTORY);
    + ASSERT_NE(-1, dirfd);
    + break;
    + case REL_CHDIR:
    + ASSERT_NE(-1, chdir(dir_s1d2));
    + dirfd = AT_FDCWD;
    + break;
    + case REL_CHROOT_ONLY:
    + /* Do chroot into dir_s1d2 (relative to dir_s2d2). */
    + ASSERT_NE(-1, chroot("../../s1d1/s1d2")) {
    + TH_LOG("Failed to chroot: %s\n", strerror(errno));
    + }
    + dirfd = AT_FDCWD;
    + break;
    + case REL_CHROOT_CHDIR:
    + /* Do chroot into dir_s1d2. */
    + ASSERT_NE(-1, chroot(".")) {
    + TH_LOG("Failed to chroot: %s\n", strerror(errno));
    + }
    + dirfd = AT_FDCWD;
    + break;
    + }
    +
    + test_path_rel(_metadata, dirfd, "..",
    + (rel == REL_CHROOT_CHDIR) ? 0 : -1);
    + test_path_rel(_metadata, dirfd, ".", 0);
    +
    + if (rel == REL_CHROOT_ONLY)
    + /* The current directory is dir_s2d2. */
    + test_path_rel(_metadata, dirfd, "./s2d3", 0);
    + else
    + /* The current directory is dir_s1d2. */
    + test_path_rel(_metadata, dirfd, "./s1d3", 0);
    +
    + if (rel != REL_CHROOT_CHDIR) {
    + test_path_rel(_metadata, dirfd, "../../s1d1", -1);
    + test_path_rel(_metadata, dirfd, "../../s1d1/s1d2", 0);
    + test_path_rel(_metadata, dirfd, "../../s1d1/s1d2/s1d3", 0);
    +
    + test_path_rel(_metadata, dirfd, "../../s2d1", -1);
    + test_path_rel(_metadata, dirfd, "../../s2d1/s2d2", 0);
    + test_path_rel(_metadata, dirfd, "../../s2d1/s2d2/s2d3", 0);
    + }
    +
    + if (rel == REL_OPEN)
    + EXPECT_EQ(0, close(dirfd));
    + EXPECT_EQ(0, close(ruleset_fd));
    +}
    +
    +TEST_F(layout1, relative_open)
    +{
    + test_relative_path(_metadata, REL_OPEN);
    +}
    +
    +TEST_F(layout1, relative_chdir)
    +{
    + test_relative_path(_metadata, REL_CHDIR);
    +}
    +
    +TEST_F(layout1, relative_chroot_only)
    +{
    + test_relative_path(_metadata, REL_CHROOT_ONLY);
    +}
    +
    +TEST_F(layout1, relative_chroot_chdir)
    +{
    + test_relative_path(_metadata, REL_CHROOT_CHDIR);
    +}
    +
    +TEST_F(layout1, chroot)
    +{
    + const struct rule rules[] = {
    + {
    + .path = dir_s1d2,
    + .access = LANDLOCK_ACCESS_FS_CHROOT,
    + },
    + {},
    + };
    + const int ruleset_fd = create_ruleset(_metadata,
    + LANDLOCK_ACCESS_FS_CHROOT, rules);
    +
    + ASSERT_NE(-1, ruleset_fd);
    +
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + ASSERT_EQ(-1, chroot(dir_s1d1));
    + ASSERT_EQ(EACCES, errno);
    + ASSERT_EQ(0, chroot(dir_s1d2)) {
    + TH_LOG("Failed to chroot into \"%s\": %s\n", file_s1d2,
    + strerror(errno));
    + };
    + /* This chroot still works because we didn't chdir(dir_s1d2). */
    + ASSERT_EQ(0, chroot(dir_s1d3));
    +}
    +
    +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\n", 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\n",
    + strerror(errno));
    + }
    + ASSERT_EQ(0, fstat(src_fd, &statbuf));
    + ASSERT_LE(0, 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 char *const path, const int ret)
    +{
    + int status;
    + char *const argv[] = {(char *)path, NULL};
    + const pid_t child = fork();
    +
    + ASSERT_LE(0, child);
    + if (child == 0) {
    + ASSERT_EQ(ret, execve(path, argv, NULL)) {
    + TH_LOG("Failed to execute \"%s\": %s\n", path,
    + strerror(errno));
    + };
    + ASSERT_EQ(EACCES, errno);
    + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE);
    + return;
    + }
    + ASSERT_EQ(child, waitpid(child, &status, 0));
    + ASSERT_EQ(1, WIFEXITED(status));
    + ASSERT_EQ(0, WEXITSTATUS(status)) {
    + TH_LOG("Unexpected return code for \"%s\": %s\n", path,
    + strerror(errno));
    + };
    +}
    +
    +TEST_F(layout1, execute)
    +{
    + const struct rule rules[] = {
    + {
    + .path = dir_s1d1,
    + .access = LANDLOCK_ACCESS_FS_EXECUTE,
    + },
    + {},
    + };
    + const int ruleset_fd = create_ruleset(_metadata,
    + LANDLOCK_ACCESS_FS_EXECUTE, rules);
    +
    + ASSERT_NE(-1, ruleset_fd);
    + copy_binary(_metadata, file_s1d1);
    + copy_binary(_metadata, file_s1d2);
    + copy_binary(_metadata, file_s1d3);
    +
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + test_execute(_metadata, file_s1d1, -1);
    + test_execute(_metadata, file_s1d2, 0);
    + test_execute(_metadata, file_s1d3, 0);
    +}
    +
    +TEST_F(layout1, link_to)
    +{
    + const struct rule rules[] = {
    + {
    + .path = dir_s1d2,
    + .access = LANDLOCK_ACCESS_FS_LINK_TO,
    + },
    + {},
    + };
    + const int ruleset_fd = create_ruleset(_metadata,
    + LANDLOCK_ACCESS_FS_LINK_TO, rules);
    +
    + ASSERT_NE(-1, ruleset_fd);
    +
    + ASSERT_EQ(0, unlink(file_s1d1));
    + ASSERT_EQ(0, unlink(file_s1d2));
    + ASSERT_EQ(0, unlink(file_s1d3));
    +
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + ASSERT_EQ(-1, link(file_s2d1, file_s1d1));
    + ASSERT_EQ(EACCES, errno);
    + ASSERT_EQ(0, link(file_s2d1, file_s1d2)) {
    + TH_LOG("Failed to link file to \"%s\": %s\n", file_s1d2,
    + strerror(errno));
    + };
    + ASSERT_EQ(0, link(file_s2d1, file_s1d3));
    +}
    +
    +static void test_rename(struct __test_metadata *const _metadata)
    +{
    + /* Renames files. */
    + ASSERT_EQ(-1, rename(file_s2d1, file_s1d1));
    + ASSERT_EQ(EACCES, errno);
    + ASSERT_EQ(0, rename(file_s2d2, file_s1d2)) {
    + TH_LOG("Failed to rename file \"%s\": %s\n", file_s2d3,
    + strerror(errno));
    + };
    + ASSERT_EQ(0, rename(file_s2d3, file_s1d3));
    +
    + /* Renames directories (reverse order). */
    + ASSERT_EQ(0, unlink(file_s1d3));
    + ASSERT_EQ(0, rename(dir_s2d3, dir_s1d3)) {
    + TH_LOG("Failed to rename directory \"%s\": %s\n", dir_s2d3,
    + strerror(errno));
    + };
    +
    + ASSERT_EQ(0, rmdir(dir_s1d3));
    + ASSERT_EQ(0, unlink(file_s1d2));
    + ASSERT_EQ(-1, rename(dir_s2d2, dir_s1d2));
    + ASSERT_EQ(EACCES, errno);
    +
    + ASSERT_EQ(0, rmdir(dir_s1d2));
    + ASSERT_EQ(0, unlink(file_s1d1));
    + ASSERT_EQ(-1, rename(dir_s2d1, dir_s1d1));
    + ASSERT_EQ(EACCES, errno);
    +}
    +
    +TEST_F(layout1, rename_from)
    +{
    + const struct rule rules[] = {
    + {
    + .path = dir_s2d2,
    + .access = LANDLOCK_ACCESS_FS_RENAME_FROM,
    + },
    + {},
    + };
    + const int ruleset_fd = create_ruleset(_metadata,
    + LANDLOCK_ACCESS_FS_RENAME_FROM, rules);
    +
    + ASSERT_NE(-1, ruleset_fd);
    +
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + test_rename(_metadata);
    +}
    +
    +TEST_F(layout1, rename_to)
    +{
    + /*
    + * Same tests as layout1/rename_from, except the rename_from access
    + * rule is on dir_s1d2.
    + */
    + const struct rule rules[] = {
    + {
    + .path = dir_s1d2,
    + .access = LANDLOCK_ACCESS_FS_RENAME_TO,
    + },
    + {},
    + };
    + const int ruleset_fd = create_ruleset(_metadata,
    + LANDLOCK_ACCESS_FS_RENAME_TO, rules);
    +
    + ASSERT_NE(-1, ruleset_fd);
    +
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + test_rename(_metadata);
    +}
    +
    +TEST_F(layout1, rmdir)
    +{
    + const struct rule rules[] = {
    + {
    + .path = dir_s1d2,
    + .access = LANDLOCK_ACCESS_FS_RMDIR,
    + },
    + {},
    + };
    + const int ruleset_fd = create_ruleset(_metadata,
    + LANDLOCK_ACCESS_FS_RMDIR, rules);
    +
    + ASSERT_NE(-1, ruleset_fd);
    +
    + ASSERT_EQ(0, unlink(file_s1d1));
    + ASSERT_EQ(0, unlink(file_s1d2));
    + ASSERT_EQ(0, unlink(file_s1d3));
    +
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + ASSERT_EQ(0, rmdir(dir_s1d3)) {
    + TH_LOG("Failed to remove directory \"%s\": %s\n", file_s1d2,
    + strerror(errno));
    + }
    + ASSERT_EQ(-1, rmdir(dir_s1d2));
    + ASSERT_EQ(EACCES, errno);
    + ASSERT_EQ(-1, rmdir(dir_s1d1));
    + ASSERT_EQ(EACCES, errno);
    +}
    +
    +TEST_F(layout1, unlink)
    +{
    + const struct rule rules[] = {
    + {
    + .path = dir_s1d2,
    + .access = LANDLOCK_ACCESS_FS_UNLINK,
    + },
    + {},
    + };
    + const int ruleset_fd = create_ruleset(_metadata,
    + LANDLOCK_ACCESS_FS_UNLINK, rules);
    +
    + ASSERT_NE(-1, ruleset_fd);
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + ASSERT_EQ(-1, unlink(file_s1d1));
    + ASSERT_EQ(EACCES, errno);
    + ASSERT_EQ(0, unlink(file_s1d2)) {
    + TH_LOG("Failed to unlink file \"%s\": %s\n", file_s1d2,
    + strerror(errno));
    + };
    + ASSERT_EQ(0, unlink(file_s1d3));
    +}
    +
    +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_NE(-1, ruleset_fd);
    +
    + ASSERT_EQ(0, unlink(file_s1d1));
    + ASSERT_EQ(0, unlink(file_s1d2));
    + ASSERT_EQ(0, unlink(file_s1d3));
    +
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + ASSERT_EQ(-1, mknod(file_s1d1, mode | 0400, dev));
    + ASSERT_EQ(EACCES, errno);
    + ASSERT_EQ(0, mknod(file_s1d2, mode | 0400, dev)) {
    + TH_LOG("Failed to make file \"%s\": %s\n",
    + file_s1d2, strerror(errno));
    + };
    + ASSERT_EQ(0, mknod(file_s1d3, mode | 0400, dev));
    +}
    +
    +TEST_F(layout1, make_char)
    +{
    + /* Creates a /dev/null device. */
    + test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_CHAR, S_IFCHR,
    + major(1) | minor(3));
    +}
    +
    +TEST_F(layout1, make_block)
    +{
    + /* Creates a /dev/loop0 device. */
    + test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_BLOCK, S_IFBLK,
    + major(7) | minor(0));
    +}
    +
    +TEST_F(layout1, make_reg)
    +{
    + test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, S_IFREG, 0);
    +}
    +
    +TEST_F(layout1, make_sock)
    +{
    + test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_SOCK, S_IFSOCK, 0);
    +}
    +
    +TEST_F(layout1, make_fifo)
    +{
    + test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_FIFO, S_IFIFO, 0);
    +}
    +
    +TEST_F(layout1, make_sym)
    +{
    + const struct rule rules[] = {
    + {
    + .path = dir_s1d2,
    + .access = LANDLOCK_ACCESS_FS_MAKE_SYM,
    + },
    + {},
    + };
    + const int ruleset_fd = create_ruleset(_metadata,
    + LANDLOCK_ACCESS_FS_MAKE_SYM, rules);
    +
    + ASSERT_NE(-1, ruleset_fd);
    +
    + ASSERT_EQ(0, unlink(file_s1d1));
    + ASSERT_EQ(0, unlink(file_s1d2));
    + ASSERT_EQ(0, unlink(file_s1d3));
    +
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + ASSERT_EQ(-1, symlink("none", file_s1d1));
    + ASSERT_EQ(EACCES, errno);
    + ASSERT_EQ(0, symlink("none", file_s1d2)) {
    + TH_LOG("Failed to make symlink \"%s\": %s\n",
    + file_s1d2, strerror(errno));
    + };
    + ASSERT_EQ(0, symlink("none", file_s1d3));
    +}
    +
    +TEST_F(layout1, make_dir)
    +{
    + const struct rule rules[] = {
    + {
    + .path = dir_s1d2,
    + .access = LANDLOCK_ACCESS_FS_MAKE_DIR,
    + },
    + {},
    + };
    + const int ruleset_fd = create_ruleset(_metadata,
    + LANDLOCK_ACCESS_FS_MAKE_DIR, rules);
    +
    + ASSERT_NE(-1, ruleset_fd);
    +
    + ASSERT_EQ(0, unlink(file_s1d1));
    + ASSERT_EQ(0, unlink(file_s1d2));
    + ASSERT_EQ(0, unlink(file_s1d3));
    +
    + enforce_ruleset(_metadata, ruleset_fd);
    + EXPECT_EQ(0, close(ruleset_fd));
    +
    + /* Uses file_* as directory names. */
    + ASSERT_EQ(-1, mkdir(file_s1d1, 0700));
    + ASSERT_EQ(EACCES, errno);
    + ASSERT_EQ(0, mkdir(file_s1d2, 0700)) {
    + TH_LOG("Failed to make directory \"%s\": %s\n",
    + file_s1d2, strerror(errno));
    + };
    + ASSERT_EQ(0, mkdir(file_s1d3, 0700));
    +}
    +
    +TEST(cleanup)
    +{
    + cleanup_layout1();
    +}
    +
    +TEST_HARNESS_MAIN
    diff --git a/tools/testing/selftests/landlock/test_ptrace.c b/tools/testing/selftests/landlock/test_ptrace.c
    new file mode 100644
    index 000000000000..407cc99e5340
    --- /dev/null
    +++ b/tools/testing/selftests/landlock/test_ptrace.c
    @@ -0,0 +1,294 @@
    +// 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, err;
    + struct landlock_attr_features attr_features;
    + struct landlock_attr_enforce attr_enforce;
    + struct landlock_attr_ruleset attr_ruleset = {
    + .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
    + };
    + struct landlock_attr_path_beneath path_beneath = {
    + .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE,
    + };
    +
    + ASSERT_EQ(0, landlock(LANDLOCK_CMD_GET_FEATURES,
    + LANDLOCK_OPT_GET_FEATURES,
    + sizeof(attr_features), &attr_features));
    + /* Only for test, use a binary AND for real application instead. */
    + ASSERT_EQ(attr_ruleset.handled_access_fs,
    + attr_ruleset.handled_access_fs &
    + attr_features.access_fs);
    + ruleset_fd = landlock(LANDLOCK_CMD_CREATE_RULESET,
    + LANDLOCK_OPT_CREATE_RULESET, sizeof(attr_ruleset),
    + &attr_ruleset);
    + ASSERT_GE(ruleset_fd, 0) {
    + TH_LOG("Failed to create a ruleset: %s\n", strerror(errno));
    + }
    + path_beneath.ruleset_fd = ruleset_fd;
    + path_beneath.parent_fd = open("/tmp", O_PATH | O_NOFOLLOW | O_DIRECTORY
    + | O_CLOEXEC);
    + ASSERT_GE(path_beneath.parent_fd, 0);
    + err = landlock(LANDLOCK_CMD_ADD_RULE,
    + LANDLOCK_OPT_ADD_RULE_PATH_BENEATH,
    + sizeof(path_beneath), &path_beneath);
    + ASSERT_EQ(err, 0);
    + ASSERT_EQ(errno, 0);
    + ASSERT_EQ(0, close(path_beneath.parent_fd));
    +
    + err = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
    + ASSERT_EQ(errno, 0);
    + ASSERT_EQ(err, 0);
    +
    + attr_enforce.ruleset_fd = ruleset_fd;
    + err = landlock(LANDLOCK_CMD_ENFORCE_RULESET,
    + LANDLOCK_OPT_ENFORCE_RULESET, sizeof(attr_enforce),
    + &attr_enforce);
    + ASSERT_EQ(err, 0);
    + ASSERT_EQ(errno, 0);
    +
    + ASSERT_EQ(0, close(ruleset_fd));
    +}
    +
    +/* test PTRACE_TRACEME and PTRACE_ATTACH for parent and child */
    +static void check_ptrace(struct __test_metadata *const _metadata,
    + const bool domain_both, const bool domain_parent,
    + const bool domain_child)
    +{
    + pid_t child, parent;
    + int status;
    + int pipe_child[2], pipe_parent[2];
    + char buf_parent;
    +
    + parent = getpid();
    + ASSERT_EQ(0, pipe(pipe_child));
    + ASSERT_EQ(0, pipe(pipe_parent));
    + if (domain_both)
    + create_domain(_metadata);
    +
    + child = fork();
    + ASSERT_LE(0, child);
    + if (child == 0) {
    + char buf_child;
    +
    + EXPECT_EQ(0, close(pipe_parent[1]));
    + EXPECT_EQ(0, close(pipe_child[0]));
    + if (domain_child)
    + create_domain(_metadata);
    +
    + /* sync #1 */
    + ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)) {
    + TH_LOG("Failed to read() sync #1 from parent");
    + }
    + ASSERT_EQ('.', buf_child);
    +
    + /* Tests the parent protection. */
    + ASSERT_EQ(domain_child ? -1 : 0,
    + ptrace(PTRACE_ATTACH, parent, NULL, 0));
    + if (domain_child) {
    + ASSERT_EQ(EPERM, errno);
    + } else {
    + ASSERT_EQ(parent, waitpid(parent, &status, 0));
    + ASSERT_EQ(1, WIFSTOPPED(status));
    + ASSERT_EQ(0, ptrace(PTRACE_DETACH, parent, NULL, 0));
    + }
    +
    + /* sync #2 */
    + ASSERT_EQ(1, write(pipe_child[1], ".", 1)) {
    + TH_LOG("Failed to write() sync #2 to parent");
    + }
    +
    + /* Tests traceme. */
    + ASSERT_EQ(domain_parent ? -1 : 0, ptrace(PTRACE_TRACEME));
    + if (domain_parent) {
    + ASSERT_EQ(EPERM, errno);
    + } else {
    + ASSERT_EQ(0, raise(SIGSTOP));
    + }
    +
    + /* sync #3 */
    + ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)) {
    + TH_LOG("Failed to read() sync #3 from parent");
    + }
    + ASSERT_EQ('.', buf_child);
    + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE);
    + }
    +
    + EXPECT_EQ(0, close(pipe_child[1]));
    + EXPECT_EQ(0, close(pipe_parent[0]));
    + if (domain_parent)
    + create_domain(_metadata);
    +
    + /* sync #1 */
    + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)) {
    + TH_LOG("Failed to write() sync #1 to child");
    + }
    +
    + /* Tests the parent protection. */
    + /* sync #2 */
    + ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1)) {
    + TH_LOG("Failed to read() sync #2 from child");
    + }
    + ASSERT_EQ('.', buf_parent);
    +
    + /* Tests traceme. */
    + if (!domain_parent) {
    + ASSERT_EQ(child, waitpid(child, &status, 0));
    + ASSERT_EQ(1, WIFSTOPPED(status));
    + ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
    + }
    + /* Tests attach. */
    + ASSERT_EQ(domain_parent ? -1 : 0,
    + ptrace(PTRACE_ATTACH, child, NULL, 0));
    + if (domain_parent) {
    + ASSERT_EQ(EPERM, errno);
    + } else {
    + ASSERT_EQ(child, waitpid(child, &status, 0));
    + ASSERT_EQ(1, WIFSTOPPED(status));
    + ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
    + }
    +
    + /* sync #3 */
    + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)) {
    + TH_LOG("Failed to write() sync #3 to child");
    + }
    + ASSERT_EQ(child, waitpid(child, &status, 0));
    + if (WIFSIGNALED(status) || WEXITSTATUS(status))
    + _metadata->passed = 0;
    +}
    +
    +/*
    + * 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
    + */
    +TEST(allow_without_domain) {
    + check_ptrace(_metadata, false, false, false);
    +}
    +
    +/*
    + * Child domain
    + *
    + * P1--. P1 -> P2 : allow
    + * \ P2 -> P1 : deny
    + * .'-----.
    + * | P2 |
    + * '------'
    + */
    +TEST(allow_with_one_domain) {
    + check_ptrace(_metadata, false, false, true);
    +}
    +
    +/*
    + * Parent domain
    + * .------.
    + * | P1 --. P1 -> P2 : deny
    + * '------' \ P2 -> P1 : allow
    + * '
    + * P2
    + */
    +TEST(deny_with_parent_domain) {
    + check_ptrace(_metadata, false, true, false);
    +}
    +
    +/*
    + * Parent + child domain (siblings)
    + * .------.
    + * | P1 ---. P1 -> P2 : deny
    + * '------' \ P2 -> P1 : deny
    + * .---'--.
    + * | P2 |
    + * '------'
    + */
    +TEST(deny_with_sibling_domain) {
    + check_ptrace(_metadata, false, true, true);
    +}
    +
    +/*
    + * Same domain (inherited)
    + * .-------------.
    + * | P1----. | P1 -> P2 : allow
    + * | \ | P2 -> P1 : allow
    + * | ' |
    + * | P2 |
    + * '-------------'
    + */
    +TEST(allow_sibling_domain) {
    + check_ptrace(_metadata, true, false, false);
    +}
    +
    +/*
    + * Inherited + child domain
    + * .-----------------.
    + * | P1----. | P1 -> P2 : allow
    + * | \ | P2 -> P1 : deny
    + * | .-'----. |
    + * | | P2 | |
    + * | '------' |
    + * '-----------------'
    + */
    +TEST(allow_with_nested_domain) {
    + check_ptrace(_metadata, true, false, true);
    +}
    +
    +/*
    + * Inherited + parent domain
    + * .-----------------.
    + * |.------. | P1 -> P2 : deny
    + * || P1 ----. | P2 -> P1 : allow
    + * |'------' \ |
    + * | ' |
    + * | P2 |
    + * '-----------------'
    + */
    +TEST(deny_with_nested_and_parent_domain) {
    + check_ptrace(_metadata, true, true, false);
    +}
    +
    +/*
    + * Inherited + parent and child domain (siblings)
    + * .-----------------.
    + * | .------. | P1 -> P2 : deny
    + * | | P1 . | P2 -> P1 : deny
    + * | '------'\ |
    + * | \ |
    + * | .--'---. |
    + * | | P2 | |
    + * | '------' |
    + * '-----------------'
    + */
    +TEST(deny_with_forked_domain) {
    + check_ptrace(_metadata, true, true, true);
    +}
    +
    +TEST_HARNESS_MAIN
    diff --git a/tools/testing/selftests/landlock/true.c b/tools/testing/selftests/landlock/true.c
    new file mode 100644
    index 000000000000..3f9ccbf52783
    --- /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.26.0.rc2
    \
     
     \ /
      Last update: 2020-03-26 21:35    [W:4.075 / U:0.564 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site