Skip to content

Commit 04f9070

Browse files
committed
selftests/landlock: Add tests for pseudo filesystems
Add generic and read-only tests for 6 pseudo filesystems to make sure they have a consistent inode management, which is required for Landlock's file hierarchy identification: - tmpfs - ramfs - cgroup2 - proc - sysfs Update related kernel configuration to support these new filesystems, remove useless CONFIG_SECURITY_PATH, and sort all entries. If these filesystems are not supported by the kernel running tests, the related tests are skipped. Expanding variants, this adds 25 new tests for layout3_fs: - tag_inode_dir_parent - tag_inode_dir_mnt - tag_inode_dir_child - tag_inode_dir_file - release_inodes Test coverage for security/landlock with kernel debug code: - 94.7% of 835 lines according to gcc/gcov-12 - 93.0% of 852 lines according to gcc/gcov-13 Test coverage for security/landlock without kernel debug code: - 95.5% of 624 lines according to gcc/gcov-12 - 93.1% of 641 lines according to gcc/gcov-13 Link: https://lore.kernel.org/r/20230612191430.339153-6-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>
1 parent 55ab3fb commit 04f9070

2 files changed

Lines changed: 259 additions & 4 deletions

File tree

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
CONFIG_CGROUPS=y
2+
CONFIG_CGROUP_SCHED=y
13
CONFIG_OVERLAY_FS=y
2-
CONFIG_SECURITY_LANDLOCK=y
3-
CONFIG_SECURITY_PATH=y
4+
CONFIG_PROC_FS=y
45
CONFIG_SECURITY=y
6+
CONFIG_SECURITY_LANDLOCK=y
57
CONFIG_SHMEM=y
6-
CONFIG_TMPFS_XATTR=y
8+
CONFIG_SYSFS=y
79
CONFIG_TMPFS=y
10+
CONFIG_TMPFS_XATTR=y

tools/testing/selftests/landlock/fs_test.c

Lines changed: 253 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ static bool supports_filesystem(const char *const filesystem)
121121
if (!inf)
122122
return true;
123123

