aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric W. Biederman <ebiederm@xmission.com>2014-07-29 18:50:44 -0400
committerEric W. Biederman <ebiederm@xmission.com>2014-07-31 20:13:15 -0400
commitdb181ce011e3c033328608299cd6fac06ea50130 (patch)
tree45122ca259c9310f7082e9920713a073fe127ddb
parentffbc6f0ead47fa5a1dc9642b0331cb75c20a640e (diff)
mnt: Add tests for unprivileged remount cases that have found to be faulty
Kenton Varda <kenton@sandstorm.io> discovered that by remounting a read-only bind mount read-only in a user namespace the MNT_LOCK_READONLY bit would be cleared, allowing an unprivileged user to the remount a read-only mount read-write. Upon review of the code in remount it was discovered that the code allowed nosuid, noexec, and nodev to be cleared. It was also discovered that the code was allowing the per mount atime flags to be changed. The first naive patch to fix these issues contained the flaw that using default atime settings when remounting a filesystem could be disallowed. To avoid this problems in the future add tests to ensure unprivileged remounts are succeeding and failing at the appropriate times. Cc: stable@vger.kernel.org Acked-by: Serge E. Hallyn <serge.hallyn@ubuntu.com> Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
-rw-r--r--tools/testing/selftests/Makefile1
-rw-r--r--tools/testing/selftests/mount/Makefile17
-rw-r--r--tools/testing/selftests/mount/unprivileged-remount-test.c242
3 files changed, 260 insertions, 0 deletions
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index e66e710cc595..0a8a9db43d34 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -4,6 +4,7 @@ TARGETS += efivarfs
4TARGETS += kcmp 4TARGETS += kcmp
5TARGETS += memory-hotplug 5TARGETS += memory-hotplug
6TARGETS += mqueue 6TARGETS += mqueue
7TARGETS += mount
7TARGETS += net 8TARGETS += net
8TARGETS += ptrace 9TARGETS += ptrace
9TARGETS += timers 10TARGETS += timers
diff --git a/tools/testing/selftests/mount/Makefile b/tools/testing/selftests/mount/Makefile
new file mode 100644
index 000000000000..337d853c2b72
--- /dev/null
+++ b/tools/testing/selftests/mount/Makefile
@@ -0,0 +1,17 @@
1# Makefile for mount selftests.
2
3all: unprivileged-remount-test
4
5unprivileged-remount-test: unprivileged-remount-test.c
6 gcc -Wall -O2 unprivileged-remount-test.c -o unprivileged-remount-test
7
8# Allow specific tests to be selected.
9test_unprivileged_remount: unprivileged-remount-test
10 @if [ -f /proc/self/uid_map ] ; then ./unprivileged-remount-test ; fi
11
12run_tests: all test_unprivileged_remount
13
14clean:
15 rm -f unprivileged-remount-test
16
17.PHONY: all test_unprivileged_remount
diff --git a/tools/testing/selftests/mount/unprivileged-remount-test.c b/tools/testing/selftests/mount/unprivileged-remount-test.c
new file mode 100644
index 000000000000..1b3ff2fda4d0
--- /dev/null
+++ b/tools/testing/selftests/mount/unprivileged-remount-test.c
@@ -0,0 +1,242 @@
1#define _GNU_SOURCE
2#include <sched.h>
3#include <stdio.h>
4#include <errno.h>
5#include <string.h>
6#include <sys/types.h>
7#include <sys/mount.h>
8#include <sys/wait.h>
9#include <stdlib.h>
10#include <unistd.h>
11#include <fcntl.h>
12#include <grp.h>
13#include <stdbool.h>
14#include <stdarg.h>
15
16#ifndef CLONE_NEWNS
17# define CLONE_NEWNS 0x00020000
18#endif
19#ifndef CLONE_NEWUTS
20# define CLONE_NEWUTS 0x04000000
21#endif
22#ifndef CLONE_NEWIPC
23# define CLONE_NEWIPC 0x08000000
24#endif
25#ifndef CLONE_NEWNET
26# define CLONE_NEWNET 0x40000000
27#endif
28#ifndef CLONE_NEWUSER
29# define CLONE_NEWUSER 0x10000000
30#endif
31#ifndef CLONE_NEWPID
32# define CLONE_NEWPID 0x20000000
33#endif
34
35#ifndef MS_RELATIME
36#define MS_RELATIME (1 << 21)
37#endif
38#ifndef MS_STRICTATIME
39#define MS_STRICTATIME (1 << 24)
40#endif
41
42static void die(char *fmt, ...)
43{
44 va_list ap;
45 va_start(ap, fmt);
46 vfprintf(stderr, fmt, ap);
47 va_end(ap);
48 exit(EXIT_FAILURE);
49}
50
51static void write_file(char *filename, char *fmt, ...)
52{
53 char buf[4096];
54 int fd;
55 ssize_t written;
56 int buf_len;
57 va_list ap;
58
59 va_start(ap, fmt);
60 buf_len = vsnprintf(buf, sizeof(buf), fmt, ap);
61 va_end(ap);
62 if (buf_len < 0) {
63 die("vsnprintf failed: %s\n",
64 strerror(errno));
65 }
66 if (buf_len >= sizeof(buf)) {
67 die("vsnprintf output truncated\n");
68 }
69
70 fd = open(filename, O_WRONLY);
71 if (fd < 0) {
72 die("open of %s failed: %s\n",
73 filename, strerror(errno));
74 }
75 written = write(fd, buf, buf_len);
76 if (written != buf_len) {
77 if (written >= 0) {
78 die("short write to %s\n", filename);
79 } else {
80 die("write to %s failed: %s\n",
81 filename, strerror(errno));
82 }
83 }
84 if (close(fd) != 0) {
85 die("close of %s failed: %s\n",
86 filename, strerror(errno));
87 }
88}
89
90static void create_and_enter_userns(void)
91{
92 uid_t uid;
93 gid_t gid;
94
95 uid = getuid();
96 gid = getgid();
97
98 if (unshare(CLONE_NEWUSER) !=0) {
99 die("unshare(CLONE_NEWUSER) failed: %s\n",
100 strerror(errno));
101 }
102
103 write_file("/proc/self/uid_map", "0 %d 1", uid);
104 write_file("/proc/self/gid_map", "0 %d 1", gid);
105
106 if (setgroups(0, NULL) != 0) {
107 die("setgroups failed: %s\n",
108 strerror(errno));
109 }
110 if (setgid(0) != 0) {
111 die ("setgid(0) failed %s\n",
112 strerror(errno));
113 }
114 if (setuid(0) != 0) {
115 die("setuid(0) failed %s\n",
116 strerror(errno));
117 }
118}
119
120static
121bool test_unpriv_remount(int mount_flags, int remount_flags, int invalid_flags)
122{
123 pid_t child;
124
125 child = fork();
126 if (child == -1) {
127 die("fork failed: %s\n",
128 strerror(errno));
129 }
130 if (child != 0) { /* parent */
131 pid_t pid;
132 int status;
133 pid = waitpid(child, &status, 0);
134 if (pid == -1) {
135 die("waitpid failed: %s\n",
136 strerror(errno));
137 }
138 if (pid != child) {
139 die("waited for %d got %d\n",
140 child, pid);
141 }
142 if (!WIFEXITED(status)) {
143 die("child did not terminate cleanly\n");
144 }
145 return WEXITSTATUS(status) == EXIT_SUCCESS ? true : false;
146 }
147
148 create_and_enter_userns();
149 if (unshare(CLONE_NEWNS) != 0) {
150 die("unshare(CLONE_NEWNS) failed: %s\n",
151 strerror(errno));
152 }
153
154 if (mount("testing", "/tmp", "ramfs", mount_flags, NULL) != 0) {
155 die("mount of /tmp failed: %s\n",
156 strerror(errno));
157 }
158
159 create_and_enter_userns();
160
161 if (unshare(CLONE_NEWNS) != 0) {
162 die("unshare(CLONE_NEWNS) failed: %s\n",
163 strerror(errno));
164 }
165
166 if (mount("/tmp", "/tmp", "none",
167 MS_REMOUNT | MS_BIND | remount_flags, NULL) != 0) {
168 /* system("cat /proc/self/mounts"); */
169 die("remount of /tmp failed: %s\n",
170 strerror(errno));
171 }
172
173 if (mount("/tmp", "/tmp", "none",
174 MS_REMOUNT | MS_BIND | invalid_flags, NULL) == 0) {
175 /* system("cat /proc/self/mounts"); */
176 die("remount of /tmp with invalid flags "
177 "succeeded unexpectedly\n");
178 }
179 exit(EXIT_SUCCESS);
180}
181
182static bool test_unpriv_remount_simple(int mount_flags)
183{
184 return test_unpriv_remount(mount_flags, mount_flags, 0);
185}
186
187static bool test_unpriv_remount_atime(int mount_flags, int invalid_flags)
188{
189 return test_unpriv_remount(mount_flags, mount_flags, invalid_flags);
190}
191
192int main(int argc, char **argv)
193{
194 if (!test_unpriv_remount_simple(MS_RDONLY|MS_NODEV)) {
195 die("MS_RDONLY malfunctions\n");
196 }
197 if (!test_unpriv_remount_simple(MS_NODEV)) {
198 die("MS_NODEV malfunctions\n");
199 }
200 if (!test_unpriv_remount_simple(MS_NOSUID|MS_NODEV)) {
201 die("MS_NOSUID malfunctions\n");
202 }
203 if (!test_unpriv_remount_simple(MS_NOEXEC|MS_NODEV)) {
204 die("MS_NOEXEC malfunctions\n");
205 }
206 if (!test_unpriv_remount_atime(MS_RELATIME|MS_NODEV,
207 MS_NOATIME|MS_NODEV))
208 {
209 die("MS_RELATIME malfunctions\n");
210 }
211 if (!test_unpriv_remount_atime(MS_STRICTATIME|MS_NODEV,
212 MS_NOATIME|MS_NODEV))
213 {
214 die("MS_STRICTATIME malfunctions\n");
215 }
216 if (!test_unpriv_remount_atime(MS_NOATIME|MS_NODEV,
217 MS_STRICTATIME|MS_NODEV))
218 {
219 die("MS_RELATIME malfunctions\n");
220 }
221 if (!test_unpriv_remount_atime(MS_RELATIME|MS_NODIRATIME|MS_NODEV,
222 MS_NOATIME|MS_NODEV))
223 {
224 die("MS_RELATIME malfunctions\n");
225 }
226 if (!test_unpriv_remount_atime(MS_STRICTATIME|MS_NODIRATIME|MS_NODEV,
227 MS_NOATIME|MS_NODEV))
228 {
229 die("MS_RELATIME malfunctions\n");
230 }
231 if (!test_unpriv_remount_atime(MS_NOATIME|MS_NODIRATIME|MS_NODEV,
232 MS_STRICTATIME|MS_NODEV))
233 {
234 die("MS_RELATIME malfunctions\n");
235 }
236 if (!test_unpriv_remount(MS_STRICTATIME|MS_NODEV, MS_NODEV,
237 MS_NOATIME|MS_NODEV))
238 {
239 die("Default atime malfunctions\n");
240 }
241 return EXIT_SUCCESS;
242}