| // 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 <errno.h> |
| #include <fcntl.h> |
| #include <sched.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/mount.h> |
| #include <sys/stat.h> |
| #include <sys/syscall.h> |
| #include <unistd.h> |
| |
| #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 |