// SPDX-License-Identifier: GPL-2.0 /* * Test ustat(2): looking up superblocks by device number. * * ustat() resolves a device number to a mounted superblock via * user_get_super(). Check that the device number of a mounted tmpfs (an * anonymous device) resolves, that it stops resolving once the filesystem * is unmounted and that bogus device numbers report EINVAL. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "../kselftest_harness.h" /* struct ustat is not exported through UAPI, mirror include/linux/types.h. */ struct ustat_buf { int f_tfree; unsigned long f_tinode; char f_fname[6]; char f_fpack[6]; /* slack in case an architecture lays the struct out differently */ char pad[64]; }; #ifdef __NR_ustat /* * The kernel decodes @dev with new_decode_dev(), which matches the low 32 * bits of the st_dev encoding stat(2) returns for any major below 4096. */ static int sys_ustat(unsigned int dev, struct ustat_buf *buf) { return syscall(__NR_ustat, dev, buf); } static int write_string(const char *path, const char *string) { ssize_t len = strlen(string); int fd; fd = open(path, O_WRONLY); if (fd < 0) return -1; if (write(fd, string, len) != len) { close(fd); return -1; } return close(fd); } /* Enter namespaces in which mounting a tmpfs instance is allowed. */ static int setup_namespaces(void) { uid_t uid = getuid(); gid_t gid = getgid(); char map[64]; if (unshare(CLONE_NEWNS | (uid ? CLONE_NEWUSER : 0))) return -1; if (uid) { if (write_string("/proc/self/setgroups", "deny")) return -1; snprintf(map, sizeof(map), "0 %d 1", uid); if (write_string("/proc/self/uid_map", map)) return -1; snprintf(map, sizeof(map), "0 %d 1", gid); if (write_string("/proc/self/gid_map", map)) return -1; } return mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL); } TEST(resolves_mounted_superblock) { char dir[] = "/tmp/ustat_test.XXXXXX"; struct ustat_buf ub; struct stat st; ASSERT_NE(NULL, mkdtemp(dir)); if (setup_namespaces()) { rmdir(dir); SKIP(return, "cannot set up namespaces: %s", strerror(errno)); } ASSERT_EQ(0, mount("ustat_test", dir, "tmpfs", 0, NULL)); ASSERT_EQ(0, stat(dir, &st)); memset(&ub, 0xff, sizeof(ub)); ASSERT_EQ(0, sys_ustat(st.st_dev, &ub)) TH_LOG("ustat(%u): %s", (unsigned int)st.st_dev, strerror(errno)); ASSERT_EQ(0, umount(dir)); /* The unmount removed the superblock, the device is gone. */ ASSERT_EQ(-1, sys_ustat(st.st_dev, &ub)); ASSERT_EQ(EINVAL, errno); rmdir(dir); } TEST(bogus_device_numbers) { struct ustat_buf ub; ASSERT_EQ(-1, sys_ustat(0, &ub)); ASSERT_EQ(EINVAL, errno); /* major 4095, minor 1048575: nothing plausible lives there */ ASSERT_EQ(-1, sys_ustat((0xfffu << 8) | 0xffu | (0xfff00u << 12), &ub)); ASSERT_EQ(EINVAL, errno); } #else /* !__NR_ustat */ TEST(unsupported) { SKIP(return, "ustat(2) is not available on this architecture"); } #endif /* __NR_ustat */ TEST_HARNESS_MAIN