root/tools/testing/selftests/landlock/fs_test.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Landlock tests - Filesystem
 *
 * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
 * Copyright © 2020 ANSSI
 * Copyright © 2020-2022 Microsoft Corporation
 */

#define _GNU_SOURCE
#include <asm/termbits.h>
#include <fcntl.h>
#include <libgen.h>
#include <linux/fiemap.h>
#include <linux/landlock.h>
#include <linux/magic.h>
#include <sched.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/capability.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/un.h>
#include <sys/vfs.h>
#include <unistd.h>

/*
 * Intentionally included last to work around header conflict.
 * See https://sourceware.org/glibc/wiki/Synchronizing_Headers.
 */
#include <linux/fs.h>
#include <linux/mount.h>

/* Defines AT_EXECVE_CHECK without type conflicts. */
#define _ASM_GENERIC_FCNTL_H
#include <linux/fcntl.h>

#include "audit.h"
#include "common.h"

#ifndef renameat2
int renameat2(int olddirfd, const char *oldpath, int newdirfd,
              const char *newpath, unsigned int flags)
{
        return syscall(__NR_renameat2, olddirfd, oldpath, newdirfd, newpath,
                       flags);
}
#endif

#ifndef open_tree
int open_tree(int dfd, const char *filename, unsigned int flags)
{
        return syscall(__NR_open_tree, dfd, filename, flags);
}
#endif

static int sys_execveat(int dirfd, const char *pathname, char *const argv[],
                        char *const envp[], int flags)
{
        return syscall(__NR_execveat, dirfd, pathname, argv, envp, flags);
}

#ifndef RENAME_EXCHANGE
#define RENAME_EXCHANGE (1 << 1)
#endif

static const char bin_true[] = "./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";
static const char file1_s3d1[] = TMP_DIR "/s3d1/f1";
/* 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 const char file1_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3/f1";
static const char dir_s3d4[] = TMP_DIR "/s3d1/s3d2/s3d4";
static const char file1_s3d4[] = TMP_DIR "/s3d1/s3d2/s3d4/f1";

/*
 * layout1 hierarchy:
 *
 * tmp
 * ├── s1d1
 * │   ├── f1
 * │   ├── f2
 * │   └── s1d2
 * │       ├── f1
 * │       ├── f2
 * │       └── s1d3
 * │           ├── f1
 * │           └── f2
 * ├── s2d1
 * │   ├── f1
 * │   └── s2d2
 * │       ├── f1
 * │       └── s2d3
 * │           ├── f1
 * │           └── f2
 * └── s3d1
 *     ├── f1
 *     └── s3d2 [mount point]
 *         ├── s3d3
 *         │   └── f1
 *         └── s3d4
 *             └── f1
 */

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_filesystem(const char *const filesystem)
{
        char str[32];
        int len;
        bool res = true;
        FILE *const inf = fopen("/proc/filesystems", "r");

        /*
         * Consider that the filesystem is supported if we cannot get the
         * supported ones.
         */
        if (!inf)
                return true;

        /* filesystem can be null for bind mounts. */
        if (!filesystem)
                goto out;

        len = snprintf(str, sizeof(str), "nodev\t%s\n", filesystem);
        if (len >= sizeof(str))
                /* Ignores too-long filesystem names. */
                goto out;

        res = fgrep(inf, str);

out:
        fclose(inf);
        return res;
}

static bool cwd_matches_fs(unsigned int fs_magic)
{
        struct statfs statfs_buf;

        if (!fs_magic)
                return true;

        if (statfs(".", &statfs_buf))
                return true;

        return statfs_buf.f_type == fs_magic;
}

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 && errno != ENOTDIR)
                        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;
}

struct mnt_opt {
        const char *const source;
        const char *const type;
        const unsigned long flags;
        const char *const data;
};

#define MNT_TMP_DATA "size=4m,mode=700"

static const struct mnt_opt mnt_tmp = {
        .type = "tmpfs",
        .data = MNT_TMP_DATA,
};

static int mount_opt(const struct mnt_opt *const mnt, const char *const target)
{
        return mount(mnt->source ?: mnt->type, target, mnt->type, mnt->flags,
                     mnt->data);
}

static void prepare_layout_opt(struct __test_metadata *const _metadata,
                               const struct mnt_opt *const mnt)
{
        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 | CLONE_NEWCGROUP));
        ASSERT_EQ(0, mount_opt(mnt, TMP_DIR))
        {
                TH_LOG("Failed to mount the %s filesystem: %s", mnt->type,
                       strerror(errno));
                /*
                 * FIXTURE_TEARDOWN() is not called when FIXTURE_SETUP()
                 * failed, so we need to explicitly do a minimal cleanup to
                 * avoid cascading errors with other tests that don't depend on
                 * the same filesystem.
                 */
                remove_path(TMP_DIR);
        }
        ASSERT_EQ(0, mount(NULL, TMP_DIR, NULL, MS_PRIVATE | MS_REC, NULL));
        clear_cap(_metadata, CAP_SYS_ADMIN);
}

static void prepare_layout(struct __test_metadata *const _metadata)
{
        prepare_layout_opt(_metadata, &mnt_tmp);
}

static void cleanup_layout(struct __test_metadata *const _metadata)
{
        set_cap(_metadata, CAP_SYS_ADMIN);
        if (umount(TMP_DIR)) {
                /*
                 * According to the test environment, the mount point of the
                 * current directory may be shared or not, which changes the
                 * visibility of the nested TMP_DIR mount point for the test's
                 * parent process doing this cleanup.
                 */
                ASSERT_EQ(EINVAL, errno);
        }
        clear_cap(_metadata, CAP_SYS_ADMIN);
        EXPECT_EQ(0, remove_path(TMP_DIR));
}

/* clang-format off */
FIXTURE(layout0) {};
/* clang-format on */

FIXTURE_SETUP(layout0)
{
        prepare_layout(_metadata);
}

FIXTURE_TEARDOWN_PARENT(layout0)
{
        cleanup_layout(_metadata);
}

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_file(_metadata, file1_s3d1);
        create_directory(_metadata, dir_s3d2);
        set_cap(_metadata, CAP_SYS_ADMIN);
        ASSERT_EQ(0, mount_opt(&mnt_tmp, dir_s3d2));
        clear_cap(_metadata, CAP_SYS_ADMIN);

        create_file(_metadata, file1_s3d3);
        create_file(_metadata, file1_s3d4);
}

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(dir_s1d3));

        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_s2d2));

        EXPECT_EQ(0, remove_path(file1_s3d1));
        EXPECT_EQ(0, remove_path(file1_s3d3));
        EXPECT_EQ(0, remove_path(file1_s3d4));
        set_cap(_metadata, CAP_SYS_ADMIN);
        umount(dir_s3d2);
        clear_cap(_metadata, CAP_SYS_ADMIN);
        EXPECT_EQ(0, remove_path(dir_s3d2));
}

/* clang-format off */
FIXTURE(layout1) {};
/* clang-format on */

FIXTURE_SETUP(layout1)
{
        prepare_layout(_metadata);

        create_layout1(_metadata);
}

FIXTURE_TEARDOWN_PARENT(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;

        /* Tests with denied-by-default access right. */
        path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_REFER;
        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_REFER;

        /* 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));
}

/* clang-format off */

#define ACCESS_FILE ( \
        LANDLOCK_ACCESS_FS_EXECUTE | \
        LANDLOCK_ACCESS_FS_WRITE_FILE | \
        LANDLOCK_ACCESS_FS_READ_FILE | \
        LANDLOCK_ACCESS_FS_TRUNCATE | \
        LANDLOCK_ACCESS_FS_IOCTL_DEV)

#define ACCESS_LAST LANDLOCK_ACCESS_FS_IOCTL_DEV

#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 | \
        LANDLOCK_ACCESS_FS_MAKE_SYM | \
        LANDLOCK_ACCESS_FS_REFER)

/* clang-format on */

TEST_F_FORK(layout1, file_and_dir_access_rights)
{
        __u64 access;
        int err;
        struct landlock_path_beneath_attr path_beneath_file = {},
                                          path_beneath_dir = {};
        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_file.parent_fd = open(file1_s1d2, O_PATH | O_CLOEXEC);
        ASSERT_LE(0, path_beneath_file.parent_fd);

        /* Tests access rights for directories. */
        path_beneath_dir.parent_fd =
                open(dir_s1d2, O_PATH | O_DIRECTORY | O_CLOEXEC);
        ASSERT_LE(0, path_beneath_dir.parent_fd);

        for (access = 1; access <= ACCESS_LAST; access <<= 1) {
                path_beneath_dir.allowed_access = access;
                ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
                                               LANDLOCK_RULE_PATH_BENEATH,
                                               &path_beneath_dir, 0));

                path_beneath_file.allowed_access = access;
                err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
                                        &path_beneath_file, 0);
                if (access & ACCESS_FILE) {
                        ASSERT_EQ(0, err);
                } else {
                        ASSERT_EQ(-1, err);
                        ASSERT_EQ(EINVAL, errno);
                }
        }
        ASSERT_EQ(0, close(path_beneath_file.parent_fd));
        ASSERT_EQ(0, close(path_beneath_dir.parent_fd));
        ASSERT_EQ(0, close(ruleset_fd));
}

TEST_F_FORK(layout0, ruleset_with_unknown_access)
{
        __u64 access_mask;

        for (access_mask = 1ULL << 63; access_mask != ACCESS_LAST;
             access_mask >>= 1) {
                struct landlock_ruleset_attr ruleset_attr = {
                        .handled_access_fs = access_mask,
                };

                ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr,
                                                      sizeof(ruleset_attr), 0));
                ASSERT_EQ(EINVAL, errno);
        }
}

TEST_F_FORK(layout0, rule_with_unknown_access)
{
        __u64 access;
        struct landlock_path_beneath_attr path_beneath = {};
        const 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);

        path_beneath.parent_fd =
                open(TMP_DIR, O_PATH | O_DIRECTORY | O_CLOEXEC);
        ASSERT_LE(0, path_beneath.parent_fd);

        for (access = 1ULL << 63; access != ACCESS_LAST; access >>= 1) {
                path_beneath.allowed_access = access;
                EXPECT_EQ(-1, landlock_add_rule(ruleset_fd,
                                                LANDLOCK_RULE_PATH_BENEATH,
                                                &path_beneath, 0));
                EXPECT_EQ(EINVAL, errno);
        }
        ASSERT_EQ(0, close(path_beneath.parent_fd));
        ASSERT_EQ(0, close(ruleset_fd));
}

TEST_F_FORK(layout1, rule_with_unhandled_access)
{
        struct landlock_ruleset_attr ruleset_attr = {
                .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE,
        };
        struct landlock_path_beneath_attr path_beneath = {};
        int ruleset_fd;
        __u64 access;

        ruleset_fd =
                landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
        ASSERT_LE(0, ruleset_fd);

        path_beneath.parent_fd = open(file1_s1d2, O_PATH | O_CLOEXEC);
        ASSERT_LE(0, path_beneath.parent_fd);

        for (access = 1; access > 0; access <<= 1) {
                int err;

                path_beneath.allowed_access = access;
                err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
                                        &path_beneath, 0);
                if (access == ruleset_attr.handled_access_fs) {
                        EXPECT_EQ(0, err);
                } else {
                        EXPECT_EQ(-1, err);
                        EXPECT_EQ(EINVAL, errno);
                }
        }

        EXPECT_EQ(0, close(path_beneath.parent_fd));
        EXPECT_EQ(0, close(ruleset_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;
};

/* clang-format off */

#define ACCESS_RO ( \
        LANDLOCK_ACCESS_FS_READ_FILE | \
        LANDLOCK_ACCESS_FS_READ_DIR)

#define ACCESS_RW ( \
        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[])
{
        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++) {
                if (!rules[i].access)
                        continue;

                add_path_beneath(_metadata, ruleset_fd, rules[i].access,
                                 rules[i].path);
        }
        return ruleset_fd;
}

TEST_F_FORK(layout0, 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(layout0, unpriv)
{
        const struct rule rules[] = {
                {
                        .path = TMP_DIR,
                        .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 (with or without O_PATH). */
        ASSERT_EQ(EACCES, test_open("/", O_RDONLY));
        ASSERT_EQ(0, test_open("/", O_RDONLY | O_PATH));
        ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY));
        ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY | O_PATH));
        ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
        ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY | O_PATH));

        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 (with or without O_PATH). */
        ASSERT_EQ(EACCES, test_open(dir_s2d2, O_RDONLY));
        ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY | O_PATH));

        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, layer_rule_unions)
{
        const struct rule layer1[] = {
                {
                        .path = dir_s1d2,
                        .access = LANDLOCK_ACCESS_FS_READ_FILE,
                },
                /* dir_s1d3 should allow READ_FILE and WRITE_FILE (O_RDWR). */
                {
                        .path = dir_s1d3,
                        .access = LANDLOCK_ACCESS_FS_WRITE_FILE,
                },
                {},
        };
        const struct rule layer2[] = {
                /* Doesn't change anything from layer1. */
                {
                        .path = dir_s1d2,
                        .access = LANDLOCK_ACCESS_FS_READ_FILE |
                                  LANDLOCK_ACCESS_FS_WRITE_FILE,
                },
                {},
        };
        const struct rule layer3[] = {
                /* Only allows write (but not read) to dir_s1d3. */
                {
                        .path = dir_s1d2,
                        .access = LANDLOCK_ACCESS_FS_WRITE_FILE,
                },
                {},
        };
        int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1);

        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /* Checks s1d1 hierarchy with layer1. */
        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 with layer1. */
        ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
        ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY));
        ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR));
        ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));

        /* Checks s1d3 hierarchy with layer1. */
        ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
        ASSERT_EQ(0, test_open(file1_s1d3, O_WRONLY));
        /* dir_s1d3 should allow READ_FILE and WRITE_FILE (O_RDWR). */
        ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR));
        ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));

        /* Doesn't change anything from layer1. */
        ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /* Checks s1d1 hierarchy with layer2. */
        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 with layer2. */
        ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
        ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY));
        ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR));
        ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));

        /* Checks s1d3 hierarchy with layer2. */
        ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
        ASSERT_EQ(0, test_open(file1_s1d3, O_WRONLY));
        /* dir_s1d3 should allow READ_FILE and WRITE_FILE (O_RDWR). */
        ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR));
        ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));

        /* Only allows write (but not read) to dir_s1d3. */
        ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /* Checks s1d1 hierarchy with layer3. */
        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 with layer3. */
        ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDONLY));
        ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY));
        ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR));
        ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));

        /* Checks s1d3 hierarchy with layer3. */
        ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDONLY));
        ASSERT_EQ(0, test_open(file1_s1d3, O_WRONLY));
        /* dir_s1d3 should now deny READ_FILE and WRITE_FILE (O_RDWR). */
        ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDWR));
        ASSERT_EQ(EACCES, test_open(dir_s1d1, 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(layout0, max_layers)
{
        int i, err;
        const struct rule rules[] = {
                {
                        .path = TMP_DIR,
                        .access = ACCESS_RO,
                },
                {},
        };
        const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);

        ASSERT_LE(0, ruleset_fd);
        for (i = 0; i < 16; 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(__NR_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(__NR_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(__NR_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD,
                             dir_s1d2, 0))
        {
                TH_LOG("Failed to move mount: %s", strerror(errno));
        }

        ASSERT_EQ(0, syscall(__NR_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(__NR_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, topology_changes_with_net_only)
{
        const struct landlock_ruleset_attr ruleset_net = {
                .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
                                      LANDLOCK_ACCESS_NET_CONNECT_TCP,
        };
        int ruleset_fd;

        /* Add network restrictions. */
        ruleset_fd =
                landlock_create_ruleset(&ruleset_net, sizeof(ruleset_net), 0);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /* Mount, remount, move_mount, umount, and pivot_root checks. */
        set_cap(_metadata, CAP_SYS_ADMIN);
        ASSERT_EQ(0, mount_opt(&mnt_tmp, dir_s1d2));
        ASSERT_EQ(0, mount(NULL, dir_s1d2, NULL, MS_PRIVATE | MS_REC, NULL));
        ASSERT_EQ(0, syscall(__NR_move_mount, AT_FDCWD, dir_s1d2, AT_FDCWD,
                             dir_s2d2, 0));
        ASSERT_EQ(0, umount(dir_s2d2));
        ASSERT_EQ(0, syscall(__NR_pivot_root, dir_s3d2, dir_s3d3));
        ASSERT_EQ(0, chdir("/"));
        clear_cap(_metadata, CAP_SYS_ADMIN);
}

TEST_F_FORK(layout1, topology_changes_with_net_and_fs)
{
        const struct landlock_ruleset_attr ruleset_net_fs = {
                .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
                                      LANDLOCK_ACCESS_NET_CONNECT_TCP,
                .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE,
        };
        int ruleset_fd;

        /* Add network and filesystem restrictions. */
        ruleset_fd = landlock_create_ruleset(&ruleset_net_fs,
                                             sizeof(ruleset_net_fs), 0);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /* Mount, remount, move_mount, umount, and pivot_root checks. */
        set_cap(_metadata, CAP_SYS_ADMIN);
        ASSERT_EQ(-1, mount_opt(&mnt_tmp, dir_s1d2));
        ASSERT_EQ(EPERM, errno);
        ASSERT_EQ(-1, mount(NULL, dir_s3d2, NULL, MS_PRIVATE | MS_REC, NULL));
        ASSERT_EQ(EPERM, errno);
        ASSERT_EQ(-1, syscall(__NR_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD,
                              dir_s2d2, 0));
        ASSERT_EQ(EPERM, errno);
        ASSERT_EQ(-1, umount(dir_s3d2));
        ASSERT_EQ(EPERM, errno);
        ASSERT_EQ(-1, syscall(__NR_pivot_root, dir_s3d2, dir_s3d3));
        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));
}

/*
 * This test checks that a rule on a directory used as a mount point does not
 * grant access to the mount covering it.  It is a generalization of the bind
 * mount case in layout3_fs.hostfs.release_inodes that tests hidden mount points.
 */
TEST_F_FORK(layout1, covered_rule)
{
        const struct rule layer1[] = {
                {
                        .path = dir_s3d2,
                        .access = LANDLOCK_ACCESS_FS_READ_DIR,
                },
                {},
        };
        int ruleset_fd;

        /* Unmount to simplify FIXTURE_TEARDOWN. */
        set_cap(_metadata, CAP_SYS_ADMIN);
        ASSERT_EQ(0, umount(dir_s3d2));
        clear_cap(_metadata, CAP_SYS_ADMIN);

        /* Creates a ruleset with the future hidden directory. */
        ruleset_fd =
                create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_DIR, layer1);
        ASSERT_LE(0, ruleset_fd);

        /* Covers with a new mount point. */
        set_cap(_metadata, CAP_SYS_ADMIN);
        ASSERT_EQ(0, mount_opt(&mnt_tmp, dir_s3d2));
        clear_cap(_metadata, CAP_SYS_ADMIN);

        ASSERT_EQ(0, test_open(dir_s3d2, O_RDONLY));

        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /* Checks that access to the new mount point is denied. */
        ASSERT_EQ(EACCES, test_open(dir_s3d2, 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_file(struct __test_metadata *const _metadata,
                      const char *const src_path, 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(src_path, O_RDONLY | O_CLOEXEC);
        ASSERT_LE(0, src_fd)
        {
                TH_LOG("Failed to open \"%s\": %s", src_path, 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(__test_passed(_metadata) ? 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\"", path);
        };
}

static void test_check_exec(struct __test_metadata *const _metadata,
                            const int err, const char *const path)
{
        int ret;
        char *const argv[] = { (char *)path, NULL };

        ret = sys_execveat(AT_FDCWD, path, argv, NULL,
                           AT_EMPTY_PATH | AT_EXECVE_CHECK);
        if (err) {
                EXPECT_EQ(-1, ret);
                EXPECT_EQ(errno, err);
        } else {
                EXPECT_EQ(0, ret);
        }
}

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_file(_metadata, bin_true, file1_s1d1);
        copy_file(_metadata, bin_true, file1_s1d2);
        copy_file(_metadata, bin_true, file1_s1d3);

        /* Checks before file1_s1d1 being denied. */
        test_execute(_metadata, 0, file1_s1d1);
        test_check_exec(_metadata, 0, file1_s1d1);

        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);
        test_check_exec(_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);
        test_check_exec(_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_check_exec(_metadata, 0, file1_s1d3);
}

TEST_F_FORK(layout1, umount_sandboxer)
{
        int pipe_child[2], pipe_parent[2];
        char buf_parent;
        pid_t child;
        int status;

        copy_file(_metadata, bin_sandbox_and_launch, file1_s3d3);
        ASSERT_EQ(0, pipe2(pipe_child, 0));
        ASSERT_EQ(0, pipe2(pipe_parent, 0));

        child = fork();
        ASSERT_LE(0, child);
        if (child == 0) {
                char pipe_child_str[12], pipe_parent_str[12];
                char *const argv[] = { (char *)file1_s3d3,
                                       (char *)bin_wait_pipe, pipe_child_str,
                                       pipe_parent_str, NULL };

                /* Passes the pipe FDs to the executed binary and its child. */
                EXPECT_EQ(0, close(pipe_child[0]));
                EXPECT_EQ(0, close(pipe_parent[1]));
                snprintf(pipe_child_str, sizeof(pipe_child_str), "%d",
                         pipe_child[1]);
                snprintf(pipe_parent_str, sizeof(pipe_parent_str), "%d",
                         pipe_parent[0]);

                /*
                 * We need bin_sandbox_and_launch (copied inside the mount as
                 * file1_s3d3) to execute bin_wait_pipe (outside the mount) to
                 * make sure the mount point will not be EBUSY because of
                 * file1_s3d3 being in use.  This avoids a potential race
                 * condition between the following read() and umount() calls.
                 */
                ASSERT_EQ(0, execve(argv[0], argv, NULL))
                {
                        TH_LOG("Failed to execute \"%s\": %s", argv[0],
                               strerror(errno));
                };
                _exit(1);
                return;
        }

        EXPECT_EQ(0, close(pipe_child[1]));
        EXPECT_EQ(0, close(pipe_parent[0]));

        /* Waits for the child to sandbox itself. */
        EXPECT_EQ(1, read(pipe_child[0], &buf_parent, 1));

        /* Tests that the sandboxer is tied to its mount point. */
        set_cap(_metadata, CAP_SYS_ADMIN);
        EXPECT_EQ(-1, umount(dir_s3d2));
        EXPECT_EQ(EBUSY, errno);
        clear_cap(_metadata, CAP_SYS_ADMIN);

        /* Signals the child to launch a grandchild. */
        EXPECT_EQ(1, write(pipe_parent[1], ".", 1));

        /* Waits for the grandchild. */
        EXPECT_EQ(1, read(pipe_child[0], &buf_parent, 1));

        /* Tests that the domain's sandboxer is not tied to its mount point. */
        set_cap(_metadata, CAP_SYS_ADMIN);
        EXPECT_EQ(0, umount(dir_s3d2))
        {
                TH_LOG("Failed to umount \"%s\": %s", dir_s3d2,
                       strerror(errno));
        };
        clear_cap(_metadata, CAP_SYS_ADMIN);

        /* Signals the grandchild to terminate. */
        EXPECT_EQ(1, write(pipe_parent[1], ".", 1));
        ASSERT_EQ(child, waitpid(child, &status, 0));
        ASSERT_EQ(1, WIFEXITED(status));
        ASSERT_EQ(0, WEXITSTATUS(status));
}

TEST_F_FORK(layout1, link)
{
        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 = create_ruleset(_metadata, layer1[0].access, layer1);

        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(-1, link(file2_s1d3, file1_s1d2));
        ASSERT_EQ(EXDEV, errno);

        ASSERT_EQ(0, link(file2_s1d2, file1_s1d2));
        ASSERT_EQ(0, link(file2_s1d3, file1_s1d3));

        /* Prepares for next unlinks. */
        ASSERT_EQ(0, unlink(file2_s1d2));
        ASSERT_EQ(0, unlink(file2_s1d3));

        ruleset_fd = create_ruleset(_metadata, layer2[0].access, layer2);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /* Checks that linkind doesn't require the ability to delete a file. */
        ASSERT_EQ(0, link(file1_s1d2, file2_s1d2));
        ASSERT_EQ(0, link(file1_s1d3, file2_s1d3));
}

static int test_rename(const char *const oldpath, const char *const newpath)
{
        if (rename(oldpath, newpath))
                return errno;
        return 0;
}

static int test_exchange(const char *const oldpath, const char *const newpath)
{
        if (renameat2(AT_FDCWD, oldpath, AT_FDCWD, newpath, RENAME_EXCHANGE))
                return errno;
        return 0;
}

static int test_renameat(int olddirfd, const char *oldpath, int newdirfd,
                         const char *newpath)
{
        if (renameat2(olddirfd, oldpath, newdirfd, newpath, 0))
                return errno;
        return 0;
}

static int test_exchangeat(int olddirfd, const char *oldpath, int newdirfd,
                           const char *newpath)
{
        if (renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_EXCHANGE))
                return errno;
        return 0;
}

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_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(EACCES, errno);
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, file1_s1d3,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, 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);
        /* Checks that file1_s2d1 cannot be removed (instead of ENOTDIR). */
        ASSERT_EQ(-1, rename(dir_s2d2, file1_s2d1));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, dir_s2d2,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);
        /* Checks that file1_s1d1 cannot be removed (instead of EISDIR). */
        ASSERT_EQ(-1, rename(file1_s1d1, dir_s1d2));
        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(EACCES, 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);
        /* Checks that dir_s1d2 cannot be removed (instead of ENOTDIR). */
        ASSERT_EQ(-1, rename(dir_s1d2, file1_s1d1));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, dir_s1d2,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);
        /* Checks that dir_s1d2 cannot be removed (instead of EISDIR). */
        ASSERT_EQ(-1, rename(file1_s1d1, dir_s1d2));
        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, reparent_refer)
{
        const struct rule layer1[] = {
                {
                        .path = dir_s1d2,
                        .access = LANDLOCK_ACCESS_FS_REFER,
                },
                {
                        .path = dir_s2d2,
                        .access = LANDLOCK_ACCESS_FS_REFER,
                },
                {},
        };
        int ruleset_fd =
                create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REFER, layer1);

        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d1));
        ASSERT_EQ(EXDEV, errno);
        ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d2));
        ASSERT_EQ(EXDEV, errno);
        ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d3));
        ASSERT_EQ(EXDEV, errno);

        ASSERT_EQ(-1, rename(dir_s1d3, dir_s2d1));
        ASSERT_EQ(EXDEV, errno);
        ASSERT_EQ(-1, rename(dir_s1d3, dir_s2d2));
        ASSERT_EQ(EXDEV, errno);
        /*
         * Moving should only be allowed when the source and the destination
         * parent directory have REFER.
         */
        ASSERT_EQ(-1, rename(dir_s1d3, dir_s2d3));
        ASSERT_EQ(ENOTEMPTY, errno);
        ASSERT_EQ(0, unlink(file1_s2d3));
        ASSERT_EQ(0, unlink(file2_s2d3));
        ASSERT_EQ(0, rename(dir_s1d3, dir_s2d3));
}

