diff options
-rw-r--r-- | src/main/tools/namespace-sandbox.c | 230 |
1 files changed, 126 insertions, 104 deletions
diff --git a/src/main/tools/namespace-sandbox.c b/src/main/tools/namespace-sandbox.c index 4a1a2bd7e6..5cf6b433be 100644 --- a/src/main/tools/namespace-sandbox.c +++ b/src/main/tools/namespace-sandbox.c @@ -34,7 +34,6 @@ #include <unistd.h> static int global_debug = 0; -static int global_cpid; // Returned by fork() #define PRINT_DEBUG(...) do { if (global_debug) {fprintf(stderr, "sandbox.c: " __VA_ARGS__);}} while(0) @@ -42,7 +41,7 @@ static int global_cpid; // Returned by fork() #define CHECK_NOT_NULL(x) if (x == NULL) { perror(#x); exit(1); } #define DIE() do { fprintf(stderr, "Error in %d\n", __LINE__); exit(-1); } while(0); -int kChildrenCleanupDelay = 1; +const int kChildrenCleanupDelay = 1; static volatile sig_atomic_t global_signal_received = 0; @@ -50,15 +49,15 @@ static volatile sig_atomic_t global_signal_received = 0; // Options parsing result // struct Options { - char *include_prefix; - char *sandbox_root; - char *tools; - char **mounts; - char **includes; - int num_mounts; - int num_includes; - int timeout; - char **args; + char **args; // Command to run (-C / --) + char *include_prefix; // Include prefix (-N) + char *sandbox_root; // Sandbox root (-S) + char *tools; // tools directory (-t) + char **mounts; // List of directories to mount (-m) + char **includes; // List of include directories (-n) + int num_mounts; // size of mounts + int num_includes; // size of includes + int timeout; // Timeout (-T) }; // Print out a usage error. argc and argv are the argument counter @@ -71,14 +70,20 @@ void ParseCommandLine(int argc, char **argv, struct Options *opt); // Signal hanlding void PropagateSignals(); void EnableAlarm(); +// Sandbox setup +void SetupDirectories(struct Options* opt); void SetupSlashDev(); +void SetupUserNamespace(int uid, int gid); +void ChangeRoot(); // Write the file "filename" using a format string specified by "fmt". // Returns -1 on failure. int WriteFile(const char *filename, const char *fmt, ...); +// Run the command specified by the argv array and kill it after +// timeout seconds. +void SpawnCommand(char **argv, int timeout); + + -// -// Main method -// int main(int argc, char *argv[]) { struct Options opt = { .args = NULL, @@ -100,80 +105,28 @@ int main(int argc, char *argv[]) { // create new namespaces in which this process and its children will live CHECK_CALL(unshare(CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWUSER)); CHECK_CALL(mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL)); - // mount sandbox and go there - CHECK_CALL(mount(opt.sandbox_root, opt.sandbox_root, NULL, MS_BIND | MS_NOSUID, NULL)); - CHECK_CALL(chdir(opt.sandbox_root)); - - SetupSlashDev(); - // mount blaze specific directories - tools/ and build-runfiles/ - if (opt.tools != NULL) { - PRINT_DEBUG("tools: %s\n", opt.tools); - CHECK_CALL(mkdir("tools", 0755)); - CHECK_CALL(mount(opt.tools, "tools", NULL, MS_BIND | MS_RDONLY, NULL)); - } - - // mounts passed in argv; those are mostly dirs for shared libs - for (int i = 0; i < opt.num_mounts; i++) { - CHECK_CALL(mount(opt.mounts[i], opt.mounts[i] + 1, NULL, MS_BIND | MS_RDONLY, NULL)); - } - - // c++ compilation - // headers go in separate directory - if (opt.include_prefix != NULL) { - CHECK_CALL(chdir(opt.include_prefix)); - for (int i = 0; i < opt.num_includes; i++) { - // TODO(bazel-team) sometimes list of -iquote given by bazel contains - // invalid (non-existing) entries, ideally we would like not to have them - PRINT_DEBUG("include: %s\n", opt.includes[i]); - if (mount(opt.includes[i], opt.includes[i] + 1 , NULL, MS_BIND, NULL) > -1) { - continue; - } - if (errno == ENOENT) { - continue; - } - CHECK_CALL(-1); - } - CHECK_CALL(chdir("..")); - } - - // Disable needs for CAP_SETGID - int r = WriteFile("/proc/self/setgroups", "deny"); - if (r < 0 && errno != ENOENT) { - // Writing to /proc/self/setgroups might fail on earlier - // version of linux because setgroups does not exist, ignore. - perror("WriteFile(\"/proc/self/setgroups\", \"deny\")"); - exit(-1); - } - // set group and user mapping from outer namespace to inner: - // no changes in the parent, be root in the child - CHECK_CALL(WriteFile("/proc/self/uid_map", "0 %d 1\n", uid)); - CHECK_CALL(WriteFile("/proc/self/gid_map", "0 %d 1\n", gid)); - - CHECK_CALL(setresuid(0, 0, 0)); - CHECK_CALL(setresgid(0, 0, 0)); - - CHECK_CALL(mkdir("proc", 0755)); - CHECK_CALL(mount("/proc", "proc", NULL, MS_REC | MS_BIND, NULL)); + // Create the sandbox directory layout + SetupDirectories(&opt); + // Set the user namespace (user_namespaces(7)) + SetupUserNamespace(uid, gid); // make sandbox actually hermetic: - // move the real root to old_root, then detach it - char old_root[16] = "old-root-XXXXXX"; - CHECK_NOT_NULL(mkdtemp(old_root)); - // pivot_root has no wrapper in libc, so we need syscall() - CHECK_CALL(syscall(SYS_pivot_root, ".", old_root)); - CHECK_CALL(chroot(".")); - CHECK_CALL(umount2(old_root, MNT_DETACH)); - CHECK_CALL(rmdir(old_root)); + ChangeRoot(); + // Finally call the command free(opt.mounts); free(opt.includes); + SpawnCommand(opt.args, opt.timeout); + return 0; +} - for (int i = 0; opt.args[i] != NULL; i += 1) { - PRINT_DEBUG("arg: %s\n", opt.args[i]); +void SpawnCommand(char **argv, int timeout) { + for (int i = 0; argv[i] != NULL; i++) { + PRINT_DEBUG("arg: %s\n", argv[i]); } // spawn child and wait until it finishes - global_cpid = fork(); - if (global_cpid == 0) { + pid_t cpid = fork(); + if (cpid == 0) { CHECK_CALL(setpgid(0, 0)); // if the execvp below fails with "No such file or directory" it means that: // a) the binary is not in the sandbox (which means it wasn't included in @@ -184,7 +137,7 @@ int main(int argc, char *argv[]) { // c) the binary uses elf interpreter which is not inside sandbox - you can // check for that by running "readelf -a a.out | grep interpreter" (the // sandbox code assumes that it is either in /lib*/ or /usr/lib*/) - CHECK_CALL(execvp(opt.args[0], opt.args)); + CHECK_CALL(execvp(argv[0], argv)); PRINT_DEBUG("Exec failed near %s:%d\n", __FILE__, __LINE__); exit(1); } else { @@ -193,17 +146,17 @@ int main(int argc, char *argv[]) { // entire sandbox) PropagateSignals(); // after given timeout, kill children - EnableAlarm(opt.timeout); + EnableAlarm(timeout); int status = 0; while (1) { PRINT_DEBUG("Waiting for the child...\n"); pid_t pid = wait(&status); if (global_signal_received) { PRINT_DEBUG("Received signal: %s\n", strsignal(global_signal_received)); - CHECK_CALL(killpg(global_cpid, global_signal_received)); + CHECK_CALL(killpg(cpid, global_signal_received)); // give children some time for cleanup before they terminate sleep(kChildrenCleanupDelay); - CHECK_CALL(killpg(global_cpid, SIGKILL)); + CHECK_CALL(killpg(cpid, SIGKILL)); exit(128 | global_signal_received); } if (errno == EINTR) { @@ -226,8 +179,22 @@ int main(int argc, char *argv[]) { } } } +} - return 0; +int WriteFile(const char *filename, const char *fmt, ...) { + int r; + va_list ap; + FILE *stream = fopen(filename, "w"); + if (stream == NULL) { + return -1; + } + va_start(ap, fmt); + r = vfprintf(stream, fmt, ap); + va_end(ap); + if (r >= 0) { + r = fclose(stream); + } + return r; } // @@ -256,6 +223,17 @@ void PropagateSignals() { } } +void EnableAlarm(int timeout) { + if (timeout <= 0) return; + + struct itimerval timer = {}; + timer.it_value.tv_sec = (long) timeout; + CHECK_CALL(setitimer(ITIMER_REAL, &timer, NULL)); +} + +// +// Sandbox setup +// void SetupSlashDev() { CHECK_CALL(mkdir("dev", 0755)); const char *devs[] = { @@ -275,29 +253,73 @@ void SetupSlashDev() { } } -void EnableAlarm(int timeout) { - if (timeout <= 0) return; +void SetupDirectories(struct Options *opt) { + // Mount the sandbox and go there. + CHECK_CALL(mount(opt->sandbox_root, opt->sandbox_root, NULL, MS_BIND | MS_NOSUID, NULL)); + CHECK_CALL(chdir(opt->sandbox_root)); + SetupSlashDev(); + // Mount blaze specific directories - tools/ and build-runfiles/. + if (opt->tools != NULL) { + PRINT_DEBUG("tools: %s\n", opt->tools); + CHECK_CALL(mkdir("tools", 0755)); + CHECK_CALL(mount(opt->tools, "tools", NULL, MS_BIND | MS_RDONLY, NULL)); + } - struct itimerval timer = {}; - timer.it_value.tv_sec = (long) timeout; - CHECK_CALL(setitimer(ITIMER_REAL, &timer, NULL)); -} + // Mount directories passed in argv; those are mostly dirs for shared libs. + for (int i = 0; i < opt->num_mounts; i++) { + CHECK_CALL(mount(opt->mounts[i], opt->mounts[i] + 1, NULL, MS_BIND | MS_RDONLY, NULL)); + } -int WriteFile(const char *filename, const char *fmt, ...) { - FILE *stream; - int r; - va_list ap; - stream = fopen(filename, "w"); - if (stream == NULL) { - return -1; + // C++ compilation + // C++ headers go in a separate directory. + if (opt->include_prefix != NULL) { + CHECK_CALL(chdir(opt->include_prefix)); + for (int i = 0; i < opt->num_includes; i++) { + // TODO(bazel-team): sometimes list of -iquote given by bazel contains + // invalid (non-existing) entries, ideally we would like not to have them + PRINT_DEBUG("include: %s\n", opt->includes[i]); + if (mount(opt->includes[i], opt->includes[i] + 1 , NULL, MS_BIND, NULL) > -1) { + continue; + } + if (errno == ENOENT) { + continue; + } + CHECK_CALL(-1); + } + CHECK_CALL(chdir("..")); } - va_start(ap, fmt); - r = vfprintf(stream, fmt, ap); - va_end(ap); - if (r >= 0) { - r = fclose(stream); + + CHECK_CALL(mkdir("proc", 0755)); + CHECK_CALL(mount("/proc", "proc", NULL, MS_REC | MS_BIND, NULL)); +} + +void SetupUserNamespace(int uid, int gid) { + // Disable needs for CAP_SETGID + int r = WriteFile("/proc/self/setgroups", "deny"); + if (r < 0 && errno != ENOENT) { + // Writing to /proc/self/setgroups might fail on earlier + // version of linux because setgroups does not exist, ignore. + perror("WriteFile(\"/proc/self/setgroups\", \"deny\")"); + exit(-1); } - return r; + // set group and user mapping from outer namespace to inner: + // no changes in the parent, be root in the child + CHECK_CALL(WriteFile("/proc/self/uid_map", "0 %d 1\n", uid)); + CHECK_CALL(WriteFile("/proc/self/gid_map", "0 %d 1\n", gid)); + + CHECK_CALL(setresuid(0, 0, 0)); + CHECK_CALL(setresgid(0, 0, 0)); +} + +void ChangeRoot() { + // move the real root to old_root, then detach it + char old_root[16] = "old-root-XXXXXX"; + CHECK_NOT_NULL(mkdtemp(old_root)); + // pivot_root has no wrapper in libc, so we need syscall() + CHECK_CALL(syscall(SYS_pivot_root, ".", old_root)); + CHECK_CALL(chroot(".")); + CHECK_CALL(umount2(old_root, MNT_DETACH)); + CHECK_CALL(rmdir(old_root)); } // |