From 4378a822c0f819edb40d6903a9fa363d7c72c84d Mon Sep 17 00:00:00 2001 From: "agl@chromium.org" Date: Wed, 8 Jul 2009 01:15:14 +0000 Subject: Linux: SUID sandbox support * Make processes dumpable when they crash. * Find crashing processes by searching for a socket inode, rather than relying on SCM_CREDENTIALS. The kernel doesn't translate PIDs between PID namespaces with SCM_CREDENTIALS, so we can't use the PID there. * Use a command line flag to the renderer to enable crash dumping. Previously it tried to access the user's home directory for this information. * Search for a sandbox helper binary and, if found, use it. * Include the source for a sandbox helper binary. It's currently not built by default. http://codereview.chromium.org/149230 R=evan,markus BUG=8081 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@20110 0039d316-1c4b-4281-b951-d872f2087c98 --- sandbox/linux/suid/sandbox.cc | 224 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 sandbox/linux/suid/sandbox.cc (limited to 'sandbox/linux/suid/sandbox.cc') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 \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; +} -- cgit v1.1