|
| 1 | +#define _GNU_SOURCE |
| 2 | +#include <sched.h> |
| 3 | +#include <unistd.h> |
| 4 | +#include <stdlib.h> |
| 5 | +#include <sys/wait.h> |
| 6 | +#include <signal.h> |
| 7 | +#include <fcntl.h> |
| 8 | +#include <stdio.h> |
| 9 | +#include <string.h> |
| 10 | +#include <limits.h> |
| 11 | +#include <errno.h> |
| 12 | +#include <sys/mount.h> |
| 13 | +#include <sys/types.h> |
| 14 | +#include <dirent.h> |
| 15 | +#include <sys/stat.h> |
| 16 | + |
| 17 | +#define err_exit(format, ...) { fprintf(stderr, format ": %s\n", ##__VA_ARGS__, strerror(errno)); exit(EXIT_FAILURE); } |
| 18 | + |
| 19 | +static void usage(char *pname) { |
| 20 | + fprintf(stderr, "Usage: %s <nixpath> <command>\n", pname); |
| 21 | + |
| 22 | + exit(EXIT_FAILURE); |
| 23 | +} |
| 24 | + |
| 25 | +static void update_map(char *mapping, char *map_file) { |
| 26 | + int fd; |
| 27 | + |
| 28 | + fd = open(map_file, O_WRONLY); |
| 29 | + if (fd < 0) { |
| 30 | + err_exit("map open"); |
| 31 | + } |
| 32 | + |
| 33 | + int map_len = strlen(mapping); |
| 34 | + if (write(fd, mapping, map_len) != map_len) { |
| 35 | + err_exit("map write"); |
| 36 | + } |
| 37 | + |
| 38 | + close(fd); |
| 39 | +} |
| 40 | + |
| 41 | +static void add_path(const char* name, const char* rootdir) { |
| 42 | + char path_buf[PATH_MAX]; |
| 43 | + char path_buf2[PATH_MAX]; |
| 44 | + |
| 45 | + snprintf(path_buf, sizeof(path_buf), "/%s", name); |
| 46 | + |
| 47 | + struct stat statbuf; |
| 48 | + if (stat(path_buf, &statbuf) < 0) { |
| 49 | + fprintf(stderr, "Cannot stat %s: %s\n", path_buf, strerror(errno)); |
| 50 | + return; |
| 51 | + } |
| 52 | + |
| 53 | + snprintf(path_buf2, sizeof(path_buf2), "%s/%s", rootdir, name); |
| 54 | + |
| 55 | + if (S_ISDIR(statbuf.st_mode)) { |
| 56 | + mkdir(path_buf2, statbuf.st_mode & ~S_IFMT); |
| 57 | + if (mount(path_buf, path_buf2, "none", MS_BIND | MS_REC, NULL) < 0) { |
| 58 | + fprintf(stderr, "Cannot bind mount %s to %s: %s\n", path_buf, path_buf2, strerror(errno)); |
| 59 | + } |
| 60 | + } |
| 61 | +} |
| 62 | + |
| 63 | +int main(int argc, char *argv[]) { |
| 64 | + if (argc < 3) { |
| 65 | + usage(argv[0]); |
| 66 | + } |
| 67 | + |
| 68 | + // Create skeleton root dir in temp. All dirs will be mounted here. |
| 69 | + char template[] = "/tmp/nixXXXXXX"; |
| 70 | + char *rootdir = mkdtemp(template); |
| 71 | + if (!rootdir) { |
| 72 | + err_exit("mkdtemp(%s)", template); |
| 73 | + } |
| 74 | + |
| 75 | + // determine absolute directory for nix dir |
| 76 | + char *nixdir = realpath(argv[1], NULL); |
| 77 | + if (!nixdir) { |
| 78 | + err_exit("realpath(%s)", argv[1]); |
| 79 | + } |
| 80 | + |
| 81 | + // get uid, gid before going to new namespace |
| 82 | + uid_t uid = getuid(); |
| 83 | + gid_t gid = getgid(); |
| 84 | + |
| 85 | + // "unshare" into new namespace |
| 86 | + if (unshare(CLONE_NEWNS | CLONE_NEWUSER) < 0) { |
| 87 | + err_exit("unshare()"); |
| 88 | + } |
| 89 | + |
| 90 | + // add necessary system stuff to rootdir namespace |
| 91 | + add_path("dev", rootdir); |
| 92 | + add_path("proc", rootdir); |
| 93 | + add_path("sys", rootdir); |
| 94 | + add_path("run", rootdir); |
| 95 | + add_path("tmp", rootdir); |
| 96 | + add_path("var", rootdir); |
| 97 | + |
| 98 | + // make sure nixdir exists |
| 99 | + struct stat statbuf2; |
| 100 | + if (stat(nixdir, &statbuf2) < 0) { |
| 101 | + err_exit("stat(%s)", nixdir); |
| 102 | + } |
| 103 | + |
| 104 | + // mount /nix to new namespace |
| 105 | + char path_buf[PATH_MAX]; |
| 106 | + snprintf(path_buf, sizeof(path_buf), "%s/nix", rootdir); |
| 107 | + mkdir(path_buf, statbuf2.st_mode & ~S_IFMT); |
| 108 | + if (mount(nixdir, path_buf, "none", MS_BIND | MS_REC, NULL) < 0) { |
| 109 | + err_exit("mount(%s, %s)", nixdir, path_buf); |
| 110 | + } |
| 111 | + |
| 112 | + // fixes issue #1 where writing to /proc/self/gid_map fails |
| 113 | + // see user_namespaces(7) for more documentation |
| 114 | + int fd_setgroups = open("/proc/self/setgroups", O_WRONLY); |
| 115 | + if (fd_setgroups > 0) { |
| 116 | + write(fd_setgroups, "deny", 4); |
| 117 | + } |
| 118 | + |
| 119 | + // map the original uid/gid in the new ns |
| 120 | + char map_buf[1024]; |
| 121 | + snprintf(map_buf, sizeof(map_buf), "%d %d 1", uid, uid); |
| 122 | + update_map(map_buf, "/proc/self/uid_map"); |
| 123 | + snprintf(map_buf, sizeof(map_buf), "%d %d 1", gid, gid); |
| 124 | + update_map(map_buf, "/proc/self/gid_map"); |
| 125 | + |
| 126 | + // chroot to rootdir |
| 127 | + if (chroot(rootdir) < 0) { |
| 128 | + err_exit("chroot(%s)", rootdir); |
| 129 | + } |
| 130 | + |
| 131 | + // execute the command |
| 132 | + execvp(argv[2], argv+2); |
| 133 | + err_exit("execvp(%s)", argv[2]); |
| 134 | +} |
0 commit comments