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 <sys/vfs.h>
10#include <sys/statvfs.h>
11#include <stdlib.h>
12#include <unistd.h>
13#include <fcntl.h>
14#include <grp.h>
15#include <stdbool.h>
16#include <stdarg.h>
17
18#ifndef CLONE_NEWNS
19# define CLONE_NEWNS 0x00020000
20#endif
21#ifndef CLONE_NEWUTS
22# define CLONE_NEWUTS 0x04000000
23#endif
24#ifndef CLONE_NEWIPC
25# define CLONE_NEWIPC 0x08000000
26#endif
27#ifndef CLONE_NEWNET
28# define CLONE_NEWNET 0x40000000
29#endif
30#ifndef CLONE_NEWUSER
31# define CLONE_NEWUSER 0x10000000
32#endif
33#ifndef CLONE_NEWPID
34# define CLONE_NEWPID 0x20000000
35#endif
36
37#ifndef MS_REC
38# define MS_REC 16384
39#endif
40#ifndef MS_RELATIME
41# define MS_RELATIME (1 << 21)
42#endif
43#ifndef MS_STRICTATIME
44# define MS_STRICTATIME (1 << 24)
45#endif
46
47static void die(char *fmt, ...)
48{
49	va_list ap;
50	va_start(ap, fmt);
51	vfprintf(stderr, fmt, ap);
52	va_end(ap);
53	exit(EXIT_FAILURE);
54}
55
56static void vmaybe_write_file(bool enoent_ok, char *filename, char *fmt, va_list ap)
57{
58	char buf[4096];
59	int fd;
60	ssize_t written;
61	int buf_len;
62
63	buf_len = vsnprintf(buf, sizeof(buf), fmt, ap);
64	if (buf_len < 0) {
65		die("vsnprintf failed: %s\n",
66		    strerror(errno));
67	}
68	if (buf_len >= sizeof(buf)) {
69		die("vsnprintf output truncated\n");
70	}
71
72	fd = open(filename, O_WRONLY);
73	if (fd < 0) {
74		if ((errno == ENOENT) && enoent_ok)
75			return;
76		die("open of %s failed: %s\n",
77		    filename, strerror(errno));
78	}
79	written = write(fd, buf, buf_len);
80	if (written != buf_len) {
81		if (written >= 0) {
82			die("short write to %s\n", filename);
83		} else {
84			die("write to %s failed: %s\n",
85				filename, strerror(errno));
86		}
87	}
88	if (close(fd) != 0) {
89		die("close of %s failed: %s\n",
90			filename, strerror(errno));
91	}
92}
93
94static void maybe_write_file(char *filename, char *fmt, ...)
95{
96	va_list ap;
97
98	va_start(ap, fmt);
99	vmaybe_write_file(true, filename, fmt, ap);
100	va_end(ap);
101
102}
103
104static void write_file(char *filename, char *fmt, ...)
105{
106	va_list ap;
107
108	va_start(ap, fmt);
109	vmaybe_write_file(false, filename, fmt, ap);
110	va_end(ap);
111
112}
113
114static int read_mnt_flags(const char *path)
115{
116	int ret;
117	struct statvfs stat;
118	int mnt_flags;
119
120	ret = statvfs(path, &stat);
121	if (ret != 0) {
122		die("statvfs of %s failed: %s\n",
123			path, strerror(errno));
124	}
125	if (stat.f_flag & ~(ST_RDONLY | ST_NOSUID | ST_NODEV | \
126			ST_NOEXEC | ST_NOATIME | ST_NODIRATIME | ST_RELATIME | \
127			ST_SYNCHRONOUS | ST_MANDLOCK)) {
128		die("Unrecognized mount flags\n");
129	}
130	mnt_flags = 0;
131	if (stat.f_flag & ST_RDONLY)
132		mnt_flags |= MS_RDONLY;
133	if (stat.f_flag & ST_NOSUID)
134		mnt_flags |= MS_NOSUID;
135	if (stat.f_flag & ST_NODEV)
136		mnt_flags |= MS_NODEV;
137	if (stat.f_flag & ST_NOEXEC)
138		mnt_flags |= MS_NOEXEC;
139	if (stat.f_flag & ST_NOATIME)
140		mnt_flags |= MS_NOATIME;
141	if (stat.f_flag & ST_NODIRATIME)
142		mnt_flags |= MS_NODIRATIME;
143	if (stat.f_flag & ST_RELATIME)
144		mnt_flags |= MS_RELATIME;
145	if (stat.f_flag & ST_SYNCHRONOUS)
146		mnt_flags |= MS_SYNCHRONOUS;
147	if (stat.f_flag & ST_MANDLOCK)
148		mnt_flags |= ST_MANDLOCK;
149
150	return mnt_flags;
151}
152
153static void create_and_enter_userns(void)
154{
155	uid_t uid;
156	gid_t gid;
157
158	uid = getuid();
159	gid = getgid();
160
161	if (unshare(CLONE_NEWUSER) !=0) {
162		die("unshare(CLONE_NEWUSER) failed: %s\n",
163			strerror(errno));
164	}
165
166	maybe_write_file("/proc/self/setgroups", "deny");
167	write_file("/proc/self/uid_map", "0 %d 1", uid);
168	write_file("/proc/self/gid_map", "0 %d 1", gid);
169
170	if (setgid(0) != 0) {
171		die ("setgid(0) failed %s\n",
172			strerror(errno));
173	}
174	if (setuid(0) != 0) {
175		die("setuid(0) failed %s\n",
176			strerror(errno));
177	}
178}
179
180static
181bool test_unpriv_remount(const char *fstype, const char *mount_options,
182			 int mount_flags, int remount_flags, int invalid_flags)
183{
184	pid_t child;
185
186	child = fork();
187	if (child == -1) {
188		die("fork failed: %s\n",
189			strerror(errno));
190	}
191	if (child != 0) { /* parent */
192		pid_t pid;
193		int status;
194		pid = waitpid(child, &status, 0);
195		if (pid == -1) {
196			die("waitpid failed: %s\n",
197				strerror(errno));
198		}
199		if (pid != child) {
200			die("waited for %d got %d\n",
201				child, pid);
202		}
203		if (!WIFEXITED(status)) {
204			die("child did not terminate cleanly\n");
205		}
206		return WEXITSTATUS(status) == EXIT_SUCCESS ? true : false;
207	}
208
209	create_and_enter_userns();
210	if (unshare(CLONE_NEWNS) != 0) {
211		die("unshare(CLONE_NEWNS) failed: %s\n",
212			strerror(errno));
213	}
214
215	if (mount("testing", "/tmp", fstype, mount_flags, mount_options) != 0) {
216		die("mount of %s with options '%s' on /tmp failed: %s\n",
217		    fstype,
218		    mount_options? mount_options : "",
219		    strerror(errno));
220	}
221
222	create_and_enter_userns();
223
224	if (unshare(CLONE_NEWNS) != 0) {
225		die("unshare(CLONE_NEWNS) failed: %s\n",
226			strerror(errno));
227	}
228
229	if (mount("/tmp", "/tmp", "none",
230		  MS_REMOUNT | MS_BIND | remount_flags, NULL) != 0) {
231		/* system("cat /proc/self/mounts"); */
232		die("remount of /tmp failed: %s\n",
233		    strerror(errno));
234	}
235
236	if (mount("/tmp", "/tmp", "none",
237		  MS_REMOUNT | MS_BIND | invalid_flags, NULL) == 0) {
238		/* system("cat /proc/self/mounts"); */
239		die("remount of /tmp with invalid flags "
240		    "succeeded unexpectedly\n");
241	}
242	exit(EXIT_SUCCESS);
243}
244
245static bool test_unpriv_remount_simple(int mount_flags)
246{
247	return test_unpriv_remount("ramfs", NULL, mount_flags, mount_flags, 0);
248}
249
250static bool test_unpriv_remount_atime(int mount_flags, int invalid_flags)
251{
252	return test_unpriv_remount("ramfs", NULL, mount_flags, mount_flags,
253				   invalid_flags);
254}
255
256static bool test_priv_mount_unpriv_remount(void)
257{
258	pid_t child;
259	int ret;
260	const char *orig_path = "/dev";
261	const char *dest_path = "/tmp";
262	int orig_mnt_flags, remount_mnt_flags;
263
264	child = fork();
265	if (child == -1) {
266		die("fork failed: %s\n",
267			strerror(errno));
268	}
269	if (child != 0) { /* parent */
270		pid_t pid;
271		int status;
272		pid = waitpid(child, &status, 0);
273		if (pid == -1) {
274			die("waitpid failed: %s\n",
275				strerror(errno));
276		}
277		if (pid != child) {
278			die("waited for %d got %d\n",
279				child, pid);
280		}
281		if (!WIFEXITED(status)) {
282			die("child did not terminate cleanly\n");
283		}
284		return WEXITSTATUS(status) == EXIT_SUCCESS ? true : false;
285	}
286
287	orig_mnt_flags = read_mnt_flags(orig_path);
288
289	create_and_enter_userns();
290	ret = unshare(CLONE_NEWNS);
291	if (ret != 0) {
292		die("unshare(CLONE_NEWNS) failed: %s\n",
293			strerror(errno));
294	}
295
296	ret = mount(orig_path, dest_path, "bind", MS_BIND | MS_REC, NULL);
297	if (ret != 0) {
298		die("recursive bind mount of %s onto %s failed: %s\n",
299			orig_path, dest_path, strerror(errno));
300	}
301
302	ret = mount(dest_path, dest_path, "none",
303		    MS_REMOUNT | MS_BIND | orig_mnt_flags , NULL);
304	if (ret != 0) {
305		/* system("cat /proc/self/mounts"); */
306		die("remount of /tmp failed: %s\n",
307		    strerror(errno));
308	}
309
310	remount_mnt_flags = read_mnt_flags(dest_path);
311	if (orig_mnt_flags != remount_mnt_flags) {
312		die("Mount flags unexpectedly changed during remount of %s originally mounted on %s\n",
313			dest_path, orig_path);
314	}
315	exit(EXIT_SUCCESS);
316}
317
318int main(int argc, char **argv)
319{
320	if (!test_unpriv_remount_simple(MS_RDONLY)) {
321		die("MS_RDONLY malfunctions\n");
322	}
323	if (!test_unpriv_remount("devpts", "newinstance", MS_NODEV, MS_NODEV, 0)) {
324		die("MS_NODEV malfunctions\n");
325	}
326	if (!test_unpriv_remount_simple(MS_NOSUID)) {
327		die("MS_NOSUID malfunctions\n");
328	}
329	if (!test_unpriv_remount_simple(MS_NOEXEC)) {
330		die("MS_NOEXEC malfunctions\n");
331	}
332	if (!test_unpriv_remount_atime(MS_RELATIME,
333				       MS_NOATIME))
334	{
335		die("MS_RELATIME malfunctions\n");
336	}
337	if (!test_unpriv_remount_atime(MS_STRICTATIME,
338				       MS_NOATIME))
339	{
340		die("MS_STRICTATIME malfunctions\n");
341	}
342	if (!test_unpriv_remount_atime(MS_NOATIME,
343				       MS_STRICTATIME))
344	{
345		die("MS_NOATIME malfunctions\n");
346	}
347	if (!test_unpriv_remount_atime(MS_RELATIME|MS_NODIRATIME,
348				       MS_NOATIME))
349	{
350		die("MS_RELATIME|MS_NODIRATIME malfunctions\n");
351	}
352	if (!test_unpriv_remount_atime(MS_STRICTATIME|MS_NODIRATIME,
353				       MS_NOATIME))
354	{
355		die("MS_STRICTATIME|MS_NODIRATIME malfunctions\n");
356	}
357	if (!test_unpriv_remount_atime(MS_NOATIME|MS_NODIRATIME,
358				       MS_STRICTATIME))
359	{
360		die("MS_NOATIME|MS_DIRATIME malfunctions\n");
361	}
362	if (!test_unpriv_remount("ramfs", NULL, MS_STRICTATIME, 0, MS_NOATIME))
363	{
364		die("Default atime malfunctions\n");
365	}
366	if (!test_priv_mount_unpriv_remount()) {
367		die("Mount flags unexpectedly changed after remount\n");
368	}
369	return EXIT_SUCCESS;
370}
371