Landlock updates for v6.5-rc1

-----BEGIN PGP SIGNATURE-----
 
 iIYEABYIAC4WIQSVyBthFV4iTW/VU1/l49DojIL20gUCZJlO/xAcbWljQGRpZ2lr
 b2QubmV0AAoJEOXj0OiMgvbS/FwA/A5GFGtKnLzFpIAXgc1G3kr8c+J/WF7RVUD+
 PJNEsH6PAP41l3BTqVSeVZ+tdLSC7NbesoX0MTd+rCgAWnr+Pko2Cw==
 =KqpG
 -----END PGP SIGNATURE-----

Merge tag 'landlock-6.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux

Pull landlock updates from Mickaël Salaün:
 "Add support for Landlock to UML.

  To do this, this fixes the way hostfs manages inodes according to the
  underlying filesystem [1]. They are now properly handled as for other
  filesystems, which enables Landlock support (and probably other
  features).

  This also extends Landlock's tests with 6 pseudo filesystems,
  including hostfs"

[1] https://lore.kernel.org/all/20230612191430.339153-1-mic@digikod.net/

* tag 'landlock-6.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux:
  selftests/landlock: Add hostfs tests
  selftests/landlock: Add tests for pseudo filesystems
  selftests/landlock: Make mounts configurable
  selftests/landlock: Add supports_filesystem() helper
  selftests/landlock: Don't create useless file layouts
  hostfs: Fix ephemeral inodes
This commit is contained in:
Linus Torvalds 2023-06-27 17:10:27 -07:00
commit 26642864f8
9 changed files with 478 additions and 144 deletions

View file

@ -1214,13 +1214,6 @@ config COMPAT_32BIT_TIME
config ARCH_NO_PREEMPT
bool
config ARCH_EPHEMERAL_INODES
def_bool n
help
An arch should select this symbol if it doesn't keep track of inode
instances on its own, but instead relies on something else (e.g. the
host kernel for an UML kernel).
config ARCH_SUPPORTS_RT
bool

View file

@ -5,7 +5,6 @@ menu "UML-specific options"
config UML
bool
default y
select ARCH_EPHEMERAL_INODES
select ARCH_HAS_CPU_FINALIZE_INIT
select ARCH_HAS_FORTIFY_SOURCE
select ARCH_HAS_GCOV_PROFILE_ALL

View file

@ -65,6 +65,7 @@ struct hostfs_stat {
unsigned long long blocks;
unsigned int maj;
unsigned int min;
dev_t dev;
};
extern int stat_file(const char *path, struct hostfs_stat *p, int fd);

View file