/* Checks renames beneath dir_s1d1. */
static void refer_denied_by_default(struct __test_metadata *const _metadata,
                                    const struct rule layer1[],
                                    const int layer1_err,
                                    const struct rule layer2[])
{
        int ruleset_fd;

        ASSERT_EQ(0, unlink(file1_s1d2));

        ruleset_fd = create_ruleset(_metadata, layer1[0].access, layer1);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /*
         * If the first layer handles LANDLOCK_ACCESS_FS_REFER (according to
         * layer1_err), then it allows some different-parent renames and links.
         */
        ASSERT_EQ(layer1_err, test_rename(file1_s1d1, file1_s1d2));
        if (layer1_err == 0)
                ASSERT_EQ(layer1_err, test_rename(file1_s1d2, file1_s1d1));
        ASSERT_EQ(layer1_err, test_exchange(file2_s1d1, file2_s1d2));
        ASSERT_EQ(layer1_err, test_exchange(file2_s1d2, file2_s1d1));

        ruleset_fd = create_ruleset(_metadata, layer2[0].access, layer2);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /*
         * Now, either the first or the second layer does not handle
         * LANDLOCK_ACCESS_FS_REFER, which means that any different-parent
         * renames and links are denied, thus making the layer handling
         * LANDLOCK_ACCESS_FS_REFER null and void.
         */
        ASSERT_EQ(EXDEV, test_rename(file1_s1d1, file1_s1d2));
        ASSERT_EQ(EXDEV, test_exchange(file2_s1d1, file2_s1d2));
        ASSERT_EQ(EXDEV, test_exchange(file2_s1d2, file2_s1d1));
}

const struct rule layer_dir_s1d1_refer[] = {
        {
                .path = dir_s1d1,
                .access = LANDLOCK_ACCESS_FS_REFER,
        },
        {},
};

const struct rule layer_dir_s1d1_execute[] = {
        {
                /* Matches a parent directory. */
                .path = dir_s1d1,
                .access = LANDLOCK_ACCESS_FS_EXECUTE,
        },
        {},
};

const struct rule layer_dir_s2d1_execute[] = {
        {
                /* Does not match a parent directory. */
                .path = dir_s2d1,
                .access = LANDLOCK_ACCESS_FS_EXECUTE,
        },
        {},
};

/*
 * Tests precedence over renames: denied by default for different parent
 * directories, *with* a rule matching a parent directory, but not directly
 * denying access (with MAKE_REG nor REMOVE).
 */
TEST_F_FORK(layout1, refer_denied_by_default1)
{
        refer_denied_by_default(_metadata, layer_dir_s1d1_refer, 0,
                                layer_dir_s1d1_execute);
}

/*
 * Same test but this time turning around the ABI version order: the first
 * layer does not handle LANDLOCK_ACCESS_FS_REFER.
 */
TEST_F_FORK(layout1, refer_denied_by_default2)
{
        refer_denied_by_default(_metadata, layer_dir_s1d1_execute, EXDEV,
                                layer_dir_s1d1_refer);
}

/*
 * Tests precedence over renames: denied by default for different parent
 * directories, *without* a rule matching a parent directory, but not directly
 * denying access (with MAKE_REG nor REMOVE).
 */
TEST_F_FORK(layout1, refer_denied_by_default3)
{
        refer_denied_by_default(_metadata, layer_dir_s1d1_refer, 0,
                                layer_dir_s2d1_execute);
}

/*
 * Same test but this time turning around the ABI version order: the first
 * layer does not handle LANDLOCK_ACCESS_FS_REFER.
 */
TEST_F_FORK(layout1, refer_denied_by_default4)
{
        refer_denied_by_default(_metadata, layer_dir_s2d1_execute, EXDEV,
                                layer_dir_s1d1_refer);
}

/*
 * Tests walking through a denied root mount.
 */
TEST_F_FORK(layout1, refer_mount_root_deny)
{
        const struct landlock_ruleset_attr ruleset_attr = {
                .handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_DIR,
        };
        int root_fd, ruleset_fd;

        /* Creates a mount object from a non-mount point. */
        set_cap(_metadata, CAP_SYS_ADMIN);
        root_fd =
                open_tree(AT_FDCWD, dir_s1d1,
                          AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
        clear_cap(_metadata, CAP_SYS_ADMIN);
        ASSERT_LE(0, root_fd);

        ruleset_fd =
                landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
        ASSERT_LE(0, ruleset_fd);

        ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
        ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
        EXPECT_EQ(0, close(ruleset_fd));

        /* Link denied by Landlock: EACCES. */
        EXPECT_EQ(-1, linkat(root_fd, ".", root_fd, "does_not_exist", 0));
        EXPECT_EQ(EACCES, errno);

        /* renameat2() always returns EBUSY. */
        EXPECT_EQ(-1, renameat2(root_fd, ".", root_fd, "does_not_exist", 0));
        EXPECT_EQ(EBUSY, errno);

        EXPECT_EQ(0, close(root_fd));
}

TEST_F_FORK(layout1, refer_part_mount_tree_is_allowed)
{
        const struct rule layer1[] = {
                {
                        /* Parent mount point. */
                        .path = dir_s3d1,
                        .access = LANDLOCK_ACCESS_FS_REFER |
                                  LANDLOCK_ACCESS_FS_MAKE_REG,
                },
                {
                        /*
                         * Removing the source file is allowed because its
                         * access rights are already a superset of the
                         * destination.
                         */
                        .path = dir_s3d4,
                        .access = LANDLOCK_ACCESS_FS_REFER |
                                  LANDLOCK_ACCESS_FS_MAKE_REG |
                                  LANDLOCK_ACCESS_FS_REMOVE_FILE,
                },
                {},
        };
        int ruleset_fd;

        ASSERT_EQ(0, unlink(file1_s3d3));
        ruleset_fd = create_ruleset(_metadata,
                                    LANDLOCK_ACCESS_FS_REFER |
                                            LANDLOCK_ACCESS_FS_MAKE_REG |
                                            LANDLOCK_ACCESS_FS_REMOVE_FILE,
                                    layer1);

        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        ASSERT_EQ(0, rename(file1_s3d4, file1_s3d3));
}

TEST_F_FORK(layout1, reparent_link)
{
        const struct rule layer1[] = {
                {
                        .path = dir_s1d2,
                        .access = LANDLOCK_ACCESS_FS_MAKE_REG,
                },
                {
                        .path = dir_s1d3,
                        .access = LANDLOCK_ACCESS_FS_REFER,
                },
                {
                        .path = dir_s2d2,
                        .access = LANDLOCK_ACCESS_FS_REFER,
                },
                {
                        .path = dir_s2d3,
                        .access = LANDLOCK_ACCESS_FS_MAKE_REG,
                },
                {},
        };
        const int ruleset_fd = create_ruleset(
                _metadata,
                LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1);

        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        ASSERT_EQ(0, unlink(file1_s1d1));
        ASSERT_EQ(0, unlink(file1_s1d2));
        ASSERT_EQ(0, unlink(file1_s1d3));

        /* Denies linking because of missing MAKE_REG. */
        ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1));
        ASSERT_EQ(EACCES, errno);
        /* Denies linking because of missing source and destination REFER. */
        ASSERT_EQ(-1, link(file1_s2d1, file1_s1d2));
        ASSERT_EQ(EXDEV, errno);
        /* Denies linking because of missing source REFER. */
        ASSERT_EQ(-1, link(file1_s2d1, file1_s1d3));
        ASSERT_EQ(EXDEV, errno);

        /* Denies linking because of missing MAKE_REG. */
        ASSERT_EQ(-1, link(file1_s2d2, file1_s1d1));
        ASSERT_EQ(EACCES, errno);
        /* Denies linking because of missing destination REFER. */
        ASSERT_EQ(-1, link(file1_s2d2, file1_s1d2));
        ASSERT_EQ(EXDEV, errno);

        /* Allows linking because of REFER and MAKE_REG. */
        ASSERT_EQ(0, link(file1_s2d2, file1_s1d3));
        ASSERT_EQ(0, unlink(file1_s2d2));
        /* Reverse linking denied because of missing MAKE_REG. */
        ASSERT_EQ(-1, link(file1_s1d3, file1_s2d2));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(0, unlink(file1_s2d3));
        /* Checks reverse linking. */
        ASSERT_EQ(0, link(file1_s1d3, file1_s2d3));
        ASSERT_EQ(0, unlink(file1_s1d3));

        /*
         * This is OK for a file link, but it should not be allowed for a
         * directory rename (because of the superset of access rights.
         */
        ASSERT_EQ(0, link(file1_s2d3, file1_s1d3));
        ASSERT_EQ(0, unlink(file1_s1d3));

        ASSERT_EQ(-1, link(file2_s1d2, file1_s1d3));
        ASSERT_EQ(EXDEV, errno);
        ASSERT_EQ(-1, link(file2_s1d3, file1_s1d2));
        ASSERT_EQ(EXDEV, errno);

        ASSERT_EQ(0, link(file2_s1d2, file1_s1d2));
        ASSERT_EQ(0, link(file2_s1d3, file1_s1d3));
}

TEST_F_FORK(layout1, reparent_rename)
{
        /* Same rules as for reparent_link. */
        const struct rule layer1[] = {
                {
                        .path = dir_s1d2,
                        .access = LANDLOCK_ACCESS_FS_MAKE_REG,
                },
                {
                        .path = dir_s1d3,
                        .access = LANDLOCK_ACCESS_FS_REFER,
                },
                {
                        .path = dir_s2d2,
                        .access = LANDLOCK_ACCESS_FS_REFER,
                },
                {
                        .path = dir_s2d3,
                        .access = LANDLOCK_ACCESS_FS_MAKE_REG,
                },
                {},
        };
        const int ruleset_fd = create_ruleset(
                _metadata,
                LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1);

        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        ASSERT_EQ(0, unlink(file1_s1d2));
        ASSERT_EQ(0, unlink(file1_s1d3));

        /* Denies renaming because of missing MAKE_REG. */
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file2_s1d1, AT_FDCWD, file1_s1d1,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, file2_s1d1,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(0, unlink(file1_s1d1));
        ASSERT_EQ(-1, rename(file2_s1d1, file1_s1d1));
        ASSERT_EQ(EACCES, errno);
        /* Even denies same file exchange. */
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file2_s1d1, AT_FDCWD, file2_s1d1,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);

        /* Denies renaming because of missing source and destination REFER. */
        ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d2));
        ASSERT_EQ(EXDEV, errno);
        /*
         * Denies renaming because of missing MAKE_REG, source and destination
         * REFER.
         */
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, file2_s1d1,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file2_s1d1, AT_FDCWD, file1_s2d1,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);

        /* Denies renaming because of missing source REFER. */
        ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d3));
        ASSERT_EQ(EXDEV, errno);
        /* Denies renaming because of missing MAKE_REG. */
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, file2_s1d3,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);

        /* Denies renaming because of missing MAKE_REG. */
        ASSERT_EQ(-1, rename(file1_s2d2, file1_s1d1));
        ASSERT_EQ(EACCES, errno);
        /* Denies renaming because of missing destination REFER*/
        ASSERT_EQ(-1, rename(file1_s2d2, file1_s1d2));
        ASSERT_EQ(EXDEV, errno);

        /* Denies exchange because of one missing MAKE_REG. */
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, file2_s1d3,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);
        /* Allows renaming because of REFER and MAKE_REG. */
        ASSERT_EQ(0, rename(file1_s2d2, file1_s1d3));

        /* Reverse renaming denied because of missing MAKE_REG. */
        ASSERT_EQ(-1, rename(file1_s1d3, file1_s2d2));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(0, unlink(file1_s2d3));
        ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3));

        /* Tests reverse renaming. */
        ASSERT_EQ(0, rename(file1_s2d3, file1_s1d3));
        ASSERT_EQ(0, renameat2(AT_FDCWD, file2_s2d3, AT_FDCWD, file1_s1d3,
                               RENAME_EXCHANGE));
        ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3));

        /*
         * This is OK for a file rename, but it should not be allowed for a
         * directory rename (because of the superset of access rights).
         */
        ASSERT_EQ(0, rename(file1_s2d3, file1_s1d3));
        ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3));

        /*
         * Tests superset restrictions applied to directories.  Not only the
         * dir_s2d3's parent (dir_s2d2) should be taken into account but also
         * access rights tied to dir_s2d3. dir_s2d2 is missing one access right
         * compared to dir_s1d3/file1_s1d3 (MAKE_REG) but it is provided
         * directly by the moved dir_s2d3.
         */
        ASSERT_EQ(0, rename(dir_s2d3, file1_s1d3));
        ASSERT_EQ(0, rename(file1_s1d3, dir_s2d3));
        /*
         * The first rename is allowed but not the exchange because dir_s1d3's
         * parent (dir_s1d2) doesn't have REFER.
         */
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, dir_s1d3,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EXDEV, errno);
        ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, file1_s2d3,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EXDEV, errno);
        ASSERT_EQ(-1, rename(file1_s2d3, dir_s1d3));
        ASSERT_EQ(EXDEV, errno);

        ASSERT_EQ(-1, rename(file2_s1d2, file1_s1d3));
        ASSERT_EQ(EXDEV, errno);
        ASSERT_EQ(-1, rename(file2_s1d3, file1_s1d2));
        ASSERT_EQ(EXDEV, errno);

        /* Renaming in the same directory is always allowed. */
        ASSERT_EQ(0, rename(file2_s1d2, file1_s1d2));
        ASSERT_EQ(0, rename(file2_s1d3, file1_s1d3));

        ASSERT_EQ(0, unlink(file1_s1d2));
        /* Denies because of missing source MAKE_REG and destination REFER. */
        ASSERT_EQ(-1, rename(dir_s2d3, file1_s1d2));
        ASSERT_EQ(EXDEV, errno);

        ASSERT_EQ(0, unlink(file1_s1d3));
        /* Denies because of missing source MAKE_REG and REFER. */
        ASSERT_EQ(-1, rename(dir_s2d2, file1_s1d3));
        ASSERT_EQ(EXDEV, errno);
}

