@@ -1,307 +0,0 @@
1−/*
2− * This file is based on @lethalman's nix-user-chroot. This file has
3− * diverged from it though.
4− *
5− * Usage: nix-user-chroot <nixpath> <command>
6− */
7−
8−#include <sched.h>
9−#include <unistd.h>
10−#include <stdlib.h>
11−#include <sys/wait.h>
12−#include <signal.h>
13−#include <fcntl.h>
14−#include <stdio.h>
15−#include <string.h>
16−#include <limits.h>
17−#include <errno.h>
18−#include <sys/mount.h>
19−#include <sys/types.h>
20−#include <dirent.h>
21−#include <sys/stat.h>
22−#include <stdint.h>
23−#include <list>
24−#include <string>
25−
26−using namespace std;
27−
28−#define err_exit(format, ...) { fprintf(stderr, format ": %s\n", ##__VA_ARGS__, strerror(errno)); exit(EXIT_FAILURE); }
29−static int child_proc(const char *rootdir, const char *nixdir, uint8_t clear_env, list<struct DirMapping> dirMappings, list<struct SetEnv> envMappings, const char *executable, char * const new_argv[]);
30−
31−volatile uint8_t child_died = 0;
32−int child_pid = 0;
33−
34−static void usage(const char *pname) {
35− fprintf(stderr, "Usage: %s -n <nixpath> -- <command>\n", pname);
36− fprintf(stderr, "\t-c\tclear all env vars\n");
37− fprintf(stderr, "\t-m <src>:<src>\tmap src on the host to dest in the sandbox\n");
38− fprintf(stderr, "\t-d\tdelete all default dir mappings, may break things\n");
39− fprintf(stderr, "\t-p <var>\tpreserve the value of a variable across the -c clear\n");
40− fprintf(stderr, "\t-e\tadd an /escape-hatch to the sandbox, and run (outside the sandbox) any strings written to it\n");
41−
42− exit(EXIT_FAILURE);
43−}
44−
45−static void update_map(const char *mapping, const char *map_file) {
46− int fd;
47−
48− fd = open(map_file, O_WRONLY);
49− if (fd < 0) {
50− err_exit("map open");
51− }
52−
53− int map_len = strlen(mapping);
54− if (write(fd, mapping, map_len) != map_len) {
55− err_exit("map write");
56− }
57−
58− close(fd);
59−}
60−
61−static void add_path(string src, string dest, string rootdir) {
62− string path_buf2;
63−
64− struct stat statbuf;
65− if (stat(src.c_str(), &statbuf) < 0) {
66− fprintf(stderr, "Cannot stat %s: %s\n", src.c_str(), strerror(errno));
67− return;
68− }
69−
70− path_buf2 = rootdir + "/" + dest;
71−
72− if (S_ISDIR(statbuf.st_mode)) {
73− mkdir(path_buf2.c_str(), statbuf.st_mode & ~S_IFMT);
74− if (mount(src.c_str(), path_buf2.c_str(), "none", MS_BIND | MS_REC, NULL) < 0) {
75− fprintf(stderr, "Cannot bind mount %s to %s: %s\n", src.c_str(), path_buf2.c_str(), strerror(errno));
76− }
77− } else if (S_ISREG(statbuf.st_mode)) {
78− printf("bind-mounting file %s not supported", src.c_str());
79− }
80−}
81−
82−struct DirMapping {
83− string src;
84− string dest;
85−};
86−
87−struct SetEnv {
88− string key;
89− string value;
90−};
91−
92−struct DirMapping parseMapping(string input) {
93− auto pos = input.find(":");
94− string src = input.substr(0, pos);
95− string dest = input.substr(pos + 1);
96− return (struct DirMapping){ src, dest };
97−}
98−
99−static void handle_child_death(int signo, siginfo_t *info, void *context) {
100− if ( (child_pid == 0) || (info->si_pid == child_pid) ) {
101− child_died = 1;
102− }
103−}
104−
105−int main(int argc, char *argv[]) {
106− uint8_t clear_env = 0;
107− uint8_t enable_escape_hatch = 0;
108− char *nixdir = NULL;
109− list<struct DirMapping> dirMappings;
110− list<struct SetEnv> envMappings;
111− const char *t;
112−
113−#define x(y) dirMappings.push_back({ "/" y, y })
114− x("dev");
115− x("proc");
116− x("sys");
117− x("run");
118− x("tmp");
119− x("var");
120− x("etc");
121− x("usr");
122− x("home");
123− x("root");
124−#undef x
125−
126− int opt;
127− while ((opt = getopt(argc, argv, "cen:m:dp:")) != -1) {
128− switch (opt) {
129− case 'c':
130− clear_env = 1;
131− break;
132− case 'e':
133− enable_escape_hatch = 1;
134− break;
135− case 'n':
136− // determine absolute directory for nix dir
137− nixdir = realpath(optarg, NULL);
138− if (!nixdir) {
139− err_exit("realpath(%s)", optarg);
140− }
141− break;
142− case 'm':
143− dirMappings.push_back(parseMapping(optarg));
144− break;
145− case 'd':
146− dirMappings.clear();
147− break;
148− case 'p':
149− t = getenv(optarg);
150− if (t) {
151− envMappings.push_back({ optarg, t });
152− }
153− break;
154− }
155− }
156−
157− if (!nixdir) {
158− fprintf(stderr, "-n <nixdir> is required\n");
159− exit(EXIT_FAILURE);
160− }
161−
162− if (argc <= optind) {
163− usage(argv[0]);
164− }
165−
166− const char *tmpdir = getenv("TMPDIR");
167− if (!tmpdir) {
168− tmpdir = "/tmp";
169− }
170−
171− char template_[PATH_MAX];
172− int needed = snprintf(template_, PATH_MAX, "%s/nixXXXXXX", tmpdir);
173− if (needed < 0) {
174− err_exit("TMPDIR too long: '%s'", tmpdir);
175− }
176−
177− char *rootdir = mkdtemp(template_);
178− if (!rootdir) {
179− err_exit("mkdtemp(%s)", template_);
180− }
181−
182− int unrace[2];
183−
184− if (pipe(unrace)) {
185− err_exit("pipe()");
186− }
187−
188− struct sigaction handle_child;
189− handle_child.sa_sigaction = handle_child_death;
190− handle_child.sa_flags = SA_SIGINFO;
191−
192− struct sigaction old_handler;
193− if (sigaction(SIGCHLD, &handle_child, &old_handler)) {
194− err_exit("sigaction()");
195− }
196−
197− int child;
198− child = child_pid = fork();
199− if (child < 0) {
200− err_exit("fork()");
201− } else if (child == 0) {
202− sigaction(SIGCHLD, &old_handler, NULL);
203− close(unrace[1]);
204− char buf[10];
205− read(unrace[0], buf, 10);
206− close(unrace[0]);
207− return child_proc(rootdir, nixdir, clear_env, dirMappings, envMappings, argv[optind], argv + optind);
208− } else {
209− close(unrace[0]);
210− char fifopath[PATH_MAX];
211− if (enable_escape_hatch) {
212− snprintf(fifopath, PATH_MAX, "%s/escape-hatch", rootdir);
213− mkfifo(fifopath, 0600);
214− }
215− close(unrace[1]);
216− if (enable_escape_hatch) {
217− char buffer[1024];
218− while (!child_died) {
219− int fd = open(fifopath, O_RDONLY);
220− if (fd < 0) {
221− if (errno == EINTR) continue;
222− fprintf(stderr, "error opening escape-hatch: %s\n", strerror(errno));
223− continue;
224− }
225− int size = read(fd, buffer, 1024);
226− buffer[size] = 0;
227− system(buffer);
228− close(fd);
229− }
230− }
231− int status;
232− int ret = waitpid(child, &status, 0);
233− return WEXITSTATUS(status);
234− }
235−}
236−
237−static int child_proc(const char *rootdir, const char *nixdir, uint8_t clear_env, list<struct DirMapping> dirMappings, list<struct SetEnv> envMappings, const char *executable, char * const new_argv[]) {
238− // get uid, gid before going to new namespace
239− uid_t uid = getuid();
240− gid_t gid = getgid();
241−
242− // "unshare" into new namespace
243− if (unshare(CLONE_NEWNS | CLONE_NEWUSER) < 0) {
244− if (errno == EPERM) {
245− fputs("Run the following to enable unprivileged namespace use:\nsudo bash -c \"sysctl -w kernel.unprivileged_userns_clone=1 ; echo kernel.unprivileged_userns_clone=1 > /etc/sysctl.d/nix-user-chroot.conf\"\n\n", stderr);
246− exit(EXIT_FAILURE);
247− } else {
248− err_exit("unshare()");
249− }
250− }
251−
252− // add necessary system stuff to rootdir namespace
253− for (list<struct DirMapping>::iterator it = dirMappings.begin();
254− it != dirMappings.end(); ++it) {
255− struct DirMapping m = *it;
256− add_path(m.src, m.dest, rootdir);
257− }
258−
259− // make sure nixdir exists
260− struct stat statbuf2;
261− if (stat(nixdir, &statbuf2) < 0) {
262− err_exit("stat(%s)", nixdir);
263− }
264−
265− char path_buf[PATH_MAX];
266− // mount /nix to new namespace
267− snprintf(path_buf, sizeof(path_buf), "%s/nix", rootdir);
268− mkdir(path_buf, statbuf2.st_mode & ~S_IFMT);
269− if (mount(nixdir, path_buf, "none", MS_BIND | MS_REC, NULL) < 0) {
270− err_exit("mount(%s, %s)", nixdir, path_buf);
271− }
272−
273− // fixes issue #1 where writing to /proc/self/gid_map fails
274− // see user_namespaces(7) for more documentation
275− int fd_setgroups = open("/proc/self/setgroups", O_WRONLY);
276− if (fd_setgroups > 0) {
277− write(fd_setgroups, "deny", 4);
278− close(fd_setgroups);
279− }
280−
281− // map the original uid/gid in the new ns
282− char map_buf[1024];
283− snprintf(map_buf, sizeof(map_buf), "%d %d 1", uid, uid);
284− update_map(map_buf, "/proc/self/uid_map");
285− snprintf(map_buf, sizeof(map_buf), "%d %d 1", gid, gid);
286− update_map(map_buf, "/proc/self/gid_map");
287−
288− // chroot to rootdir
289− if (chroot(rootdir) < 0) {
290− err_exit("chroot(%s)", rootdir);
291− }
292−
293− chdir("/");
294−
295− if (clear_env) clearenv();
296− setenv("PATH", ENV_PATH, 1);
297−
298− for (list<struct SetEnv>::iterator it = envMappings.begin();
299− it != envMappings.end(); ++it) {
300− struct SetEnv e = *it;
301− setenv(e.key.c_str(), e.value.c_str(), 1);
302− }
303−
304− // execute the command
305− execvp(executable, new_argv);
306− err_exit("execvp(%s)", executable);
307−}