summaryrefslogtreecommitdiffstats
path: root/sandbox/linux/suid/sandbox.c
diff options
context:
space:
mode:
Diffstat (limited to 'sandbox/linux/suid/sandbox.c')
-rw-r--r--sandbox/linux/suid/sandbox.c311
1 files changed, 311 insertions, 0 deletions
diff --git a/sandbox/linux/suid/sandbox.c b/sandbox/linux/suid/sandbox.c
new file mode 100644
index 0000000..992ee54f
--- /dev/null
+++ b/sandbox/linux/suid/sandbox.c
@@ -0,0 +1,311 @@
+// 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.
+
+// http://code.google.com/p/chromium/wiki/LinuxSUIDSandbox
+
+#define _GNU_SOURCE
+#include <asm/unistd.h>
+#include <errno.h>
+#include <fcntl.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>
+#include <stdbool.h>
+
+#include "suid_unsafe_environment_variables.h"
+
+#if !defined(CLONE_NEWPID)
+#define CLONE_NEWPID 0x20000000
+#endif
+
+#if defined(LINUX_SANDBOX_CHROME_PATH)
+static const char kChromeBinary[] = LINUX_SANDBOX_CHROME_PATH;
+#endif
+
+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;
+ }
+
+ // We create a temp directory for our chroot. Nobody should ever write into
+ // it, so it's root:root mode 000.
+ char kTempDirectoryTemplate[] = "/tmp/chrome-sandbox-chroot-XXXXXX";
+ const char* temp_dir = mkdtemp(kTempDirectoryTemplate);
+ if (!temp_dir) {
+ perror("Failed to create temp directory for chroot");
+ return -1;
+ }
+
+ const int chroot_dir_fd = open(temp_dir, O_DIRECTORY | O_RDONLY);
+ if (chroot_dir_fd < 0) {
+ rmdir(temp_dir);
+ perror("Failed to open chroot temp directory");
+ return -1;
+ }
+
+ if (rmdir(temp_dir)) {
+ perror("rmdir");
+ return -1;
+ }
+
+ char proc_self_fd_str[128];
+ int printed = snprintf(proc_self_fd_str, sizeof(proc_self_fd_str),
+ "/proc/self/fd/%d", chroot_dir_fd);
+ if (printed < 0 || printed >= sizeof(proc_self_fd_str)) {
+ fprintf(stderr, "Error in snprintf");
+ return -1;
+ }
+
+ if (fchown(chroot_dir_fd, 0 /* root */, 0 /* root */)) {
+ perror("fchown");
+ return -1;
+ }
+
+ if (fchmod(chroot_dir_fd, 0000 /* no-access */)) {
+ perror("fchmod");
+ 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 / use SECURE_NOROOT
+
+ const struct rlimit nofile = {0, 0};
+ if (setrlimit(RLIMIT_NOFILE, &nofile))
+ FatalError("Setting RLIMIT_NOFILE");
+
+ if (close(sv[1]))
+ FatalError("close");
+
+ // wait for message
+ 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");
+
+ // do chrooting
+ if (msg != kMsgChrootMe)
+ FatalError("Unknown message from sandboxed process");
+
+ if (fchdir(chroot_dir_fd))
+ FatalError("Cannot chdir into chroot temp directory");
+
+ struct stat st;
+ if (fstat(chroot_dir_fd, &st))
+ FatalError("stat");
+
+ if (st.st_uid || st.st_gid || st.st_mode & 0777)
+ FatalError("Bad permissions on chroot temp directory");
+
+ if (chroot(proc_self_fd_str))
+ FatalError("Cannot chroot into temp directory");
+
+ 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(chroot_dir_fd)) {
+ close(sv[0]);
+ close(sv[1]);
+ perror("close(chroot_dir_fd)");
+ return false;
+ }
+
+ 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];
+ int printed = snprintf(desc_str, sizeof(desc_str), "%d", chroot_signal_fd);
+ if (printed < 0 || printed >= sizeof(desc_str)) {
+ fprintf(stderr, "Failed to snprintf\n");
+ return false;
+ }
+
+ 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;
+}
+
+static bool SetupChildEnvironment() {
+
+ unsigned i;
+
+ // ld.so may have cleared several environment variables because we are SUID.
+ // However, the child process might need them so zygote_host_linux.cc saves a
+ // copy in SANDBOX_$x. This is safe because we have dropped root by this
+ // point, so we can only exec a binary with the permissions of the user who
+ // ran us in the first place.
+
+ for (i = 0; kSUIDUnsafeEnvironmentVariables[i]; ++i) {
+ const char* const envvar = kSUIDUnsafeEnvironmentVariables[i];
+ char* const saved_envvar = SandboxSavedEnvironmentVariable(envvar);
+ if (!saved_envvar)
+ return false;
+
+ const char* const value = getenv(saved_envvar);
+ if (value) {
+ setenv(envvar, value, 1 /* overwrite */);
+ unsetenv(saved_envvar);
+ }
+
+ free(saved_envvar);
+ }
+
+ return true;
+}
+
+int main(int argc, char **argv) {
+ if (argc <= 1) {
+ fprintf(stderr, "Usage: %s <renderer process> <args...>\n", argv[0]);
+ return 1;
+ }
+
+ if (!MoveToNewPIDNamespace())
+ return 1;
+ if (!SpawnChrootHelper())
+ return 1;
+ if (!DropRoot())
+ return 1;
+ if (!SetupChildEnvironment())
+ return 1;
+
+ execv(argv[1], &argv[1]);
+ FatalError("execv failed");
+
+ return 1;
+}