diff options
Diffstat (limited to 'sandbox/linux')
-rw-r--r-- | sandbox/linux/suid/sandbox.cc | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/sandbox/linux/suid/sandbox.cc b/sandbox/linux/suid/sandbox.cc new file mode 100644 index 0000000..1ea2af3 --- /dev/null +++ b/sandbox/linux/suid/sandbox.cc @@ -0,0 +1,224 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <asm/unistd.h> +#include <errno.h> +#include <sched.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/prctl.h> +#include <sys/resource.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> + +#if !defined(CLONE_NEWPID) +#define CLONE_NEWPID 0x20000000 +#endif + +static const char kSandboxPath[] = "/var/run/chrome-sandbox"; +static const char kChromeBinary[] = "/opt/google/chrome/chrome"; +static const char kSandboxDescriptorEnvironmentVarName[] = "SBX_D"; + +// These are the magic byte values which the sandboxed process uses to request +// that it be chrooted. +static const char kMsgChrootMe = 'C'; +static const char kMsgChrootSuccessful = 'O'; + +static void FatalError(const char *msg, ...) + __attribute__((noreturn, format(printf,1,2))); + +static void FatalError(const char *msg, ...) { + va_list ap; + va_start(ap, msg); + + vfprintf(stderr, msg, ap); + fprintf(stderr, ": %s\n", strerror(errno)); + fflush(stderr); + exit(1); +} + +static int CloneChrootHelperProcess() { + int sv[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) { + perror("socketpair"); + return -1; + } + + const pid_t pid = syscall( + __NR_clone, CLONE_FS | SIGCHLD, 0, 0, 0); + + if (pid == -1) { + perror("clone"); + close(sv[0]); + close(sv[1]); + return -1; + } + + if (pid == 0) { + // We share our files structure with an untrusted process. As a security in + // depth measure, we make sure that we can't open anything by mistake. + // TODO: drop CAP_SYS_RESOURCE + + const struct rlimit nofile = {0, 0}; + if (setrlimit(RLIMIT_NOFILE, &nofile)) + FatalError("Setting RLIMIT_NOFILE"); + + if (close(sv[1])) + FatalError("close"); + + char msg; + ssize_t bytes; + do { + bytes = read(sv[0], &msg, 1); + } while (bytes == -1 && errno == EINTR); + + if (bytes == 0) + _exit(0); + if (bytes != 1) + FatalError("read"); + + if (msg != kMsgChrootMe) + FatalError("Unknown message from sandboxed process"); + + if (chdir(kSandboxPath)) + FatalError("Cannot chdir into %s", kSandboxPath); + + struct stat st; + if (stat(".", &st)) + FatalError("stat"); + + if (st.st_uid || st.st_gid || st.st_mode & S_IWOTH) + FatalError("Bad permissions on chroot directory (%s)", kSandboxPath); + + if (chroot(".")) + FatalError("Cannot chroot into %s", kSandboxPath); + + if (chdir("/")) + FatalError("Cannot chdir to / after chroot"); + + const char reply = kMsgChrootSuccessful; + do { + bytes = write(sv[0], &reply, 1); + } while (bytes == -1 && errno == EINTR); + + if (bytes != 1) + FatalError("Writing reply"); + + _exit(0); + } + + if (close(sv[0])) { + close(sv[1]); + perror("close"); + return false; + } + + return sv[1]; +} + +static bool SpawnChrootHelper() { + const int chroot_signal_fd = CloneChrootHelperProcess(); + + if (chroot_signal_fd == -1) + return false; + + // In the parent process, we install an environment variable containing the + // number of the file descriptor. + char desc_str[64]; + snprintf(desc_str, sizeof(desc_str), "%d", chroot_signal_fd); + + if (setenv(kSandboxDescriptorEnvironmentVarName, desc_str, 1)) { + perror("setenv"); + close(chroot_signal_fd); + return false; + } + + return true; +} + +static bool MoveToNewPIDNamespace() { + const pid_t pid = syscall( + __NR_clone, CLONE_NEWPID | SIGCHLD, 0, 0, 0); + + if (pid == -1) { + if (errno == EINVAL) { + // System doesn't support NEWPID. We carry on anyway. + return true; + } + + perror("Failed to move to new PID namespace"); + return false; + } + + if (pid) + _exit(0); + + return true; +} + +static bool DropRoot() { + if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0)) { + perror("prctl(PR_SET_DUMPABLE)"); + return false; + } + + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0)) { + perror("Still dumpable after prctl(PR_SET_DUMPABLE)"); + return false; + } + + gid_t rgid, egid, sgid; + if (getresgid(&rgid, &egid, &sgid)) { + perror("getresgid"); + return false; + } + + if (setresgid(rgid, rgid, rgid)) { + perror("setresgid"); + return false; + } + + uid_t ruid, euid, suid; + if (getresuid(&ruid, &euid, &suid)) { + perror("getresuid"); + return false; + } + + if (setresuid(ruid, ruid, ruid)) { + perror("setresuid"); + return false; + } + + return true; +} + +int main(int argc, char **argv) { + if (argc == 1) { + fprintf(stderr, "Usage: %s <renderer process> <args...>\n", argv[0]); + return 1; + } + + if (strcmp(argv[1], kChromeBinary)) { + fprintf(stderr, "This wrapper can only run %s!\n", kChromeBinary); + return 1; + } + + if (!MoveToNewPIDNamespace()) + return 1; + if (!SpawnChrootHelper()) + return 1; + if (!DropRoot()) + return 1; + + execv(argv[1], &argv[1]); + FatalError("execv failed"); + + return 1; +} |