Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Linux/tools/testing/selftests/landlock/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 169 kB image not shown  

Quelle  fs_test.c   Sprache: 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;
}

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,
  },
  {
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=94 H=92 G=92

¤ Dauer der Verarbeitung: 0.17 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.