static void
reparent_exdev_layers_enforce1(struct __test_metadata *const _metadata)
{
        const struct rule layer1[] = {
                {
                        .path = dir_s1d2,
                        .access = LANDLOCK_ACCESS_FS_REFER,
                },
                {
                        /* Interesting for the layer2 tests. */
                        .path = dir_s1d3,
                        .access = LANDLOCK_ACCESS_FS_MAKE_REG,
                },
                {
                        .path = dir_s2d2,
                        .access = LANDLOCK_ACCESS_FS_REFER,
                },
                {
                        .path = dir_s2d3,
                        .access = LANDLOCK_ACCESS_FS_MAKE_REG,
                },
                {},
        };
        const int ruleset_fd = create_ruleset(
                _metadata,
                LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1);

        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));
}

static void
reparent_exdev_layers_enforce2(struct __test_metadata *const _metadata)
{
        const struct rule layer2[] = {
                {
                        .path = dir_s2d3,
                        .access = LANDLOCK_ACCESS_FS_MAKE_DIR,
                },
                {},
        };
        /*
         * Same checks as before but with a second layer and a new MAKE_DIR
         * rule (and no explicit handling of REFER).
         */
        const int ruleset_fd =
                create_ruleset(_metadata, LANDLOCK_ACCESS_FS_MAKE_DIR, layer2);

        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));
}

TEST_F_FORK(layout1, reparent_exdev_layers_rename1)
{
        ASSERT_EQ(0, unlink(file1_s2d2));
        ASSERT_EQ(0, unlink(file1_s2d3));

        reparent_exdev_layers_enforce1(_metadata);

        /*
         * Moving the dir_s1d3 directory below dir_s2d2 is allowed by Landlock
         * because it doesn't inherit new access rights.
         */
        ASSERT_EQ(0, rename(dir_s1d3, file1_s2d2));
        ASSERT_EQ(0, rename(file1_s2d2, dir_s1d3));

        /*
         * Moving the dir_s1d3 directory below dir_s2d3 is allowed, even if it
         * gets a new inherited access rights (MAKE_REG), because MAKE_REG is
         * already allowed for dir_s1d3.
         */
        ASSERT_EQ(0, rename(dir_s1d3, file1_s2d3));
        ASSERT_EQ(0, rename(file1_s2d3, dir_s1d3));

        /*
         * However, moving the file1_s1d3 file below dir_s2d3 is allowed
         * because it cannot inherit MAKE_REG right (which is dedicated to
         * directories).
         */
        ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3));

        reparent_exdev_layers_enforce2(_metadata);

        /*
         * Moving the dir_s1d3 directory below dir_s2d2 is now denied because
         * MAKE_DIR is not tied to dir_s2d2.
         */
        ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d2));
        ASSERT_EQ(EACCES, errno);

        /*
         * Moving the dir_s1d3 directory below dir_s2d3 is forbidden because it
         * would grants MAKE_REG and MAKE_DIR rights to it.
         */
        ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d3));
        ASSERT_EQ(EXDEV, errno);

        /*
         * Moving the file2_s1d3 file below dir_s2d3 is denied because the
         * second layer does not handle REFER, which is always denied by
         * default.
         */
        ASSERT_EQ(-1, rename(file2_s1d3, file1_s2d3));
        ASSERT_EQ(EXDEV, errno);
}

TEST_F_FORK(layout1, reparent_exdev_layers_rename2)
{
        reparent_exdev_layers_enforce1(_metadata);

        /* Checks EACCES predominance over EXDEV. */
        ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d2));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, rename(file1_s1d2, file1_s2d2));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d3));
        ASSERT_EQ(EXDEV, errno);
        /* Modify layout! */
        ASSERT_EQ(0, rename(file1_s1d2, file1_s2d3));

        /* Without REFER source. */
        ASSERT_EQ(-1, rename(dir_s1d1, file1_s2d2));
        ASSERT_EQ(EXDEV, errno);
        ASSERT_EQ(-1, rename(dir_s1d2, file1_s2d2));
        ASSERT_EQ(EXDEV, errno);

        reparent_exdev_layers_enforce2(_metadata);

        /* Checks EACCES predominance over EXDEV. */
        ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d2));
        ASSERT_EQ(EACCES, errno);
        /* Checks with actual file2_s1d2. */
        ASSERT_EQ(-1, rename(file2_s1d2, file1_s2d2));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d3));
        ASSERT_EQ(EXDEV, errno);
        /*
         * Modifying the layout is now denied because the second layer does not
         * handle REFER, which is always denied by default.
         */
        ASSERT_EQ(-1, rename(file2_s1d2, file1_s2d3));
        ASSERT_EQ(EXDEV, errno);

        /* Without REFER source, EACCES wins over EXDEV. */
        ASSERT_EQ(-1, rename(dir_s1d1, file1_s2d2));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, rename(dir_s1d2, file1_s2d2));
        ASSERT_EQ(EACCES, errno);
}

TEST_F_FORK(layout1, reparent_exdev_layers_exchange1)
{
        const char *const dir_file1_s1d2 = file1_s1d2, *const dir_file2_s2d3 =
                                                               file2_s2d3;

        ASSERT_EQ(0, unlink(file1_s1d2));
        ASSERT_EQ(0, mkdir(file1_s1d2, 0700));
        ASSERT_EQ(0, unlink(file2_s2d3));
        ASSERT_EQ(0, mkdir(file2_s2d3, 0700));

        reparent_exdev_layers_enforce1(_metadata);

        /* Error predominance with file exchange: returns EXDEV and EACCES. */
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d3,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, file1_s1d1,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);

        /*
         * Checks with directories which creation could be allowed, but denied
         * because of access rights that would be inherited.
         */
        ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD,
                                dir_file2_s2d3, RENAME_EXCHANGE));
        ASSERT_EQ(EXDEV, errno);
        ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD,
                                dir_file1_s1d2, RENAME_EXCHANGE));
        ASSERT_EQ(EXDEV, errno);

        /* Checks with same access rights. */
        ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, dir_s2d3,
                               RENAME_EXCHANGE));
        ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_s1d3,
                               RENAME_EXCHANGE));

        /* Checks with different (child-only) access rights. */
        ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_file1_s1d2,
                               RENAME_EXCHANGE));
        ASSERT_EQ(0, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD, dir_s2d3,
                               RENAME_EXCHANGE));

        /*
         * Checks that exchange between file and directory are consistent.
         *
         * Moving a file (file1_s2d2) to a directory which only grants more
         * directory-related access rights is allowed, and at the same time
         * moving a directory (dir_file2_s2d3) to another directory which
         * grants less access rights is allowed too.
         *
         * See layout1.reparent_exdev_layers_exchange3 for inverted arguments.
         */
        ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3,
                               RENAME_EXCHANGE));
        /*
         * However, moving back the directory is denied because it would get
         * more access rights than the current state and because file creation
         * is forbidden (in dir_s2d2).
         */
        ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);

        reparent_exdev_layers_enforce2(_metadata);

        /* Error predominance with file exchange: returns EXDEV and EACCES. */
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d3,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, file1_s1d1,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);

        /* Checks with directories which creation is now denied. */
        ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD,
                                dir_file2_s2d3, RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD,
                                dir_file1_s1d2, RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);

        /* Checks with different (child-only) access rights. */
        ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, dir_s2d3,
                                RENAME_EXCHANGE));
        /* Denied because of MAKE_DIR. */
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_s1d3,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);

        /* Checks with different (child-only) access rights. */
        ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_file1_s1d2,
                                RENAME_EXCHANGE));
        /* Denied because of MAKE_DIR. */
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD, dir_s2d3,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);

        /* See layout1.reparent_exdev_layers_exchange2 for complement. */
}

TEST_F_FORK(layout1, reparent_exdev_layers_exchange2)
{
        const char *const dir_file2_s2d3 = file2_s2d3;

        ASSERT_EQ(0, unlink(file2_s2d3));
        ASSERT_EQ(0, mkdir(file2_s2d3, 0700));

        reparent_exdev_layers_enforce1(_metadata);
        reparent_exdev_layers_enforce2(_metadata);

        /* Checks that exchange between file and directory are consistent. */
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);
}

TEST_F_FORK(layout1, reparent_exdev_layers_exchange3)
{
        const char *const dir_file2_s2d3 = file2_s2d3;

        ASSERT_EQ(0, unlink(file2_s2d3));
        ASSERT_EQ(0, mkdir(file2_s2d3, 0700));

        reparent_exdev_layers_enforce1(_metadata);

        /*
         * Checks that exchange between file and directory are consistent,
         * including with inverted arguments (see
         * layout1.reparent_exdev_layers_exchange1).
         */
        ASSERT_EQ(0, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2,
                               RENAME_EXCHANGE));
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);
}

TEST_F_FORK(layout1, reparent_remove)
{
        const struct rule layer1[] = {
                {
                        .path = dir_s1d1,
                        .access = LANDLOCK_ACCESS_FS_REFER |
                                  LANDLOCK_ACCESS_FS_REMOVE_DIR,
                },
                {
                        .path = dir_s1d2,
                        .access = LANDLOCK_ACCESS_FS_REMOVE_FILE,
                },
                {
                        .path = dir_s2d1,
                        .access = LANDLOCK_ACCESS_FS_REFER |
                                  LANDLOCK_ACCESS_FS_REMOVE_FILE,
                },
                {},
        };
        const int ruleset_fd = create_ruleset(
                _metadata,
                LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_REMOVE_DIR |
                        LANDLOCK_ACCESS_FS_REMOVE_FILE,
                layer1);

        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /* Access denied because of wrong/swapped remove file/dir. */
        ASSERT_EQ(-1, rename(file1_s1d1, dir_s2d2));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, rename(dir_s2d2, file1_s1d1));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, dir_s2d2,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, dir_s2d3,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);

        /* Access allowed thanks to the matching rights. */
        ASSERT_EQ(-1, rename(file1_s2d1, dir_s1d2));
        ASSERT_EQ(EISDIR, errno);
        ASSERT_EQ(-1, rename(dir_s1d2, file1_s2d1));
        ASSERT_EQ(ENOTDIR, errno);
        ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d1));
        ASSERT_EQ(ENOTDIR, errno);
        ASSERT_EQ(0, unlink(file1_s2d1));
        ASSERT_EQ(0, unlink(file1_s1d3));
        ASSERT_EQ(0, unlink(file2_s1d3));
        ASSERT_EQ(0, rename(dir_s1d3, file1_s2d1));

        /* Effectively removes a file and a directory by exchanging them. */
        ASSERT_EQ(0, mkdir(dir_s1d3, 0700));
        ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s1d3,
                               RENAME_EXCHANGE));
        ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s1d3,
                                RENAME_EXCHANGE));
        ASSERT_EQ(EACCES, errno);
}

TEST_F_FORK(layout1, reparent_dom_superset)
{
        const struct rule layer1[] = {
                {
                        .path = dir_s1d2,
                        .access = LANDLOCK_ACCESS_FS_REFER,
                },
                {
                        .path = file1_s1d2,
                        .access = LANDLOCK_ACCESS_FS_EXECUTE,
                },
                {
                        .path = dir_s1d3,
                        .access = LANDLOCK_ACCESS_FS_MAKE_SOCK |
                                  LANDLOCK_ACCESS_FS_EXECUTE,
                },
                {
                        .path = dir_s2d2,
                        .access = LANDLOCK_ACCESS_FS_REFER |
                                  LANDLOCK_ACCESS_FS_EXECUTE |
                                  LANDLOCK_ACCESS_FS_MAKE_SOCK,
                },
                {
                        .path = dir_s2d3,
                        .access = LANDLOCK_ACCESS_FS_READ_FILE |
                                  LANDLOCK_ACCESS_FS_MAKE_FIFO,
                },
                {},
        };
        int ruleset_fd = create_ruleset(_metadata,
                                        LANDLOCK_ACCESS_FS_REFER |
                                                LANDLOCK_ACCESS_FS_EXECUTE |
                                                LANDLOCK_ACCESS_FS_MAKE_SOCK |
                                                LANDLOCK_ACCESS_FS_READ_FILE |
                                                LANDLOCK_ACCESS_FS_MAKE_FIFO,
                                        layer1);

        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        ASSERT_EQ(-1, rename(file1_s1d2, file1_s2d1));
        ASSERT_EQ(EXDEV, errno);
        /*
         * Moving file1_s1d2 beneath dir_s2d3 would grant it the READ_FILE
         * access right.
         */
        ASSERT_EQ(-1, rename(file1_s1d2, file1_s2d3));
        ASSERT_EQ(EXDEV, errno);
        /*
         * Moving file1_s1d2 should be allowed even if dir_s2d2 grants a
         * superset of access rights compared to dir_s1d2, because file1_s1d2
         * already has these access rights anyway.
         */
        ASSERT_EQ(0, rename(file1_s1d2, file1_s2d2));
        ASSERT_EQ(0, rename(file1_s2d2, file1_s1d2));

        ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d1));
        ASSERT_EQ(EXDEV, errno);
        /*
         * Moving dir_s1d3 beneath dir_s2d3 would grant it the MAKE_FIFO access
         * right.
         */
        ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d3));
        ASSERT_EQ(EXDEV, errno);
        /*
         * Moving dir_s1d3 should be allowed even if dir_s2d2 grants a superset
         * of access rights compared to dir_s1d2, because dir_s1d3 already has
         * these access rights anyway.
         */
        ASSERT_EQ(0, rename(dir_s1d3, file1_s2d2));
        ASSERT_EQ(0, rename(file1_s2d2, dir_s1d3));

        /*
         * Moving file1_s2d3 beneath dir_s1d2 is allowed, but moving it back
         * will be denied because the new inherited access rights from dir_s1d2
         * will be less than the destination (original) dir_s2d3.  This is a
         * sinkhole scenario where we cannot move back files or directories.
         */
        ASSERT_EQ(0, rename(file1_s2d3, file2_s1d2));
        ASSERT_EQ(-1, rename(file2_s1d2, file1_s2d3));
        ASSERT_EQ(EXDEV, errno);
        ASSERT_EQ(0, unlink(file2_s1d2));
        ASSERT_EQ(0, unlink(file2_s2d3));
        /*
         * Checks similar directory one-way move: dir_s2d3 loses EXECUTE and
         * MAKE_SOCK which were inherited from dir_s1d3.
         */
        ASSERT_EQ(0, rename(dir_s2d3, file2_s1d2));
        ASSERT_EQ(-1, rename(file2_s1d2, dir_s2d3));
        ASSERT_EQ(EXDEV, errno);
}

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]));
}

/* Invokes truncate(2) and returns its errno or 0. */
static int test_truncate(const char *const path)
{
        if (truncate(path, 10) < 0)
                return errno;
        return 0;
}

/*
 * Invokes creat(2) and returns its errno or 0.
 * Closes the opened file descriptor on success.
 */
static int test_creat(const char *const path)
{
        int fd = creat(path, 0600);

        if (fd < 0)
                return errno;

        /*
         * Mixing error codes from close(2) and creat(2) should not lead to any
         * (access type) confusion for this test.
         */
        if (close(fd) < 0)
                return errno;
        return 0;
}

/*
 * Exercises file truncation when it's not restricted,
 * as it was the case before LANDLOCK_ACCESS_FS_TRUNCATE existed.
 */
TEST_F_FORK(layout1, truncate_unhandled)
{
        const char *const file_r = file1_s1d1;
        const char *const file_w = file2_s1d1;
        const char *const file_none = file1_s1d2;
        const struct rule rules[] = {
                {
                        .path = file_r,
                        .access = LANDLOCK_ACCESS_FS_READ_FILE,
                },
                {
                        .path = file_w,
                        .access = LANDLOCK_ACCESS_FS_WRITE_FILE,
                },
                /* Implicitly: No rights for file_none. */
                {},
        };

        const __u64 handled = LANDLOCK_ACCESS_FS_READ_FILE |
                              LANDLOCK_ACCESS_FS_WRITE_FILE;
        int ruleset_fd;

        /* Enables Landlock. */
        ruleset_fd = create_ruleset(_metadata, handled, rules);

        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /*
         * Checks read right: truncate and open with O_TRUNC work, unless the
         * file is attempted to be opened for writing.
         */
        EXPECT_EQ(0, test_truncate(file_r));
        EXPECT_EQ(0, test_open(file_r, O_RDONLY | O_TRUNC));
        EXPECT_EQ(EACCES, test_open(file_r, O_WRONLY | O_TRUNC));
        EXPECT_EQ(EACCES, test_creat(file_r));

        /*
         * Checks write right: truncate and open with O_TRUNC work, unless the
         * file is attempted to be opened for reading.
         */
        EXPECT_EQ(0, test_truncate(file_w));
        EXPECT_EQ(EACCES, test_open(file_w, O_RDONLY | O_TRUNC));
        EXPECT_EQ(0, test_open(file_w, O_WRONLY | O_TRUNC));
        EXPECT_EQ(0, test_creat(file_w));

        /*
         * Checks "no rights" case: truncate works but all open attempts fail,
         * including creat.
         */
        EXPECT_EQ(0, test_truncate(file_none));
        EXPECT_EQ(EACCES, test_open(file_none, O_RDONLY | O_TRUNC));
        EXPECT_EQ(EACCES, test_open(file_none, O_WRONLY | O_TRUNC));
        EXPECT_EQ(EACCES, test_creat(file_none));
}