@ -26,6 +26,7 @@ struct hostfs_inode_info {
fmode_t mode;
struct inode vfs_inode;
struct mutex open_mutex;
dev_t dev;
};
static inline struct hostfs_inode_info *HOSTFS_I(struct inode *inode)
@ -182,14 +183,6 @@ static char *follow_link(char *link)
return ERR_PTR(n);
}
static struct inode *hostfs_iget(struct super_block *sb)
{
struct inode *inode = new_inode(sb);
if (!inode)
return ERR_PTR(-ENOMEM);
return inode;
}
static int hostfs_statfs(struct dentry *dentry, struct kstatfs *sf)
{
/*
@ -228,6 +221,7 @@ static struct inode *hostfs_alloc_inode(struct super_block *sb)
return NULL;
hi->fd = -1;
hi->mode = 0;
hi->dev = 0;
inode_init_once(&hi->vfs_inode);
mutex_init(&hi->open_mutex);
return &hi->vfs_inode;
@ -240,6 +234,7 @@ static void hostfs_evict_inode(struct inode *inode)
if (HOSTFS_I(inode)->fd != -1) {
close_file(&HOSTFS_I(inode)->fd);
HOSTFS_I(inode)->fd = -1;
HOSTFS_I(inode)->dev = 0;
}
}
@ -265,6 +260,7 @@ static int hostfs_show_options(struct seq_file *seq, struct dentry *root)
static const struct super_operations hostfs_sbops = {
.alloc_inode = hostfs_alloc_inode,
.free_inode = hostfs_free_inode,
.drop_inode = generic_delete_inode,
.evict_inode = hostfs_evict_inode,
.statfs = hostfs_statfs,
.show_options = hostfs_show_options,
@ -512,18 +508,31 @@ static const struct address_space_operations hostfs_aops = {
.write_end = hostfs_write_end,
};
static int read_name(struct inode *ino, char *name)
static int hostfs_inode_update(struct inode *ino, const struct hostfs_stat *st)
{
set_nlink(ino, st->nlink);
i_uid_write(ino, st->uid);
i_gid_write(ino, st->gid);
ino->i_atime =
(struct timespec64){ st->atime.tv_sec, st->atime.tv_nsec };
ino->i_mtime =
(struct timespec64){ st->mtime.tv_sec, st->mtime.tv_nsec };
ino->i_ctime =
(struct timespec64){ st->ctime.tv_sec, st->ctime.tv_nsec };
ino->i_size = st->size;
ino->i_blocks = st->blocks;
return 0;
}
static int hostfs_inode_set(struct inode *ino, void *data)
{
struct hostfs_stat *st = data;
dev_t rdev;
struct hostfs_stat st;
int err = stat_file(name, &st, -1);
if (err)
return err;
/* Reencode maj and min with the kernel encoding.*/
rdev = MKDEV(st.maj, st.min);
rdev = MKDEV(st->maj, st->min);
switch (st.mode & S_IFMT) {
switch (st->mode & S_IFMT) {
case S_IFLNK:
ino->i_op = &hostfs_link_iops;
break;
@ -535,7 +544,7 @@ static int read_name(struct inode *ino, char *name)
case S_IFBLK:
case S_IFIFO:
case S_IFSOCK:
init_special_inode(ino, st.mode & S_IFMT, rdev);
init_special_inode(ino, st->mode & S_IFMT, rdev);
ino->i_op = &hostfs_iops;
break;
case S_IFREG:
@ -547,17 +556,42 @@ static int read_name(struct inode *ino, char *name)
return -EIO;
}
ino->i_ino = st.ino;
ino->i_mode = st.mode;
set_nlink(ino, st.nlink);
i_uid_write(ino, st.uid);
i_gid_write(ino, st.gid);
ino->i_atime = (struct timespec64){ st.atime.tv_sec, st.atime.tv_nsec };
ino->i_mtime = (struct timespec64){ st.mtime.tv_sec, st.mtime.tv_nsec };
ino->i_ctime = (struct timespec64){ st.ctime.tv_sec, st.ctime.tv_nsec };
ino->i_size = st.size;
ino->i_blocks = st.blocks;
return 0;
HOSTFS_I(ino)->dev = st->dev;
ino->i_ino = st->ino;
ino->i_mode = st->mode;
return hostfs_inode_update(ino, st);
}
static int hostfs_inode_test(struct inode *inode, void *data)
{
const struct hostfs_stat *st = data;
return inode->i_ino == st->ino && HOSTFS_I(inode)->dev == st->dev;
}
static struct inode *hostfs_iget(struct super_block *sb, char *name)
{
struct inode *inode;
struct hostfs_stat st;
int err = stat_file(name, &st, -1);
if (err)
return ERR_PTR(err);
inode = iget5_locked(sb, st.ino, hostfs_inode_test, hostfs_inode_set,
&st);
if (!inode)
return ERR_PTR(-ENOMEM);
if (inode->i_state & I_NEW) {
unlock_new_inode(inode);
} else {
spin_lock(&inode->i_lock);
hostfs_inode_update(inode, &st);
spin_unlock(&inode->i_lock);
}
return inode;
}
static int hostfs_create(struct mnt_idmap *idmap, struct inode *dir,
@ -565,62 +599,48 @@ static int hostfs_create(struct mnt_idmap *idmap, struct inode *dir,
{
struct inode *inode;
char *name;
int error, fd;
int fd;
inode = hostfs_iget(dir->i_sb);
if (IS_ERR(inode)) {
error = PTR_ERR(inode);
goto out;
}
error = -ENOMEM;
name = dentry_name(dentry);
if (name == NULL)
goto out_put;
return -ENOMEM;
fd = file_create(name, mode & 0777);
if (fd < 0)
error = fd;
else
error = read_name(inode, name);
if (fd < 0) {
__putname(name);
return fd;
}
inode = hostfs_iget(dir->i_sb, name);
__putname(name);
if (error)
goto out_put;
if (IS_ERR(inode))
return PTR_ERR(inode);
HOSTFS_I(inode)->fd = fd;
HOSTFS_I(inode)->mode = FMODE_READ | FMODE_WRITE;
d_instantiate(dentry, inode);
return 0;
out_put:
iput(inode);
out:
return error;
}
static struct dentry *hostfs_lookup(struct inode *ino, struct dentry *dentry,
unsigned int flags)
{
struct inode *inode;
struct inode *inode = NULL;
char *name;
int err;
inode = hostfs_iget(ino->i_sb);
if (IS_ERR(inode))
goto out;
err = -ENOMEM;
name = dentry_name(dentry);
if (name) {
err = read_name(inode, name);
__putname(name);
if (name == NULL)
return ERR_PTR(-ENOMEM);
inode = hostfs_iget(ino->i_sb, name);
__putname(name);
if (IS_ERR(inode)) {
if (PTR_ERR(inode) == -ENOENT)
inode = NULL;
else
return ERR_CAST(inode);
}
if (err) {
iput(inode);
inode = (err == -ENOENT) ? NULL : ERR_PTR(err);
}
out:
return d_splice_alias(inode, dentry);
}
@ -704,35 +724,23 @@ static int hostfs_mknod(struct mnt_idmap *idmap, struct inode *dir,
char *name;
int err;
inode = hostfs_iget(dir->i_sb);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
goto out;
}
err = -ENOMEM;
name = dentry_name(dentry);
if (name == NULL)
goto out_put;
return -ENOMEM;
err = do_mknod(name, mode, MAJOR(dev), MINOR(dev));
if (err)
goto out_free;
if (err) {
__putname(name);
return err;
}
err = read_name(inode, name);
inode = hostfs_iget(dir->i_sb, name);
__putname(name);
if (err)
goto out_put;
if (IS_ERR(inode))
return PTR_ERR(inode);
d_instantiate(dentry, inode);
return 0;
out_free:
__putname(name);
out_put:
iput(inode);
out:
return err;
}
static int hostfs_rename2(struct mnt_idmap *idmap,
@ -929,49 +937,40 @@ static int hostfs_fill_sb_common(struct super_block *sb, void *d, int silent)
sb->s_maxbytes = MAX_LFS_FILESIZE;
err = super_setup_bdi(sb);
if (err)
goto out;
return err;
/* NULL is printed as '(null)' by printf(): avoid that. */
if (req_root == NULL)
req_root = "";
err = -ENOMEM;
sb->s_fs_info = host_root_path =
kasprintf(GFP_KERNEL, "%s/%s", root_ino, req_root);
if (host_root_path == NULL)
goto out;
return -ENOMEM;
root_inode = new_inode(sb);
if (!root_inode)
goto out;
err = read_name(root_inode, host_root_path);
if (err)
goto out_put;
root_inode = hostfs_iget(sb, host_root_path);
if (IS_ERR(root_inode))
return PTR_ERR(root_inode);
if (S_ISLNK(root_inode->i_mode)) {
char *name = follow_link(host_root_path);
if (IS_ERR(name)) {
err = PTR_ERR(name);
goto out_put;
}
err = read_name(root_inode, name);
char *name;
iput(root_inode);
name = follow_link(host_root_path);
if (IS_ERR(name))
return PTR_ERR(name);
root_inode = hostfs_iget(sb, name);
kfree(name);
if (err)
goto out_put;
if (IS_ERR(root_inode))
return PTR_ERR(root_inode);
}
err = -ENOMEM;
sb->s_root = d_make_root(root_inode);
if (sb->s_root == NULL)
goto out;
return -ENOMEM;
return 0;
out_put:
iput(root_inode);
out:
return err;
}
static struct dentry *hostfs_read_sb(struct file_system_type *type,

View file

@ -36,6 +36,7 @@ static void stat64_to_hostfs(const struct stat64 *buf, struct hostfs_stat *p)
p->blocks = buf->st_blocks;
p->maj = os_major(buf->st_rdev);
p->min = os_minor(buf->st_rdev);
p->dev = buf->st_dev;
}
int stat_file(const char *path, struct hostfs_stat *p, int fd)

View file

@ -2,7 +2,7 @@
config SECURITY_LANDLOCK
bool "Landlock support"
depends on SECURITY && !ARCH_EPHEMERAL_INODES
depends on SECURITY
select SECURITY_PATH
help
Landlock is a sandboxing mechanism that enables processes to restrict

View file

@ -1,7 +1,10 @@
CONFIG_CGROUPS=y
CONFIG_CGROUP_SCHED=y
CONFIG_OVERLAY_FS=y
CONFIG_SECURITY_LANDLOCK=y
CONFIG_SECURITY_PATH=y
CONFIG_PROC_FS=y
CONFIG_SECURITY=y
CONFIG_SECURITY_LANDLOCK=y
CONFIG_SHMEM=y
CONFIG_TMPFS_XATTR=y
CONFIG_SYSFS=y
CONFIG_TMPFS=y
CONFIG_TMPFS_XATTR=y

View file

@ -0,0 +1 @@
CONFIG_HOSTFS=y

View file

@ -10,6 +10,7 @@
#define _GNU_SOURCE
#include <fcntl.h>
#include <linux/landlock.h>
#include <linux/magic.h>
#include <sched.h>
#include <stdio.h>
#include <string.h>
@ -19,6 +20,7 @@
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/vfs.h>
#include <unistd.h>
#include "common.h"
@ -107,8 +109,10 @@ static bool fgrep(FILE *const inf, const char *const str)
return false;
}
static bool supports_overlayfs(void)
static bool supports_filesystem(const char *const filesystem)
{
char str[32];
int len;
bool res;
FILE *const inf = fopen("/proc/filesystems", "r");
@ -119,11 +123,33 @@ static bool supports_overlayfs(void)
if (!inf)
return true;
res = fgrep(inf, "nodev\toverlay\n");
/* filesystem can be null for bind mounts. */
if (!filesystem)
return true;
len = snprintf(str, sizeof(str), "nodev\t%s\n", filesystem);
if (len >= sizeof(str))
/* Ignores too-long filesystem names. */
return true;
res = fgrep(inf, str);
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)
{
@ -206,7 +232,26 @@ static int remove_path(const char *const path)
return err;
}
static void prepare_layout(struct __test_metadata *const _metadata)
struct mnt_opt {
const char *const source;
const char *const type;
const unsigned long flags;
const char *const data;
};
const struct mnt_opt mnt_tmp = {
.type = "tmpfs",
.data = "size=4m,mode=700",
};
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);
@ -217,12 +262,28 @@ static void prepare_layout(struct __test_metadata *const _metadata)
* for tests relying on pivot_root(2) and move_mount(2).
*/
set_cap(_metadata, CAP_SYS_ADMIN);
ASSERT_EQ(0, unshare(CLONE_NEWNS));
ASSERT_EQ(0, mount("tmp", TMP_DIR, "tmpfs", 0, "size=4m,mode=700"));
ASSERT_EQ(0, 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);
@ -231,6 +292,20 @@ static void cleanup_layout(struct __test_metadata *const _metadata)
EXPECT_EQ(0, remove_path(TMP_DIR));
}
/* clang-format off */
FIXTURE(layout0) {};
/* clang-format on */
FIXTURE_SETUP(layout0)
{
prepare_layout(_metadata);
}
FIXTURE_TEARDOWN(layout0)
{
cleanup_layout(_metadata);
}
static void create_layout1(struct __test_metadata *const _metadata)
{
create_file(_metadata, file1_s1d1);
@ -248,7 +323,7 @@ static void create_layout1(struct __test_metadata *const _metadata)
create_file(_metadata, file1_s3d1);
create_directory(_metadata, dir_s3d2);
set_cap(_metadata, CAP_SYS_ADMIN);
ASSERT_EQ(0, mount("tmp", dir_s3d2, "tmpfs", 0, "size=4m,mode=700"));
ASSERT_EQ(0, mount_opt(&mnt_tmp, dir_s3d2));
clear_cap(_metadata, CAP_SYS_ADMIN);
ASSERT_EQ(0, mkdir(dir_s3d3, 0700));
@ -262,11 +337,13 @@ static void remove_layout1(struct __test_metadata *const _metadata)
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(dir_s3d3));
@ -510,7 +587,7 @@ TEST_F_FORK(layout1, file_and_dir_access_rights)
ASSERT_EQ(0, close(ruleset_fd));
}
TEST_F_FORK(layout1, unknown_access_rights)
TEST_F_FORK(layout0, unknown_access_rights)
{
__u64 access_mask;
@ -608,7 +685,7 @@ static void enforce_ruleset(struct __test_metadata *const _metadata,
}
}
TEST_F_FORK(layout1, proc_nsfs)
TEST_F_FORK(layout0, proc_nsfs)
{
const struct rule rules[] = {
{
@ -657,11 +734,11 @@ TEST_F_FORK(layout1, proc_nsfs)
ASSERT_EQ(0, close(path_beneath.parent_fd));
}
TEST_F_FORK(layout1, unpriv)
TEST_F_FORK(layout0, unpriv)
{
const struct rule rules[] = {
{
.path = dir_s1d2,
.path = TMP_DIR,
.access = ACCESS_RO,
},
{},
@ -1301,12 +1378,12 @@ TEST_F_FORK(layout1, inherit_superset)
ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
}
TEST_F_FORK(layout1, max_layers)
TEST_F_FORK(layout0, max_layers)
{
int i, err;
const struct rule rules[] = {
{
.path = dir_s1d2,
.path = TMP_DIR,
.access = ACCESS_RO,
},
{},
@ -4030,21 +4107,24 @@ static const char (*merge_sub_files[])[] = {
* work
*/
/* clang-format off */
FIXTURE(layout2_overlay) {};
/* clang-format on */
FIXTURE(layout2_overlay)
{
bool skip_test;
};
FIXTURE_SETUP(layout2_overlay)
{
if (!supports_overlayfs())
SKIP(return, "overlayfs is not supported");
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("tmp", LOWER_BASE, "tmpfs", 0, "size=4m,mode=700"));
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);
@ -4054,7 +4134,7 @@ FIXTURE_SETUP(layout2_overlay)
create_directory(_metadata, UPPER_BASE);
set_cap(_metadata, CAP_SYS_ADMIN);
ASSERT_EQ(0, mount("tmp", UPPER_BASE, "tmpfs", 0, "size=4m,mode=700"));
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);
@ -4075,8 +4155,8 @@ FIXTURE_SETUP(layout2_overlay)
FIXTURE_TEARDOWN(layout2_overlay)
{
if (!supports_overlayfs())
SKIP(return, "overlayfs is not supported");
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));
@ -4109,8 +4189,8 @@ FIXTURE_TEARDOWN(layout2_overlay)
TEST_F_FORK(layout2_overlay, no_restriction)
{
if (!supports_overlayfs())
SKIP(return, "overlayfs is not supported");
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));
@ -4275,8 +4355,8 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
size_t i;
const char *path_entry;
if (!supports_overlayfs())
SKIP(return, "overlayfs is not supported");
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);
@ -4423,4 +4503,261 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
}
}
FIXTURE(layout3_fs)
{
bool has_created_dir;
bool has_created_file;
char *dir_path;
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 = mnt_tmp,
.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,
};
FIXTURE_SETUP(layout3_fs)
{
struct stat statbuf;
const char *slash;
size_t dir_len;
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)");
}
slash = strrchr(variant->file_path, '/');
ASSERT_NE(slash, NULL);
dir_len = (size_t)slash - (size_t)variant->file_path;
ASSERT_LT(0, dir_len);
self->dir_path = malloc(dir_len + 1);
self->dir_path[dir_len] = '\0';
strncpy(self->dir_path, variant->file_path, dir_len);
prepare_layout_opt(_metadata, &variant->mnt);
/* Creates directory when required. */
if (stat(self->dir_path, &statbuf)) {
set_cap(_metadata, CAP_DAC_OVERRIDE);
EXPECT_EQ(0, mkdir(self->dir_path, 0700))
{
TH_LOG("Failed to create directory \"%s\": %s",
self->dir_path, strerror(errno));
free(self->dir_path);
self->dir_path = NULL;
}
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);
}
}
FIXTURE_TEARDOWN(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) {
set_cap(_metadata, CAP_DAC_OVERRIDE);
/*
* Don't check for error because the directory might already
* have been removed (cf. release_inode test).
*/
rmdir(self->dir_path);
clear_cap(_metadata, CAP_DAC_OVERRIDE);
}
free(self->dir_path);
self->dir_path = NULL;
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)
{
layer3_fs_tag_inode(_metadata, self, variant, self->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)
/* Don't check for error because of cgroup specificities. */
remove_path(self->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));
}
TEST_HARNESS_MAIN