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