nixpkgs mirror (for testing) github.com/NixOS/nixpkgs
nix
at devShellTools-shell 169 lines 4.6 kB view raw
1#define _GNU_SOURCE 2 3#include <glib.h> 4#include <glib/gstdio.h> 5 6#include <errno.h> 7#include <sched.h> 8#include <unistd.h> 9 10#include <sys/mount.h> 11#include <sys/stat.h> 12#include <sys/types.h> 13#include <sys/wait.h> 14#include <sys/syscall.h> 15 16#define fail(s, err) g_error("%s: %s: %s", __func__, s, g_strerror(err)) 17#define fail_if(expr) \ 18 if (expr) \ 19 fail(#expr, errno); 20 21const gchar *bind_blacklist[] = {"bin", "etc", "host", "real-host", "usr", "lib", "lib64", "lib32", "sbin", "opt", NULL}; 22 23int pivot_root(const char *new_root, const char *put_old) { 24 return syscall(SYS_pivot_root, new_root, put_old); 25} 26 27void mount_tmpfs(const gchar *target) { 28 fail_if(mount("none", target, "tmpfs", 0, NULL)); 29} 30 31void bind_mount(const gchar *source, const gchar *target) { 32 fail_if(g_mkdir(target, 0755)); 33 fail_if(mount(source, target, NULL, MS_BIND | MS_REC, NULL)); 34} 35 36const gchar *create_tmpdir() { 37 gchar *prefix = 38 g_build_filename(g_get_tmp_dir(), "chrootenvXXXXXX", NULL); 39 fail_if(!g_mkdtemp_full(prefix, 0755)); 40 return prefix; 41} 42 43void pivot_host(const gchar *guest) { 44 g_autofree gchar *point = g_build_filename(guest, "host", NULL); 45 fail_if(g_mkdir(point, 0755)); 46 fail_if(pivot_root(guest, point)); 47} 48 49void bind_mount_item(const gchar *host, const gchar *guest, const gchar *name) { 50 g_autofree gchar *source = g_build_filename(host, name, NULL); 51 g_autofree gchar *target = g_build_filename(guest, name, NULL); 52 53 if (G_LIKELY(g_file_test(source, G_FILE_TEST_IS_DIR))) 54 bind_mount(source, target); 55} 56 57void bind(const gchar *host, const gchar *guest) { 58 mount_tmpfs(guest); 59 60 pivot_host(guest); 61 62 g_autofree gchar *host_dir = g_build_filename("/host", host, NULL); 63 64 g_autoptr(GError) err = NULL; 65 g_autoptr(GDir) dir = g_dir_open(host_dir, 0, &err); 66 67 if (err != NULL) 68 fail("g_dir_open", errno); 69 70 const gchar *item; 71 72 while ((item = g_dir_read_name(dir))) 73 if (!g_strv_contains(bind_blacklist, item)) 74 bind_mount_item(host_dir, "/", item); 75} 76 77void spit(const char *path, char *fmt, ...) { 78 va_list args; 79 va_start(args, fmt); 80 81 FILE *f = g_fopen(path, "w"); 82 83 if (f == NULL) 84 fail("g_fopen", errno); 85 86 g_vfprintf(f, fmt, args); 87 fclose(f); 88} 89 90int main(gint argc, gchar **argv) { 91 const gchar *self = *argv++; 92 93 if (argc < 2) { 94 g_message("%s command [arguments...]", self); 95 return 1; 96 } 97 98 g_autofree const gchar *prefix = create_tmpdir(); 99 100 pid_t cpid = fork(); 101 102 if (cpid < 0) 103 fail("fork", errno); 104 105 else if (cpid == 0) { 106 uid_t uid = getuid(); 107 gid_t gid = getgid(); 108 109 int namespaces = CLONE_NEWNS; 110 if (uid != 0) { 111 namespaces |= CLONE_NEWUSER; 112 } 113 if (unshare(namespaces) < 0) { 114 int unshare_errno = errno; 115 116 g_message("Requires Linux version >= 3.19 built with CONFIG_USER_NS"); 117 if (g_file_test("/proc/sys/kernel/unprivileged_userns_clone", 118 G_FILE_TEST_EXISTS)) 119 g_message("Run: sudo sysctl -w kernel.unprivileged_userns_clone=1"); 120 121 fail("unshare", unshare_errno); 122 } 123 124 // hide all mounts we do from the parent 125 fail_if(mount(0, "/", 0, MS_SLAVE | MS_REC, 0)); 126 127 if (uid != 0) { 128 spit("/proc/self/setgroups", "deny"); 129 spit("/proc/self/uid_map", "%d %d 1", uid, uid); 130 spit("/proc/self/gid_map", "%d %d 1", gid, gid); 131 } 132 133 // If there is a /host directory, assume this is nested chrootenv and use it as host instead. 134 gboolean nested_host = g_file_test("/host", G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR); 135 g_autofree const gchar *host = nested_host ? "/host" : "/"; 136 137 bind(host, prefix); 138 139 // Replace /host by an actual (inner) /host. 140 if (nested_host) { 141 fail_if(g_mkdir("/real-host", 0755)); 142 fail_if(mount("/host/host", "/real-host", NULL, MS_BIND | MS_REC, NULL)); 143 // For some reason umount("/host") returns EBUSY even immediately after 144 // pivot_root. We detach it at least to keep `/proc/mounts` from blowing 145 // up in nested cases. 146 fail_if(umount2("/host", MNT_DETACH)); 147 fail_if(mount("/real-host", "/host", NULL, MS_MOVE, NULL)); 148 fail_if(rmdir("/real-host")); 149 } 150 151 fail_if(chdir("/")); 152 fail_if(execvp(*argv, argv)); 153 } 154 155 else { 156 int status; 157 158 fail_if(waitpid(cpid, &status, 0) != cpid); 159 fail_if(rmdir(prefix)); 160 161 if (WIFEXITED(status)) 162 return WEXITSTATUS(status); 163 164 else if (WIFSIGNALED(status)) 165 kill(getpid(), WTERMSIG(status)); 166 167 return 1; 168 } 169}