unprivileged-remount-test.c (8385B)
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}