TEST_F_FORK(layout1, truncate)
{
        const char *const file_rwt = file1_s1d1;
        const char *const file_rw = file2_s1d1;
        const char *const file_rt = file1_s1d2;
        const char *const file_t = file2_s1d2;
        const char *const file_none = file1_s1d3;
        const char *const dir_t = dir_s2d1;
        const char *const file_in_dir_t = file1_s2d1;
        const char *const dir_w = dir_s3d1;
        const char *const file_in_dir_w = file1_s3d1;
        const struct rule rules[] = {
                {
                        .path = file_rwt,
                        .access = LANDLOCK_ACCESS_FS_READ_FILE |
                                  LANDLOCK_ACCESS_FS_WRITE_FILE |
                                  LANDLOCK_ACCESS_FS_TRUNCATE,
                },
                {
                        .path = file_rw,
                        .access = LANDLOCK_ACCESS_FS_READ_FILE |
                                  LANDLOCK_ACCESS_FS_WRITE_FILE,
                },
                {
                        .path = file_rt,
                        .access = LANDLOCK_ACCESS_FS_READ_FILE |
                                  LANDLOCK_ACCESS_FS_TRUNCATE,
                },
                {
                        .path = file_t,
                        .access = LANDLOCK_ACCESS_FS_TRUNCATE,
                },
                /* Implicitly: No access rights for file_none. */
                {
                        .path = dir_t,
                        .access = LANDLOCK_ACCESS_FS_TRUNCATE,
                },
                {
                        .path = dir_w,
                        .access = LANDLOCK_ACCESS_FS_WRITE_FILE,
                },
                {},
        };
        const __u64 handled = LANDLOCK_ACCESS_FS_READ_FILE |
                              LANDLOCK_ACCESS_FS_WRITE_FILE |
                              LANDLOCK_ACCESS_FS_TRUNCATE;
        int ruleset_fd;

        /* Enables Landlock. */
        ruleset_fd = create_ruleset(_metadata, handled, rules);

        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /* Checks read, write and truncate rights: truncation works. */
        EXPECT_EQ(0, test_truncate(file_rwt));
        EXPECT_EQ(0, test_open(file_rwt, O_RDONLY | O_TRUNC));
        EXPECT_EQ(0, test_open(file_rwt, O_WRONLY | O_TRUNC));

        /* Checks read and write rights: no truncate variant works. */
        EXPECT_EQ(EACCES, test_truncate(file_rw));
        EXPECT_EQ(EACCES, test_open(file_rw, O_RDONLY | O_TRUNC));
        EXPECT_EQ(EACCES, test_open(file_rw, O_WRONLY | O_TRUNC));

        /*
         * Checks read and truncate rights: truncation works.
         *
         * Note: Files can get truncated using open() even with O_RDONLY.
         */
        EXPECT_EQ(0, test_truncate(file_rt));
        EXPECT_EQ(0, test_open(file_rt, O_RDONLY | O_TRUNC));
        EXPECT_EQ(EACCES, test_open(file_rt, O_WRONLY | O_TRUNC));

        /* Checks truncate right: truncate works, but can't open file. */
        EXPECT_EQ(0, test_truncate(file_t));
        EXPECT_EQ(EACCES, test_open(file_t, O_RDONLY | O_TRUNC));
        EXPECT_EQ(EACCES, test_open(file_t, O_WRONLY | O_TRUNC));

        /* Checks "no rights" case: No form of truncation works. */
        EXPECT_EQ(EACCES, test_truncate(file_none));
        EXPECT_EQ(EACCES, test_open(file_none, O_RDONLY | O_TRUNC));
        EXPECT_EQ(EACCES, test_open(file_none, O_WRONLY | O_TRUNC));

        /*
         * Checks truncate right on directory: truncate works on contained
         * files.
         */
        EXPECT_EQ(0, test_truncate(file_in_dir_t));
        EXPECT_EQ(EACCES, test_open(file_in_dir_t, O_RDONLY | O_TRUNC));
        EXPECT_EQ(EACCES, test_open(file_in_dir_t, O_WRONLY | O_TRUNC));

        /*
         * Checks creat in dir_w: This requires the truncate right when
         * overwriting an existing file, but does not require it when the file
         * is new.
         */
        EXPECT_EQ(EACCES, test_creat(file_in_dir_w));

        ASSERT_EQ(0, unlink(file_in_dir_w));
        EXPECT_EQ(0, test_creat(file_in_dir_w));
}

/* Invokes ftruncate(2) and returns its errno or 0. */
static int test_ftruncate(int fd)
{
        if (ftruncate(fd, 10) < 0)
                return errno;
        return 0;
}

TEST_F_FORK(layout1, ftruncate)
{
        /*
         * This test opens a new file descriptor at different stages of
         * Landlock restriction:
         *
         * without restriction:                    ftruncate works
         * something else but truncate restricted: ftruncate works
         * truncate restricted and permitted:      ftruncate works
         * truncate restricted and not permitted:  ftruncate fails
         *
         * Whether this works or not is expected to depend on the time when the
         * FD was opened, not to depend on the time when ftruncate() was
         * called.
         */
        const char *const path = file1_s1d1;
        const __u64 handled1 = LANDLOCK_ACCESS_FS_READ_FILE |
                               LANDLOCK_ACCESS_FS_WRITE_FILE;
        const struct rule layer1[] = {
                {
                        .path = path,
                        .access = LANDLOCK_ACCESS_FS_WRITE_FILE,
                },
                {},
        };
        const __u64 handled2 = LANDLOCK_ACCESS_FS_TRUNCATE;
        const struct rule layer2[] = {
                {
                        .path = path,
                        .access = LANDLOCK_ACCESS_FS_TRUNCATE,
                },
                {},
        };
        const __u64 handled3 = LANDLOCK_ACCESS_FS_TRUNCATE |
                               LANDLOCK_ACCESS_FS_WRITE_FILE;
        const struct rule layer3[] = {
                {
                        .path = path,
                        .access = LANDLOCK_ACCESS_FS_WRITE_FILE,
                },
                {},
        };
        int fd_layer0, fd_layer1, fd_layer2, fd_layer3, ruleset_fd;

        fd_layer0 = open(path, O_WRONLY);
        EXPECT_EQ(0, test_ftruncate(fd_layer0));

        ruleset_fd = create_ruleset(_metadata, handled1, layer1);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        fd_layer1 = open(path, O_WRONLY);
        EXPECT_EQ(0, test_ftruncate(fd_layer0));
        EXPECT_EQ(0, test_ftruncate(fd_layer1));

        ruleset_fd = create_ruleset(_metadata, handled2, layer2);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        fd_layer2 = open(path, O_WRONLY);
        EXPECT_EQ(0, test_ftruncate(fd_layer0));
        EXPECT_EQ(0, test_ftruncate(fd_layer1));
        EXPECT_EQ(0, test_ftruncate(fd_layer2));

        ruleset_fd = create_ruleset(_metadata, handled3, layer3);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        fd_layer3 = open(path, O_WRONLY);
        EXPECT_EQ(0, test_ftruncate(fd_layer0));
        EXPECT_EQ(0, test_ftruncate(fd_layer1));
        EXPECT_EQ(0, test_ftruncate(fd_layer2));
        EXPECT_EQ(EACCES, test_ftruncate(fd_layer3));

        ASSERT_EQ(0, close(fd_layer0));
        ASSERT_EQ(0, close(fd_layer1));
        ASSERT_EQ(0, close(fd_layer2));
        ASSERT_EQ(0, close(fd_layer3));
}

/* clang-format off */
FIXTURE(ftruncate) {};
/* clang-format on */

FIXTURE_SETUP(ftruncate)
{
        prepare_layout(_metadata);
        create_file(_metadata, file1_s1d1);
}

FIXTURE_TEARDOWN_PARENT(ftruncate)
{
        EXPECT_EQ(0, remove_path(file1_s1d1));
        cleanup_layout(_metadata);
}

FIXTURE_VARIANT(ftruncate)
{
        const __u64 handled;
        const __u64 allowed;
        const int expected_open_result;
        const int expected_ftruncate_result;
};

/* clang-format off */
FIXTURE_VARIANT_ADD(ftruncate, w_w) {
        /* clang-format on */
        .handled = LANDLOCK_ACCESS_FS_WRITE_FILE,
        .allowed = LANDLOCK_ACCESS_FS_WRITE_FILE,
        .expected_open_result = 0,
        .expected_ftruncate_result = 0,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(ftruncate, t_t) {
        /* clang-format on */
        .handled = LANDLOCK_ACCESS_FS_TRUNCATE,
        .allowed = LANDLOCK_ACCESS_FS_TRUNCATE,
        .expected_open_result = 0,
        .expected_ftruncate_result = 0,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(ftruncate, wt_w) {
        /* clang-format on */
        .handled = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_TRUNCATE,
        .allowed = LANDLOCK_ACCESS_FS_WRITE_FILE,
        .expected_open_result = 0,
        .expected_ftruncate_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(ftruncate, wt_wt) {
        /* clang-format on */
        .handled = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_TRUNCATE,
        .allowed = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_TRUNCATE,
        .expected_open_result = 0,
        .expected_ftruncate_result = 0,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(ftruncate, wt_t) {
        /* clang-format on */
        .handled = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_TRUNCATE,
        .allowed = LANDLOCK_ACCESS_FS_TRUNCATE,
        .expected_open_result = EACCES,
};

TEST_F_FORK(ftruncate, open_and_ftruncate)
{
        const char *const path = file1_s1d1;
        const struct rule rules[] = {
                {
                        .path = path,
                        .access = variant->allowed,
                },
                {},
        };
        int fd, ruleset_fd;

        /* Enables Landlock. */
        ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        fd = open(path, O_WRONLY);
        EXPECT_EQ(variant->expected_open_result, (fd < 0 ? errno : 0));
        if (fd >= 0) {
                EXPECT_EQ(variant->expected_ftruncate_result,
                          test_ftruncate(fd));
                ASSERT_EQ(0, close(fd));
        }
}

TEST_F_FORK(ftruncate, open_and_ftruncate_in_different_processes)
{
        int child, fd, status;
        int socket_fds[2];

        ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0,
                                socket_fds));

        child = fork();
        ASSERT_LE(0, child);
        if (child == 0) {
                /*
                 * Enables Landlock in the child process, open a file descriptor
                 * where truncation is forbidden and send it to the
                 * non-landlocked parent process.
                 */
                const char *const path = file1_s1d1;
                const struct rule rules[] = {
                        {
                                .path = path,
                                .access = variant->allowed,
                        },
                        {},
                };
                int fd, ruleset_fd;

                ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
                ASSERT_LE(0, ruleset_fd);
                enforce_ruleset(_metadata, ruleset_fd);
                ASSERT_EQ(0, close(ruleset_fd));

                fd = open(path, O_WRONLY);
                ASSERT_EQ(variant->expected_open_result, (fd < 0 ? errno : 0));

                if (fd >= 0) {
                        ASSERT_EQ(0, send_fd(socket_fds[0], fd));
                        ASSERT_EQ(0, close(fd));
                }

                ASSERT_EQ(0, close(socket_fds[0]));

                _exit(_metadata->exit_code);
                return;
        }

        if (variant->expected_open_result == 0) {
                fd = recv_fd(socket_fds[1]);
                ASSERT_LE(0, fd);

                EXPECT_EQ(variant->expected_ftruncate_result,
                          test_ftruncate(fd));
                ASSERT_EQ(0, close(fd));
        }

        ASSERT_EQ(child, waitpid(child, &status, 0));
        ASSERT_EQ(1, WIFEXITED(status));
        ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));

        ASSERT_EQ(0, close(socket_fds[0]));
        ASSERT_EQ(0, close(socket_fds[1]));
}

/* Invokes the FS_IOC_GETFLAGS IOCTL and returns its errno or 0. */
static int test_fs_ioc_getflags_ioctl(int fd)
{
        uint32_t flags;

        if (ioctl(fd, FS_IOC_GETFLAGS, &flags) < 0)
                return errno;
        return 0;
}

TEST(memfd_ftruncate_and_ioctl)
{
        const struct landlock_ruleset_attr attr = {
                .handled_access_fs = ACCESS_ALL,
        };
        int ruleset_fd, fd, i;

        /*
         * We exercise the same test both with and without Landlock enabled, to
         * ensure that it behaves the same in both cases.
         */
        for (i = 0; i < 2; i++) {
                /* Creates a new memfd. */
                fd = memfd_create("name", MFD_CLOEXEC);
                ASSERT_LE(0, fd);

                /*
                 * Checks that operations associated with the opened file
                 * (ftruncate, ioctl) are permitted on file descriptors that are
                 * created in ways other than open(2).
                 */
                EXPECT_EQ(0, test_ftruncate(fd));
                EXPECT_EQ(0, test_fs_ioc_getflags_ioctl(fd));

                ASSERT_EQ(0, close(fd));

                /* Enables Landlock. */
                ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
                ASSERT_LE(0, ruleset_fd);
                enforce_ruleset(_metadata, ruleset_fd);
                ASSERT_EQ(0, close(ruleset_fd));
        }
}

static int test_fionread_ioctl(int fd)
{
        size_t sz = 0;

        if (ioctl(fd, FIONREAD, &sz) < 0 && errno == EACCES)
                return errno;
        return 0;
}

TEST_F_FORK(layout1, o_path_ftruncate_and_ioctl)
{
        const struct landlock_ruleset_attr attr = {
                .handled_access_fs = ACCESS_ALL,
        };
        int ruleset_fd, fd;

        /*
         * Checks that for files opened with O_PATH, both ioctl(2) and
         * ftruncate(2) yield EBADF, as it is documented in open(2) for the
         * O_PATH flag.
         */
        fd = open(dir_s1d1, O_PATH | O_CLOEXEC);
        ASSERT_LE(0, fd);

        EXPECT_EQ(EBADF, test_ftruncate(fd));
        EXPECT_EQ(EBADF, test_fs_ioc_getflags_ioctl(fd));

        ASSERT_EQ(0, close(fd));

        /* Enables Landlock. */
        ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /*
         * Checks that after enabling Landlock,
         * - the file can still be opened with O_PATH
         * - both ioctl and truncate still yield EBADF (not EACCES).
         */
        fd = open(dir_s1d1, O_PATH | O_CLOEXEC);
        ASSERT_LE(0, fd);

        EXPECT_EQ(EBADF, test_ftruncate(fd));
        EXPECT_EQ(EBADF, test_fs_ioc_getflags_ioctl(fd));

        ASSERT_EQ(0, close(fd));
}

/*
 * ioctl_error - generically call the given ioctl with a pointer to a
 * sufficiently large zeroed-out memory region.
 *
 * Returns the IOCTLs error, or 0.
 */
static int ioctl_error(struct __test_metadata *const _metadata, int fd,
                       unsigned int cmd)
{
        char buf[128]; /* sufficiently large */
        int res, stdinbak_fd;

        /*
         * Depending on the IOCTL command, parts of the zeroed-out buffer might
         * be interpreted as file descriptor numbers.  We do not want to
         * accidentally operate on file descriptor 0 (stdin), so we temporarily
         * move stdin to a different FD and close FD 0 for the IOCTL call.
         */
        stdinbak_fd = dup(0);
        ASSERT_LT(0, stdinbak_fd);
        ASSERT_EQ(0, close(0));

        /* Invokes the IOCTL with a zeroed-out buffer. */
        bzero(&buf, sizeof(buf));
        res = ioctl(fd, cmd, &buf);

        /* Restores the old FD 0 and closes the backup FD. */
        ASSERT_EQ(0, dup2(stdinbak_fd, 0));
        ASSERT_EQ(0, close(stdinbak_fd));

        if (res < 0)
                return errno;

        return 0;
}

/* Define some linux/falloc.h IOCTL commands which are not available in uapi headers. */
struct space_resv {
        __s16 l_type;
        __s16 l_whence;
        __s64 l_start;
        __s64 l_len; /* len == 0 means until end of file */
        __s32 l_sysid;
        __u32 l_pid;
        __s32 l_pad[4]; /* reserved area */
};

#define FS_IOC_RESVSP _IOW('X', 40, struct space_resv)
#define FS_IOC_UNRESVSP _IOW('X', 41, struct space_resv)
#define FS_IOC_RESVSP64 _IOW('X', 42, struct space_resv)
#define FS_IOC_UNRESVSP64 _IOW('X', 43, struct space_resv)
#define FS_IOC_ZERO_RANGE _IOW('X', 57, struct space_resv)

/*
 * Tests a series of blanket-permitted and denied IOCTLs.
 */
TEST_F_FORK(layout1, blanket_permitted_ioctls)
{
        const struct landlock_ruleset_attr attr = {
                .handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV,
        };
        int ruleset_fd, fd;

        /* Enables Landlock. */
        ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        fd = open("/dev/null", O_RDWR | O_CLOEXEC);
        ASSERT_LE(0, fd);

        /*
         * Checks permitted commands.
         * These ones may return errors, but should not be blocked by Landlock.
         */
        EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIOCLEX));
        EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIONCLEX));
        EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIONBIO));
        EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIOASYNC));
        EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIOQSIZE));
        EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIFREEZE));
        EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FITHAW));
        EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FS_IOC_FIEMAP));
        EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIGETBSZ));
        EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FICLONE));
        EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FICLONERANGE));
        EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIDEDUPERANGE));
        EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FS_IOC_GETFSUUID));
        EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FS_IOC_GETFSSYSFSPATH));

        /*
         * Checks blocked commands.
         * A call to a blocked IOCTL command always returns EACCES.
         */
        EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FIONREAD));
        EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_GETFLAGS));
        EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_SETFLAGS));
        EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_FSGETXATTR));
        EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_FSSETXATTR));
        EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FIBMAP));
        EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_RESVSP));
        EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_RESVSP64));
        EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_UNRESVSP));
        EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_UNRESVSP64));
        EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_ZERO_RANGE));

        /* Default case is also blocked. */
        EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, 0xc00ffeee));

        ASSERT_EQ(0, close(fd));
}

/*
 * Named pipes are not governed by the LANDLOCK_ACCESS_FS_IOCTL_DEV right,
 * because they are not character or block devices.
 */
TEST_F_FORK(layout1, named_pipe_ioctl)
{
        pid_t child_pid;
        int fd, ruleset_fd;
        const char *const path = file1_s1d1;
        const struct landlock_ruleset_attr attr = {
                .handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV,
        };

        ASSERT_EQ(0, unlink(path));
        ASSERT_EQ(0, mkfifo(path, 0600));

        /* Enables Landlock. */
        ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /* The child process opens the pipe for writing. */
        child_pid = fork();
        ASSERT_NE(-1, child_pid);
        if (child_pid == 0) {
                fd = open(path, O_WRONLY);
                close(fd);
                exit(0);
        }

        fd = open(path, O_RDONLY);
        ASSERT_LE(0, fd);

        /* FIONREAD is implemented by pipefifo_fops. */
        EXPECT_EQ(0, test_fionread_ioctl(fd));

        ASSERT_EQ(0, close(fd));
        ASSERT_EQ(0, unlink(path));

        ASSERT_EQ(child_pid, waitpid(child_pid, NULL, 0));
}

/* For named UNIX domain sockets, no IOCTL restrictions apply. */
TEST_F_FORK(layout1, named_unix_domain_socket_ioctl)
{
        const char *const path = file1_s1d1;
        int srv_fd, cli_fd, ruleset_fd;
        struct sockaddr_un srv_un = {
                .sun_family = AF_UNIX,
        };
        struct sockaddr_un cli_un = {
                .sun_family = AF_UNIX,
        };
        const struct landlock_ruleset_attr attr = {
                .handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV,
        };

        /* Sets up a server */
        ASSERT_EQ(0, unlink(path));
        srv_fd = socket(AF_UNIX, SOCK_STREAM, 0);
        ASSERT_LE(0, srv_fd);

        strncpy(srv_un.sun_path, path, sizeof(srv_un.sun_path));
        ASSERT_EQ(0, bind(srv_fd, (struct sockaddr *)&srv_un, sizeof(srv_un)));

        ASSERT_EQ(0, listen(srv_fd, 10 /* qlen */));

        /* Enables Landlock. */
        ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /* Sets up a client connection to it */
        cli_fd = socket(AF_UNIX, SOCK_STREAM, 0);
        ASSERT_LE(0, cli_fd);

        strncpy(cli_un.sun_path, path, sizeof(cli_un.sun_path));
        ASSERT_EQ(0,
                  connect(cli_fd, (struct sockaddr *)&cli_un, sizeof(cli_un)));

        /* FIONREAD and other IOCTLs should not be forbidden. */
        EXPECT_EQ(0, test_fionread_ioctl(cli_fd));

        EXPECT_EQ(0, close(cli_fd));
        EXPECT_EQ(0, close(srv_fd));
}

