blob: 1ea2af35e4408010290e967e95d1ec14f088a5bb [file] [log] [blame]
// 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;
}