372 lines · c
1// SPDX-License-Identifier: GPL-2.02#define _GNU_SOURCE3#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_NEWNS20# define CLONE_NEWNS 0x0002000021#endif22#ifndef CLONE_NEWUTS23# define CLONE_NEWUTS 0x0400000024#endif25#ifndef CLONE_NEWIPC26# define CLONE_NEWIPC 0x0800000027#endif28#ifndef CLONE_NEWNET29# define CLONE_NEWNET 0x4000000030#endif31#ifndef CLONE_NEWUSER32# define CLONE_NEWUSER 0x1000000033#endif34#ifndef CLONE_NEWPID35# define CLONE_NEWPID 0x2000000036#endif37 38#ifndef MS_REC39# define MS_REC 1638440#endif41#ifndef MS_RELATIME42# define MS_RELATIME (1 << 21)43#endif44#ifndef MS_STRICTATIME45# define MS_STRICTATIME (1 << 24)46#endif47 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 181static182bool 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