/* clang-format off */
FIXTURE(ioctl) {};

FIXTURE_SETUP(ioctl) {};

FIXTURE_TEARDOWN(ioctl) {};
/* clang-format on */

FIXTURE_VARIANT(ioctl)
{
        const __u64 handled;
        const __u64 allowed;
        const mode_t open_mode;
        /*
         * FIONREAD is used as a characteristic device-specific IOCTL command.
         * It is implemented in fs/ioctl.c for regular files,
         * but we do not blanket-permit it for devices.
         */
        const int expected_fionread_result;
};

/* clang-format off */
FIXTURE_VARIANT_ADD(ioctl, handled_i_allowed_none) {
        /* clang-format on */
        .handled = LANDLOCK_ACCESS_FS_IOCTL_DEV,
        .allowed = 0,
        .open_mode = O_RDWR,
        .expected_fionread_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(ioctl, handled_i_allowed_i) {
        /* clang-format on */
        .handled = LANDLOCK_ACCESS_FS_IOCTL_DEV,
        .allowed = LANDLOCK_ACCESS_FS_IOCTL_DEV,
        .open_mode = O_RDWR,
        .expected_fionread_result = 0,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(ioctl, unhandled) {
        /* clang-format on */
        .handled = LANDLOCK_ACCESS_FS_EXECUTE,
        .allowed = LANDLOCK_ACCESS_FS_EXECUTE,
        .open_mode = O_RDWR,
        .expected_fionread_result = 0,
};

TEST_F_FORK(ioctl, handle_dir_access_file)
{
        const int flag = 0;
        const struct rule rules[] = {
                {
                        .path = "/dev",
                        .access = variant->allowed,
                },
                {},
        };
        int file_fd, ruleset_fd;

        /* Enables Landlock. */
        ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        file_fd = open("/dev/zero", variant->open_mode);
        ASSERT_LE(0, file_fd);

        /* Checks that IOCTL commands return the expected errors. */
        EXPECT_EQ(variant->expected_fionread_result,
                  test_fionread_ioctl(file_fd));

        /* Checks that unrestrictable commands are unrestricted. */
        EXPECT_EQ(0, ioctl(file_fd, FIOCLEX));
        EXPECT_EQ(0, ioctl(file_fd, FIONCLEX));
        EXPECT_EQ(0, ioctl(file_fd, FIONBIO, &flag));
        EXPECT_EQ(0, ioctl(file_fd, FIOASYNC, &flag));
        EXPECT_EQ(0, ioctl(file_fd, FIGETBSZ, &flag));

        ASSERT_EQ(0, close(file_fd));
}

TEST_F_FORK(ioctl, handle_dir_access_dir)
{
        const int flag = 0;
        const struct rule rules[] = {
                {
                        .path = "/dev",
                        .access = variant->allowed,
                },
                {},
        };
        int dir_fd, ruleset_fd;

        /* Enables Landlock. */
        ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /*
         * Ignore variant->open_mode for this test, as we intend to open a
         * directory.  If the directory can not be opened, the variant is
         * infeasible to test with an opened directory.
         */
        dir_fd = open("/dev", O_RDONLY);
        if (dir_fd < 0)
                return;

        /*
         * Checks that IOCTL commands return the expected errors.
         * We do not use the expected values from the fixture here.
         *
         * When using IOCTL on a directory, no Landlock restrictions apply.
         */
        EXPECT_EQ(0, test_fionread_ioctl(dir_fd));

        /* Checks that unrestrictable commands are unrestricted. */
        EXPECT_EQ(0, ioctl(dir_fd, FIOCLEX));
        EXPECT_EQ(0, ioctl(dir_fd, FIONCLEX));
        EXPECT_EQ(0, ioctl(dir_fd, FIONBIO, &flag));
        EXPECT_EQ(0, ioctl(dir_fd, FIOASYNC, &flag));
        EXPECT_EQ(0, ioctl(dir_fd, FIGETBSZ, &flag));

        ASSERT_EQ(0, close(dir_fd));
}

TEST_F_FORK(ioctl, handle_file_access_file)
{
        const int flag = 0;
        const struct rule rules[] = {
                {
                        .path = "/dev/zero",
                        .access = variant->allowed,
                },
                {},
        };
        int file_fd, ruleset_fd;

        /* Enables Landlock. */
        ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        file_fd = open("/dev/zero", variant->open_mode);
        ASSERT_LE(0, file_fd)
        {
                TH_LOG("Failed to open /dev/zero: %s", strerror(errno));
        }

        /* Checks that IOCTL commands return the expected errors. */
        EXPECT_EQ(variant->expected_fionread_result,
                  test_fionread_ioctl(file_fd));

        /* Checks that unrestrictable commands are unrestricted. */
        EXPECT_EQ(0, ioctl(file_fd, FIOCLEX));
        EXPECT_EQ(0, ioctl(file_fd, FIONCLEX));
        EXPECT_EQ(0, ioctl(file_fd, FIONBIO, &flag));
        EXPECT_EQ(0, ioctl(file_fd, FIOASYNC, &flag));
        EXPECT_EQ(0, ioctl(file_fd, FIGETBSZ, &flag));

        ASSERT_EQ(0, close(file_fd));
}

/* clang-format off */
FIXTURE(layout1_bind) {};
/* clang-format on */

static const char bind_dir_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3";
static const char bind_file1_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3/f1";

/* Move targets for disconnected path tests. */
static const char dir_s4d1[] = TMP_DIR "/s4d1";
static const char file1_s4d1[] = TMP_DIR "/s4d1/f1";
static const char file2_s4d1[] = TMP_DIR "/s4d1/f2";
static const char dir_s4d2[] = TMP_DIR "/s4d1/s4d2";
static const char file1_s4d2[] = TMP_DIR "/s4d1/s4d2/f1";
static const char file1_name[] = "f1";
static const char file2_name[] = "f2";

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_PARENT(layout1_bind)
{
        /* umount(dir_s2d2)) is handled by namespace lifetime. */

        remove_path(file1_s4d1);
        remove_path(file2_s4d1);

        remove_layout1(_metadata);

        cleanup_layout(_metadata);
}

/*
 * layout1_bind hierarchy:
 *
 * tmp
 * ├── s1d1
 * │   ├── f1
 * │   ├── f2
 * │   └── s1d2
 * │       ├── f1
 * │       ├── f2
 * │       └── s1d3 [disconnected by path_disconnected]
 * │           ├── f1
 * │           └── f2
 * ├── s2d1
 * │   ├── f1
 * │   └── s2d2 [bind mount from s1d2]
 * │       ├── f1
 * │       ├── f2
 * │       └── s1d3
 * │           ├── f1
 * │           └── f2
 * ├── s3d1
 * │   └── s3d2
 * │       └── s3d3
 * └── s4d1 [renamed from s1d3 by path_disconnected]
 *     ├── f1
 *     ├── f2
 *     └── s4d2
 *         └── f1
 */

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));
}

TEST_F_FORK(layout1_bind, reparent_cross_mount)
{
        const struct rule layer1[] = {
                {
                        /* dir_s2d1 is beneath the dir_s2d2 mount point. */
                        .path = dir_s2d1,
                        .access = LANDLOCK_ACCESS_FS_REFER,
                },
                {
                        .path = bind_dir_s1d3,
                        .access = LANDLOCK_ACCESS_FS_EXECUTE,
                },
                {},
        };
        int ruleset_fd = create_ruleset(
                _metadata,
                LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_EXECUTE, layer1);

        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /* Checks basic denied move. */
        ASSERT_EQ(-1, rename(file1_s1d1, file1_s1d2));
        ASSERT_EQ(EXDEV, errno);

        /* Checks real cross-mount move (Landlock is not involved). */
        ASSERT_EQ(-1, rename(file1_s2d1, file1_s2d2));
        ASSERT_EQ(EXDEV, errno);

        /* Checks move that will give more accesses. */
        ASSERT_EQ(-1, rename(file1_s2d2, bind_file1_s1d3));
        ASSERT_EQ(EXDEV, errno);

        /* Checks legitimate downgrade move. */
        ASSERT_EQ(0, rename(bind_file1_s1d3, file1_s2d2));
}

/*
 * Make sure access to file through a disconnected path works as expected.
 * This test moves s1d3 to s4d1.
 */
TEST_F_FORK(layout1_bind, path_disconnected)
{
        const struct rule layer1_allow_all[] = {
                {
                        .path = TMP_DIR,
                        .access = ACCESS_ALL,
                },
                {},
        };
        const struct rule layer2_allow_just_f1[] = {
                {
                        .path = file1_s1d3,
                        .access = LANDLOCK_ACCESS_FS_READ_FILE,
                },
                {},
        };
        const struct rule layer3_only_s1d2[] = {
                {
                        .path = dir_s1d2,
                        .access = LANDLOCK_ACCESS_FS_READ_FILE,
                },
                {},
        };

        /* Landlock should not deny access just because it is disconnected. */
        int ruleset_fd_l1 =
                create_ruleset(_metadata, ACCESS_ALL, layer1_allow_all);

        /* Creates the new ruleset now before we move the dir containing the file. */
        int ruleset_fd_l2 =
                create_ruleset(_metadata, ACCESS_RW, layer2_allow_just_f1);
        int ruleset_fd_l3 =
                create_ruleset(_metadata, ACCESS_RW, layer3_only_s1d2);
        int bind_s1d3_fd;

        ASSERT_LE(0, ruleset_fd_l1);
        ASSERT_LE(0, ruleset_fd_l2);
        ASSERT_LE(0, ruleset_fd_l3);

        enforce_ruleset(_metadata, ruleset_fd_l1);
        EXPECT_EQ(0, close(ruleset_fd_l1));

        bind_s1d3_fd = open(bind_dir_s1d3, O_PATH | O_CLOEXEC);
        ASSERT_LE(0, bind_s1d3_fd);

        /* Tests access is possible before we move. */
        EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
        EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file2_name, O_RDONLY));
        EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, "..", O_RDONLY | O_DIRECTORY));

        /* Makes it disconnected. */
        ASSERT_EQ(0, rename(dir_s1d3, dir_s4d1))
        {
                TH_LOG("Failed to rename %s to %s: %s", dir_s1d3, dir_s4d1,
                       strerror(errno));
        }

        /* Tests that access is still possible. */
        EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
        EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file2_name, O_RDONLY));

        /*
         * Tests that ".." is not possible (not because of Landlock, but just
         * because it's disconnected).
         */
        EXPECT_EQ(ENOENT,
                  test_open_rel(bind_s1d3_fd, "..", O_RDONLY | O_DIRECTORY));

        /* This should still work with a narrower rule. */
        enforce_ruleset(_metadata, ruleset_fd_l2);
        EXPECT_EQ(0, close(ruleset_fd_l2));

        EXPECT_EQ(0, test_open(file1_s4d1, O_RDONLY));
        /*
         * Accessing a file through a disconnected file descriptor can still be
         * allowed by a rule tied to this file, even if it is no longer visible in
         * its mount point.
         */
        EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
        EXPECT_EQ(EACCES, test_open_rel(bind_s1d3_fd, file2_name, O_RDONLY));

        enforce_ruleset(_metadata, ruleset_fd_l3);
        EXPECT_EQ(0, close(ruleset_fd_l3));

        EXPECT_EQ(EACCES, test_open(file1_s4d1, O_RDONLY));
        /*
         * Accessing a file through a disconnected file descriptor can still be
         * allowed by a rule tied to the original mount point, even if it is no
         * longer visible in its mount point.
         */
        EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
        EXPECT_EQ(EACCES, test_open_rel(bind_s1d3_fd, file2_name, O_RDONLY));
}

/*
 * Test that renameat with disconnected paths works under Landlock.  This test
 * moves s1d3 to s4d2, so that we can have a rule allowing refers on the move
 * target's immediate parent.
 */
TEST_F_FORK(layout1_bind, path_disconnected_rename)
{
        const struct rule layer1[] = {
                {
                        .path = dir_s1d2,
                        .access = LANDLOCK_ACCESS_FS_REFER |
                                  LANDLOCK_ACCESS_FS_MAKE_DIR |
                                  LANDLOCK_ACCESS_FS_REMOVE_DIR |
                                  LANDLOCK_ACCESS_FS_MAKE_REG |
                                  LANDLOCK_ACCESS_FS_REMOVE_FILE |
                                  LANDLOCK_ACCESS_FS_READ_FILE,
                },
                {
                        .path = dir_s4d1,
                        .access = LANDLOCK_ACCESS_FS_REFER |
                                  LANDLOCK_ACCESS_FS_MAKE_DIR |
                                  LANDLOCK_ACCESS_FS_REMOVE_DIR |
                                  LANDLOCK_ACCESS_FS_MAKE_REG |
                                  LANDLOCK_ACCESS_FS_REMOVE_FILE |
                                  LANDLOCK_ACCESS_FS_READ_FILE,
                },
                {}
        };

        /* This layer only handles LANDLOCK_ACCESS_FS_READ_FILE. */
        const struct rule layer2_only_s1d2[] = {
                {
                        .path = dir_s1d2,
                        .access = LANDLOCK_ACCESS_FS_READ_FILE,
                },
                {},
        };
        int ruleset_fd_l1, ruleset_fd_l2;
        pid_t child_pid;
        int bind_s1d3_fd, status;

        ASSERT_EQ(0, mkdir(dir_s4d1, 0755))
        {
                TH_LOG("Failed to create %s: %s", dir_s4d1, strerror(errno));
        }
        ruleset_fd_l1 = create_ruleset(_metadata, ACCESS_ALL, layer1);
        ruleset_fd_l2 = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE,
                                       layer2_only_s1d2);
        ASSERT_LE(0, ruleset_fd_l1);
        ASSERT_LE(0, ruleset_fd_l2);

        enforce_ruleset(_metadata, ruleset_fd_l1);
        EXPECT_EQ(0, close(ruleset_fd_l1));

        bind_s1d3_fd = open(bind_dir_s1d3, O_PATH | O_CLOEXEC);
        ASSERT_LE(0, bind_s1d3_fd);
        EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));

        /* Tests ENOENT priority over EACCES for disconnected directory. */
        EXPECT_EQ(EACCES, test_open_rel(bind_s1d3_fd, "..", O_DIRECTORY));
        ASSERT_EQ(0, rename(dir_s1d3, dir_s4d2))
        {
                TH_LOG("Failed to rename %s to %s: %s", dir_s1d3, dir_s4d2,
                       strerror(errno));
        }
        EXPECT_EQ(ENOENT, test_open_rel(bind_s1d3_fd, "..", O_DIRECTORY));

        /*
         * The file is no longer under s1d2 but we should still be able to access it
         * with layer 2 because its mount point is evaluated as the first valid
         * directory because it was initially a parent.  Do a fork to test this so
         * we don't prevent ourselves from renaming it back later.
         */
        child_pid = fork();
        ASSERT_LE(0, child_pid);
        if (child_pid == 0) {
                enforce_ruleset(_metadata, ruleset_fd_l2);
                EXPECT_EQ(0, close(ruleset_fd_l2));
                EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
                EXPECT_EQ(EACCES, test_open(file1_s4d2, O_RDONLY));

                /*
                 * Tests that access widening checks indeed prevents us from renaming it
                 * back.
                 */
                EXPECT_EQ(-1, rename(dir_s4d2, dir_s1d3));
                EXPECT_EQ(EXDEV, errno);

                /*
                 * Including through the now disconnected fd (but it should return
                 * EXDEV).
                 */
                EXPECT_EQ(-1, renameat(bind_s1d3_fd, file1_name, AT_FDCWD,
                                       file1_s2d2));
                EXPECT_EQ(EXDEV, errno);
                _exit(_metadata->exit_code);
                return;
        }

        EXPECT_EQ(child_pid, waitpid(child_pid, &status, 0));
        EXPECT_EQ(1, WIFEXITED(status));
        EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));

        ASSERT_EQ(0, rename(dir_s4d2, dir_s1d3))
        {
                TH_LOG("Failed to rename %s back to %s: %s", dir_s4d1, dir_s1d3,
                       strerror(errno));
        }

        /* Now checks that we can access it under l2. */
        child_pid = fork();
        ASSERT_LE(0, child_pid);
        if (child_pid == 0) {
                enforce_ruleset(_metadata, ruleset_fd_l2);
                EXPECT_EQ(0, close(ruleset_fd_l2));
                EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
                EXPECT_EQ(0, test_open(file1_s1d3, O_RDONLY));
                _exit(_metadata->exit_code);
                return;
        }

        EXPECT_EQ(child_pid, waitpid(child_pid, &status, 0));
        EXPECT_EQ(1, WIFEXITED(status));
        EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));

        /*
         * Also test that we can rename via a disconnected path.  We move the
         * dir back to the disconnected place first, then we rename file1 to
         * file2 through our dir fd.
         */
        ASSERT_EQ(0, rename(dir_s1d3, dir_s4d2))
        {
                TH_LOG("Failed to rename %s to %s: %s", dir_s1d3, dir_s4d2,
                       strerror(errno));
        }
        ASSERT_EQ(0,
                  renameat(bind_s1d3_fd, file1_name, bind_s1d3_fd, file2_name))
        {
                TH_LOG("Failed to rename %s to %s within disconnected %s: %s",
                       file1_name, file2_name, bind_dir_s1d3, strerror(errno));
        }
        EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file2_name, O_RDONLY));
        ASSERT_EQ(0, renameat(bind_s1d3_fd, file2_name, AT_FDCWD, file1_s2d2))
        {
                TH_LOG("Failed to rename %s to %s through disconnected %s: %s",
                       file2_name, file1_s2d2, bind_dir_s1d3, strerror(errno));
        }
        EXPECT_EQ(0, test_open(file1_s2d2, O_RDONLY));
        EXPECT_EQ(0, test_open(file1_s1d2, O_RDONLY));

        /* Move it back using the disconnected path as the target. */
        ASSERT_EQ(0, renameat(AT_FDCWD, file1_s2d2, bind_s1d3_fd, file1_name))
        {
                TH_LOG("Failed to rename %s to %s through disconnected %s: %s",
                       file1_s1d2, file1_name, bind_dir_s1d3, strerror(errno));
        }

        /* Now make it connected again. */
        ASSERT_EQ(0, rename(dir_s4d2, dir_s1d3))
        {
                TH_LOG("Failed to rename %s back to %s: %s", dir_s4d2, dir_s1d3,
                       strerror(errno));
        }

        /* Checks again that we can access it under l2. */
        enforce_ruleset(_metadata, ruleset_fd_l2);
        EXPECT_EQ(0, close(ruleset_fd_l2));
        EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));
        EXPECT_EQ(0, test_open(file1_s1d3, O_RDONLY));
}

/*
 * Test that linkat(2) with disconnected paths works under Landlock. This
 * test moves s1d3 to s4d1.
 */
