1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * The root of an empty mount namespace is a nullfs mount. Its superblock is
4 * kernel-internal and shared by every mount namespace. It can't be
5 * reconfigured, neither through fspick() nor through mount(MS_REMOUNT) nor
6 * through umount() of the root which remounts it read-only.
7 */
8 #define _GNU_SOURCE
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <sched.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <sys/mount.h>
15 #include <sys/statfs.h>
16 #include <sys/statvfs.h>
17 #include <sys/syscall.h>
18 #include <sys/vfs.h>
19 #include <sys/wait.h>
20 #include <unistd.h>
21
22 #include "../utils.h"
23 #include "../wrappers.h"
24 #include "empty_mntns.h"
25 #include "kselftest_harness.h"
26
27 #ifndef __NR_fspick
28 #define __NR_fspick 433
29 #endif
30
sys_fspick(int dfd,const char * path,unsigned int flags)31 static int sys_fspick(int dfd, const char *path, unsigned int flags)
32 {
33 return syscall(__NR_fspick, dfd, path, flags);
34 }
35
36 /* Child exit codes. */
37 enum {
38 CHILD_OK,
39 CHILD_USERNS, /* could not create the user namespace */
40 CHILD_UNSHARE, /* could not create the empty mount namespace */
41 CHILD_FSPICK, /* fspick() of the root was not refused with EINVAL */
42 CHILD_REMOUNT, /* mount(MS_REMOUNT) was not refused with EINVAL */
43 CHILD_UMOUNT, /* umount() of the root succeeded */
44 CHILD_STATFS, /* statfs() of the root failed */
45 CHILD_RDONLY, /* the root ended up read-only */
46 };
47
empty_mntns_child(void)48 static int empty_mntns_child(void)
49 {
50 struct statfs st;
51
52 if (enter_userns())
53 return CHILD_USERNS;
54 if (unshare(UNSHARE_EMPTY_MNTNS))
55 return CHILD_UNSHARE;
56
57 if (sys_fspick(AT_FDCWD, "/", 0) >= 0 || errno != EINVAL)
58 return CHILD_FSPICK;
59 if (!mount(NULL, "/", NULL, MS_REMOUNT | MS_RDONLY, NULL) ||
60 errno != EINVAL)
61 return CHILD_REMOUNT;
62 if (!umount2("/", 0))
63 return CHILD_UMOUNT;
64 if (statfs("/", &st))
65 return CHILD_STATFS;
66 if (st.f_flags & ST_RDONLY)
67 return CHILD_RDONLY;
68 return CHILD_OK;
69 }
70
FIXTURE(internal_sb_reconfigure)71 FIXTURE(internal_sb_reconfigure) {};
72
FIXTURE_SETUP(internal_sb_reconfigure)73 FIXTURE_SETUP(internal_sb_reconfigure)
74 {
75 pid_t pid;
76 int status;
77
78 pid = fork();
79 ASSERT_GE(pid, 0);
80 if (pid == 0) {
81 if (enter_userns())
82 _exit(1);
83 if (unshare(UNSHARE_EMPTY_MNTNS))
84 _exit(1);
85 _exit(0);
86 }
87 ASSERT_EQ(waitpid(pid, &status, 0), pid);
88 if (!WIFEXITED(status) || WEXITSTATUS(status))
89 SKIP(return, "UNSHARE_EMPTY_MNTNS not supported");
90 }
91
FIXTURE_TEARDOWN(internal_sb_reconfigure)92 FIXTURE_TEARDOWN(internal_sb_reconfigure) {}
93
TEST_F(internal_sb_reconfigure,nullfs_root)94 TEST_F(internal_sb_reconfigure, nullfs_root)
95 {
96 pid_t pid;
97 int status;
98
99 pid = fork();
100 ASSERT_GE(pid, 0);
101 if (pid == 0)
102 _exit(empty_mntns_child());
103 ASSERT_EQ(waitpid(pid, &status, 0), pid);
104 ASSERT_TRUE(WIFEXITED(status));
105 ASSERT_EQ(WEXITSTATUS(status), CHILD_OK);
106 }
107
108 TEST_HARNESS_MAIN
109