diff options
| author | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2025-08-06 10:08:54 -0700 | 
|---|---|---|
| committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2025-08-06 10:08:54 -0700 | 
| commit | ab93e0dd72c37d378dd936f031ffb83ff2bd87ce (patch) | |
| tree | 4e5ed022e3c48cedd519954e4fb529dac0240c94 /tools/testing/selftests/filesystems | |
| parent | bcce05041b21888f10b80ea903dcfe51a25c586e (diff) | |
| parent | 4f67c41894674d351a4b4e7dd3471380b71b5bb3 (diff) | |
Merge branch 'next' into for-linus
Prepare input updates for 6.17 merge window.
Diffstat (limited to 'tools/testing/selftests/filesystems')
18 files changed, 833 insertions, 124 deletions
| diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore index 828b66a10c63..7afa58e2bb20 100644 --- a/tools/testing/selftests/filesystems/.gitignore +++ b/tools/testing/selftests/filesystems/.gitignore @@ -2,3 +2,4 @@  dnotify_test  devpts_pts  file_stressor +anon_inode_test diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/selftests/filesystems/Makefile index 66305fc34c60..b02326193fee 100644 --- a/tools/testing/selftests/filesystems/Makefile +++ b/tools/testing/selftests/filesystems/Makefile @@ -1,7 +1,7 @@  # SPDX-License-Identifier: GPL-2.0  CFLAGS += $(KHDR_INCLUDES) -TEST_GEN_PROGS := devpts_pts file_stressor +TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test  TEST_GEN_PROGS_EXTENDED := dnotify_test  include ../lib.mk diff --git a/tools/testing/selftests/filesystems/anon_inode_test.c b/tools/testing/selftests/filesystems/anon_inode_test.c new file mode 100644 index 000000000000..73e0a4d4fb2f --- /dev/null +++ b/tools/testing/selftests/filesystems/anon_inode_test.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#define __SANE_USERSPACE_TYPES__ + +#include <fcntl.h> +#include <stdio.h> +#include <sys/stat.h> + +#include "../kselftest_harness.h" +#include "wrappers.h" + +TEST(anon_inode_no_chown) +{ +	int fd_context; + +	fd_context = sys_fsopen("tmpfs", 0); +	ASSERT_GE(fd_context, 0); + +	ASSERT_LT(fchown(fd_context, 1234, 5678), 0); +	ASSERT_EQ(errno, EOPNOTSUPP); + +	EXPECT_EQ(close(fd_context), 0); +} + +TEST(anon_inode_no_chmod) +{ +	int fd_context; + +	fd_context = sys_fsopen("tmpfs", 0); +	ASSERT_GE(fd_context, 0); + +	ASSERT_LT(fchmod(fd_context, 0777), 0); +	ASSERT_EQ(errno, EOPNOTSUPP); + +	EXPECT_EQ(close(fd_context), 0); +} + +TEST(anon_inode_no_exec) +{ +	int fd_context; + +	fd_context = sys_fsopen("tmpfs", 0); +	ASSERT_GE(fd_context, 0); + +	ASSERT_LT(execveat(fd_context, "", NULL, NULL, AT_EMPTY_PATH), 0); +	ASSERT_EQ(errno, EACCES); + +	EXPECT_EQ(close(fd_context), 0); +} + +TEST(anon_inode_no_open) +{ +	int fd_context; + +	fd_context = sys_fsopen("tmpfs", 0); +	ASSERT_GE(fd_context, 0); + +	ASSERT_GE(dup2(fd_context, 500), 0); +	ASSERT_EQ(close(fd_context), 0); +	fd_context = 500; + +	ASSERT_LT(open("/proc/self/fd/500", 0), 0); +	ASSERT_EQ(errno, ENXIO); + +	EXPECT_EQ(close(fd_context), 0); +} + +TEST_HARNESS_MAIN + diff --git a/tools/testing/selftests/filesystems/eventfd/eventfd_test.c b/tools/testing/selftests/filesystems/eventfd/eventfd_test.c index 85acb4e3ef00..72d51ad0ee0e 100644 --- a/tools/testing/selftests/filesystems/eventfd/eventfd_test.c +++ b/tools/testing/selftests/filesystems/eventfd/eventfd_test.c @@ -50,7 +50,7 @@ TEST(eventfd_check_flag_rdwr)  	ASSERT_GE(fd, 0);  	flags = fcntl(fd, F_GETFL); -	// since the kernel automatically added O_RDWR. +	// The kernel automatically adds the O_RDWR flag.  	EXPECT_EQ(flags, O_RDWR);  	close(fd); @@ -85,7 +85,7 @@ TEST(eventfd_check_flag_nonblock)  	close(fd);  } -TEST(eventfd_chek_flag_cloexec_and_nonblock) +TEST(eventfd_check_flag_cloexec_and_nonblock)  {  	int fd, flags; @@ -178,8 +178,7 @@ TEST(eventfd_check_flag_semaphore)  	// The semaphore could only be obtained from fdinfo.  	ret = verify_fdinfo(fd, &err, "eventfd-semaphore: ", 19, "1\n");  	if (ret != 0) -		ksft_print_msg("eventfd-semaphore check failed, msg: %s\n", -				err.msg); +		ksft_print_msg("eventfd semaphore flag check failed: %s\n", err.msg);  	EXPECT_EQ(ret, 0);  	close(fd); diff --git a/tools/testing/selftests/filesystems/file_stressor.c b/tools/testing/selftests/filesystems/file_stressor.c index 1136f93a9977..01dd89f8e52f 100644 --- a/tools/testing/selftests/filesystems/file_stressor.c +++ b/tools/testing/selftests/filesystems/file_stressor.c @@ -156,7 +156,7 @@ TEST_F_TIMEOUT(file_stressor, slab_typesafe_by_rcu, 900 * 2)  			ssize_t nr_read;  			/* -			 * Concurrently read /proc/<pid>/fd/ which rougly does: +			 * Concurrently read /proc/<pid>/fd/ which roughly does:  			 *  			 * f = fget_task_next(p, &fd);  			 * if (!f) diff --git a/tools/testing/selftests/filesystems/mount-notify/.gitignore b/tools/testing/selftests/filesystems/mount-notify/.gitignore index 82a4846cbc4b..124339ea7845 100644 --- a/tools/testing/selftests/filesystems/mount-notify/.gitignore +++ b/tools/testing/selftests/filesystems/mount-notify/.gitignore @@ -1,2 +1,3 @@  # SPDX-License-Identifier: GPL-2.0-only  /*_test +/*_test_ns diff --git a/tools/testing/selftests/filesystems/mount-notify/Makefile b/tools/testing/selftests/filesystems/mount-notify/Makefile index 10be0227b5ae..836a4eb7be06 100644 --- a/tools/testing/selftests/filesystems/mount-notify/Makefile +++ b/tools/testing/selftests/filesystems/mount-notify/Makefile @@ -1,6 +1,11 @@  # SPDX-License-Identifier: GPL-2.0-or-later -CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) -TEST_GEN_PROGS := mount-notify_test +CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES) +LDLIBS += -lcap + +TEST_GEN_PROGS := mount-notify_test mount-notify_test_ns  include ../../lib.mk + +$(OUTPUT)/mount-notify_test: ../utils.c +$(OUTPUT)/mount-notify_test_ns: ../utils.c diff --git a/tools/testing/selftests/filesystems/mount-notify/mount-notify_test.c b/tools/testing/selftests/filesystems/mount-notify/mount-notify_test.c index 59a71f22fb11..63ce708d93ed 100644 --- a/tools/testing/selftests/filesystems/mount-notify/mount-notify_test.c +++ b/tools/testing/selftests/filesystems/mount-notify/mount-notify_test.c @@ -8,43 +8,21 @@  #include <string.h>  #include <sys/stat.h>  #include <sys/mount.h> -#include <linux/fanotify.h>  #include <unistd.h> -#include <sys/fanotify.h>  #include <sys/syscall.h>  #include "../../kselftest_harness.h"  #include "../statmount/statmount.h" +#include "../utils.h" -#ifndef FAN_MNT_ATTACH -struct fanotify_event_info_mnt { -	struct fanotify_event_info_header hdr; -	__u64 mnt_id; -}; -#define FAN_MNT_ATTACH 0x01000000 /* Mount was attached */ -#endif - -#ifndef FAN_MNT_DETACH -#define FAN_MNT_DETACH 0x02000000 /* Mount was detached */ -#endif - -#ifndef FAN_REPORT_MNT -#define FAN_REPORT_MNT 0x00004000 /* Report mount events */ +// Needed for linux/fanotify.h +#ifndef __kernel_fsid_t +typedef struct { +	int	val[2]; +} __kernel_fsid_t;  #endif -#ifndef FAN_MARK_MNTNS -#define FAN_MARK_MNTNS 0x00000110 -#endif - -static uint64_t get_mnt_id(struct __test_metadata *const _metadata, -			   const char *path) -{ -	struct statx sx; - -	ASSERT_EQ(statx(AT_FDCWD, path, 0, STATX_MNT_ID_UNIQUE, &sx), 0); -	ASSERT_TRUE(!!(sx.stx_mask & STATX_MNT_ID_UNIQUE)); -	return sx.stx_mnt_id; -} +#include <sys/fanotify.h>  static const char root_mntpoint_templ[] = "/tmp/mount-notify_test_root.XXXXXX"; @@ -94,7 +72,7 @@ FIXTURE_SETUP(fanotify)  	ASSERT_EQ(mkdir("b", 0700), 0); -	self->root_id = get_mnt_id(_metadata, "/"); +	self->root_id = get_unique_mnt_id("/");  	ASSERT_NE(self->root_id, 0);  	for (i = 0; i < NUM_FAN_FDS; i++) { diff --git a/tools/testing/selftests/filesystems/mount-notify/mount-notify_test_ns.c b/tools/testing/selftests/filesystems/mount-notify/mount-notify_test_ns.c new file mode 100644 index 000000000000..090a5ca65004 --- /dev/null +++ b/tools/testing/selftests/filesystems/mount-notify/mount-notify_test_ns.c @@ -0,0 +1,557 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2025 Miklos Szeredi <miklos@szeredi.hu> + +#define _GNU_SOURCE +#include <fcntl.h> +#include <sched.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/mount.h> +#include <unistd.h> +#include <sys/syscall.h> + +#include "../../kselftest_harness.h" +#include "../../pidfd/pidfd.h" +#include "../statmount/statmount.h" +#include "../utils.h" + +// Needed for linux/fanotify.h +#ifndef __kernel_fsid_t +typedef struct { +	int	val[2]; +} __kernel_fsid_t; +#endif + +#include <sys/fanotify.h> + +static const char root_mntpoint_templ[] = "/tmp/mount-notify_test_root.XXXXXX"; + +static const int mark_types[] = { +	FAN_MARK_FILESYSTEM, +	FAN_MARK_MOUNT, +	FAN_MARK_INODE +}; + +static const int mark_cmds[] = { +	FAN_MARK_ADD, +	FAN_MARK_REMOVE, +	FAN_MARK_FLUSH +}; + +#define NUM_FAN_FDS ARRAY_SIZE(mark_cmds) + +FIXTURE(fanotify) { +	int fan_fd[NUM_FAN_FDS]; +	char buf[256]; +	unsigned int rem; +	void *next; +	char root_mntpoint[sizeof(root_mntpoint_templ)]; +	int orig_root; +	int orig_ns_fd; +	int ns_fd; +	uint64_t root_id; +}; + +FIXTURE_SETUP(fanotify) +{ +	int i, ret; + +	self->orig_ns_fd = open("/proc/self/ns/mnt", O_RDONLY); +	ASSERT_GE(self->orig_ns_fd, 0); + +	ret = setup_userns(); +	ASSERT_EQ(ret, 0); + +	self->ns_fd = open("/proc/self/ns/mnt", O_RDONLY); +	ASSERT_GE(self->ns_fd, 0); + +	strcpy(self->root_mntpoint, root_mntpoint_templ); +	ASSERT_NE(mkdtemp(self->root_mntpoint), NULL); + +	self->orig_root = open("/", O_PATH | O_CLOEXEC); +	ASSERT_GE(self->orig_root, 0); + +	ASSERT_EQ(mount("tmpfs", self->root_mntpoint, "tmpfs", 0, NULL), 0); + +	ASSERT_EQ(chroot(self->root_mntpoint), 0); + +	ASSERT_EQ(chdir("/"), 0); + +	ASSERT_EQ(mkdir("a", 0700), 0); + +	ASSERT_EQ(mkdir("b", 0700), 0); + +	self->root_id = get_unique_mnt_id("/"); +	ASSERT_NE(self->root_id, 0); + +	for (i = 0; i < NUM_FAN_FDS; i++) { +		int fan_fd = fanotify_init(FAN_REPORT_FID, 0); +		// Verify that watching tmpfs mounted inside userns is allowed +		ret = fanotify_mark(fan_fd, FAN_MARK_ADD | mark_types[i], +				    FAN_OPEN, AT_FDCWD, "/"); +		ASSERT_EQ(ret, 0); +		// ...but watching entire orig root filesystem is not allowed +		ret = fanotify_mark(fan_fd, FAN_MARK_ADD | FAN_MARK_FILESYSTEM, +				    FAN_OPEN, self->orig_root, "."); +		ASSERT_NE(ret, 0); +		close(fan_fd); + +		self->fan_fd[i] = fanotify_init(FAN_REPORT_MNT | FAN_NONBLOCK, +						0); +		ASSERT_GE(self->fan_fd[i], 0); +		// Verify that watching mntns where group was created is allowed +		ret = fanotify_mark(self->fan_fd[i], FAN_MARK_ADD | +				    FAN_MARK_MNTNS, +				    FAN_MNT_ATTACH | FAN_MNT_DETACH, +				    self->ns_fd, NULL); +		ASSERT_EQ(ret, 0); +		// ...but watching orig mntns is not allowed +		ret = fanotify_mark(self->fan_fd[i], FAN_MARK_ADD | +				    FAN_MARK_MNTNS, +				    FAN_MNT_ATTACH | FAN_MNT_DETACH, +				    self->orig_ns_fd, NULL); +		ASSERT_NE(ret, 0); +		// On fd[0] we do an extra ADD that changes nothing. +		// On fd[1]/fd[2] we REMOVE/FLUSH which removes the mark. +		ret = fanotify_mark(self->fan_fd[i], mark_cmds[i] | +				    FAN_MARK_MNTNS, +				    FAN_MNT_ATTACH | FAN_MNT_DETACH, +				    self->ns_fd, NULL); +		ASSERT_EQ(ret, 0); +	} + +	self->rem = 0; +} + +FIXTURE_TEARDOWN(fanotify) +{ +	int i; + +	ASSERT_EQ(self->rem, 0); +	for (i = 0; i < NUM_FAN_FDS; i++) +		close(self->fan_fd[i]); + +	ASSERT_EQ(fchdir(self->orig_root), 0); + +	ASSERT_EQ(chroot("."), 0); + +	EXPECT_EQ(umount2(self->root_mntpoint, MNT_DETACH), 0); +	EXPECT_EQ(chdir(self->root_mntpoint), 0); +	EXPECT_EQ(chdir("/"), 0); +	EXPECT_EQ(rmdir(self->root_mntpoint), 0); +} + +static uint64_t expect_notify(struct __test_metadata *const _metadata, +			      FIXTURE_DATA(fanotify) *self, +			      uint64_t *mask) +{ +	struct fanotify_event_metadata *meta; +	struct fanotify_event_info_mnt *mnt; +	unsigned int thislen; + +	if (!self->rem) { +		ssize_t len; +		int i; + +		for (i = NUM_FAN_FDS - 1; i >= 0; i--) { +			len = read(self->fan_fd[i], self->buf, +				   sizeof(self->buf)); +			if (i > 0) { +				// Groups 1,2 should get EAGAIN +				ASSERT_EQ(len, -1); +				ASSERT_EQ(errno, EAGAIN); +			} else { +				// Group 0 should get events +				ASSERT_GT(len, 0); +			} +		} + +		self->rem = len; +		self->next = (void *) self->buf; +	} + +	meta = self->next; +	ASSERT_TRUE(FAN_EVENT_OK(meta, self->rem)); + +	thislen = meta->event_len; +	self->rem -= thislen; +	self->next += thislen; + +	*mask = meta->mask; +	thislen -= sizeof(*meta); + +	mnt = ((void *) meta) + meta->event_len - thislen; + +	ASSERT_EQ(thislen, sizeof(*mnt)); + +	return mnt->mnt_id; +} + +static void expect_notify_n(struct __test_metadata *const _metadata, +				 FIXTURE_DATA(fanotify) *self, +				 unsigned int n, uint64_t mask[], uint64_t mnts[]) +{ +	unsigned int i; + +	for (i = 0; i < n; i++) +		mnts[i] = expect_notify(_metadata, self, &mask[i]); +} + +static uint64_t expect_notify_mask(struct __test_metadata *const _metadata, +				   FIXTURE_DATA(fanotify) *self, +				   uint64_t expect_mask) +{ +	uint64_t mntid, mask; + +	mntid = expect_notify(_metadata, self, &mask); +	ASSERT_EQ(expect_mask, mask); + +	return mntid; +} + + +static void expect_notify_mask_n(struct __test_metadata *const _metadata, +				 FIXTURE_DATA(fanotify) *self, +				 uint64_t mask, unsigned int n, uint64_t mnts[]) +{ +	unsigned int i; + +	for (i = 0; i < n; i++) +		mnts[i] = expect_notify_mask(_metadata, self, mask); +} + +static void verify_mount_ids(struct __test_metadata *const _metadata, +			     const uint64_t list1[], const uint64_t list2[], +			     size_t num) +{ +	unsigned int i, j; + +	// Check that neither list has any duplicates +	for (i = 0; i < num; i++) { +		for (j = 0; j < num; j++) { +			if (i != j) { +				ASSERT_NE(list1[i], list1[j]); +				ASSERT_NE(list2[i], list2[j]); +			} +		} +	} +	// Check that all list1 memebers can be found in list2. Together with +	// the above it means that the list1 and list2 represent the same sets. +	for (i = 0; i < num; i++) { +		for (j = 0; j < num; j++) { +			if (list1[i] == list2[j]) +				break; +		} +		ASSERT_NE(j, num); +	} +} + +static void check_mounted(struct __test_metadata *const _metadata, +			  const uint64_t mnts[], size_t num) +{ +	ssize_t ret; +	uint64_t *list; + +	list = malloc((num + 1) * sizeof(list[0])); +	ASSERT_NE(list, NULL); + +	ret = listmount(LSMT_ROOT, 0, 0, list, num + 1, 0); +	ASSERT_EQ(ret, num); + +	verify_mount_ids(_metadata, mnts, list, num); + +	free(list); +} + +static void setup_mount_tree(struct __test_metadata *const _metadata, +			    int log2_num) +{ +	int ret, i; + +	ret = mount("", "/", NULL, MS_SHARED, NULL); +	ASSERT_EQ(ret, 0); + +	for (i = 0; i < log2_num; i++) { +		ret = mount("/", "/", NULL, MS_BIND, NULL); +		ASSERT_EQ(ret, 0); +	} +} + +TEST_F(fanotify, bind) +{ +	int ret; +	uint64_t mnts[2] = { self->root_id }; + +	ret = mount("/", "/", NULL, MS_BIND, NULL); +	ASSERT_EQ(ret, 0); + +	mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH); +	ASSERT_NE(mnts[0], mnts[1]); + +	check_mounted(_metadata, mnts, 2); + +	// Cleanup +	uint64_t detach_id; +	ret = umount("/"); +	ASSERT_EQ(ret, 0); + +	detach_id = expect_notify_mask(_metadata, self, FAN_MNT_DETACH); +	ASSERT_EQ(detach_id, mnts[1]); + +	check_mounted(_metadata, mnts, 1); +} + +TEST_F(fanotify, move) +{ +	int ret; +	uint64_t mnts[2] = { self->root_id }; +	uint64_t move_id; + +	ret = mount("/", "/a", NULL, MS_BIND, NULL); +	ASSERT_EQ(ret, 0); + +	mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH); +	ASSERT_NE(mnts[0], mnts[1]); + +	check_mounted(_metadata, mnts, 2); + +	ret = move_mount(AT_FDCWD, "/a", AT_FDCWD, "/b", 0); +	ASSERT_EQ(ret, 0); + +	move_id = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH | FAN_MNT_DETACH); +	ASSERT_EQ(move_id, mnts[1]); + +	// Cleanup +	ret = umount("/b"); +	ASSERT_EQ(ret, 0); + +	check_mounted(_metadata, mnts, 1); +} + +TEST_F(fanotify, propagate) +{ +	const unsigned int log2_num = 4; +	const unsigned int num = (1 << log2_num); +	uint64_t mnts[num]; + +	setup_mount_tree(_metadata, log2_num); + +	expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, num - 1, mnts + 1); + +	mnts[0] = self->root_id; +	check_mounted(_metadata, mnts, num); + +	// Cleanup +	int ret; +	uint64_t mnts2[num]; +	ret = umount2("/", MNT_DETACH); +	ASSERT_EQ(ret, 0); + +	ret = mount("", "/", NULL, MS_PRIVATE, NULL); +	ASSERT_EQ(ret, 0); + +	mnts2[0] = self->root_id; +	expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, num - 1, mnts2 + 1); +	verify_mount_ids(_metadata, mnts, mnts2, num); + +	check_mounted(_metadata, mnts, 1); +} + +TEST_F(fanotify, fsmount) +{ +	int ret, fs, mnt; +	uint64_t mnts[2] = { self->root_id }; + +	fs = fsopen("tmpfs", 0); +	ASSERT_GE(fs, 0); + +	ret = fsconfig(fs, FSCONFIG_CMD_CREATE, 0, 0, 0); +	ASSERT_EQ(ret, 0); + +	mnt = fsmount(fs, 0, 0); +	ASSERT_GE(mnt, 0); + +	close(fs); + +	ret = move_mount(mnt, "", AT_FDCWD, "/a", MOVE_MOUNT_F_EMPTY_PATH); +	ASSERT_EQ(ret, 0); + +	close(mnt); + +	mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH); +	ASSERT_NE(mnts[0], mnts[1]); + +	check_mounted(_metadata, mnts, 2); + +	// Cleanup +	uint64_t detach_id; +	ret = umount("/a"); +	ASSERT_EQ(ret, 0); + +	detach_id = expect_notify_mask(_metadata, self, FAN_MNT_DETACH); +	ASSERT_EQ(detach_id, mnts[1]); + +	check_mounted(_metadata, mnts, 1); +} + +TEST_F(fanotify, reparent) +{ +	uint64_t mnts[6] = { self->root_id }; +	uint64_t dmnts[3]; +	uint64_t masks[3]; +	unsigned int i; +	int ret; + +	// Create setup with a[1] -> b[2] propagation +	ret = mount("/", "/a", NULL, MS_BIND, NULL); +	ASSERT_EQ(ret, 0); + +	ret = mount("", "/a", NULL, MS_SHARED, NULL); +	ASSERT_EQ(ret, 0); + +	ret = mount("/a", "/b", NULL, MS_BIND, NULL); +	ASSERT_EQ(ret, 0); + +	ret = mount("", "/b", NULL, MS_SLAVE, NULL); +	ASSERT_EQ(ret, 0); + +	expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 1); + +	check_mounted(_metadata, mnts, 3); + +	// Mount on a[3], which is propagated to b[4] +	ret = mount("/", "/a", NULL, MS_BIND, NULL); +	ASSERT_EQ(ret, 0); + +	expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 3); + +	check_mounted(_metadata, mnts, 5); + +	// Mount on b[5], not propagated +	ret = mount("/", "/b", NULL, MS_BIND, NULL); +	ASSERT_EQ(ret, 0); + +	mnts[5] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH); + +	check_mounted(_metadata, mnts, 6); + +	// Umount a[3], which is propagated to b[4], but not b[5] +	// This will result in b[5] "falling" on b[2] +	ret = umount("/a"); +	ASSERT_EQ(ret, 0); + +	expect_notify_n(_metadata, self, 3, masks, dmnts); +	verify_mount_ids(_metadata, mnts + 3, dmnts, 3); + +	for (i = 0; i < 3; i++) { +		if (dmnts[i] == mnts[5]) { +			ASSERT_EQ(masks[i], FAN_MNT_ATTACH | FAN_MNT_DETACH); +		} else { +			ASSERT_EQ(masks[i], FAN_MNT_DETACH); +		} +	} + +	mnts[3] = mnts[5]; +	check_mounted(_metadata, mnts, 4); + +	// Cleanup +	ret = umount("/b"); +	ASSERT_EQ(ret, 0); + +	ret = umount("/a"); +	ASSERT_EQ(ret, 0); + +	ret = umount("/b"); +	ASSERT_EQ(ret, 0); + +	expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, 3, dmnts); +	verify_mount_ids(_metadata, mnts + 1, dmnts, 3); + +	check_mounted(_metadata, mnts, 1); +} + +TEST_F(fanotify, rmdir) +{ +	uint64_t mnts[3] = { self->root_id }; +	int ret; + +	ret = mount("/", "/a", NULL, MS_BIND, NULL); +	ASSERT_EQ(ret, 0); + +	ret = mount("/", "/a/b", NULL, MS_BIND, NULL); +	ASSERT_EQ(ret, 0); + +	expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 1); + +	check_mounted(_metadata, mnts, 3); + +	ret = chdir("/a"); +	ASSERT_EQ(ret, 0); + +	ret = fork(); +	ASSERT_GE(ret, 0); + +	if (ret == 0) { +		chdir("/"); +		unshare(CLONE_NEWNS); +		mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL); +		umount2("/a", MNT_DETACH); +		// This triggers a detach in the other namespace +		rmdir("/a"); +		exit(0); +	} +	wait(NULL); + +	expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, 2, mnts + 1); +	check_mounted(_metadata, mnts, 1); + +	// Cleanup +	ret = chdir("/"); +	ASSERT_EQ(ret, 0); +} + +TEST_F(fanotify, pivot_root) +{ +	uint64_t mnts[3] = { self->root_id }; +	uint64_t mnts2[3]; +	int ret; + +	ret = mount("tmpfs", "/a", "tmpfs", 0, NULL); +	ASSERT_EQ(ret, 0); + +	mnts[2] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH); + +	ret = mkdir("/a/new", 0700); +	ASSERT_EQ(ret, 0); + +	ret = mkdir("/a/old", 0700); +	ASSERT_EQ(ret, 0); + +	ret = mount("/a", "/a/new", NULL, MS_BIND, NULL); +	ASSERT_EQ(ret, 0); + +	mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH); +	check_mounted(_metadata, mnts, 3); + +	ret = syscall(SYS_pivot_root, "/a/new", "/a/new/old"); +	ASSERT_EQ(ret, 0); + +	expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH | FAN_MNT_DETACH, 2, mnts2); +	verify_mount_ids(_metadata, mnts, mnts2, 2); +	check_mounted(_metadata, mnts, 3); + +	// Cleanup +	ret = syscall(SYS_pivot_root, "/old", "/old/a/new"); +	ASSERT_EQ(ret, 0); + +	ret = umount("/a/new"); +	ASSERT_EQ(ret, 0); + +	ret = umount("/a"); +	ASSERT_EQ(ret, 0); + +	check_mounted(_metadata, mnts, 1); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/overlayfs/Makefile b/tools/testing/selftests/filesystems/overlayfs/Makefile index 6c661232b3b5..d3ad4a77db9b 100644 --- a/tools/testing/selftests/filesystems/overlayfs/Makefile +++ b/tools/testing/selftests/filesystems/overlayfs/Makefile @@ -4,7 +4,7 @@ CFLAGS += -Wall  CFLAGS += $(KHDR_INCLUDES)  LDLIBS += -lcap -LOCAL_HDRS += wrappers.h log.h +LOCAL_HDRS += ../wrappers.h log.h  TEST_GEN_PROGS := dev_in_maps  TEST_GEN_PROGS += set_layers_via_fds diff --git a/tools/testing/selftests/filesystems/overlayfs/dev_in_maps.c b/tools/testing/selftests/filesystems/overlayfs/dev_in_maps.c index 3b796264223f..31db54b00e64 100644 --- a/tools/testing/selftests/filesystems/overlayfs/dev_in_maps.c +++ b/tools/testing/selftests/filesystems/overlayfs/dev_in_maps.c @@ -17,7 +17,7 @@  #include "../../kselftest.h"  #include "log.h" -#include "wrappers.h" +#include "../wrappers.h"  static long get_file_dev_and_inode(void *addr, struct statx *stx)  { diff --git a/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c b/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c index 5074e64e74a8..dc0449fa628f 100644 --- a/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c +++ b/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c @@ -16,7 +16,7 @@  #include "../../pidfd/pidfd.h"  #include "log.h"  #include "../utils.h" -#include "wrappers.h" +#include "../wrappers.h"  FIXTURE(set_layers_via_fds) {  	int pidfd; diff --git a/tools/testing/selftests/filesystems/statmount/Makefile b/tools/testing/selftests/filesystems/statmount/Makefile index 14ee91a41650..8e354fe99b44 100644 --- a/tools/testing/selftests/filesystems/statmount/Makefile +++ b/tools/testing/selftests/filesystems/statmount/Makefile @@ -1,6 +1,10 @@  # SPDX-License-Identifier: GPL-2.0-or-later -CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) +CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES) +LDLIBS += -lcap +  TEST_GEN_PROGS := statmount_test statmount_test_ns listmount_test  include ../../lib.mk + +$(OUTPUT)/statmount_test_ns: ../utils.c diff --git a/tools/testing/selftests/filesystems/statmount/statmount.h b/tools/testing/selftests/filesystems/statmount/statmount.h index a7a5289ddae9..99e5ad082fb1 100644 --- a/tools/testing/selftests/filesystems/statmount/statmount.h +++ b/tools/testing/selftests/filesystems/statmount/statmount.h @@ -7,6 +7,42 @@  #include <linux/mount.h>  #include <asm/unistd.h> +#ifndef __NR_statmount +	#if defined __alpha__ +		#define __NR_statmount 567 +	#elif defined _MIPS_SIM +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */ +			#define __NR_statmount 4457 +		#endif +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */ +			#define __NR_statmount 6457 +		#endif +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */ +			#define __NR_statmount 5457 +		#endif +	#else +		#define __NR_statmount 457 +	#endif +#endif + +#ifndef __NR_listmount +	#if defined __alpha__ +		#define __NR_listmount 568 +	#elif defined _MIPS_SIM +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */ +			#define __NR_listmount 4458 +		#endif +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */ +			#define __NR_listmount 6458 +		#endif +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */ +			#define __NR_listmount 5458 +		#endif +	#else +		#define __NR_listmount 458 +	#endif +#endif +  static inline int statmount(uint64_t mnt_id, uint64_t mnt_ns_id, uint64_t mask,  			    struct statmount *buf, size_t bufsize,  			    unsigned int flags) diff --git a/tools/testing/selftests/filesystems/statmount/statmount_test_ns.c b/tools/testing/selftests/filesystems/statmount/statmount_test_ns.c index 70cb0c8b21cf..605a3fa16bf7 100644 --- a/tools/testing/selftests/filesystems/statmount/statmount_test_ns.c +++ b/tools/testing/selftests/filesystems/statmount/statmount_test_ns.c @@ -14,6 +14,7 @@  #include <linux/stat.h>  #include "statmount.h" +#include "../utils.h"  #include "../../kselftest.h"  #define NSID_PASS 0 @@ -78,87 +79,10 @@ static int get_mnt_ns_id(const char *mnt_ns, uint64_t *mnt_ns_id)  	return NSID_PASS;  } -static int get_mnt_id(const char *path, uint64_t *mnt_id) -{ -	struct statx sx; -	int ret; - -	ret = statx(AT_FDCWD, path, 0, STATX_MNT_ID_UNIQUE, &sx); -	if (ret == -1) { -		ksft_print_msg("retrieving unique mount ID for %s: %s\n", path, -			       strerror(errno)); -		return NSID_ERROR; -	} - -	if (!(sx.stx_mask & STATX_MNT_ID_UNIQUE)) { -		ksft_print_msg("no unique mount ID available for %s\n", path); -		return NSID_ERROR; -	} - -	*mnt_id = sx.stx_mnt_id; -	return NSID_PASS; -} - -static int write_file(const char *path, const char *val) -{ -	int fd = open(path, O_WRONLY); -	size_t len = strlen(val); -	int ret; - -	if (fd == -1) { -		ksft_print_msg("opening %s for write: %s\n", path, strerror(errno)); -		return NSID_ERROR; -	} - -	ret = write(fd, val, len); -	if (ret == -1) { -		ksft_print_msg("writing to %s: %s\n", path, strerror(errno)); -		return NSID_ERROR; -	} -	if (ret != len) { -		ksft_print_msg("short write to %s\n", path); -		return NSID_ERROR; -	} - -	ret = close(fd); -	if (ret == -1) { -		ksft_print_msg("closing %s\n", path); -		return NSID_ERROR; -	} - -	return NSID_PASS; -} -  static int setup_namespace(void)  { -	int ret; -	char buf[32]; -	uid_t uid = getuid(); -	gid_t gid = getgid(); - -	ret = unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWPID); -	if (ret == -1) -		ksft_exit_fail_msg("unsharing mountns and userns: %s\n", -				   strerror(errno)); - -	sprintf(buf, "0 %d 1", uid); -	ret = write_file("/proc/self/uid_map", buf); -	if (ret != NSID_PASS) -		return ret; -	ret = write_file("/proc/self/setgroups", "deny"); -	if (ret != NSID_PASS) -		return ret; -	sprintf(buf, "0 %d 1", gid); -	ret = write_file("/proc/self/gid_map", buf); -	if (ret != NSID_PASS) -		return ret; - -	ret = mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL); -	if (ret == -1) { -		ksft_print_msg("making mount tree private: %s\n", -			       strerror(errno)); +	if (setup_userns() != 0)  		return NSID_ERROR; -	}  	return NSID_PASS;  } @@ -174,9 +98,9 @@ static int _test_statmount_mnt_ns_id(void)  	if (ret != NSID_PASS)  		return ret; -	ret = get_mnt_id("/", &root_id); -	if (ret != NSID_PASS) -		return ret; +	root_id = get_unique_mnt_id("/"); +	if (!root_id) +		return NSID_ERROR;  	ret = statmount(root_id, 0, STATMOUNT_MNT_NS_ID, &sm, sizeof(sm), 0);  	if (ret == -1) { diff --git a/tools/testing/selftests/filesystems/utils.c b/tools/testing/selftests/filesystems/utils.c index e553c89c5b19..c43a69dffd83 100644 --- a/tools/testing/selftests/filesystems/utils.c +++ b/tools/testing/selftests/filesystems/utils.c @@ -18,7 +18,10 @@  #include <sys/types.h>  #include <sys/wait.h>  #include <sys/xattr.h> +#include <sys/mount.h> +#include "../kselftest.h" +#include "wrappers.h"  #include "utils.h"  #define MAX_USERNS_LEVEL 32 @@ -447,6 +450,71 @@ out_close:  	return fret;  } +static int write_file(const char *path, const char *val) +{ +	int fd = open(path, O_WRONLY); +	size_t len = strlen(val); +	int ret; + +	if (fd == -1) { +		ksft_print_msg("opening %s for write: %s\n", path, strerror(errno)); +		return -1; +	} + +	ret = write(fd, val, len); +	if (ret == -1) { +		ksft_print_msg("writing to %s: %s\n", path, strerror(errno)); +		return -1; +	} +	if (ret != len) { +		ksft_print_msg("short write to %s\n", path); +		return -1; +	} + +	ret = close(fd); +	if (ret == -1) { +		ksft_print_msg("closing %s\n", path); +		return -1; +	} + +	return 0; +} + +int setup_userns(void) +{ +	int ret; +	char buf[32]; +	uid_t uid = getuid(); +	gid_t gid = getgid(); + +	ret = unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWPID); +	if (ret) { +		ksft_exit_fail_msg("unsharing mountns and userns: %s\n", +				   strerror(errno)); +		return ret; +	} + +	sprintf(buf, "0 %d 1", uid); +	ret = write_file("/proc/self/uid_map", buf); +	if (ret) +		return ret; +	ret = write_file("/proc/self/setgroups", "deny"); +	if (ret) +		return ret; +	sprintf(buf, "0 %d 1", gid); +	ret = write_file("/proc/self/gid_map", buf); +	if (ret) +		return ret; + +	ret = mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL); +	if (ret) { +		ksft_print_msg("making mount tree private: %s\n", strerror(errno)); +		return ret; +	} + +	return 0; +} +  /* caps_down - lower all effective caps */  int caps_down(void)  { @@ -499,3 +567,23 @@ out:  	cap_free(caps);  	return fret;  } + +uint64_t get_unique_mnt_id(const char *path) +{ +	struct statx sx; +	int ret; + +	ret = statx(AT_FDCWD, path, 0, STATX_MNT_ID_UNIQUE, &sx); +	if (ret == -1) { +		ksft_print_msg("retrieving unique mount ID for %s: %s\n", path, +			 strerror(errno)); +		return 0; +	} + +	if (!(sx.stx_mask & STATX_MNT_ID_UNIQUE)) { +		ksft_print_msg("no unique mount ID available for %s\n", path); +		return 0; +	} + +	return sx.stx_mnt_id; +} diff --git a/tools/testing/selftests/filesystems/utils.h b/tools/testing/selftests/filesystems/utils.h index 7f1df2a3e94c..70f7ccc607f4 100644 --- a/tools/testing/selftests/filesystems/utils.h +++ b/tools/testing/selftests/filesystems/utils.h @@ -27,6 +27,7 @@ extern int caps_down(void);  extern int cap_down(cap_value_t down);  extern bool switch_ids(uid_t uid, gid_t gid); +extern int setup_userns(void);  static inline bool switch_userns(int fd, uid_t uid, gid_t gid, bool drop_caps)  { @@ -42,4 +43,6 @@ static inline bool switch_userns(int fd, uid_t uid, gid_t gid, bool drop_caps)  	return true;  } +extern uint64_t get_unique_mnt_id(const char *path); +  #endif /* __IDMAP_UTILS_H */ diff --git a/tools/testing/selftests/filesystems/overlayfs/wrappers.h b/tools/testing/selftests/filesystems/wrappers.h index c38bc48e0cfa..420ae4f908cf 100644 --- a/tools/testing/selftests/filesystems/overlayfs/wrappers.h +++ b/tools/testing/selftests/filesystems/wrappers.h @@ -9,6 +9,10 @@  #include <linux/mount.h>  #include <sys/syscall.h> +#ifndef STATX_MNT_ID_UNIQUE +#define STATX_MNT_ID_UNIQUE 0x00004000U /* Want/got extended stx_mount_id */ +#endif +  static inline int sys_fsopen(const char *fsname, unsigned int flags)  {  	return syscall(__NR_fsopen, fsname, flags); @@ -36,6 +40,28 @@ static inline int sys_mount(const char *src, const char *tgt, const char *fst,  #define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */  #endif +#ifndef MOVE_MOUNT_T_EMPTY_PATH +#define MOVE_MOUNT_T_EMPTY_PATH 0x00000040 /* Empty to path permitted */ +#endif + +#ifndef __NR_move_mount +	#if defined __alpha__ +		#define __NR_move_mount 539 +	#elif defined _MIPS_SIM +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */ +			#define __NR_move_mount 4429 +		#endif +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */ +			#define __NR_move_mount 6429 +		#endif +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */ +			#define __NR_move_mount 5429 +		#endif +	#else +		#define __NR_move_mount 429 +	#endif +#endif +  static inline int sys_move_mount(int from_dfd, const char *from_pathname,  				 int to_dfd, const char *to_pathname,  				 unsigned int flags) @@ -53,7 +79,25 @@ static inline int sys_move_mount(int from_dfd, const char *from_pathname,  #endif  #ifndef AT_RECURSIVE -#define AT_RECURSIVE 0x8000 +#define AT_RECURSIVE 0x8000 /* Apply to the entire subtree */ +#endif + +#ifndef __NR_open_tree +	#if defined __alpha__ +		#define __NR_open_tree 538 +	#elif defined _MIPS_SIM +		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */ +			#define __NR_open_tree 4428 +		#endif +		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */ +			#define __NR_open_tree 6428 +		#endif +		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */ +			#define __NR_open_tree 5428 +		#endif +	#else +		#define __NR_open_tree 428 +	#endif  #endif  static inline int sys_open_tree(int dfd, const char *filename, unsigned int flags) | 
