@@ -26,6 +26,10 @@
26using namespace std;
26using namespace std;
28#define err_exit(format, ...) { fprintf(stderr, format ": %s\n", ##__VA_ARGS__, strerror(errno)); exit(EXIT_FAILURE); }
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[]);
31+volatile uint8_t child_died = 0;
30static void usage(const char *pname) {
34static void usage(const char *pname) {
31 fprintf(stderr, "Usage: %s -n <nixpath> -- <command>\n", pname);
35 fprintf(stderr, "Usage: %s -n <nixpath> -- <command>\n", pname);
@@ -33,6 +37,7 @@ static void usage(const char *pname) {
33 fprintf(stderr, "\t-m <src>:<src>\tmap src on the host to dest in the sandbox\n");
37 fprintf(stderr, "\t-m <src>:<src>\tmap src on the host to dest in the sandbox\n");
34 fprintf(stderr, "\t-d\tdelete all default dir mappings, may break things\n");
38 fprintf(stderr, "\t-d\tdelete all default dir mappings, may break things\n");
35 fprintf(stderr, "\t-p <var>\tpreserve the value of a variable across the -c clear\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");
37 exit(EXIT_FAILURE);
42 exit(EXIT_FAILURE);
@@ -91,8 +96,15 @@ struct DirMapping parseMapping(string input) {
91 return (struct DirMapping){ src, dest };
96 return (struct DirMapping){ src, dest };
99+static void handle_child_death(int signo, siginfo_t *info, void *context) {
100+ if ( (child_pid == 0) || (info->si_pid == child_pid) ) {
94int main(int argc, char *argv[]) {
105int main(int argc, char *argv[]) {
95 uint8_t clear_env = 0;
106 uint8_t clear_env = 0;
107+ uint8_t enable_escape_hatch = 0;
96 char *nixdir = NULL;
108 char *nixdir = NULL;
97 list<struct DirMapping> dirMappings;
109 list<struct DirMapping> dirMappings;
98 list<struct SetEnv> envMappings;
110 list<struct SetEnv> envMappings;
@@ -107,14 +119,19 @@ int main(int argc, char *argv[]) {
107 x("var");
119 x("var");
108 x("etc");
120 x("etc");
109 x("usr");
121 x("usr");
113− while ((opt = getopt(argc, argv, "cn:m:dp:")) != -1) {
127+ while ((opt = getopt(argc, argv, "cen:m:dp:")) != -1) {
114 switch (opt) {
128 switch (opt) {
115 case 'c':
129 case 'c':
116 clear_env = 1;
130 clear_env = 1;
133+ enable_escape_hatch = 1;
118 case 'n':
135 case 'n':
119 // determine absolute directory for nix dir
136 // determine absolute directory for nix dir
120 nixdir = realpath(optarg, NULL);
137 nixdir = realpath(optarg, NULL);
@@ -162,6 +179,62 @@ int main(int argc, char *argv[]) {
162 err_exit("mkdtemp(%s)", template_);
179 err_exit("mkdtemp(%s)", template_);
188+ struct sigaction handle_child;
189+ handle_child.sa_sigaction = handle_child_death;
190+ handle_child.sa_flags = SA_SIGINFO;
192+ struct sigaction old_handler;
193+ if (sigaction(SIGCHLD, &handle_child, &old_handler)) {
194+ err_exit("sigaction()");
198+ child = child_pid = fork();
201+ } else if (child == 0) {
202+ sigaction(SIGCHLD, &old_handler, NULL);
205+ read(unrace[0], buf, 10);
207+ return child_proc(rootdir, nixdir, clear_env, dirMappings, envMappings, argv[optind], argv + optind);
210+ char fifopath[PATH_MAX];
211+ if (enable_escape_hatch) {
212+ snprintf(fifopath, PATH_MAX, "%s/escape-hatch", rootdir);
213+ mkfifo(fifopath, 0600);
216+ if (enable_escape_hatch) {
218+ while (!child_died) {
219+ int fd = open(fifopath, O_RDONLY);
221+ if (errno == EINTR) continue;
222+ fprintf(stderr, "error opening escape-hatch: %s\n", strerror(errno));
225+ int size = read(fd, buffer, 1024);
232+ int ret = waitpid(child, &status, 0);
233+ return WEXITSTATUS(status);
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[]) {
165 // get uid, gid before going to new namespace
238 // get uid, gid before going to new namespace
166 uid_t uid = getuid();
239 uid_t uid = getuid();
167 gid_t gid = getgid();
240 gid_t gid = getgid();
@@ -169,7 +242,7 @@ int main(int argc, char *argv[]) {
169 // "unshare" into new namespace
242 // "unshare" into new namespace
170 if (unshare(CLONE_NEWNS | CLONE_NEWUSER) < 0) {
243 if (unshare(CLONE_NEWNS | CLONE_NEWUSER) < 0) {
171 if (errno == EPERM) {
244 if (errno == EPERM) {
172− fputs("Run the following as root to enable unprivileged namespace use:\nsysctl -w kernel.unprivileged_userns_clone=1 ; echo kernel.unprivileged_userns_clone=1 > /etc/sysctl.d/nix-user-chroot.conf\n\n", stderr);
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);
173 exit(EXIT_FAILURE);
246 exit(EXIT_FAILURE);
175 err_exit("unshare()");
248 err_exit("unshare()");
@@ -229,6 +302,6 @@ int main(int argc, char *argv[]) {
231 // execute the command
304 // execute the command
232− execvp(argv[optind], argv + optind);
305+ execvp(executable, new_argv);
233− err_exit("execvp(%s)", argv[optind]);
306+ err_exit("execvp(%s)", executable);