Merge from Chromium at DEPS revision r167172
This commit was generated by merge_to_master.py.
Change-Id: Ib8d56fd5ae39a2d7e8c91dcd76cc6d13f25f2aab
diff --git a/sandbox/linux/suid/client/setuid_sandbox_client.cc b/sandbox/linux/suid/client/setuid_sandbox_client.cc
new file mode 100644
index 0000000..45d700b
--- /dev/null
+++ b/sandbox/linux/suid/client/setuid_sandbox_client.cc
@@ -0,0 +1,179 @@
+// Copyright (c) 2012 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 <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "base/eintr_wrapper.h"
+#include "base/environment.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/string_number_conversions.h"
+
+#include "sandbox/linux/suid/common/sandbox.h"
+#include "sandbox/linux/suid/common/suid_unsafe_environment_variables.h"
+#include "setuid_sandbox_client.h"
+
+namespace {
+
+// Set an environment variable that reflects the API version we expect from the
+// setuid sandbox. Old versions of the sandbox will ignore this.
+void SetSandboxAPIEnvironmentVariable(base::Environment* env) {
+ env->SetVar(sandbox::kSandboxEnvironmentApiRequest,
+ base::IntToString(sandbox::kSUIDSandboxApiNumber));
+}
+
+// Wrapper around a shared C function.
+// Returns the "saved" environment variable name corresponding to |envvar|
+// in a new string or NULL.
+std::string* CreateSavedVariableName(const char* env_var) {
+ char* const saved_env_var = SandboxSavedEnvironmentVariable(env_var);
+ if (!saved_env_var)
+ return NULL;
+ std::string* saved_env_var_copy = new std::string(saved_env_var);
+ // SandboxSavedEnvironmentVariable is the C function that we wrap and uses
+ // malloc() to allocate memory.
+ free(saved_env_var);
+ return saved_env_var_copy;
+}
+
+// The ELF loader will clear many environment variables so we save them to
+// different names here so that the SUID sandbox can resolve them for the
+// renderer.
+void SaveSUIDUnsafeEnvironmentVariables(base::Environment* env) {
+ for (unsigned i = 0; kSUIDUnsafeEnvironmentVariables[i]; ++i) {
+ const char* env_var = kSUIDUnsafeEnvironmentVariables[i];
+ // Get the saved environment variable corresponding to envvar.
+ scoped_ptr<std::string> saved_env_var(CreateSavedVariableName(env_var));
+ if (saved_env_var == NULL)
+ continue;
+
+ std::string value;
+ if (env->GetVar(env_var, &value))
+ env->SetVar(saved_env_var->c_str(), value);
+ else
+ env->UnSetVar(saved_env_var->c_str());
+ }
+}
+
+int GetHelperApi(base::Environment* env) {
+ std::string api_string;
+ int api_number = 0; // Assume API version 0 if no environment was found.
+ if (env->GetVar(sandbox::kSandboxEnvironmentApiProvides, &api_string) &&
+ !base::StringToInt(api_string, &api_number)) {
+ // It's an error if we could not convert the API number.
+ api_number = -1;
+ }
+ return api_number;
+}
+
+// Convert |var_name| from the environment |env| to an int.
+// Return -1 if the variable does not exist or the value cannot be converted.
+int EnvToInt(base::Environment* env, const char* var_name) {
+ std::string var_string;
+ int var_value = -1;
+ if (env->GetVar(var_name, &var_string) &&
+ !base::StringToInt(var_string, &var_value)) {
+ var_value = -1;
+ }
+ return var_value;
+}
+
+pid_t GetHelperPID(base::Environment* env) {
+ return EnvToInt(env, sandbox::kSandboxHelperPidEnvironmentVarName);
+}
+
+// Get the IPC file descriptor used to communicate with the setuid helper.
+int GetIPCDescriptor(base::Environment* env) {
+ return EnvToInt(env, sandbox::kSandboxDescriptorEnvironmentVarName);
+}
+
+} // namespace
+
+namespace sandbox {
+
+SetuidSandboxClient* SetuidSandboxClient::Create() {
+ base::Environment* environment(base::Environment::Create());
+ SetuidSandboxClient* sandbox_client(new(SetuidSandboxClient));
+
+ CHECK(environment);
+ sandbox_client->env_ = environment;
+ return sandbox_client;
+}
+
+SetuidSandboxClient::SetuidSandboxClient()
+ : env_(NULL),
+ sandboxed_(false) {
+}
+
+SetuidSandboxClient::~SetuidSandboxClient() {
+ delete env_;
+}
+
+bool SetuidSandboxClient::ChrootMe() {
+ int ipc_fd = GetIPCDescriptor(env_);
+
+ if (ipc_fd < 0) {
+ LOG(ERROR) << "Failed to obtain the sandbox IPC descriptor";
+ return false;
+ }
+
+ if (HANDLE_EINTR(write(ipc_fd, &kMsgChrootMe, 1)) != 1) {
+ PLOG(ERROR) << "Failed to write to chroot pipe";
+ return false;
+ }
+
+ // We need to reap the chroot helper process in any event.
+ pid_t helper_pid = GetHelperPID(env_);
+ // If helper_pid is -1 we wait for any child.
+ if (waitpid(helper_pid, NULL, 0) < 0) {
+ PLOG(ERROR) << "Failed to wait for setuid helper to die";
+ return false;
+ }
+
+ char reply;
+ if (HANDLE_EINTR(read(ipc_fd, &reply, 1)) != 1) {
+ PLOG(ERROR) << "Failed to read from chroot pipe";
+ return false;
+ }
+
+ if (reply != kMsgChrootSuccessful) {
+ LOG(ERROR) << "Error code reply from chroot helper";
+ return false;
+ }
+
+ // We now consider ourselves "fully sandboxed" as far as the
+ // setuid sandbox is concerned.
+ sandboxed_ = true;
+ return true;
+}
+
+bool SetuidSandboxClient::IsSuidSandboxUpToDate() const {
+ return GetHelperApi(env_) == kSUIDSandboxApiNumber;
+}
+
+bool SetuidSandboxClient::IsSuidSandboxChild() const {
+ return GetIPCDescriptor(env_) >= 0;
+}
+
+bool SetuidSandboxClient::IsInNewPIDNamespace() const {
+ return env_->HasVar(kSandboxPIDNSEnvironmentVarName);
+}
+
+bool SetuidSandboxClient::IsInNewNETNamespace() const {
+ return env_->HasVar(kSandboxNETNSEnvironmentVarName);
+}
+
+bool SetuidSandboxClient::IsSandboxed() const {
+ return sandboxed_;
+}
+
+void SetuidSandboxClient::SetupLaunchEnvironment() {
+ SaveSUIDUnsafeEnvironmentVariables(env_);
+ SetSandboxAPIEnvironmentVariable(env_);
+}
+
+} // namespace sandbox
+
diff --git a/sandbox/linux/suid/client/setuid_sandbox_client.h b/sandbox/linux/suid/client/setuid_sandbox_client.h
new file mode 100644
index 0000000..a9f6536
--- /dev/null
+++ b/sandbox/linux/suid/client/setuid_sandbox_client.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2012 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.
+
+#ifndef SANDBOX_LINUX_SUID_SETUID_SANDBOX_CLIENT_H_
+#define SANDBOX_LINUX_SUID_SETUID_SANDBOX_CLIENT_H_
+
+#include "base/basictypes.h"
+
+namespace base { class Environment; }
+
+namespace sandbox {
+
+// Helper class to use the setuid sandbox. This class is to be used both
+// before launching the setuid helper and after being executed through the
+// setuid helper.
+//
+// A typical use would be:
+// 1. The browser calls SetupLaunchEnvironment()
+// 2. The browser launches a renderer through the setuid sandbox.
+// 3. The renderer requests being chroot-ed through ChrootMe() and
+// requests other sandboxing status via the status functions.
+class SetuidSandboxClient {
+ public:
+ // All instantation should go through this factory method.
+ static class SetuidSandboxClient* Create();
+ ~SetuidSandboxClient();
+
+ // Ask the setuid helper over the setuid sandbox IPC channel to chroot() us
+ // to an empty directory.
+ // Will only work if we have been launched through the setuid helper.
+ bool ChrootMe();
+
+ // Did we get launched through an up to date setuid binary ?
+ bool IsSuidSandboxUpToDate() const;
+ // Did we get launched through the setuid helper ?
+ bool IsSuidSandboxChild() const;
+ // Did the setuid helper create a new PID namespace ?
+ bool IsInNewPIDNamespace() const;
+ // Did the setuid helper create a new network namespace ?
+ bool IsInNewNETNamespace() const;
+ // Are we done and fully sandboxed ?
+ bool IsSandboxed() const;
+
+ // Set-up the environment. This should be done prior to launching the setuid
+ // helper.
+ void SetupLaunchEnvironment();
+
+ private:
+ // Holds the environment. Will never be NULL.
+ base::Environment* env_;
+ bool sandboxed_;
+ DISALLOW_IMPLICIT_CONSTRUCTORS(SetuidSandboxClient);
+};
+
+} // namespace sandbox
+
+#endif // SANDBOX_LINUX_SUID_SETUID_SANDBOX_CLIENT_H_
+
diff --git a/sandbox/linux/suid/client/setuid_sandbox_client_unittest.cc b/sandbox/linux/suid/client/setuid_sandbox_client_unittest.cc
new file mode 100644
index 0000000..293f423
--- /dev/null
+++ b/sandbox/linux/suid/client/setuid_sandbox_client_unittest.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 2012 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 "base/environment.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/string_number_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "sandbox/linux/suid/common/sandbox.h"
+#include "setuid_sandbox_client.h"
+
+namespace sandbox {
+
+TEST(SetuidSandboxClient, SetupLaunchEnvironment) {
+ const char kTestValue[] = "This is a test";
+ scoped_ptr<base::Environment> env(base::Environment::Create());
+ EXPECT_TRUE(env != NULL);
+
+ std::string saved_ld_preload;
+ bool environment_had_ld_preload;
+ // First, back-up the real LD_PRELOAD if any.
+ environment_had_ld_preload = env->GetVar("LD_PRELOAD", &saved_ld_preload);
+ // Setup environment variables to save or not save.
+ EXPECT_TRUE(env->SetVar("LD_PRELOAD", kTestValue));
+ EXPECT_TRUE(env->UnSetVar("LD_ORIGIN_PATH"));
+
+ scoped_ptr<SetuidSandboxClient>
+ sandbox_client(SetuidSandboxClient::Create());
+ EXPECT_TRUE(sandbox_client != NULL);
+
+ // Make sure the environment is clean.
+ EXPECT_TRUE(env->UnSetVar(kSandboxEnvironmentApiRequest));
+ EXPECT_TRUE(env->UnSetVar(kSandboxEnvironmentApiProvides));
+
+ sandbox_client->SetupLaunchEnvironment();
+
+ // Check if the requested API environment was set.
+ std::string api_request;
+ EXPECT_TRUE(env->GetVar(kSandboxEnvironmentApiRequest, &api_request));
+ int api_request_num;
+ EXPECT_TRUE(base::StringToInt(api_request, &api_request_num));
+ EXPECT_EQ(api_request_num, kSUIDSandboxApiNumber);
+
+ // Now check if LD_PRELOAD was saved to SANDBOX_LD_PRELOAD.
+ std::string sandbox_ld_preload;
+ EXPECT_TRUE(env->GetVar("SANDBOX_LD_PRELOAD", &sandbox_ld_preload));
+ EXPECT_EQ(sandbox_ld_preload, kTestValue);
+
+ // Check that LD_ORIGIN_PATH was not saved.
+ EXPECT_FALSE(env->HasVar("SANDBOX_LD_ORIGIN_PATH"));
+
+ // We should not forget to restore LD_PRELOAD at the end, or this environment
+ // variable will affect the next running tests!
+ if (environment_had_ld_preload) {
+ EXPECT_TRUE(env->SetVar("LD_PRELOAD", saved_ld_preload));
+ } else {
+ EXPECT_TRUE(env->UnSetVar("LD_PRELOAD"));
+ }
+}
+
+TEST(SetuidSandboxClient, SandboxedClientAPI) {
+ scoped_ptr<base::Environment> env(base::Environment::Create());
+ EXPECT_TRUE(env != NULL);
+
+ scoped_ptr<SetuidSandboxClient>
+ sandbox_client(SetuidSandboxClient::Create());
+ EXPECT_TRUE(sandbox_client != NULL);
+
+ // Set-up a fake environment as if we went through the setuid sandbox.
+ EXPECT_TRUE(env->SetVar(kSandboxEnvironmentApiProvides,
+ base::IntToString(kSUIDSandboxApiNumber)));
+ EXPECT_TRUE(env->SetVar(kSandboxDescriptorEnvironmentVarName, "1"));
+ EXPECT_TRUE(env->SetVar(kSandboxPIDNSEnvironmentVarName, "1"));
+ EXPECT_TRUE(env->UnSetVar(kSandboxNETNSEnvironmentVarName));
+
+ // Check the API.
+ EXPECT_TRUE(sandbox_client->IsSuidSandboxUpToDate());
+ EXPECT_TRUE(sandbox_client->IsSuidSandboxChild());
+ EXPECT_TRUE(sandbox_client->IsInNewPIDNamespace());
+ EXPECT_FALSE(sandbox_client->IsInNewNETNamespace());
+
+ // Forge an incorrect API version and check.
+ EXPECT_TRUE(env->SetVar(kSandboxEnvironmentApiProvides,
+ base::IntToString(kSUIDSandboxApiNumber + 1)));
+ EXPECT_FALSE(sandbox_client->IsSuidSandboxUpToDate());
+ // We didn't go through the actual sandboxing mechanism as it is
+ // very hard in a unit test.
+ EXPECT_FALSE(sandbox_client->IsSandboxed());
+}
+
+} // namespace sandbox
+
diff --git a/sandbox/linux/suid/common/sandbox.h b/sandbox/linux/suid/common/sandbox.h
new file mode 100644
index 0000000..aad4ff8
--- /dev/null
+++ b/sandbox/linux/suid/common/sandbox.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 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.
+
+#ifndef SANDBOX_LINUX_SUID_SANDBOX_H_
+#define SANDBOX_LINUX_SUID_SANDBOX_H_
+
+#if defined(__cplusplus)
+namespace sandbox {
+#endif
+
+// These are command line switches that may be used by other programs
+// (e.g. Chrome) to construct a command line for the sandbox.
+static const char kAdjustOOMScoreSwitch[] = "--adjust-oom-score";
+#if defined(OS_CHROMEOS)
+static const char kAdjustLowMemMarginSwitch[] = "--adjust-low-mem";
+#endif
+
+static const char kSandboxDescriptorEnvironmentVarName[] = "SBX_D";
+static const char kSandboxHelperPidEnvironmentVarName[] = "SBX_HELPER_PID";
+
+static const long kSUIDSandboxApiNumber = 1;
+static const char kSandboxEnvironmentApiRequest[] = "SBX_CHROME_API_RQ";
+static const char kSandboxEnvironmentApiProvides[] = "SBX_CHROME_API_PRV";
+
+// This number must be kept in sync with common/zygote_commands_linux.h
+static const int kZygoteIdFd = 7;
+
+// 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';
+
+// These are set if we have respectively switched to a new PID or NET namespace
+// by going through the setuid binary helper.
+static const char kSandboxPIDNSEnvironmentVarName[] = "SBX_PID_NS";
+static const char kSandboxNETNSEnvironmentVarName[] = "SBX_NET_NS";
+
+#if defined(__cplusplus)
+} // namespace sandbox
+#endif
+
+#endif // SANDBOX_LINUX_SUID_SANDBOX_H_
diff --git a/sandbox/linux/suid/common/suid_unsafe_environment_variables.h b/sandbox/linux/suid/common/suid_unsafe_environment_variables.h
new file mode 100644
index 0000000..ee4db76
--- /dev/null
+++ b/sandbox/linux/suid/common/suid_unsafe_environment_variables.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2012 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.
+
+// This is a list of environment variables which the ELF loader unsets when
+// loading a SUID binary. Because they are unset rather than just ignored, they
+// aren't passed to child processes of SUID processes either.
+//
+// We need to save these environment variables before running a SUID sandbox
+// and restore them before running child processes (but after dropping root).
+//
+// List gathered from glibc sources (00ebd7ed58df389a78e41dece058048725cb585e):
+// sysdeps/unix/sysv/linux/i386/dl-librecon.h
+// sysdeps/generic/unsecvars.h
+
+#ifndef SANDBOX_LINUX_SUID_SUID_UNSAFE_ENVIRONMENT_VARIABLES_H_
+#define SANDBOX_LINUX_SUID_SUID_UNSAFE_ENVIRONMENT_VARIABLES_H_
+
+#if defined(__cplusplus)
+#include <limits>
+#define SIZE_MAX std::numeric_limits<size_t>::max()
+#endif
+
+#include <stdlib.h> // malloc
+#include <string.h> // memcpy
+
+static const char* kSUIDUnsafeEnvironmentVariables[] = {
+ "LD_AOUT_LIBRARY_PATH",
+ "LD_AOUT_PRELOAD",
+ "GCONV_PATH",
+ "GETCONF_DIR",
+ "HOSTALIASES",
+ "LD_AUDIT",
+ "LD_DEBUG",
+ "LD_DEBUG_OUTPUT",
+ "LD_DYNAMIC_WEAK",
+ "LD_LIBRARY_PATH",
+ "LD_ORIGIN_PATH",
+ "LD_PRELOAD",
+ "LD_PROFILE",
+ "LD_SHOW_AUXV",
+ "LD_USE_LOAD_BIAS",
+ "LOCALDOMAIN",
+ "LOCPATH",
+ "MALLOC_TRACE",
+ "NIS_PATH",
+ "NLSPATH",
+ "RESOLV_HOST_CONF",
+ "RES_OPTIONS",
+ "TMPDIR",
+ "TZDIR",
+ NULL,
+};
+
+// Return a malloc allocated string containing the 'saved' environment variable
+// name for a given environment variable.
+static inline char* SandboxSavedEnvironmentVariable(const char* envvar) {
+ const size_t envvar_len = strlen(envvar);
+
+ if (envvar_len > SIZE_MAX - 1 -8)
+ return NULL;
+
+ const size_t saved_envvarlen = envvar_len + 1 /* NUL terminator */ +
+ 8 /* strlen("SANDBOX_") */;
+ char* const saved_envvar = (char*) malloc(saved_envvarlen);
+ if (!saved_envvar)
+ return NULL;
+
+ memcpy(saved_envvar, "SANDBOX_", 8);
+ memcpy(saved_envvar + 8, envvar, envvar_len);
+ saved_envvar[8 + envvar_len] = 0;
+
+ return saved_envvar;
+}
+
+#if defined(__cplusplus)
+#undef SIZE_MAX
+#endif
+
+#endif // SANDBOX_LINUX_SUID_SUID_UNSAFE_ENVIRONMENT_VARIABLES_H_
diff --git a/sandbox/linux/suid/linux_util.c b/sandbox/linux/suid/linux_util.c
new file mode 100644
index 0000000..c5af0d0
--- /dev/null
+++ b/sandbox/linux/suid/linux_util.c
@@ -0,0 +1,112 @@
+// 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.
+
+// The following is duplicated from base/linux_utils.cc.
+// We shouldn't link against C++ code in a setuid binary.
+
+#include "linux_util.h"
+
+#include <dirent.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+// expected prefix of the target of the /proc/self/fd/%d link for a socket
+static const char kSocketLinkPrefix[] = "socket:[";
+
+// Parse a symlink in /proc/pid/fd/$x and return the inode number of the
+// socket.
+// inode_out: (output) set to the inode number on success
+// path: e.g. /proc/1234/fd/5 (must be a UNIX domain socket descriptor)
+static bool ProcPathGetInode(ino_t* inode_out, const char* path) {
+ char buf[256];
+ const ssize_t n = readlink(path, buf, sizeof(buf) - 1);
+ if (n == -1)
+ return false;
+ buf[n] = 0;
+
+ if (memcmp(kSocketLinkPrefix, buf, sizeof(kSocketLinkPrefix) - 1))
+ return false;
+
+ char *endptr;
+ const unsigned long long int inode_ul =
+ strtoull(buf + sizeof(kSocketLinkPrefix) - 1, &endptr, 10);
+ if (*endptr != ']')
+ return false;
+
+ if (inode_ul == ULLONG_MAX)
+ return false;
+
+ *inode_out = inode_ul;
+ return true;
+}
+
+bool FindProcessHoldingSocket(pid_t* pid_out, ino_t socket_inode) {
+ bool already_found = false;
+
+ DIR* proc = opendir("/proc");
+ if (!proc)
+ return false;
+
+ const uid_t uid = getuid();
+ struct dirent* dent;
+ while ((dent = readdir(proc))) {
+ char *endptr;
+ const unsigned long int pid_ul = strtoul(dent->d_name, &endptr, 10);
+ if (pid_ul == ULONG_MAX || *endptr)
+ continue;
+
+ // We have this setuid code here because the zygote and its children have
+ // /proc/$pid/fd owned by root. While scanning through /proc, we add this
+ // extra check so users cannot accidentally gain information about other
+ // users' processes. To determine process ownership, we use the property
+ // that if user foo owns process N, then /proc/N is owned by foo.
+ {
+ char buf[256];
+ struct stat statbuf;
+ snprintf(buf, sizeof(buf), "/proc/%lu", pid_ul);
+ if (stat(buf, &statbuf) < 0)
+ continue;
+ if (uid != statbuf.st_uid)
+ continue;
+ }
+
+ char buf[256];
+ snprintf(buf, sizeof(buf), "/proc/%lu/fd", pid_ul);
+ DIR* fd = opendir(buf);
+ if (!fd)
+ continue;
+
+ while ((dent = readdir(fd))) {
+ int printed = snprintf(buf, sizeof(buf), "/proc/%lu/fd/%s", pid_ul,
+ dent->d_name);
+ if (printed < 0 || printed >= (int)(sizeof(buf) - 1)) {
+ continue;
+ }
+
+ ino_t fd_inode;
+ if (ProcPathGetInode(&fd_inode, buf)) {
+ if (fd_inode == socket_inode) {
+ if (already_found) {
+ closedir(fd);
+ closedir(proc);
+ return false;
+ }
+
+ already_found = true;
+ *pid_out = pid_ul;
+ break;
+ }
+ }
+ }
+ closedir(fd);
+ }
+ closedir(proc);
+
+ return already_found;
+}
diff --git a/sandbox/linux/suid/linux_util.h b/sandbox/linux/suid/linux_util.h
new file mode 100644
index 0000000..d064252
--- /dev/null
+++ b/sandbox/linux/suid/linux_util.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 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.
+
+// The following is duplicated from base/linux_utils.h.
+// We shouldn't link against C++ code in a setuid binary.
+
+#ifndef SANDBOX_LINUX_SUID_LINUX_UTIL_H_
+#define SANDBOX_LINUX_SUID_LINUX_UTIL_H_
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+static const char kFindInodeSwitch[] = "--find-inode";
+static const char kSuidSandboxGetApiSwitch[] = "--get-api";
+
+// Find the process which holds the given socket, named by inode number. If
+// multiple processes hold the socket, this function returns false.
+bool FindProcessHoldingSocket(pid_t* pid_out, ino_t socket_inode);
+
+#endif // SANDBOX_LINUX_SUID_LINUX_UTIL_H_
diff --git a/sandbox/linux/suid/process_util.h b/sandbox/linux/suid/process_util.h
new file mode 100644
index 0000000..f6b4c31
--- /dev/null
+++ b/sandbox/linux/suid/process_util.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 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.
+
+// The following is duplicated from base/process_utils.h.
+// We shouldn't link against C++ code in a setuid binary.
+
+#ifndef SANDBOX_LINUX_SUID_PROCESS_UTIL_H_
+#define SANDBOX_LINUX_SUID_PROCESS_UTIL_H_
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "base/base_export.h"
+
+// This adjusts /proc/process/oom_score_adj so the Linux OOM killer
+// will prefer certain process types over others. The range for the
+// adjustment is [-1000, 1000], with [0, 1000] being user accessible.
+//
+// If the Linux system isn't new enough to use oom_score_adj, then we
+// try to set the older oom_adj value instead, scaling the score to
+// the required range of [0, 15]. This may result in some aliasing of
+// values, of course.
+BASE_EXPORT bool AdjustOOMScore(pid_t process, int score);
+
+// This adjusts /sys/kernel/mm/chromeos-low_mem/margin so that
+// the kernel notifies us that we are low on memory when less than
+// |margin_mb| megabytes are available. Setting |margin_mb| to -1
+// turns off low memory notification.
+BASE_EXPORT bool AdjustLowMemoryMargin(int64_t margin_mb);
+
+#endif // SANDBOX_LINUX_SUID_PROCESS_UTIL_H_
diff --git a/sandbox/linux/suid/process_util_linux.c b/sandbox/linux/suid/process_util_linux.c
new file mode 100644
index 0000000..5e6b33b
--- /dev/null
+++ b/sandbox/linux/suid/process_util_linux.c
@@ -0,0 +1,106 @@
+// Copyright (c) 2012 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.
+
+// The following is the C version of code from base/process_utils_linux.cc.
+// We shouldn't link against C++ code in a setuid binary.
+
+#define _GNU_SOURCE // needed for O_DIRECTORY
+
+#include "process_util.h"
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+// Ranges for the current (oom_score_adj) and previous (oom_adj)
+// flavors of OOM score.
+static const int kMaxOomScore = 1000;
+static const int kMaxOldOomScore = 15;
+
+// Kernel pseudo-file that allows setting of the low memory margin.
+static const char kLowMemMarginFile[] =
+ "/sys/kernel/mm/chromeos-low_mem/margin";
+
+// NOTE: This is not the only version of this function in the source:
+// the base library (in process_util_linux.cc) also has its own C++ version.
+bool AdjustOOMScore(pid_t process, int score) {
+ if (score < 0 || score > kMaxOomScore)
+ return false;
+
+ char oom_adj[27]; // "/proc/" + log_10(2**64) + "\0"
+ // 6 + 20 + 1 = 27
+ snprintf(oom_adj, sizeof(oom_adj), "/proc/%" PRIdMAX, (intmax_t)process);
+
+ const int dirfd = open(oom_adj, O_RDONLY | O_DIRECTORY);
+ if (dirfd < 0)
+ return false;
+
+ struct stat statbuf;
+ if (fstat(dirfd, &statbuf) < 0) {
+ close(dirfd);
+ return false;
+ }
+ if (getuid() != statbuf.st_uid) {
+ close(dirfd);
+ return false;
+ }
+
+ int fd = openat(dirfd, "oom_score_adj", O_WRONLY);
+ if (fd < 0) {
+ // We failed to open oom_score_adj, so let's try for the older
+ // oom_adj file instead.
+ fd = openat(dirfd, "oom_adj", O_WRONLY);
+ if (fd < 0) {
+ // Nope, that doesn't work either.
+ return false;
+ } else {
+ // If we're using the old oom_adj file, the allowed range is now
+ // [0, kMaxOldOomScore], so we scale the score. This may result in some
+ // aliasing of values, of course.
+ score = score * kMaxOldOomScore / kMaxOomScore;
+ }
+ }
+ close(dirfd);
+
+ char buf[11]; // 0 <= |score| <= kMaxOomScore; using log_10(2**32) + 1 size
+ snprintf(buf, sizeof(buf), "%d", score);
+ size_t len = strlen(buf);
+
+ ssize_t bytes_written = write(fd, buf, len);
+ close(fd);
+ return (bytes_written == len);
+}
+
+bool AdjustLowMemoryMargin(int64_t margin_mb) {
+ int file_descriptor = open(kLowMemMarginFile, O_WRONLY);
+ if (file_descriptor < 0)
+ return false;
+
+ // Only allow those values which are reasonable, to prevent mischief.
+ char value[21];
+ switch (margin_mb) {
+ case -1L:
+ snprintf(value, sizeof(value), "off");
+ break;
+ case 0L:
+ case 25L:
+ case 50L:
+ case 100L:
+ case 200L:
+ snprintf(value, sizeof(value), "%lld", (long long int)margin_mb);
+ break;
+ default:
+ return false;
+ }
+
+ bool success = (write(file_descriptor, value, strlen(value)) >= 0);
+ close(file_descriptor);
+ return success;
+}
diff --git a/sandbox/linux/suid/sandbox.c b/sandbox/linux/suid/sandbox.c
new file mode 100644
index 0000000..32435a7
--- /dev/null
+++ b/sandbox/linux/suid/sandbox.c
@@ -0,0 +1,482 @@
+// Copyright (c) 2012 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
+
+#include "common/sandbox.h"
+
+#define _GNU_SOURCE
+#include <asm/unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.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 <sys/vfs.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "linux_util.h"
+#include "process_util.h"
+#include "common/suid_unsafe_environment_variables.h"
+
+#if !defined(CLONE_NEWPID)
+#define CLONE_NEWPID 0x20000000
+#endif
+#if !defined(CLONE_NEWNET)
+#define CLONE_NEWNET 0x40000000
+#endif
+
+static bool DropRoot();
+
+#define HANDLE_EINTR(x) TEMP_FAILURE_RETRY(x)
+
+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);
+ va_end(ap);
+ _exit(1);
+}
+
+// We will chroot() to the helper's /proc/self directory. Anything there will
+// not exist anymore if we make sure to wait() for the helper.
+//
+// /proc/self/fdinfo or /proc/self/fd are especially safe and will be empty
+// even if the helper survives as a zombie.
+//
+// There is very little reason to use fdinfo/ instead of fd/ but we are
+// paranoid. fdinfo/ only exists since 2.6.22 so we allow fallback to fd/
+#define SAFE_DIR "/proc/self/fdinfo"
+#define SAFE_DIR2 "/proc/self/fd"
+
+static bool SpawnChrootHelper() {
+ int sv[2];
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
+ perror("socketpair");
+ return false;
+ }
+
+ char *safedir = NULL;
+ struct stat sdir_stat;
+ if (!stat(SAFE_DIR, &sdir_stat) && S_ISDIR(sdir_stat.st_mode))
+ safedir = SAFE_DIR;
+ else
+ if (!stat(SAFE_DIR2, &sdir_stat) && S_ISDIR(sdir_stat.st_mode))
+ safedir = SAFE_DIR2;
+ else {
+ fprintf(stderr, "Could not find %s\n", SAFE_DIR2);
+ return false;
+ }
+
+ 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 false;
+ }
+
+ 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(agl): 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");
+
+ // sanity check
+ if (chdir(safedir))
+ FatalError("Cannot chdir into /proc/ directory");
+
+ if (chroot(safedir))
+ FatalError("Cannot chroot into /proc/ 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);
+ // We now become a zombie. /proc/self/fd(info) is now an empty dir and we
+ // are chrooted there.
+ // Our (unprivileged) parent should not even be able to open "." or "/"
+ // since they would need to pass the ptrace() check. If our parent wait()
+ // for us, our root directory will completely disappear.
+ }
+
+ if (close(sv[0])) {
+ close(sv[1]);
+ perror("close");
+ 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), "%u", sv[1]);
+ if (printed < 0 || printed >= (int)sizeof(desc_str)) {
+ fprintf(stderr, "Failed to snprintf\n");
+ return false;
+ }
+
+ if (setenv(kSandboxDescriptorEnvironmentVarName, desc_str, 1)) {
+ perror("setenv");
+ close(sv[1]);
+ return false;
+ }
+
+ // We also install an environment variable containing the pid of the child
+ char helper_pid_str[64];
+ printed = snprintf(helper_pid_str, sizeof(helper_pid_str), "%u", pid);
+ if (printed < 0 || printed >= (int)sizeof(helper_pid_str)) {
+ fprintf(stderr, "Failed to snprintf\n");
+ return false;
+ }
+
+ if (setenv(kSandboxHelperPidEnvironmentVarName, helper_pid_str, 1)) {
+ perror("setenv");
+ close(sv[1]);
+ return false;
+ }
+
+ return true;
+}
+
+// Block until child_pid exits, then exit. Try to preserve the exit code.
+static void WaitForChildAndExit(pid_t child_pid) {
+ int exit_code = -1;
+ siginfo_t reaped_child_info;
+
+ int wait_ret =
+ HANDLE_EINTR(waitid(P_PID, child_pid, &reaped_child_info, WEXITED));
+
+ if (!wait_ret && reaped_child_info.si_pid == child_pid) {
+ if (reaped_child_info.si_code == CLD_EXITED) {
+ exit_code = reaped_child_info.si_status;
+ } else {
+ // Exit with code 0 if the child got signaled.
+ exit_code = 0;
+ }
+ }
+ _exit(exit_code);
+}
+
+static bool MoveToNewNamespaces() {
+ // These are the sets of flags which we'll try, in order.
+ const int kCloneExtraFlags[] = {
+ CLONE_NEWPID | CLONE_NEWNET,
+ CLONE_NEWPID,
+ };
+
+ // We need to close kZygoteIdFd before the child can continue. We use this
+ // socketpair to tell the child when to continue;
+ int sync_fds[2];
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sync_fds)) {
+ FatalError("Failed to create a socketpair");
+ }
+
+ for (size_t i = 0;
+ i < sizeof(kCloneExtraFlags) / sizeof(kCloneExtraFlags[0]);
+ i++) {
+ pid_t pid = syscall(__NR_clone, SIGCHLD | kCloneExtraFlags[i], 0, 0, 0);
+
+ if (pid > 0) {
+ if (!DropRoot()) {
+ FatalError("Could not drop privileges");
+ } else {
+ if (close(sync_fds[0]) || shutdown(sync_fds[1], SHUT_RD))
+ FatalError("Could not close socketpair");
+ // The kZygoteIdFd needs to be closed in the parent before
+ // Zygote gets started.
+ if (close(kZygoteIdFd))
+ FatalError("close");
+ // Tell our child to continue
+ if (HANDLE_EINTR(send(sync_fds[1], "C", 1, MSG_NOSIGNAL)) != 1)
+ FatalError("send");
+ if (close(sync_fds[1]))
+ FatalError("close");
+ // We want to keep a full process tree and we don't want our childs to
+ // be reparented to (the outer PID namespace) init. So we wait for it.
+ WaitForChildAndExit(pid);
+ }
+ // NOTREACHED
+ FatalError("Not reached");
+ }
+
+ if (pid == 0) {
+ if (close(sync_fds[1]) || shutdown(sync_fds[0], SHUT_WR))
+ FatalError("Could not close socketpair");
+
+ // Wait for the parent to confirm it closed kZygoteIdFd before we
+ // continue
+ char should_continue;
+ if (HANDLE_EINTR(read(sync_fds[0], &should_continue, 1)) != 1)
+ FatalError("Read on socketpair");
+ if (close(sync_fds[0]))
+ FatalError("close");
+
+ if (kCloneExtraFlags[i] & CLONE_NEWPID) {
+ setenv(kSandboxPIDNSEnvironmentVarName, "", 1 /* overwrite */);
+ } else {
+ unsetenv(kSandboxPIDNSEnvironmentVarName);
+ }
+
+ if (kCloneExtraFlags[i] & CLONE_NEWNET) {
+ setenv(kSandboxNETNSEnvironmentVarName, "", 1 /* overwrite */);
+ } else {
+ unsetenv(kSandboxNETNSEnvironmentVarName);
+ }
+
+ break;
+ }
+
+ if (errno != EINVAL) {
+ perror("Failed to move to new PID namespace");
+ return false;
+ }
+ }
+
+ // If the system doesn't support NEWPID then we carry on anyway.
+ 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;
+}
+
+bool CheckAndExportApiVersion() {
+ // Check the environment to see if a specific API version was requested.
+ // assume version 0 if none.
+ long api_number = -1;
+ char *api_string = getenv(kSandboxEnvironmentApiRequest);
+ if (!api_string) {
+ api_number = 0;
+ } else {
+ errno = 0;
+ char* endptr = NULL;
+ api_number = strtol(api_string, &endptr, 10);
+ if (!endptr || *endptr || errno != 0)
+ return false;
+ }
+
+ // Warn only for now.
+ if (api_number != kSUIDSandboxApiNumber) {
+ fprintf(stderr, "The setuid sandbox provides API version %ld, "
+ "but you need %ld\n"
+ "Please read "
+ "https://code.google.com/p/chromium/wiki/LinuxSUIDSandboxDevelopment."
+ "\n\n",
+ kSUIDSandboxApiNumber,
+ api_number);
+ }
+
+ // Export our version so that the sandboxed process can verify it did not
+ // use an old sandbox.
+ char version_string[64];
+ snprintf(version_string, sizeof(version_string), "%ld",
+ kSUIDSandboxApiNumber);
+ if (setenv(kSandboxEnvironmentApiProvides, version_string, 1)) {
+ perror("setenv");
+ return false;
+ }
+
+ return true;
+}
+
+int main(int argc, char **argv) {
+ if (argc <= 1) {
+ if (argc <= 0) {
+ return 1;
+ }
+
+ fprintf(stderr, "Usage: %s <renderer process> <args...>\n", argv[0]);
+ return 1;
+ }
+
+ // Allow someone to query our API version
+ if (argc == 2 && 0 == strcmp(argv[1], kSuidSandboxGetApiSwitch)) {
+ printf("%ld\n", kSUIDSandboxApiNumber);
+ return 0;
+ }
+
+ // In the SUID sandbox, if we succeed in calling MoveToNewNamespaces()
+ // below, then the zygote and all the renderers are in an alternate PID
+ // namespace and do not know their real PIDs. As such, they report the wrong
+ // PIDs to the task manager.
+ //
+ // To fix this, when the zygote spawns a new renderer, it gives the renderer
+ // a dummy socket, which has a unique inode number. Then it asks the sandbox
+ // host to find the PID of the process holding that fd by searching /proc.
+ //
+ // Since the zygote and renderers are all spawned by this setuid executable,
+ // their entries in /proc are owned by root and only readable by root. In
+ // order to search /proc for the fd we want, this setuid executable has to
+ // double as a helper and perform the search. The code block below does this
+ // when you call it with --find-inode INODE_NUMBER.
+ if (argc == 3 && (0 == strcmp(argv[1], kFindInodeSwitch))) {
+ pid_t pid;
+ char* endptr = NULL;
+ errno = 0;
+ ino_t inode = strtoull(argv[2], &endptr, 10);
+ if (inode == ULLONG_MAX || !endptr || *endptr || errno != 0)
+ return 1;
+ if (!FindProcessHoldingSocket(&pid, inode))
+ return 1;
+ printf("%d\n", pid);
+ return 0;
+ }
+ // Likewise, we cannot adjust /proc/pid/oom_adj for sandboxed renderers
+ // because those files are owned by root. So we need another helper here.
+ if (argc == 4 && (0 == strcmp(argv[1], kAdjustOOMScoreSwitch))) {
+ char* endptr = NULL;
+ long score;
+ errno = 0;
+ unsigned long pid_ul = strtoul(argv[2], &endptr, 10);
+ if (pid_ul == ULONG_MAX || !endptr || *endptr || errno != 0)
+ return 1;
+ pid_t pid = pid_ul;
+ endptr = NULL;
+ errno = 0;
+ score = strtol(argv[3], &endptr, 10);
+ if (score == LONG_MAX || score == LONG_MIN ||
+ !endptr || *endptr || errno != 0)
+ return 1;
+ return AdjustOOMScore(pid, score);
+ }
+#if defined(OS_CHROMEOS)
+ if (argc == 3 && (0 == strcmp(argv[1], kAdjustLowMemMarginSwitch))) {
+ char* endptr = NULL;
+ errno = 0;
+ unsigned long margin_mb = strtoul(argv[2], &endptr, 10);
+ if (!endptr || *endptr || errno != 0)
+ return 1;
+ return AdjustLowMemoryMargin(margin_mb);
+ }
+#endif
+
+ // Protect the core setuid sandbox functionality with an API version
+ if (!CheckAndExportApiVersion()) {
+ return 1;
+ }
+
+ if (!MoveToNewNamespaces())
+ return 1;
+ if (!SpawnChrootHelper())
+ return 1;
+ if (!DropRoot())
+ return 1;
+ if (!SetupChildEnvironment())
+ return 1;
+
+ execv(argv[1], &argv[1]);
+ FatalError("execv failed");
+
+ return 1;
+}