Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

+259 -4
+6 -3
tools/testing/selftests/landlock/config
··· 1 + CONFIG_CGROUPS=y 2 + CONFIG_CGROUP_SCHED=y 1 3 CONFIG_OVERLAY_FS=y 2 - CONFIG_SECURITY_LANDLOCK=y 3 - CONFIG_SECURITY_PATH=y 4 + CONFIG_PROC_FS=y 4 5 CONFIG_SECURITY=y 6 + CONFIG_SECURITY_LANDLOCK=y 5 7 CONFIG_SHMEM=y 6 - CONFIG_TMPFS_XATTR=y 8 + CONFIG_SYSFS=y 7 9 CONFIG_TMPFS=y 10 + CONFIG_TMPFS_XATTR=y
+253 -1
tools/testing/selftests/landlock/fs_test.c
··· 121 121 if (!inf) 122 122 return true; 123 123 124 + /* filesystem can be null for bind mounts. */ 125 + if (!filesystem) 126 + return true; 127 + 124 128 len = snprintf(str, sizeof(str), "nodev\t%s\n", filesystem); 125 129 if (len >= sizeof(str)) 126 130 /* Ignores too-long filesystem names. */ ··· 247 243 * for tests relying on pivot_root(2) and move_mount(2). 248 244 */ 249 245 set_cap(_metadata, CAP_SYS_ADMIN); 250 - ASSERT_EQ(0, unshare(CLONE_NEWNS)); 246 + ASSERT_EQ(0, unshare(CLONE_NEWNS | CLONE_NEWCGROUP)); 251 247 ASSERT_EQ(0, mount_opt(mnt, TMP_DIR)) 252 248 { 253 249 TH_LOG("Failed to mount the %s filesystem: %s", mnt->type, ··· 322 318 EXPECT_EQ(0, remove_path(file1_s1d3)); 323 319 EXPECT_EQ(0, remove_path(file1_s1d2)); 324 320 EXPECT_EQ(0, remove_path(file1_s1d1)); 321 + EXPECT_EQ(0, remove_path(dir_s1d3)); 325 322 326 323 EXPECT_EQ(0, remove_path(file2_s2d3)); 327 324 EXPECT_EQ(0, remove_path(file1_s2d3)); 328 325 EXPECT_EQ(0, remove_path(file1_s2d2)); 329 326 EXPECT_EQ(0, remove_path(file1_s2d1)); 327 + EXPECT_EQ(0, remove_path(dir_s2d2)); 330 328 331 329 EXPECT_EQ(0, remove_path(file1_s3d1)); 332 330 EXPECT_EQ(0, remove_path(dir_s3d3)); ··· 4486 4480 for_each_path(merge_sub_files, path_entry, i) { 4487 4481 ASSERT_EQ(0, test_open(path_entry, O_RDWR)); 4488 4482 } 4483 + } 4484 + 4485 + FIXTURE(layout3_fs) 4486 + { 4487 + bool has_created_dir; 4488 + bool has_created_file; 4489 + char *dir_path; 4490 + bool skip_test; 4491 + }; 4492 + 4493 + FIXTURE_VARIANT(layout3_fs) 4494 + { 4495 + const struct mnt_opt mnt; 4496 + const char *const file_path; 4497 + }; 4498 + 4499 + /* clang-format off */ 4500 + FIXTURE_VARIANT_ADD(layout3_fs, tmpfs) { 4501 + /* clang-format on */ 4502 + .mnt = mnt_tmp, 4503 + .file_path = file1_s1d1, 4504 + }; 4505 + 4506 + FIXTURE_VARIANT_ADD(layout3_fs, ramfs) { 4507 + .mnt = { 4508 + .type = "ramfs", 4509 + .data = "mode=700", 4510 + }, 4511 + .file_path = TMP_DIR "/dir/file", 4512 + }; 4513 + 4514 + FIXTURE_VARIANT_ADD(layout3_fs, cgroup2) { 4515 + .mnt = { 4516 + .type = "cgroup2", 4517 + }, 4518 + .file_path = TMP_DIR "/test/cgroup.procs", 4519 + }; 4520 + 4521 + FIXTURE_VARIANT_ADD(layout3_fs, proc) { 4522 + .mnt = { 4523 + .type = "proc", 4524 + }, 4525 + .file_path = TMP_DIR "/self/status", 4526 + }; 4527 + 4528 + FIXTURE_VARIANT_ADD(layout3_fs, sysfs) { 4529 + .mnt = { 4530 + .type = "sysfs", 4531 + }, 4532 + .file_path = TMP_DIR "/kernel/notes", 4533 + }; 4534 + 4535 + FIXTURE_SETUP(layout3_fs) 4536 + { 4537 + struct stat statbuf; 4538 + const char *slash; 4539 + size_t dir_len; 4540 + 4541 + if (!supports_filesystem(variant->mnt.type)) { 4542 + self->skip_test = true; 4543 + SKIP(return, "this filesystem is not supported (setup)"); 4544 + } 4545 + 4546 + slash = strrchr(variant->file_path, '/'); 4547 + ASSERT_NE(slash, NULL); 4548 + dir_len = (size_t)slash - (size_t)variant->file_path; 4549 + ASSERT_LT(0, dir_len); 4550 + self->dir_path = malloc(dir_len + 1); 4551 + self->dir_path[dir_len] = '\0'; 4552 + strncpy(self->dir_path, variant->file_path, dir_len); 4553 + 4554 + prepare_layout_opt(_metadata, &variant->mnt); 4555 + 4556 + /* Creates directory when required. */ 4557 + if (stat(self->dir_path, &statbuf)) { 4558 + set_cap(_metadata, CAP_DAC_OVERRIDE); 4559 + EXPECT_EQ(0, mkdir(self->dir_path, 0700)) 4560 + { 4561 + TH_LOG("Failed to create directory \"%s\": %s", 4562 + self->dir_path, strerror(errno)); 4563 + free(self->dir_path); 4564 + self->dir_path = NULL; 4565 + } 4566 + self->has_created_dir = true; 4567 + clear_cap(_metadata, CAP_DAC_OVERRIDE); 4568 + } 4569 + 4570 + /* Creates file when required. */ 4571 + if (stat(variant->file_path, &statbuf)) { 4572 + int fd; 4573 + 4574 + set_cap(_metadata, CAP_DAC_OVERRIDE); 4575 + fd = creat(variant->file_path, 0600); 4576 + EXPECT_LE(0, fd) 4577 + { 4578 + TH_LOG("Failed to create file \"%s\": %s", 4579 + variant->file_path, strerror(errno)); 4580 + } 4581 + EXPECT_EQ(0, close(fd)); 4582 + self->has_created_file = true; 4583 + clear_cap(_metadata, CAP_DAC_OVERRIDE); 4584 + } 4585 + } 4586 + 4587 + FIXTURE_TEARDOWN(layout3_fs) 4588 + { 4589 + if (self->skip_test) 4590 + SKIP(return, "this filesystem is not supported (teardown)"); 4591 + 4592 + if (self->has_created_file) { 4593 + set_cap(_metadata, CAP_DAC_OVERRIDE); 4594 + /* 4595 + * Don't check for error because the file might already 4596 + * have been removed (cf. release_inode test). 4597 + */ 4598 + unlink(variant->file_path); 4599 + clear_cap(_metadata, CAP_DAC_OVERRIDE); 4600 + } 4601 + 4602 + if (self->has_created_dir) { 4603 + set_cap(_metadata, CAP_DAC_OVERRIDE); 4604 + /* 4605 + * Don't check for error because the directory might already 4606 + * have been removed (cf. release_inode test). 4607 + */ 4608 + rmdir(self->dir_path); 4609 + clear_cap(_metadata, CAP_DAC_OVERRIDE); 4610 + } 4611 + free(self->dir_path); 4612 + self->dir_path = NULL; 4613 + 4614 + cleanup_layout(_metadata); 4615 + } 4616 + 4617 + static void layer3_fs_tag_inode(struct __test_metadata *const _metadata, 4618 + FIXTURE_DATA(layout3_fs) * self, 4619 + const FIXTURE_VARIANT(layout3_fs) * variant, 4620 + const char *const rule_path) 4621 + { 4622 + const struct rule layer1_allow_read_file[] = { 4623 + { 4624 + .path = rule_path, 4625 + .access = LANDLOCK_ACCESS_FS_READ_FILE, 4626 + }, 4627 + {}, 4628 + }; 4629 + const struct landlock_ruleset_attr layer2_deny_everything_attr = { 4630 + .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, 4631 + }; 4632 + const char *const dev_null_path = "/dev/null"; 4633 + int ruleset_fd; 4634 + 4635 + if (self->skip_test) 4636 + SKIP(return, "this filesystem is not supported (test)"); 4637 + 4638 + /* Checks without Landlock. */ 4639 + EXPECT_EQ(0, test_open(dev_null_path, O_RDONLY | O_CLOEXEC)); 4640 + EXPECT_EQ(0, test_open(variant->file_path, O_RDONLY | O_CLOEXEC)); 4641 + 4642 + ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, 4643 + layer1_allow_read_file); 4644 + EXPECT_LE(0, ruleset_fd); 4645 + enforce_ruleset(_metadata, ruleset_fd); 4646 + EXPECT_EQ(0, close(ruleset_fd)); 4647 + 4648 + EXPECT_EQ(EACCES, test_open(dev_null_path, O_RDONLY | O_CLOEXEC)); 4649 + EXPECT_EQ(0, test_open(variant->file_path, O_RDONLY | O_CLOEXEC)); 4650 + 4651 + /* Forbids directory reading. */ 4652 + ruleset_fd = 4653 + landlock_create_ruleset(&layer2_deny_everything_attr, 4654 + sizeof(layer2_deny_everything_attr), 0); 4655 + EXPECT_LE(0, ruleset_fd); 4656 + enforce_ruleset(_metadata, ruleset_fd); 4657 + EXPECT_EQ(0, close(ruleset_fd)); 4658 + 4659 + /* Checks with Landlock and forbidden access. */ 4660 + EXPECT_EQ(EACCES, test_open(dev_null_path, O_RDONLY | O_CLOEXEC)); 4661 + EXPECT_EQ(EACCES, test_open(variant->file_path, O_RDONLY | O_CLOEXEC)); 4662 + } 4663 + 4664 + /* Matrix of tests to check file hierarchy evaluation. */ 4665 + 4666 + TEST_F_FORK(layout3_fs, tag_inode_dir_parent) 4667 + { 4668 + /* The current directory must not be the root for this test. */ 4669 + layer3_fs_tag_inode(_metadata, self, variant, "."); 4670 + } 4671 + 4672 + TEST_F_FORK(layout3_fs, tag_inode_dir_mnt) 4673 + { 4674 + layer3_fs_tag_inode(_metadata, self, variant, TMP_DIR); 4675 + } 4676 + 4677 + TEST_F_FORK(layout3_fs, tag_inode_dir_child) 4678 + { 4679 + layer3_fs_tag_inode(_metadata, self, variant, self->dir_path); 4680 + } 4681 + 4682 + TEST_F_FORK(layout3_fs, tag_inode_file) 4683 + { 4684 + layer3_fs_tag_inode(_metadata, self, variant, variant->file_path); 4685 + } 4686 + 4687 + /* Light version of layout1.release_inodes */ 4688 + TEST_F_FORK(layout3_fs, release_inodes) 4689 + { 4690 + const struct rule layer1[] = { 4691 + { 4692 + .path = TMP_DIR, 4693 + .access = LANDLOCK_ACCESS_FS_READ_DIR, 4694 + }, 4695 + {}, 4696 + }; 4697 + int ruleset_fd; 4698 + 4699 + if (self->skip_test) 4700 + SKIP(return, "this filesystem is not supported (test)"); 4701 + 4702 + /* Clean up for the teardown to not fail. */ 4703 + if (self->has_created_file) 4704 + EXPECT_EQ(0, remove_path(variant->file_path)); 4705 + 4706 + if (self->has_created_dir) 4707 + /* Don't check for error because of cgroup specificities. */ 4708 + remove_path(self->dir_path); 4709 + 4710 + ruleset_fd = 4711 + create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_DIR, layer1); 4712 + ASSERT_LE(0, ruleset_fd); 4713 + 4714 + /* Unmount the filesystem while it is being used by a ruleset. */ 4715 + set_cap(_metadata, CAP_SYS_ADMIN); 4716 + ASSERT_EQ(0, umount(TMP_DIR)); 4717 + clear_cap(_metadata, CAP_SYS_ADMIN); 4718 + 4719 + /* Replaces with a new mount point to simplify FIXTURE_TEARDOWN. */ 4720 + set_cap(_metadata, CAP_SYS_ADMIN); 4721 + ASSERT_EQ(0, mount_opt(&mnt_tmp, TMP_DIR)); 4722 + clear_cap(_metadata, CAP_SYS_ADMIN); 4723 + 4724 + enforce_ruleset(_metadata, ruleset_fd); 4725 + ASSERT_EQ(0, close(ruleset_fd)); 4726 + 4727 + /* Checks that access to the new mount point is denied. */ 4728 + ASSERT_EQ(EACCES, test_open(TMP_DIR, O_RDONLY)); 4489 4729 } 4490 4730 4491 4731 TEST_HARNESS_MAIN