TEST_F_FORK(layout1_bind, path_disconnected_link)
{
        /* Ruleset to be applied after renaming s1d3 to s4d1. */
        const struct rule layer1[] = {
                {
                        .path = dir_s4d1,
                        .access = LANDLOCK_ACCESS_FS_REFER |
                                  LANDLOCK_ACCESS_FS_READ_FILE |
                                  LANDLOCK_ACCESS_FS_MAKE_REG |
                                  LANDLOCK_ACCESS_FS_REMOVE_FILE,
                },
                {
                        .path = dir_s2d2,
                        .access = LANDLOCK_ACCESS_FS_REFER |
                                  LANDLOCK_ACCESS_FS_READ_FILE |
                                  LANDLOCK_ACCESS_FS_MAKE_REG |
                                  LANDLOCK_ACCESS_FS_REMOVE_FILE,
                },
                {}
        };
        int ruleset_fd, bind_s1d3_fd;

        /* Removes unneeded files created by layout1, otherwise it will EEXIST. */
        ASSERT_EQ(0, unlink(file1_s1d2));
        ASSERT_EQ(0, unlink(file2_s1d3));

        bind_s1d3_fd = open(bind_dir_s1d3, O_PATH | O_CLOEXEC);
        ASSERT_LE(0, bind_s1d3_fd);
        EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY));

        /* Disconnects bind_s1d3_fd. */
        ASSERT_EQ(0, rename(dir_s1d3, dir_s4d1))
        {
                TH_LOG("Failed to rename %s to %s: %s", dir_s1d3, dir_s4d1,
                       strerror(errno));
        }

        /* Need this later to test different parent link. */
        ASSERT_EQ(0, mkdir(dir_s4d2, 0755))
        {
                TH_LOG("Failed to create %s: %s", dir_s4d2, strerror(errno));
        }

        ruleset_fd = create_ruleset(_metadata, ACCESS_ALL, layer1);
        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        EXPECT_EQ(0, close(ruleset_fd));

        /* From disconnected to connected. */
        ASSERT_EQ(0, linkat(bind_s1d3_fd, file1_name, AT_FDCWD, file1_s2d2, 0))
        {
                TH_LOG("Failed to link %s to %s via disconnected %s: %s",
                       file1_name, file1_s2d2, bind_dir_s1d3, strerror(errno));
        }

        /* Tests that we can access via the new link... */
        EXPECT_EQ(0, test_open(file1_s2d2, O_RDONLY))
        {
                TH_LOG("Failed to open newly linked %s: %s", file1_s2d2,
                       strerror(errno));
        }

        /* ...as well as the old one. */
        EXPECT_EQ(0, test_open(file1_s4d1, O_RDONLY))
        {
                TH_LOG("Failed to open original %s: %s", file1_s4d1,
                       strerror(errno));
        }

        /* From connected to disconnected. */
        ASSERT_EQ(0, unlink(file1_s4d1));
        ASSERT_EQ(0, linkat(AT_FDCWD, file1_s2d2, bind_s1d3_fd, file2_name, 0))
        {
                TH_LOG("Failed to link %s to %s via disconnected %s: %s",
                       file1_s2d2, file2_name, bind_dir_s1d3, strerror(errno));
        }
        EXPECT_EQ(0, test_open(file2_s4d1, O_RDONLY));
        ASSERT_EQ(0, unlink(file1_s2d2));

        /* From disconnected to disconnected (same parent). */
        ASSERT_EQ(0,
                  linkat(bind_s1d3_fd, file2_name, bind_s1d3_fd, file1_name, 0))
        {
                TH_LOG("Failed to link %s to %s within disconnected %s: %s",
                       file2_name, file1_name, bind_dir_s1d3, strerror(errno));
        }
        EXPECT_EQ(0, test_open(file1_s4d1, O_RDONLY))
        {
                TH_LOG("Failed to open newly linked %s: %s", file1_s4d1,
                       strerror(errno));
        }
        EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, file1_name, O_RDONLY))
        {
                TH_LOG("Failed to open %s through newly created link under disconnected path: %s",
                       file1_name, strerror(errno));
        }
        ASSERT_EQ(0, unlink(file2_s4d1));

        /* From disconnected to disconnected (different parent). */
        ASSERT_EQ(0,
                  linkat(bind_s1d3_fd, file1_name, bind_s1d3_fd, "s4d2/f1", 0))
        {
                TH_LOG("Failed to link %s to %s within disconnected %s: %s",
                       file1_name, "s4d2/f1", bind_dir_s1d3, strerror(errno));
        }
        EXPECT_EQ(0, test_open(file1_s4d2, O_RDONLY))
        {
                TH_LOG("Failed to open %s after link: %s", file1_s4d2,
                       strerror(errno));
        }
        EXPECT_EQ(0, test_open_rel(bind_s1d3_fd, "s4d2/f1", O_RDONLY))
        {
                TH_LOG("Failed to open %s through disconnected path after link: %s",
                       "s4d2/f1", strerror(errno));
        }
}

/*
 * layout4_disconnected_leafs with bind mount and renames:
 *
 * tmp
 * ├── s1d1
 * │   └── s1d2 [source of the bind mount]
 * │       ├── s1d31
 * │       │   └── s1d41 [now renamed beneath s3d1]
 * │       │       ├── f1
 * │       │       └── f2
 * │       └── s1d32
 * │           └── s1d42 [now renamed beneath s4d1]
 * │               ├── f3
 * │               └── f4
 * ├── s2d1
 * │   └── s2d2 [bind mount of s1d2]
 * │       ├── s1d31
 * │       │   └── s1d41 [opened FD, now renamed beneath s3d1]
 * │       │       ├── f1
 * │       │       └── f2
 * │       └── s1d32
 * │           └── s1d42 [opened FD, now renamed beneath s4d1]
 * │               ├── f3
 * │               └── f4
 * ├── s3d1
 * │   └── s1d41 [renamed here]
 * │       ├── f1
 * │       └── f2
 * └── s4d1
 *     └── s1d42 [renamed here]
 *         ├── f3
 *         └── f4
 */
/* clang-format off */
FIXTURE(layout4_disconnected_leafs) {
        int s2d2_fd;
};
/* clang-format on */

FIXTURE_SETUP(layout4_disconnected_leafs)
{
        prepare_layout(_metadata);

        create_file(_metadata, TMP_DIR "/s1d1/s1d2/s1d31/s1d41/f1");
        create_file(_metadata, TMP_DIR "/s1d1/s1d2/s1d31/s1d41/f2");
        create_file(_metadata, TMP_DIR "/s1d1/s1d2/s1d32/s1d42/f3");
        create_file(_metadata, TMP_DIR "/s1d1/s1d2/s1d32/s1d42/f4");
        create_directory(_metadata, TMP_DIR "/s2d1/s2d2");
        create_directory(_metadata, TMP_DIR "/s3d1");
        create_directory(_metadata, TMP_DIR "/s4d1");

        self->s2d2_fd =
                open(TMP_DIR "/s2d1/s2d2", O_DIRECTORY | O_PATH | O_CLOEXEC);
        ASSERT_LE(0, self->s2d2_fd);

        set_cap(_metadata, CAP_SYS_ADMIN);
        ASSERT_EQ(0, mount(TMP_DIR "/s1d1/s1d2", TMP_DIR "/s2d1/s2d2", NULL,
                           MS_BIND, NULL));
        clear_cap(_metadata, CAP_SYS_ADMIN);
}

FIXTURE_TEARDOWN_PARENT(layout4_disconnected_leafs)
{
        /* umount(TMP_DIR "/s2d1") is handled by namespace lifetime. */

        /* Removes files after renames. */
        remove_path(TMP_DIR "/s3d1/s1d41/f1");
        remove_path(TMP_DIR "/s3d1/s1d41/f2");
        remove_path(TMP_DIR "/s4d1/s1d42/f1");
        remove_path(TMP_DIR "/s4d1/s1d42/f3");
        remove_path(TMP_DIR "/s4d1/s1d42/f4");
        remove_path(TMP_DIR "/s4d1/s1d42/f5");

        cleanup_layout(_metadata);
}

FIXTURE_VARIANT(layout4_disconnected_leafs)
{
        /*
         * Parent of the bind mount source.  It should always be ignored when
         * testing against files under the s1d41 or s1d42 disconnected directories.
         */
        const __u64 allowed_s1d1;
        /*
         * Source of bind mount (to s2d2).  It should always be enforced when
         * testing against files under the s1d41 or s1d42 disconnected directories.
         */
        const __u64 allowed_s1d2;
        /*
         * Original parent of s1d41.  It should always be ignored when testing
         * against files under the s1d41 disconnected directory.
         */
        const __u64 allowed_s1d31;
        /*
         * Original parent of s1d42.  It should always be ignored when testing
         * against files under the s1d42 disconnected directory.
         */
        const __u64 allowed_s1d32;
        /*
         * Opened and disconnected source directory.  It should always be enforced
         * when testing against files under the s1d41 disconnected directory.
         */
        const __u64 allowed_s1d41;
        /*
         * Opened and disconnected source directory.  It should always be enforced
         * when testing against files under the s1d42 disconnected directory.
         */
        const __u64 allowed_s1d42;
        /*
         * File in the s1d41 disconnected directory.  It should always be enforced
         * when testing against itself under the s1d41 disconnected directory.
         */
        const __u64 allowed_f1;
        /*
         * File in the s1d41 disconnected directory.  It should always be enforced
         * when testing against itself under the s1d41 disconnected directory.
         */
        const __u64 allowed_f2;
        /*
         * File in the s1d42 disconnected directory.  It should always be enforced
         * when testing against itself under the s1d42 disconnected directory.
         */
        const __u64 allowed_f3;
        /*
         * Parent of the bind mount destination.  It should always be enforced when
         * testing against files under the s1d41 or s1d42 disconnected directories.
         */
        const __u64 allowed_s2d1;
        /*
         * Directory covered by the bind mount.  It should always be ignored when
         * testing against files under the s1d41 or s1d42 disconnected directories.
         */
        const __u64 allowed_s2d2;
        /*
         * New parent of the renamed s1d41.  It should always be ignored when
         * testing against files under the s1d41 disconnected directory.
         */
        const __u64 allowed_s3d1;
        /*
         * New parent of the renamed s1d42.  It should always be ignored when
         * testing against files under the s1d42 disconnected directory.
         */
        const __u64 allowed_s4d1;

        /* Expected result of the call to open([fd:s1d41]/f1, O_RDONLY). */
        const int expected_read_result;
        /* Expected result of the call to renameat([fd:s1d41]/f1, [fd:s1d42]/f1). */
        const int expected_rename_result;
        /*
         * Expected result of the call to renameat([fd:s1d41]/f2, [fd:s1d42]/f3,
         * RENAME_EXCHANGE).
         */
        const int expected_exchange_result;
        /* Expected result of the call to renameat([fd:s1d42]/f4, [fd:s1d42]/f5). */
        const int expected_same_dir_rename_result;
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d1_mount_src_parent) {
        /* clang-format on */
        .allowed_s1d1 = LANDLOCK_ACCESS_FS_REFER |
                        LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_EXECUTE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d2_mount_src_refer) {
        /* clang-format on */
        .allowed_s1d2 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d2_mount_src_create) {
        /* clang-format on */
        .allowed_s1d2 = LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = EXDEV,
        .expected_exchange_result = EXDEV,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d2_mount_src_rename) {
        /* clang-format on */
        .allowed_s1d2 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = 0,
        .expected_exchange_result = 0,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d31_s1d32_old_parent) {
        /* clang-format on */
        .allowed_s1d31 = LANDLOCK_ACCESS_FS_REFER |
                         LANDLOCK_ACCESS_FS_READ_FILE |
                         LANDLOCK_ACCESS_FS_EXECUTE |
                         LANDLOCK_ACCESS_FS_MAKE_REG,
        .allowed_s1d32 = LANDLOCK_ACCESS_FS_REFER |
                         LANDLOCK_ACCESS_FS_READ_FILE |
                         LANDLOCK_ACCESS_FS_EXECUTE |
                         LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d41_s1d42_disconnected_refer) {
        /* clang-format on */
        .allowed_s1d41 = LANDLOCK_ACCESS_FS_REFER |
                         LANDLOCK_ACCESS_FS_READ_FILE,
        .allowed_s1d42 = LANDLOCK_ACCESS_FS_REFER |
                         LANDLOCK_ACCESS_FS_READ_FILE,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d41_s1d42_disconnected_create) {
        /* clang-format on */
        .allowed_s1d41 = LANDLOCK_ACCESS_FS_READ_FILE |
                         LANDLOCK_ACCESS_FS_MAKE_REG,
        .allowed_s1d42 = LANDLOCK_ACCESS_FS_READ_FILE |
                         LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = EXDEV,
        .expected_exchange_result = EXDEV,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d41_s1d42_disconnected_rename_even) {
        /* clang-format on */
        .allowed_s1d41 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
        .allowed_s1d42 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = 0,
        .expected_exchange_result = 0,
};

/* The destination directory has more access right. */
/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d41_s1d42_disconnected_rename_more) {
        /* clang-format on */
        .allowed_s1d41 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
        .allowed_s1d42 = LANDLOCK_ACCESS_FS_REFER |
                         LANDLOCK_ACCESS_FS_MAKE_REG |
                         LANDLOCK_ACCESS_FS_EXECUTE,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = 0,
        /* Access denied. */
        .expected_rename_result = EXDEV,
        .expected_exchange_result = EXDEV,
};

/* The destination directory has less access right. */
/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s1d41_s1d42_disconnected_rename_less) {
        /* clang-format on */
        .allowed_s1d41 = LANDLOCK_ACCESS_FS_REFER |
                         LANDLOCK_ACCESS_FS_MAKE_REG |
                         LANDLOCK_ACCESS_FS_EXECUTE,
        .allowed_s1d42 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = 0,
        /* Access allowed. */
        .expected_rename_result = 0,
        .expected_exchange_result = EXDEV,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s2d1_mount_dst_parent_create) {
        /* clang-format on */
        .allowed_s2d1 = LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = EXDEV,
        .expected_exchange_result = EXDEV,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s2d1_mount_dst_parent_refer) {
        /* clang-format on */
        .allowed_s2d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s2d1_mount_dst_parent_mini) {
        /* clang-format on */
        .allowed_s2d1 = LANDLOCK_ACCESS_FS_REFER |
                        LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = 0,
        .expected_exchange_result = 0,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s2d2_covered_by_mount) {
        /* clang-format on */
        .allowed_s2d2 = LANDLOCK_ACCESS_FS_REFER |
                        LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_EXECUTE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

/* Tests collect_domain_accesses(). */
/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s3d1_s4d1_new_parent_refer) {
        /* clang-format on */
        .allowed_s3d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
        .allowed_s4d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s3d1_s4d1_new_parent_create) {
        /* clang-format on */
        .allowed_s3d1 = LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .allowed_s4d1 = LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = EXDEV,
        .expected_exchange_result = EXDEV,
};

FIXTURE_VARIANT_ADD(layout4_disconnected_leafs,
                    s3d1_s4d1_disconnected_rename_even){
        /* clang-format on */
        .allowed_s3d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
        .allowed_s4d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = 0,
        .expected_exchange_result = 0,
};

/* The destination directory has more access right. */
/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s3d1_s4d1_disconnected_rename_more) {
        /* clang-format on */
        .allowed_s3d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
        .allowed_s4d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG |
                        LANDLOCK_ACCESS_FS_EXECUTE,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = 0,
        /* Access denied. */
        .expected_rename_result = EXDEV,
        .expected_exchange_result = EXDEV,
};