124+
/* filesystem can be null for bind mounts. */
125+
if (!filesystem)
126+
return true;
127+
124128
len = snprintf(str, sizeof(str), "nodev\t%s\n", filesystem);
125129
if (len >= sizeof(str))
126130
/* Ignores too-long filesystem names. */
@@ -243,7 +247,7 @@ static void prepare_layout_opt(struct __test_metadata *const _metadata,
243247
* for tests relying on pivot_root(2) and move_mount(2).
244248
*/
245249
set_cap(_metadata, CAP_SYS_ADMIN);
246-
ASSERT_EQ(0, unshare(CLONE_NEWNS));
250+
ASSERT_EQ(0, unshare(CLONE_NEWNS | CLONE_NEWCGROUP));
247251
ASSERT_EQ(0, mount_opt(mnt, TMP_DIR))
248252
{
249253
TH_LOG("Failed to mount the %s filesystem: %s", mnt->type,
@@ -318,11 +322,13 @@ static void remove_layout1(struct __test_metadata *const _metadata)
318322
EXPECT_EQ(0, remove_path(file1_s1d3));
319323
EXPECT_EQ(0, remove_path(file1_s1d2));
320324
EXPECT_EQ(0, remove_path(file1_s1d1));
325+
EXPECT_EQ(0, remove_path(dir_s1d3));
321326

322327
EXPECT_EQ(0, remove_path(file2_s2d3));
323328
EXPECT_EQ(0, remove_path(file1_s2d3));
324329
EXPECT_EQ(0, remove_path(file1_s2d2));
325330
EXPECT_EQ(0, remove_path(file1_s2d1));
331+
EXPECT_EQ(0, remove_path(dir_s2d2));
326332

327333
EXPECT_EQ(0, remove_path(file1_s3d1));
328334
EXPECT_EQ(0, remove_path(dir_s3d3));
@@ -4482,4 +4488,250 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
44824488
}
44834489
}
44844490

4491+
FIXTURE(layout3_fs)
4492+
{
4493+
bool has_created_dir;
4494+
bool has_created_file;
4495+
char *dir_path;
4496+
bool skip_test;
4497+
};
4498+
4499+
FIXTURE_VARIANT(layout3_fs)
4500+
{
4501+
const struct mnt_opt mnt;
4502+
const char *const file_path;
4503+
};
4504+
4505+
/* clang-format off */
4506+
FIXTURE_VARIANT_ADD(layout3_fs, tmpfs) {
4507+
/* clang-format on */
4508+
.mnt = mnt_tmp,
4509+
.file_path = file1_s1d1,
4510+
};
4511+
4512+
FIXTURE_VARIANT_ADD(layout3_fs, ramfs) {
4513+
.mnt = {
4514+
.type = "ramfs",
4515+
.data = "mode=700",
4516+
},
4517+
.file_path = TMP_DIR "/dir/file",
4518+
};
4519+
4520+
FIXTURE_VARIANT_ADD(layout3_fs, cgroup2) {
4521+
.mnt = {
4522+
.type = "cgroup2",
4523+
},
4524+
.file_path = TMP_DIR "/test/cgroup.procs",
4525+
};
4526+
4527+
FIXTURE_VARIANT_ADD(layout3_fs, proc) {
4528+
.mnt = {
4529+
.type = "proc",
4530+
},
4531+
.file_path = TMP_DIR "/self/status",
4532+
};
4533+
4534+
FIXTURE_VARIANT_ADD(layout3_fs, sysfs) {
4535+
.mnt = {
4536+
.type = "sysfs",
4537+
},
4538+
.file_path = TMP_DIR "/kernel/notes",
4539+
};
4540+
4541+
FIXTURE_SETUP(layout3_fs)
4542+
{
4543+
struct stat statbuf;
4544+
const char *slash;
4545+
size_t dir_len;
4546+
4547+
if (!supports_filesystem(variant->mnt.type)) {
4548+
self->skip_test = true;
4549+
SKIP(return, "this filesystem is not supported (setup)");
4550+
}
4551+
4552+
slash = strrchr(variant->file_path, '/');
4553+
ASSERT_NE(slash, NULL);
4554+
dir_len = (size_t)slash - (size_t)variant->file_path;
4555+
ASSERT_LT(0, dir_len);
4556+
self->dir_path = malloc(dir_len + 1);
4557+
self->dir_path[dir_len] = '\0';
4558+
strncpy(self->dir_path, variant->file_path, dir_len);
4559+
4560+
prepare_layout_opt(_metadata, &variant->mnt);
4561+
4562+
/* Creates directory when required. */
4563+
if (stat(self->dir_path, &statbuf)) {
4564+
set_cap(_metadata, CAP_DAC_OVERRIDE);
4565+
EXPECT_EQ(0, mkdir(self->dir_path, 0700))
4566+
{
4567+
TH_LOG("Failed to create directory \"%s\": %s",
4568+
self->dir_path, strerror(errno));
4569+
free(self->dir_path);
4570+
self->dir_path = NULL;
4571+
}
4572+
self->has_created_dir = true;
4573+
clear_cap(_metadata, CAP_DAC_OVERRIDE);
4574+
}
4575+
4576+
/* Creates file when required. */
4577+
if (stat(variant->file_path, &statbuf)) {
4578+
int fd;
4579+
4580+
set_cap(_metadata, CAP_DAC_OVERRIDE);
4581+
fd = creat(variant->file_path, 0600);
4582+
EXPECT_LE(0, fd)
4583+
{
4584+
TH_LOG("Failed to create file \"%s\": %s",
4585+
variant->file_path, strerror(errno));
4586+
}
4587+
EXPECT_EQ(0, close(fd));
4588+
self->has_created_file = true;
4589+
clear_cap(_metadata, CAP_DAC_OVERRIDE);
4590+
}
4591+
}
4592+
4593+
FIXTURE_TEARDOWN(layout3_fs)
4594+
{
4595+
if (self->skip_test)
4596+
SKIP(return, "this filesystem is not supported (teardown)");
4597+
4598+
if (self->has_created_file) {
4599+
set_cap(_metadata, CAP_DAC_OVERRIDE);
4600+
/*
4601+
* Don't check for error because the file might already
4602+
* have been removed (cf. release_inode test).
4603+
*/
4604+
unlink(variant->file_path);
4605+
clear_cap(_metadata, CAP_DAC_OVERRIDE);
4606+
}
4607+
4608+
if (self->has_created_dir) {
4609+
set_cap(_metadata, CAP_DAC_OVERRIDE);
4610+
/*
4611+
* Don't check for error because the directory might already
4612+
* have been removed (cf. release_inode test).
4613+
*/
4614+
rmdir(self->dir_path);
4615+
clear_cap(_metadata, CAP_DAC_OVERRIDE);
4616+
}
4617+
free(self->dir_path);
4618+
self->dir_path = NULL;
4619+
4620+
cleanup_layout(_metadata);
4621+
}
4622+
4623+
static void layer3_fs_tag_inode(struct __test_metadata *const _metadata,
4624+
FIXTURE_DATA(layout3_fs) * self,
4625+
const FIXTURE_VARIANT(layout3_fs) * variant,
4626+
const char *const rule_path)
4627+
{
4628+
const struct rule layer1_allow_read_file[] = {
4629+
{
4630+
.path = rule_path,
4631+
.access = LANDLOCK_ACCESS_FS_READ_FILE,
4632+
},
4633+
{},
4634+
};
4635+
const struct landlock_ruleset_attr layer2_deny_everything_attr = {
4636+
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
4637+
};
4638+
const char *const dev_null_path = "/dev/null";
4639+
int ruleset_fd;
4640+
4641+
if (self->skip_test)
4642+
SKIP(return, "this filesystem is not supported (test)");
4643+
4644+
/* Checks without Landlock. */
4645+
EXPECT_EQ(0, test_open(dev_null_path, O_RDONLY | O_CLOEXEC));
4646+
EXPECT_EQ(0, test_open(variant->file_path, O_RDONLY | O_CLOEXEC));
4647+
4648+
ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE,
4649+
layer1_allow_read_file);
4650+
EXPECT_LE(0, ruleset_fd);
4651+
enforce_ruleset(_metadata, ruleset_fd);
4652+
EXPECT_EQ(0, close(ruleset_fd));
4653+
4654+
EXPECT_EQ(EACCES, test_open(dev_null_path, O_RDONLY | O_CLOEXEC));
4655+
EXPECT_EQ(0, test_open(variant->file_path, O_RDONLY | O_CLOEXEC));
4656+
4657+
/* Forbids directory reading. */
4658+
ruleset_fd =
4659+
landlock_create_ruleset(&layer2_deny_everything_attr,
4660+
sizeof(layer2_deny_everything_attr), 0);
4661+
EXPECT_LE(0, ruleset_fd);
4662+
enforce_ruleset(_metadata, ruleset_fd);
4663+
EXPECT_EQ(0, close(ruleset_fd));
4664+
4665+
/* Checks with Landlock and forbidden access. */
4666+
EXPECT_EQ(EACCES, test_open(dev_null_path, O_RDONLY | O_CLOEXEC));
4667+
EXPECT_EQ(EACCES, test_open(variant->file_path, O_RDONLY | O_CLOEXEC));
4668+
}
4669+
4670+
/* Matrix of tests to check file hierarchy evaluation. */
4671+
4672+
TEST_F_FORK(layout3_fs, tag_inode_dir_parent)
4673+
{
4674+
/* The current directory must not be the root for this test. */
4675+
layer3_fs_tag_inode(_metadata, self, variant, ".");
4676+
}
4677+
4678+
TEST_F_FORK(layout3_fs, tag_inode_dir_mnt)
4679+
{
4680+
layer3_fs_tag_inode(_metadata, self, variant, TMP_DIR);
4681+
}
4682+
4683+
TEST_F_FORK(layout3_fs, tag_inode_dir_child)
4684+
{
4685+
layer3_fs_tag_inode(_metadata, self, variant, self->dir_path);
4686+
}
4687+
4688+
TEST_F_FORK(layout3_fs, tag_inode_file)
4689+
{
4690+
layer3_fs_tag_inode(_metadata, self, variant, variant->file_path);
4691+
}
4692+
4693+
/* Light version of layout1.release_inodes */
4694+
TEST_F_FORK(layout3_fs, release_inodes)
4695+
{
4696+
const struct rule layer1[] = {
4697+
{
4698+
.path = TMP_DIR,
4699+
.access = LANDLOCK_ACCESS_FS_READ_DIR,
4700+
},
4701+
{},
4702+
};
4703+
int ruleset_fd;
4704+
4705+
if (self->skip_test)
4706+
SKIP(return, "this filesystem is not supported (test)");
4707+
4708+
/* Clean up for the teardown to not fail. */
4709+
if (self->has_created_file)
4710+
EXPECT_EQ(0, remove_path(variant->file_path));
4711+
4712+
if (self->has_created_dir)
4713+
/* Don't check for error because of cgroup specificities. */
4714+
remove_path(self->dir_path);
4715+
4716+
ruleset_fd =
4717+
create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_DIR, layer1);
4718+
ASSERT_LE(0, ruleset_fd);
4719+
4720+
/* Unmount the filesystem while it is being used by a ruleset. */
4721+
set_cap(_metadata, CAP_SYS_ADMIN);
4722+
ASSERT_EQ(0, umount(TMP_DIR));
4723+
clear_cap(_metadata, CAP_SYS_ADMIN);
4724+
4725+
/* Replaces with a new mount point to simplify FIXTURE_TEARDOWN. */
4726+
set_cap(_metadata, CAP_SYS_ADMIN);
4727+
ASSERT_EQ(0, mount_opt(&mnt_tmp, TMP_DIR));
4728+
clear_cap(_metadata, CAP_SYS_ADMIN);
4729+
4730+
enforce_ruleset(_metadata, ruleset_fd);
4731+
ASSERT_EQ(0, close(ruleset_fd));
4732+
4733+
/* Checks that access to the new mount point is denied. */
4734+
ASSERT_EQ(EACCES, test_open(TMP_DIR, O_RDONLY));
4735+
}
4736+
44854737
TEST_HARNESS_MAIN

0 commit comments

Comments
 (0)