1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
// 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
|