/* The destination directory has less access right. */
/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, s3d1_s4d1_disconnected_rename_less) {
        /* clang-format on */
        .allowed_s3d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG |
                        LANDLOCK_ACCESS_FS_EXECUTE,
        .allowed_s4d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = 0,
        /* Access allowed. */
        .expected_rename_result = 0,
        .expected_exchange_result = EXDEV,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, f1_f2_f3) {
        /* clang-format on */
        .allowed_f1 = LANDLOCK_ACCESS_FS_READ_FILE,
        .allowed_f2 = LANDLOCK_ACCESS_FS_READ_FILE,
        .allowed_f3 = LANDLOCK_ACCESS_FS_READ_FILE,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

TEST_F_FORK(layout4_disconnected_leafs, read_rename_exchange)
{
        const __u64 handled_access =
                LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE |
                LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_MAKE_REG;
        const struct rule rules[] = {
                {
                        .path = TMP_DIR "/s1d1",
                        .access = variant->allowed_s1d1,
                },
                {
                        .path = TMP_DIR "/s1d1/s1d2",
                        .access = variant->allowed_s1d2,
                },
                {
                        .path = TMP_DIR "/s1d1/s1d2/s1d31",
                        .access = variant->allowed_s1d31,
                },
                {
                        .path = TMP_DIR "/s1d1/s1d2/s1d32",
                        .access = variant->allowed_s1d32,
                },
                {
                        .path = TMP_DIR "/s1d1/s1d2/s1d31/s1d41",
                        .access = variant->allowed_s1d41,
                },
                {
                        .path = TMP_DIR "/s1d1/s1d2/s1d32/s1d42",
                        .access = variant->allowed_s1d42,
                },
                {
                        .path = TMP_DIR "/s1d1/s1d2/s1d31/s1d41/f1",
                        .access = variant->allowed_f1,
                },
                {
                        .path = TMP_DIR "/s1d1/s1d2/s1d31/s1d41/f2",
                        .access = variant->allowed_f2,
                },
                {
                        .path = TMP_DIR "/s1d1/s1d2/s1d32/s1d42/f3",
                        .access = variant->allowed_f3,
                },
                {
                        .path = TMP_DIR "/s2d1",
                        .access = variant->allowed_s2d1,
                },
                /* s2d2_fd */
                {
                        .path = TMP_DIR "/s3d1",
                        .access = variant->allowed_s3d1,
                },
                {
                        .path = TMP_DIR "/s4d1",
                        .access = variant->allowed_s4d1,
                },
                {},
        };
        int ruleset_fd, s1d41_bind_fd, s1d42_bind_fd;

        ruleset_fd = create_ruleset(_metadata, handled_access, rules);
        ASSERT_LE(0, ruleset_fd);

        /* Adds rule for the covered directory. */
        if (variant->allowed_s2d2) {
                ASSERT_EQ(0, landlock_add_rule(
                                     ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
                                     &(struct landlock_path_beneath_attr){
                                             .parent_fd = self->s2d2_fd,
                                             .allowed_access =
                                                     variant->allowed_s2d2,
                                     },
                                     0));
        }
        EXPECT_EQ(0, close(self->s2d2_fd));

        s1d41_bind_fd = open(TMP_DIR "/s2d1/s2d2/s1d31/s1d41",
                             O_DIRECTORY | O_PATH | O_CLOEXEC);
        ASSERT_LE(0, s1d41_bind_fd);
        s1d42_bind_fd = open(TMP_DIR "/s2d1/s2d2/s1d32/s1d42",
                             O_DIRECTORY | O_PATH | O_CLOEXEC);
        ASSERT_LE(0, s1d42_bind_fd);

        /* Disconnects and checks source and destination directories. */
        EXPECT_EQ(0, test_open_rel(s1d41_bind_fd, "..", O_DIRECTORY));
        EXPECT_EQ(0, test_open_rel(s1d42_bind_fd, "..", O_DIRECTORY));
        /* Renames to make it accessible through s3d1/s1d41 */
        ASSERT_EQ(0, test_renameat(AT_FDCWD, TMP_DIR "/s1d1/s1d2/s1d31/s1d41",
                                   AT_FDCWD, TMP_DIR "/s3d1/s1d41"));
        /* Renames to make it accessible through s4d1/s1d42 */
        ASSERT_EQ(0, test_renameat(AT_FDCWD, TMP_DIR "/s1d1/s1d2/s1d32/s1d42",
                                   AT_FDCWD, TMP_DIR "/s4d1/s1d42"));
        EXPECT_EQ(ENOENT, test_open_rel(s1d41_bind_fd, "..", O_DIRECTORY));
        EXPECT_EQ(ENOENT, test_open_rel(s1d42_bind_fd, "..", O_DIRECTORY));

        enforce_ruleset(_metadata, ruleset_fd);
        EXPECT_EQ(0, close(ruleset_fd));

        EXPECT_EQ(variant->expected_read_result,
                  test_open_rel(s1d41_bind_fd, "f1", O_RDONLY));

        EXPECT_EQ(variant->expected_rename_result,
                  test_renameat(s1d41_bind_fd, "f1", s1d42_bind_fd, "f1"));
        EXPECT_EQ(variant->expected_exchange_result,
                  test_exchangeat(s1d41_bind_fd, "f2", s1d42_bind_fd, "f3"));

        EXPECT_EQ(variant->expected_same_dir_rename_result,
                  test_renameat(s1d42_bind_fd, "f4", s1d42_bind_fd, "f5"));
}

/*
 * layout5_disconnected_branch before rename:
 *
 * tmp
 * ├── s1d1
 * │   └── s1d2 [source of the first bind mount]
 * │       └── s1d3
 * │           ├── s1d41
 * │           │   ├── f1
 * │           │   └── f2
 * │           └── s1d42
 * │               ├── f3
 * │               └── f4
 * ├── s2d1
 * │   └── s2d2 [source of the second bind mount]
 * │       └── s2d3
 * │           └── s2d4 [first s1d2 bind mount]
 * │               └── s1d3
 * │                   ├── s1d41
 * │                   │   ├── f1
 * │                   │   └── f2
 * │                   └── s1d42
 * │                       ├── f3
 * │                       └── f4
 * ├── s3d1
 * │   └── s3d2 [second s2d2 bind mount]
 * │       └── s2d3
 * │           └── s2d4 [first s1d2 bind mount]
 * │               └── s1d3
 * │                   ├── s1d41
 * │                   │   ├── f1
 * │                   │   └── f2
 * │                   └── s1d42
 * │                       ├── f3
 * │                       └── f4
 * └── s4d1
 *
 * After rename:
 *
 * tmp
 * ├── s1d1
 * │   └── s1d2 [source of the first bind mount]
 * │       └── s1d3
 * │           ├── s1d41
 * │           │   ├── f1
 * │           │   └── f2
 * │           └── s1d42
 * │               ├── f3
 * │               └── f4
 * ├── s2d1
 * │   └── s2d2 [source of the second bind mount]
 * ├── s3d1
 * │   └── s3d2 [second s2d2 bind mount]
 * └── s4d1
 *     └── s2d3 [renamed here]
 *         └── s2d4 [first s1d2 bind mount]
 *             └── s1d3
 *                 ├── s1d41
 *                 │   ├── f1
 *                 │   └── f2
 *                 └── s1d42
 *                     ├── f3
 *                     └── f4
 *
 * Decision path for access from the s3d1/s3d2/s2d3/s2d4/s1d3 file descriptor:
 *   1. first bind mount:   s1d3 -> s1d2
 *   2. second bind mount:    s2d3
 *   3. tmp mount:              s4d1 -> tmp [disconnected branch]
 *   4. second bind mount:        s2d2
 *   5. tmp mount:                  s3d1 -> tmp
 *   6. parent mounts:                [...] -> /
 *
 * The s4d1 directory is evaluated even if it is not in the s2d2 mount.
 */

/* clang-format off */
FIXTURE(layout5_disconnected_branch) {
        int s2d4_fd, s3d2_fd;
};
/* clang-format on */

FIXTURE_SETUP(layout5_disconnected_branch)
{
        prepare_layout(_metadata);

        create_file(_metadata, TMP_DIR "/s1d1/s1d2/s1d3/s1d41/f1");
        create_file(_metadata, TMP_DIR "/s1d1/s1d2/s1d3/s1d41/f2");
        create_file(_metadata, TMP_DIR "/s1d1/s1d2/s1d3/s1d42/f3");
        create_file(_metadata, TMP_DIR "/s1d1/s1d2/s1d3/s1d42/f4");
        create_directory(_metadata, TMP_DIR "/s2d1/s2d2/s2d3/s2d4");
        create_directory(_metadata, TMP_DIR "/s3d1/s3d2");
        create_directory(_metadata, TMP_DIR "/s4d1");

        self->s2d4_fd = open(TMP_DIR "/s2d1/s2d2/s2d3/s2d4",
                             O_DIRECTORY | O_PATH | O_CLOEXEC);
        ASSERT_LE(0, self->s2d4_fd);

        self->s3d2_fd =
                open(TMP_DIR "/s3d1/s3d2", O_DIRECTORY | O_PATH | O_CLOEXEC);
        ASSERT_LE(0, self->s3d2_fd);

        set_cap(_metadata, CAP_SYS_ADMIN);
        ASSERT_EQ(0, mount(TMP_DIR "/s1d1/s1d2", TMP_DIR "/s2d1/s2d2/s2d3/s2d4",
                           NULL, MS_BIND, NULL));
        ASSERT_EQ(0, mount(TMP_DIR "/s2d1/s2d2", TMP_DIR "/s3d1/s3d2", NULL,
                           MS_BIND | MS_REC, NULL));
        clear_cap(_metadata, CAP_SYS_ADMIN);
}

FIXTURE_TEARDOWN_PARENT(layout5_disconnected_branch)
{
        /* Bind mounts are handled by namespace lifetime. */

        /* Removes files after renames. */
        remove_path(TMP_DIR "/s1d1/s1d2/s1d3/s1d41/f1");
        remove_path(TMP_DIR "/s1d1/s1d2/s1d3/s1d41/f2");
        remove_path(TMP_DIR "/s1d1/s1d2/s1d3/s1d42/f1");
        remove_path(TMP_DIR "/s1d1/s1d2/s1d3/s1d42/f3");
        remove_path(TMP_DIR "/s1d1/s1d2/s1d3/s1d42/f4");
        remove_path(TMP_DIR "/s1d1/s1d2/s1d3/s1d42/f5");

        cleanup_layout(_metadata);
}

FIXTURE_VARIANT(layout5_disconnected_branch)
{
        /*
         * Parent of all files.  It should always be enforced when testing against
         * files under the s1d41 or s1d42 disconnected directories.
         */
        const __u64 allowed_base;
        /*
         * Parent of the first bind mount source.  It should always be ignored when
         * testing against files under the s1d41 or s1d42 disconnected directories.
         */
        const __u64 allowed_s1d1;
        const __u64 allowed_s1d2;
        const __u64 allowed_s1d3;
        const __u64 allowed_s2d1;
        const __u64 allowed_s2d2;
        const __u64 allowed_s2d3;
        const __u64 allowed_s2d4;
        const __u64 allowed_s3d1;
        const __u64 allowed_s3d2;
        const __u64 allowed_s4d1;

        /* Expected result of the call to open([fd:s1d3]/s1d41/f1, O_RDONLY). */
        const int expected_read_result;
        /*
         * Expected result of the call to renameat([fd:s1d3]/s1d41/f1,
         * [fd:s1d3]/s1d42/f1).
         */
        const int expected_rename_result;
        /*
         * Expected result of the call to renameat([fd:s1d3]/s1d41/f2,
         * [fd:s1d3]/s1d42/f3,  RENAME_EXCHANGE).
         */
        const int expected_exchange_result;
        /*
         * Expected result of the call to renameat([fd:s1d3]/s1d42/f4,
         * [fd:s1d3]/s1d42/f5).
         */
        const int expected_same_dir_rename_result;
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s1d1_mount1_src_parent) {
        /* clang-format on */
        .allowed_s1d1 = LANDLOCK_ACCESS_FS_REFER |
                        LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_EXECUTE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s1d2_mount1_src_refer) {
        /* clang-format on */
        .allowed_s1d2 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s1d2_mount1_src_create) {
        /* clang-format on */
        .allowed_s1d2 = LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = EXDEV,
        .expected_exchange_result = EXDEV,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s1d2_mount1_src_rename) {
        /* clang-format on */
        .allowed_s1d2 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = 0,
        .expected_exchange_result = 0,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s1d3_fd_refer) {
        /* clang-format on */
        .allowed_s1d3 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s1d3_fd_create) {
        /* clang-format on */
        .allowed_s1d3 = LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = EXDEV,
        .expected_exchange_result = EXDEV,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s1d3_fd_rename) {
        /* clang-format on */
        .allowed_s1d3 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = 0,
        .expected_exchange_result = 0,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s1d3_fd_full) {
        /* clang-format on */
        .allowed_s1d3 = LANDLOCK_ACCESS_FS_REFER |
                        LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_EXECUTE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = 0,
        .expected_exchange_result = 0,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s2d1_mount2_src_parent) {
        /* clang-format on */
        .allowed_s2d1 = LANDLOCK_ACCESS_FS_REFER |
                        LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_EXECUTE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s2d2_mount2_src_refer) {
        /* clang-format on */
        .allowed_s2d2 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s2d2_mount2_src_create) {
        /* clang-format on */
        .allowed_s2d2 = LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = EXDEV,
        .expected_exchange_result = EXDEV,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s2d2_mount2_src_rename) {
        /* clang-format on */
        .allowed_s2d2 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = 0,
        .expected_exchange_result = 0,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s2d3_mount1_dst_parent_refer) {
        /* clang-format on */
        .allowed_s2d3 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s2d3_mount1_dst_parent_create) {
        /* clang-format on */
        .allowed_s2d3 = LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = EXDEV,
        .expected_exchange_result = EXDEV,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s2d3_mount1_dst_parent_rename) {
        /* clang-format on */
        .allowed_s2d3 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = 0,
        .expected_exchange_result = 0,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s2d4_mount1_dst) {
        /* clang-format on */
        .allowed_s2d4 = LANDLOCK_ACCESS_FS_REFER |
                        LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_EXECUTE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s3d1_mount2_dst_parent_refer) {
        /* clang-format on */
        .allowed_s3d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s3d1_mount2_dst_parent_create) {
        /* clang-format on */
        .allowed_s3d1 = LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = EXDEV,
        .expected_exchange_result = EXDEV,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s3d1_mount2_dst_parent_rename) {
        /* clang-format on */
        .allowed_s3d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = 0,
        .expected_exchange_result = 0,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s3d2_mount1_dst) {
        /* clang-format on */
        .allowed_s3d2 = LANDLOCK_ACCESS_FS_REFER |
                        LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_EXECUTE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s4d1_rename_parent_refer) {
        /* clang-format on */
        .allowed_s4d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = EACCES,
        .expected_rename_result = EACCES,
        .expected_exchange_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s4d1_rename_parent_create) {
        /* clang-format on */
        .allowed_s4d1 = LANDLOCK_ACCESS_FS_READ_FILE |
                        LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = 0,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = EXDEV,
        .expected_exchange_result = EXDEV,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout5_disconnected_branch, s4d1_rename_parent_rename) {
        /* clang-format on */
        .allowed_s4d1 = LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG,
        .expected_read_result = EACCES,
        .expected_same_dir_rename_result = 0,
        .expected_rename_result = 0,
        .expected_exchange_result = 0,
};

TEST_F_FORK(layout5_disconnected_branch, read_rename_exchange)
{
        const __u64 handled_access =
                LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_READ_FILE |
                LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_MAKE_REG;
        const struct rule rules[] = {
                {
                        .path = TMP_DIR "/s1d1",
                        .access = variant->allowed_s1d1,
                },
                {
                        .path = TMP_DIR "/s1d1/s1d2",
                        .access = variant->allowed_s1d2,
                },
                {
                        .path = TMP_DIR "/s1d1/s1d2/s1d3",
                        .access = variant->allowed_s1d3,
                },
                {
                        .path = TMP_DIR "/s2d1",
                        .access = variant->allowed_s2d1,
                },
                {
                        .path = TMP_DIR "/s2d1/s2d2",
                        .access = variant->allowed_s2d2,
                },
                {
                        .path = TMP_DIR "/s2d1/s2d2/s2d3",
                        .access = variant->allowed_s2d3,
                },
                /* s2d4_fd */
                {
                        .path = TMP_DIR "/s3d1",
                        .access = variant->allowed_s3d1,
                },
                /* s3d2_fd */
                {
                        .path = TMP_DIR "/s4d1",
                        .access = variant->allowed_s4d1,
                },
                {},
        };
        int ruleset_fd, s1d3_bind_fd;

        ruleset_fd = create_ruleset(_metadata, handled_access, rules);
        ASSERT_LE(0, ruleset_fd);

        /* Adds rules for the covered directories. */
        if (variant->allowed_s2d4) {
                ASSERT_EQ(0, landlock_add_rule(
                                     ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
                                     &(struct landlock_path_beneath_attr){
                                             .parent_fd = self->s2d4_fd,
                                             .allowed_access =
                                                     variant->allowed_s2d4,
                                     },
                                     0));
        }
        EXPECT_EQ(0, close(self->s2d4_fd));

        if (variant->allowed_s3d2) {
                ASSERT_EQ(0, landlock_add_rule(
                                     ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
                                     &(struct landlock_path_beneath_attr){
                                             .parent_fd = self->s3d2_fd,
                                             .allowed_access =
                                                     variant->allowed_s3d2,
                                     },
                                     0));
        }
        EXPECT_EQ(0, close(self->s3d2_fd));

        s1d3_bind_fd = open(TMP_DIR "/s3d1/s3d2/s2d3/s2d4/s1d3",
                            O_DIRECTORY | O_PATH | O_CLOEXEC);
        ASSERT_LE(0, s1d3_bind_fd);

        /* Disconnects and checks source and destination directories. */
        EXPECT_EQ(0, test_open_rel(s1d3_bind_fd, "..", O_DIRECTORY));
        EXPECT_EQ(0, test_open_rel(s1d3_bind_fd, "../..", O_DIRECTORY));
        /* Renames to make it accessible through s3d1/s1d41 */
        ASSERT_EQ(0, test_renameat(AT_FDCWD, TMP_DIR "/s2d1/s2d2/s2d3",
                                   AT_FDCWD, TMP_DIR "/s4d1/s2d3"));
        EXPECT_EQ(0, test_open_rel(s1d3_bind_fd, "..", O_DIRECTORY));
        EXPECT_EQ(ENOENT, test_open_rel(s1d3_bind_fd, "../..", O_DIRECTORY));

        enforce_ruleset(_metadata, ruleset_fd);
        EXPECT_EQ(0, close(ruleset_fd));

        EXPECT_EQ(variant->expected_read_result,
                  test_open_rel(s1d3_bind_fd, "s1d41/f1", O_RDONLY));

        EXPECT_EQ(variant->expected_rename_result,
                  test_renameat(s1d3_bind_fd, "s1d41/f1", s1d3_bind_fd,
                                "s1d42/f1"));
        EXPECT_EQ(variant->expected_exchange_result,
                  test_exchangeat(s1d3_bind_fd, "s1d41/f2", s1d3_bind_fd,
                                  "s1d42/f3"));

        EXPECT_EQ(variant->expected_same_dir_rename_result,
                  test_renameat(s1d3_bind_fd, "s1d42/f4", s1d3_bind_fd,
                                "s1d42/f5"));
}

#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)
{
        bool skip_test;
};

FIXTURE_SETUP(layout2_overlay)
{
        if (!supports_filesystem("overlay")) {
                self->skip_test = true;
                SKIP(return, "overlayfs is not supported (setup)");
        }

        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_opt(&mnt_tmp, LOWER_BASE));
        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_opt(&mnt_tmp, UPPER_BASE));
        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_PARENT(layout2_overlay)
{
        if (self->skip_test)
                SKIP(return, "overlayfs is not supported (teardown)");

        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));

        /* umount(LOWER_BASE)) is handled by namespace lifetime. */
        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"));

        /* umount(UPPER_BASE)) is handled by namespace lifetime. */
        EXPECT_EQ(0, remove_path(UPPER_BASE));

        /* umount(MERGE_DATA)) is handled by namespace lifetime. */
        EXPECT_EQ(0, remove_path(MERGE_DATA));

        cleanup_layout(_metadata);
}

TEST_F_FORK(layout2_overlay, no_restriction)
{
        if (self->skip_test)
                SKIP(return, "overlayfs is not supported (test)");

        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;

        if (self->skip_test)
                SKIP(return, "overlayfs is not supported (test)");

        /* 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));
        }
}

FIXTURE(layout3_fs)
{
        bool has_created_dir;
        bool has_created_file;
        bool skip_test;
};

FIXTURE_VARIANT(layout3_fs)
{
        const struct mnt_opt mnt;
        const char *const file_path;
        unsigned int cwd_fs_magic;
};

/* clang-format off */
FIXTURE_VARIANT_ADD(layout3_fs, tmpfs) {
        /* clang-format on */
        .mnt = {
                .type = "tmpfs",
                .data = MNT_TMP_DATA,
        },
        .file_path = file1_s1d1,
};

FIXTURE_VARIANT_ADD(layout3_fs, ramfs) {
        .mnt = {
                .type = "ramfs",
                .data = "mode=700",
        },
        .file_path = TMP_DIR "/dir/file",
};

FIXTURE_VARIANT_ADD(layout3_fs, cgroup2) {
        .mnt = {
                .type = "cgroup2",
        },
        .file_path = TMP_DIR "/test/cgroup.procs",
};

FIXTURE_VARIANT_ADD(layout3_fs, proc) {
        .mnt = {
                .type = "proc",
        },
        .file_path = TMP_DIR "/self/status",
};

FIXTURE_VARIANT_ADD(layout3_fs, sysfs) {
        .mnt = {
                .type = "sysfs",
        },
        .file_path = TMP_DIR "/kernel/notes",
};

FIXTURE_VARIANT_ADD(layout3_fs, hostfs) {
        .mnt = {
                .source = TMP_DIR,
                .flags = MS_BIND,
        },
        .file_path = TMP_DIR "/dir/file",
        .cwd_fs_magic = HOSTFS_SUPER_MAGIC,
};

static char *dirname_alloc(const char *path)
{
        char *dup;

        if (!path)
                return NULL;

        dup = strdup(path);
        if (!dup)
                return NULL;

        return dirname(dup);
}

FIXTURE_SETUP(layout3_fs)
{
        struct stat statbuf;
        char *dir_path = dirname_alloc(variant->file_path);

        if (!supports_filesystem(variant->mnt.type) ||
            !cwd_matches_fs(variant->cwd_fs_magic)) {
                self->skip_test = true;
                SKIP(return, "this filesystem is not supported (setup)");
        }

        prepare_layout_opt(_metadata, &variant->mnt);

        /* Creates directory when required. */
        if (stat(dir_path, &statbuf)) {
                set_cap(_metadata, CAP_DAC_OVERRIDE);
                EXPECT_EQ(0, mkdir(dir_path, 0700))
                {
                        TH_LOG("Failed to create directory \"%s\": %s",
                               dir_path, strerror(errno));
                }
                self->has_created_dir = true;
                clear_cap(_metadata, CAP_DAC_OVERRIDE);
        }

        /* Creates file when required. */
        if (stat(variant->file_path, &statbuf)) {
                int fd;

                set_cap(_metadata, CAP_DAC_OVERRIDE);
                fd = creat(variant->file_path, 0600);
                EXPECT_LE(0, fd)
                {
                        TH_LOG("Failed to create file \"%s\": %s",
                               variant->file_path, strerror(errno));
                }
                EXPECT_EQ(0, close(fd));
                self->has_created_file = true;
                clear_cap(_metadata, CAP_DAC_OVERRIDE);
        }

        free(dir_path);
}

FIXTURE_TEARDOWN_PARENT(layout3_fs)
{
        if (self->skip_test)
                SKIP(return, "this filesystem is not supported (teardown)");

        if (self->has_created_file) {
                set_cap(_metadata, CAP_DAC_OVERRIDE);
                /*
                 * Don't check for error because the file might already
                 * have been removed (cf. release_inode test).
                 */
                unlink(variant->file_path);
                clear_cap(_metadata, CAP_DAC_OVERRIDE);
        }

        if (self->has_created_dir) {
                char *dir_path = dirname_alloc(variant->file_path);

                set_cap(_metadata, CAP_DAC_OVERRIDE);
                /*
                 * Don't check for error because the directory might already
                 * have been removed (cf. release_inode test).
                 */
                rmdir(dir_path);
                clear_cap(_metadata, CAP_DAC_OVERRIDE);
                free(dir_path);
        }

        cleanup_layout(_metadata);
}

static void layer3_fs_tag_inode(struct __test_metadata *const _metadata,
                                FIXTURE_DATA(layout3_fs) * self,
                                const FIXTURE_VARIANT(layout3_fs) * variant,
                                const char *const rule_path)
{
        const struct rule layer1_allow_read_file[] = {
                {
                        .path = rule_path,
                        .access = LANDLOCK_ACCESS_FS_READ_FILE,
                },
                {},
        };
        const struct landlock_ruleset_attr layer2_deny_everything_attr = {
                .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
        };
        const char *const dev_null_path = "/dev/null";
        int ruleset_fd;

        if (self->skip_test)
                SKIP(return, "this filesystem is not supported (test)");

        /* Checks without Landlock. */
        EXPECT_EQ(0, test_open(dev_null_path, O_RDONLY | O_CLOEXEC));
        EXPECT_EQ(0, test_open(variant->file_path, O_RDONLY | O_CLOEXEC));

        ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE,
                                    layer1_allow_read_file);
        EXPECT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        EXPECT_EQ(0, close(ruleset_fd));

        EXPECT_EQ(EACCES, test_open(dev_null_path, O_RDONLY | O_CLOEXEC));
        EXPECT_EQ(0, test_open(variant->file_path, O_RDONLY | O_CLOEXEC));

        /* Forbids directory reading. */
        ruleset_fd =
                landlock_create_ruleset(&layer2_deny_everything_attr,
                                        sizeof(layer2_deny_everything_attr), 0);
        EXPECT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        EXPECT_EQ(0, close(ruleset_fd));

        /* Checks with Landlock and forbidden access. */
        EXPECT_EQ(EACCES, test_open(dev_null_path, O_RDONLY | O_CLOEXEC));
        EXPECT_EQ(EACCES, test_open(variant->file_path, O_RDONLY | O_CLOEXEC));
}

