// link with -lcap, can run both as root and as regular user #define _GNU_SOURCE #include <sched.h> #include <sys/capability.h> #include <sys/mount.h> #include <sys/stat.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <stdbool.h>
_Bool drop_caps(void) { cap_value_t cap_value[] = { CAP_SYS_ADMIN }; cap_t cap = cap_get_proc(); if (!cap) { perror("cap_get_proc"); return false; } return true; }
void do_unshare(void) { FILE *f; uid_t uid = geteuid(); gid_t gid = getegid(); unshare(CLONE_NEWNS|CLONE_NEWUSER); f = fopen("/proc/self/uid_map", "w"); fprintf(f, "0 %d 1", uid); fclose(f); f = fopen("/proc/self/setgroups", "w"); fprintf(f, "deny"); fclose(f); f = fopen("/proc/self/gid_map", "w"); fprintf(f, "0 %d 1", gid); fclose(f); mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL); }
void bind(char *p) { mount(p, p, NULL, MS_BIND, NULL); }
void change_type(char *p, int type) { errno = 0; mount(NULL, p, NULL, type, NULL); }
void set_group(int fd1, char *p1, int fd2, char *p2) { int flags = MOVE_MOUNT_SET_GROUP; int n;
if (!p1 || !*p1) { p1 = ""; // be kind to old kernels flags |= MOVE_MOUNT_F_EMPTY_PATH; } if (!p2 || !*p2) { p2 = ""; // be kind to old kernels flags |= MOVE_MOUNT_T_EMPTY_PATH; } errno = 0; move_mount(fd1, p1, fd2, p2, flags); }
_Bool result(int expected) { if (expected != errno) { printf(" failed: %d != %d\n", expected, errno); return false; } printf(" OK\n"); return true; }
int fd1[2], fd2[2];
void in_child(void) { printf("from should be someplace we have permissions for"); set_group(AT_FDCWD, "mnt/a", AT_FDCWD, "/mnt/a/private"); result(EPERM); printf("to should be someplace we have permissions for"); set_group(AT_FDCWD, "/mnt/a", AT_FDCWD, "mnt/a/private"); result(EPERM); printf("change_type: should have permissions for target"); change_type("mnt/locked", MS_PRIVATE); result(EPERM); }
void in_parent(void) { printf("from should be mounted (pipes are not)"); set_group(fd1[0], NULL, AT_FDCWD, "/mnt/a/private"); result(EINVAL);
printf("to should be mounted (pipes are not)"); set_group(AT_FDCWD, "/mnt", fd1[0], NULL); result(EINVAL);
printf("from should be someplace we have permissions for"); set_group(AT_FDCWD, "mnt/a", AT_FDCWD, "/mnt/a/private"); if (result(0)) change_type("/mnt/a/private", MS_PRIVATE);
printf("to should be someplace we have permissions for"); set_group(AT_FDCWD, "/mnt", AT_FDCWD, "mnt/a/private"); if (result(0)) change_type("mnt/a/private", MS_PRIVATE);
printf("from should be mountpoint"); set_group(AT_FDCWD, "/mnt/a", AT_FDCWD, "/mnt/a/private"); result(EINVAL);
printf("to should be mountpoint"); set_group(AT_FDCWD, "/mnt/a", AT_FDCWD, "/mnt/a/private/b"); result(EINVAL);
printf("to and from should be on the same filesystem"); mount("none", "/mnt/no-locked", "tmpfs", 0, NULL); set_group(AT_FDCWD, "/mnt/a/shared", AT_FDCWD, "/mnt/no-locked"); result(EINVAL); umount("/mnt/no-locked");
printf("from should contain the counterpart of to"); set_group(AT_FDCWD, "/mnt/a/shared", AT_FDCWD, "/mnt/no-locked"); result(EINVAL);
printf("from should not have anything locked in counterpart of to"); set_group(AT_FDCWD, "mnt", AT_FDCWD, "/mnt/no-locked"); if (result(0)) change_type("/mnt/no-locked", MS_PRIVATE);
printf("change_type: should have permissions for target"); change_type("mnt/a/private", MS_PRIVATE); result(0);
printf("change_type: target should be a mountpoint"); change_type("/mnt/a", MS_PRIVATE); result(EINVAL);
chdir("/mnt/a/private"); umount2("/mnt/a/private", MNT_DETACH); printf("change_type: target should be mounted"); change_type(".", MS_PRIVATE); result(EINVAL); }
int main() { char path[40]; pid_t child; int root_fd; char c;
if (pipe(fd1) < 0 || pipe(fd2) < 0) { perror("pipe"); return -1; } if (!drop_caps()) return -1; do_unshare();
root_fd = open("/", O_PATH);
errno = 0; mount("none", "/mnt", "tmpfs", 0, NULL); mkdir("/mnt/a", 0777); mkdir("/mnt/a/private", 0777); mkdir("/mnt/a/private/b", 0777); mkdir("/mnt/a/shared", 0777); mkdir("/mnt/a/slave", 0777); mkdir("/mnt/a/shared-slave", 0777); mkdir("/mnt/locked", 0777); mkdir("/mnt/no-locked", 0777); bind("/mnt/locked");
child = fork(); if (child < 0) { perror("fork"); return -1; } else if (child == 0) { do_unshare(); change_type("/mnt/", MS_SHARED); bind("/mnt/a"); bind("/mnt/a/private"); change_type("/mnt/a/private", MS_PRIVATE); write(fd1[1], &c, 1); read(fd2[0], &c, 1);
fchdir(root_fd); in_child();
write(fd1[1], &c, 1); return 0; } read(fd1[0], &c, 1); sprintf(path, "/proc/%d/root", child); chdir(path);
change_type("/mnt", MS_SHARED); bind("/mnt/a/private"); bind("/mnt/a/shared"); bind("/mnt/a/slave"); bind("/mnt/a/slave-shared"); bind("/mnt/no-locked"); change_type("/mnt/a/private", MS_PRIVATE); change_type("/mnt/a/slave", MS_SLAVE); change_type("/mnt/a/shared-slave", MS_SLAVE); change_type("/mnt/a/shared-slave", MS_SHARED); change_type("/mnt/no-locked", MS_PRIVATE);
in_parent();
fflush(stdout); write(fd2[1], &c, 1); read(fd1[0], &c, 1); return 0; }