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