/* Matrix of tests to check file hierarchy evaluation. */

TEST_F_FORK(layout3_fs, tag_inode_dir_parent)
{
        /* The current directory must not be the root for this test. */
        layer3_fs_tag_inode(_metadata, self, variant, ".");
}

TEST_F_FORK(layout3_fs, tag_inode_dir_mnt)
{
        layer3_fs_tag_inode(_metadata, self, variant, TMP_DIR);
}

TEST_F_FORK(layout3_fs, tag_inode_dir_child)
{
        char *dir_path = dirname_alloc(variant->file_path);

        layer3_fs_tag_inode(_metadata, self, variant, dir_path);
        free(dir_path);
}

TEST_F_FORK(layout3_fs, tag_inode_file)
{
        layer3_fs_tag_inode(_metadata, self, variant, variant->file_path);
}

/* Light version of layout1.release_inodes */
TEST_F_FORK(layout3_fs, release_inodes)
{
        const struct rule layer1[] = {
                {
                        .path = TMP_DIR,
                        .access = LANDLOCK_ACCESS_FS_READ_DIR,
                },
                {},
        };
        int ruleset_fd;

        if (self->skip_test)
                SKIP(return, "this filesystem is not supported (test)");

        /* Clean up for the teardown to not fail. */
        if (self->has_created_file)
                EXPECT_EQ(0, remove_path(variant->file_path));

        if (self->has_created_dir) {
                char *dir_path = dirname_alloc(variant->file_path);

                /* Don't check for error because of cgroup specificities. */
                remove_path(dir_path);
                free(dir_path);
        }

        ruleset_fd =
                create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_DIR, layer1);
        ASSERT_LE(0, ruleset_fd);

        /* Unmount the filesystem while it is being used by a ruleset. */
        set_cap(_metadata, CAP_SYS_ADMIN);
        ASSERT_EQ(0, umount(TMP_DIR));
        clear_cap(_metadata, CAP_SYS_ADMIN);

        /* Replaces with a new mount point to simplify FIXTURE_TEARDOWN. */
        set_cap(_metadata, CAP_SYS_ADMIN);
        ASSERT_EQ(0, mount_opt(&mnt_tmp, TMP_DIR));
        clear_cap(_metadata, CAP_SYS_ADMIN);

        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        /* Checks that access to the new mount point is denied. */
        ASSERT_EQ(EACCES, test_open(TMP_DIR, O_RDONLY));
}

static int matches_log_fs_extra(struct __test_metadata *const _metadata,
                                int audit_fd, const char *const blockers,
                                const char *const path, const char *const extra)
{
        static const char log_template[] = REGEX_LANDLOCK_PREFIX
                " blockers=fs\\.%s path=\"%s\" dev=\"[^\"]\\+\" ino=[0-9]\\+$";
        char *absolute_path = NULL;
        size_t log_match_remaining = sizeof(log_template) + strlen(blockers) +
                                     PATH_MAX * 2 +
                                     (extra ? strlen(extra) : 0) + 1;
        char log_match[log_match_remaining];
        char *log_match_cursor = log_match;
        size_t chunk_len;

        chunk_len = snprintf(log_match_cursor, log_match_remaining,
                             REGEX_LANDLOCK_PREFIX " blockers=%s path=\"",
                             blockers);
        if (chunk_len < 0 || chunk_len >= log_match_remaining)
                return -E2BIG;

        /*
         * It is assumed that absolute_path does not contain control
         * characters nor spaces, see audit_string_contains_control().
         */
        absolute_path = realpath(path, NULL);
        if (!absolute_path)
                return -errno;

        log_match_remaining -= chunk_len;
        log_match_cursor += chunk_len;
        log_match_cursor = regex_escape(absolute_path, log_match_cursor,
                                        log_match_remaining);
        free(absolute_path);
        if (log_match_cursor < 0)
                return (long long)log_match_cursor;

        log_match_remaining -= log_match_cursor - log_match;
        chunk_len = snprintf(log_match_cursor, log_match_remaining,
                             "\" dev=\"[^\"]\\+\" ino=[0-9]\\+%s$",
                             extra ?: "");
        if (chunk_len < 0 || chunk_len >= log_match_remaining)
                return -E2BIG;

        return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match,
                                  NULL);
}

static int matches_log_fs(struct __test_metadata *const _metadata, int audit_fd,
                          const char *const blockers, const char *const path)
{
        return matches_log_fs_extra(_metadata, audit_fd, blockers, path, NULL);
}

FIXTURE(audit_layout1)
{
        struct audit_filter audit_filter;
        int audit_fd;
};

FIXTURE_SETUP(audit_layout1)
{
        prepare_layout(_metadata);

        create_layout1(_metadata);

        set_cap(_metadata, CAP_AUDIT_CONTROL);
        self->audit_fd = audit_init_with_exe_filter(&self->audit_filter);
        EXPECT_LE(0, self->audit_fd);
        disable_caps(_metadata);
}

FIXTURE_TEARDOWN_PARENT(audit_layout1)
{
        remove_layout1(_metadata);

        cleanup_layout(_metadata);

        EXPECT_EQ(0, audit_cleanup(-1, NULL));
}

TEST_F(audit_layout1, execute_make)
{
        struct audit_records records;

        copy_file(_metadata, bin_true, file1_s1d1);
        test_execute(_metadata, 0, file1_s1d1);
        test_check_exec(_metadata, 0, file1_s1d1);

        drop_access_rights(_metadata,
                           &(struct landlock_ruleset_attr){
                                   .handled_access_fs =
                                           LANDLOCK_ACCESS_FS_EXECUTE,
                           });

        test_execute(_metadata, EACCES, file1_s1d1);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.execute",
                                    file1_s1d1));
        test_check_exec(_metadata, EACCES, file1_s1d1);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.execute",
                                    file1_s1d1));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(0, records.domain);
}

/*
 * Using a set of handled/denied access rights make it possible to check that
 * only the blocked ones are logged.
 */

/* clang-format off */
static const __u64 access_fs_16 =
        LANDLOCK_ACCESS_FS_EXECUTE |
        LANDLOCK_ACCESS_FS_WRITE_FILE |
        LANDLOCK_ACCESS_FS_READ_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 |
        LANDLOCK_ACCESS_FS_MAKE_SYM |
        LANDLOCK_ACCESS_FS_REFER |
        LANDLOCK_ACCESS_FS_TRUNCATE |
        LANDLOCK_ACCESS_FS_IOCTL_DEV;
/* clang-format on */

TEST_F(audit_layout1, execute_read)
{
        struct audit_records records;

        copy_file(_metadata, bin_true, file1_s1d1);
        test_execute(_metadata, 0, file1_s1d1);
        test_check_exec(_metadata, 0, file1_s1d1);

        drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
                                              .handled_access_fs = access_fs_16,
                                      });

        /*
         * The only difference with the previous audit_layout1.execute_read test is
         * the extra ",fs\\.read_file" blocked by the executable file.
         */
        test_execute(_metadata, EACCES, file1_s1d1);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
                                    "fs\\.execute,fs\\.read_file", file1_s1d1));
        test_check_exec(_metadata, EACCES, file1_s1d1);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
                                    "fs\\.execute,fs\\.read_file", file1_s1d1));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(0, records.domain);
}

TEST_F(audit_layout1, write_file)
{
        struct audit_records records;

        drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
                                              .handled_access_fs = access_fs_16,
                                      });

        EXPECT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY));
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
                                    "fs\\.write_file", file1_s1d1));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(1, records.domain);
}

TEST_F(audit_layout1, read_file)
{
        struct audit_records records;

        drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
                                              .handled_access_fs = access_fs_16,
                                      });

        EXPECT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.read_file",
                                    file1_s1d1));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(1, records.domain);
}

TEST_F(audit_layout1, read_dir)
{
        struct audit_records records;

        drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
                                              .handled_access_fs = access_fs_16,
                                      });

        EXPECT_EQ(EACCES, test_open(dir_s1d1, O_DIRECTORY));
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.read_dir",
                                    dir_s1d1));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(1, records.domain);
}

TEST_F(audit_layout1, remove_dir)
{
        struct audit_records records;

        EXPECT_EQ(0, unlink(file1_s1d3));
        EXPECT_EQ(0, unlink(file2_s1d3));

        drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
                                              .handled_access_fs = access_fs_16,
                                      });

        EXPECT_EQ(-1, rmdir(dir_s1d3));
        EXPECT_EQ(EACCES, errno);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
                                    "fs\\.remove_dir", dir_s1d2));

        EXPECT_EQ(-1, unlinkat(AT_FDCWD, dir_s1d3, AT_REMOVEDIR));
        EXPECT_EQ(EACCES, errno);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
                                    "fs\\.remove_dir", dir_s1d2));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(0, records.domain);
}

TEST_F(audit_layout1, remove_file)
{
        struct audit_records records;

        drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
                                              .handled_access_fs = access_fs_16,
                                      });

        EXPECT_EQ(-1, unlink(file1_s1d3));
        EXPECT_EQ(EACCES, errno);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
                                    "fs\\.remove_file", dir_s1d3));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(1, records.domain);
}

TEST_F(audit_layout1, make_char)
{
        struct audit_records records;

        EXPECT_EQ(0, unlink(file1_s1d3));

        drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
                                              .handled_access_fs = access_fs_16,
                                      });

        EXPECT_EQ(-1, mknod(file1_s1d3, S_IFCHR | 0644, 0));
        EXPECT_EQ(EACCES, errno);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.make_char",
                                    dir_s1d3));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(1, records.domain);
}

TEST_F(audit_layout1, make_dir)
{
        struct audit_records records;

        EXPECT_EQ(0, unlink(file1_s1d3));

        drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
                                              .handled_access_fs = access_fs_16,
                                      });

        EXPECT_EQ(-1, mkdir(file1_s1d3, 0755));
        EXPECT_EQ(EACCES, errno);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.make_dir",
                                    dir_s1d3));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(1, records.domain);
}

TEST_F(audit_layout1, make_reg)
{
        struct audit_records records;

        EXPECT_EQ(0, unlink(file1_s1d3));

        drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
                                              .handled_access_fs = access_fs_16,
                                      });

        EXPECT_EQ(-1, mknod(file1_s1d3, S_IFREG | 0644, 0));
        EXPECT_EQ(EACCES, errno);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.make_reg",
                                    dir_s1d3));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(1, records.domain);
}

TEST_F(audit_layout1, make_sock)
{
        struct audit_records records;

        EXPECT_EQ(0, unlink(file1_s1d3));

        drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
                                              .handled_access_fs = access_fs_16,
                                      });

        EXPECT_EQ(-1, mknod(file1_s1d3, S_IFSOCK | 0644, 0));
        EXPECT_EQ(EACCES, errno);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.make_sock",
                                    dir_s1d3));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(1, records.domain);
}

TEST_F(audit_layout1, make_fifo)
{
        struct audit_records records;

        EXPECT_EQ(0, unlink(file1_s1d3));

        drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
                                              .handled_access_fs = access_fs_16,
                                      });

        EXPECT_EQ(-1, mknod(file1_s1d3, S_IFIFO | 0644, 0));
        EXPECT_EQ(EACCES, errno);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.make_fifo",
                                    dir_s1d3));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(1, records.domain);
}

TEST_F(audit_layout1, make_block)
{
        struct audit_records records;

        EXPECT_EQ(0, unlink(file1_s1d3));

        drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
                                              .handled_access_fs = access_fs_16,
                                      });

        EXPECT_EQ(-1, mknod(file1_s1d3, S_IFBLK | 0644, 0));
        EXPECT_EQ(EACCES, errno);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
                                    "fs\\.make_block", dir_s1d3));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(1, records.domain);
}

TEST_F(audit_layout1, make_sym)
{
        struct audit_records records;

        EXPECT_EQ(0, unlink(file1_s1d3));

        drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
                                              .handled_access_fs = access_fs_16,
                                      });

        EXPECT_EQ(-1, symlink("target", file1_s1d3));
        EXPECT_EQ(EACCES, errno);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.make_sym",
                                    dir_s1d3));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(1, records.domain);
}

TEST_F(audit_layout1, refer_handled)
{
        struct audit_records records;

        EXPECT_EQ(0, unlink(file1_s1d3));

        drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
                                              .handled_access_fs =
                                                      LANDLOCK_ACCESS_FS_REFER,
                                      });

        EXPECT_EQ(-1, link(file1_s1d1, file1_s1d3));
        EXPECT_EQ(EXDEV, errno);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.refer",
                                    dir_s1d1));
        EXPECT_EQ(0,
                  matches_log_domain_allocated(self->audit_fd, getpid(), NULL));
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.refer",
                                    dir_s1d3));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(0, records.domain);
}

TEST_F(audit_layout1, refer_make)
{
        struct audit_records records;

        EXPECT_EQ(0, unlink(file1_s1d3));

        drop_access_rights(_metadata,
                           &(struct landlock_ruleset_attr){
                                   .handled_access_fs =
                                           LANDLOCK_ACCESS_FS_MAKE_REG |
                                           LANDLOCK_ACCESS_FS_REFER,
                           });

        EXPECT_EQ(-1, link(file1_s1d1, file1_s1d3));
        EXPECT_EQ(EACCES, errno);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.refer",
                                    dir_s1d1));
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
                                    "fs\\.make_reg,fs\\.refer", dir_s1d3));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(0, records.domain);
}

TEST_F(audit_layout1, refer_rename)
{
        struct audit_records records;

        EXPECT_EQ(0, unlink(file1_s1d3));

        drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
                                              .handled_access_fs = access_fs_16,
                                      });

        EXPECT_EQ(EACCES, test_rename(file1_s1d2, file1_s2d3));
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
                                    "fs\\.remove_file,fs\\.refer", dir_s1d2));
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
                                    "fs\\.remove_file,fs\\.make_reg,fs\\.refer",
                                    dir_s2d3));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(0, records.domain);
}

TEST_F(audit_layout1, refer_exchange)
{
        struct audit_records records;

        EXPECT_EQ(0, unlink(file1_s1d3));

        drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
                                              .handled_access_fs = access_fs_16,
                                      });

        /*
         * The only difference with the previous audit_layout1.refer_rename test is
         * the extra ",fs\\.make_reg" blocked by the source directory.
         */
        EXPECT_EQ(EACCES, test_exchange(file1_s1d2, file1_s2d3));
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
                                    "fs\\.remove_file,fs\\.make_reg,fs\\.refer",
                                    dir_s1d2));
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
                                    "fs\\.remove_file,fs\\.make_reg,fs\\.refer",
                                    dir_s2d3));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(0, records.domain);
}

/*
 * This test checks that the audit record is correctly generated when the
 * operation is only partially denied.  This is the case for rename(2) when the
 * source file is allowed to be referenced but the destination directory is not.
 *
 * This is also a regression test for commit d617f0d72d80 ("landlock: Optimize
 * file path walks and prepare for audit support") and commit 058518c20920
 * ("landlock: Align partial refer access checks with final ones").
 */
TEST_F(audit_layout1, refer_rename_half)
{
        struct audit_records records;
        const struct rule layer1[] = {
                {
                        .path = dir_s2d2,
                        .access = LANDLOCK_ACCESS_FS_REFER,
                },
                {},
        };
        int ruleset_fd =
                create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REFER, layer1);

        ASSERT_LE(0, ruleset_fd);
        enforce_ruleset(_metadata, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));

        ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d3));
        ASSERT_EQ(EXDEV, errno);

        /* Only half of the request is denied. */
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.refer",
                                    dir_s1d1));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(1, records.domain);
}

TEST_F(audit_layout1, truncate)
{
        struct audit_records records;

        drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
                                              .handled_access_fs = access_fs_16,
                                      });

        EXPECT_EQ(-1, truncate(file1_s1d3, 0));
        EXPECT_EQ(EACCES, errno);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.truncate",
                                    file1_s1d3));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(1, records.domain);
}

TEST_F(audit_layout1, ioctl_dev)
{
        struct audit_records records;
        int fd;

        drop_access_rights(_metadata,
                           &(struct landlock_ruleset_attr){
                                   .handled_access_fs =
                                           access_fs_16 &
                                           ~LANDLOCK_ACCESS_FS_READ_FILE,
                           });

        fd = open("/dev/null", O_RDONLY | O_CLOEXEC);
        ASSERT_LE(0, fd);
        EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FIONREAD));
        EXPECT_EQ(0, matches_log_fs_extra(_metadata, self->audit_fd,
                                          "fs\\.ioctl_dev", "/dev/null",
                                          " ioctlcmd=0x541b"));

        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(1, records.domain);
}

TEST_F(audit_layout1, mount)
{
        struct audit_records records;

        drop_access_rights(_metadata,
                           &(struct landlock_ruleset_attr){
                                   .handled_access_fs =
                                           LANDLOCK_ACCESS_FS_EXECUTE,
                           });

        set_cap(_metadata, CAP_SYS_ADMIN);
        EXPECT_EQ(-1, mount(NULL, dir_s3d2, NULL, MS_RDONLY, NULL));
        EXPECT_EQ(EPERM, errno);
        clear_cap(_metadata, CAP_SYS_ADMIN);
        EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
                                    "fs\\.change_topology", dir_s3d2));
        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(1, records.domain);
}

TEST_HARNESS_MAIN