Commit 8f235daf authored by rickyz's avatar rickyz Committed by Commit bot

Add namespace sandbox class.

BUG=312380

Review URL: https://codereview.chromium.org/881733002

Cr-Commit-Position: refs/heads/master@{#314284}
parent 763f8be2
......@@ -114,6 +114,7 @@ test("sandbox_linux_unittests") {
if (compile_credentials) {
sources += [
"services/credentials_unittest.cc",
"services/namespace_sandbox_unittest.cc",
"services/namespace_utils_unittest.cc",
"services/proc_util_unittest.cc",
"services/unix_domain_socket_unittest.cc",
......@@ -255,6 +256,8 @@ component("sandbox_services") {
sources += [
"services/credentials.cc",
"services/credentials.h",
"services/namespace_sandbox.cc",
"services/namespace_sandbox.h",
"services/namespace_utils.cc",
"services/namespace_utils.h",
"services/proc_util.cc",
......
......@@ -254,6 +254,8 @@
'sources': [
'services/credentials.cc',
'services/credentials.h',
'services/namespace_sandbox.cc',
'services/namespace_sandbox.h',
'services/namespace_utils.cc',
'services/namespace_utils.h',
'services/proc_util.cc',
......
......@@ -54,6 +54,7 @@
[ 'compile_credentials==1', {
'sources': [
'services/credentials_unittest.cc',
'services/namespace_sandbox_unittest.cc',
'services/namespace_utils_unittest.cc',
'services/proc_util_unittest.cc',
'services/unix_domain_socket_unittest.cc',
......
// Copyright 2015 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 "sandbox/linux/services/namespace_sandbox.h"
#include <sched.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <utility>
#include "base/command_line.h"
#include "base/environment.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "sandbox/linux/services/namespace_utils.h"
namespace sandbox {
namespace {
class WriteUidGidMapDelegate : public base::LaunchOptions::PreExecDelegate {
public:
WriteUidGidMapDelegate() : uid_(getuid()), gid_(getgid()) {}
~WriteUidGidMapDelegate() override {}
void RunAsyncSafe() override {
RAW_CHECK(NamespaceUtils::WriteToIdMapFile("/proc/self/uid_map", uid_));
RAW_CHECK(NamespaceUtils::WriteToIdMapFile("/proc/self/gid_map", gid_));
}
private:
uid_t uid_;
gid_t gid_;
DISALLOW_COPY_AND_ASSIGN(WriteUidGidMapDelegate);
};
void SetEnvironForNamespaceType(base::EnvironmentMap* environ,
base::NativeEnvironmentString env_var,
bool value) {
// An empty string causes the env var to be unset in the child process.
(*environ)[env_var] = value ? "1" : "";
}
const char kSandboxUSERNSEnvironmentVarName[] = "SBX_USER_NS";
const char kSandboxPIDNSEnvironmentVarName[] = "SBX_PID_NS";
const char kSandboxNETNSEnvironmentVarName[] = "SBX_NET_NS";
} // namespace
// static
base::Process NamespaceSandbox::LaunchProcess(
const base::CommandLine& cmdline,
const base::LaunchOptions& options) {
int clone_flags = 0;
int ns_types[] = {CLONE_NEWUSER, CLONE_NEWPID, CLONE_NEWNET};
for (const int ns_type : ns_types) {
if (NamespaceUtils::KernelSupportsUnprivilegedNamespace(ns_type)) {
clone_flags |= ns_type;
}
}
CHECK(clone_flags & CLONE_NEWUSER);
// These fields may not be set by the caller.
CHECK(options.pre_exec_delegate == nullptr);
CHECK_EQ(0, options.clone_flags);
WriteUidGidMapDelegate write_uid_gid_map_delegate;
base::LaunchOptions launch_options = options;
launch_options.pre_exec_delegate = &write_uid_gid_map_delegate;
launch_options.clone_flags = clone_flags;
const std::pair<int, const char*> clone_flag_environ[] = {
std::make_pair(CLONE_NEWUSER, kSandboxUSERNSEnvironmentVarName),
std::make_pair(CLONE_NEWPID, kSandboxPIDNSEnvironmentVarName),
std::make_pair(CLONE_NEWNET, kSandboxNETNSEnvironmentVarName),
};
base::EnvironmentMap* environ = &launch_options.environ;
for (const auto& entry : clone_flag_environ) {
const int flag = entry.first;
const char* environ_name = entry.second;
SetEnvironForNamespaceType(environ, environ_name, clone_flags & flag);
}
return base::LaunchProcess(cmdline, launch_options);
}
// static
bool NamespaceSandbox::InNewUserNamespace() {
return getenv(kSandboxUSERNSEnvironmentVarName) != nullptr;
}
// static
bool NamespaceSandbox::InNewPidNamespace() {
return getenv(kSandboxPIDNSEnvironmentVarName) != nullptr;
}
// static
bool NamespaceSandbox::InNewNetNamespace() {
return getenv(kSandboxNETNSEnvironmentVarName) != nullptr;
}
} // namespace sandbox
// Copyright 2015 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_SERVICES_NAMESPACE_SANDBOX_H_
#define SANDBOX_LINUX_SERVICES_NAMESPACE_SANDBOX_H_
#include "base/command_line.h"
#include "base/macros.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "sandbox/sandbox_export.h"
namespace sandbox {
// Helper class for starting a process inside a new user, PID, and network
// namespace. Before using a namespace sandbox, check for namespaces support
// using Credentials::CanCreateProcessInNewUserNS.
//
// A typical use for "A" launching a sandboxed process "B" would be:
// 1. A sets up a command line and launch options for process B.
// 2. A launches B with LaunchProcess.
// 3. B should be prepared to assume the role of init(1). In particular, apart
// from SIGKILL and SIGSTOP, B cannot receive any signal for which it does
// not have an explicit signal handler registered.
// If B dies, all the processes in the namespace will die.
// B can fork() and the parent can assume the role of init(1), by using
// CreateInitProcessReaper().
// 4. B chroots using Credentials::MoveToNewUserNS() and
// Credentials::DropFileSystemAccess()
// 5. B drops capabilities gained by entering the new user namespace with
// Credentials::DropAllCapabilities().
class SANDBOX_EXPORT NamespaceSandbox {
public:
// Launch a new process inside its own user/PID/network namespaces (depending
// on kernel support). Requires at a minimum that user namespaces are
// supported (use Credentials::CanCreateProcessInNewUserNS to check this).
//
// pre_exec_delegate and clone_flags fields of LaunchOptions should be nullptr
// and 0, respectively, since this function makes a copy of options and
// overrides them.
static base::Process LaunchProcess(const base::CommandLine& cmdline,
const base::LaunchOptions& options);
// Returns whether the namespace sandbox created a new user, PID, and network
// namespace. In particular, InNewUserNamespace should return true iff the
// process was started via this class.
static bool InNewUserNamespace();
static bool InNewPidNamespace();
static bool InNewNetNamespace();
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(NamespaceSandbox);
};
} // namespace sandbox
#endif // SANDBOX_LINUX_SERVICES_NAMESPACE_SANDBOX_H_
// Copyright 2015 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 "sandbox/linux/services/namespace_sandbox.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string>
#include <utility>
#include "base/command_line.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/test/multiprocess_test.h"
#include "sandbox/linux/services/credentials.h"
#include "sandbox/linux/services/namespace_utils.h"
#include "sandbox/linux/tests/unit_tests.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
namespace sandbox {
namespace {
bool RootDirectoryIsEmpty() {
base::FilePath root("/");
int file_type =
base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES;
base::FileEnumerator enumerator_before(root, false, file_type);
return enumerator_before.Next().empty();
}
class NamespaceSandboxTest : public base::MultiProcessTest {
public:
void TestProc(const std::string& procname) {
if (!Credentials::CanCreateProcessInNewUserNS()) {
return;
}
base::FileHandleMappingVector fds_to_remap = {
std::make_pair(STDOUT_FILENO, STDOUT_FILENO),
std::make_pair(STDERR_FILENO, STDERR_FILENO),
};
base::LaunchOptions launch_options;
launch_options.fds_to_remap = &fds_to_remap;
base::Process process =
NamespaceSandbox::LaunchProcess(MakeCmdLine(procname), launch_options);
ASSERT_TRUE(process.IsValid());
const int kDummyExitCode = 42;
int exit_code = kDummyExitCode;
EXPECT_TRUE(process.WaitForExit(&exit_code));
EXPECT_EQ(0, exit_code);
}
};
MULTIPROCESS_TEST_MAIN(SimpleChildProcess) {
scoped_ptr<base::Environment> env(base::Environment::Create());
bool in_user_ns = NamespaceSandbox::InNewUserNamespace();
bool in_pid_ns = NamespaceSandbox::InNewPidNamespace();
bool in_net_ns = NamespaceSandbox::InNewNetNamespace();
CHECK(in_user_ns);
CHECK_EQ(in_pid_ns,
NamespaceUtils::KernelSupportsUnprivilegedNamespace(CLONE_NEWPID));
CHECK_EQ(in_net_ns,
NamespaceUtils::KernelSupportsUnprivilegedNamespace(CLONE_NEWNET));
if (in_pid_ns) {
CHECK_EQ(1, getpid());
}
return 0;
}
TEST_F(NamespaceSandboxTest, BasicUsage) {
TestProc("SimpleChildProcess");
}
MULTIPROCESS_TEST_MAIN(ChrootMe) {
CHECK(!RootDirectoryIsEmpty());
CHECK(sandbox::Credentials::MoveToNewUserNS());
CHECK(sandbox::Credentials::DropFileSystemAccess());
CHECK(RootDirectoryIsEmpty());
return 0;
}
TEST_F(NamespaceSandboxTest, ChrootAndDropCapabilities) {
TestProc("ChrootMe");
}
MULTIPROCESS_TEST_MAIN(NestedNamespaceSandbox) {
base::FileHandleMappingVector fds_to_remap = {
std::make_pair(STDOUT_FILENO, STDOUT_FILENO),
std::make_pair(STDERR_FILENO, STDERR_FILENO),
};
base::LaunchOptions launch_options;
launch_options.fds_to_remap = &fds_to_remap;
base::Process process = NamespaceSandbox::LaunchProcess(
base::CommandLine(base::FilePath("/bin/true")), launch_options);
CHECK(process.IsValid());
const int kDummyExitCode = 42;
int exit_code = kDummyExitCode;
CHECK(process.WaitForExit(&exit_code));
CHECK_EQ(0, exit_code);
return 0;
}
TEST_F(NamespaceSandboxTest, NestedNamespaceSandbox) {
TestProc("NestedNamespaceSandbox");
}
} // namespace
} // namespace sandbox
......@@ -18,7 +18,7 @@
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/launch.h"
#include "base/strings/stringprintf.h"
#include "base/strings/safe_sprintf.h"
#include "base/third_party/valgrind/valgrind.h"
namespace sandbox {
......@@ -31,18 +31,23 @@ bool IsRunningOnValgrind() {
// static
bool NamespaceUtils::WriteToIdMapFile(const char* map_file, generic_id_t id) {
base::ScopedFD fd(HANDLE_EINTR(open(map_file, O_WRONLY)));
if (!fd.is_valid()) {
// This function needs to be async-signal-safe, as it may be called in between
// fork and exec.
int fd = HANDLE_EINTR(open(map_file, O_WRONLY));
if (fd == -1) {
return false;
}
const generic_id_t inside_id = id;
const generic_id_t outside_id = id;
const std::string mapping =
base::StringPrintf("%d %d 1\n", inside_id, outside_id);
const size_t len = mapping.size();
const ssize_t rc = HANDLE_EINTR(write(fd.get(), mapping.c_str(), len));
return rc == static_cast<ssize_t>(len);
char mapping[64];
ssize_t len =
base::strings::SafeSPrintf(mapping, "%d %d 1\n", inside_id, outside_id);
const ssize_t rc = HANDLE_EINTR(write(fd, mapping, len));
RAW_CHECK(IGNORE_EINTR(close(fd)) == 0);
return rc == len;
}
// static
......
......@@ -7,6 +7,7 @@
#include <sys/types.h>
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/template_util.h"
#include "sandbox/sandbox_export.h"
......@@ -20,8 +21,10 @@ class SANDBOX_EXPORT NamespaceUtils {
// generic_id_t can be used for either uid_t or gid_t.
typedef uid_t generic_id_t;
// Write a uid or gid mapping from |id| to |id| in |map_file|.
static bool WriteToIdMapFile(const char* map_file, generic_id_t id);
// Write a uid or gid mapping from |id| to |id| in |map_file|. This function
// is async-signal-safe.
static bool WriteToIdMapFile(const char* map_file,
generic_id_t id) WARN_UNUSED_RESULT;
// Returns true if unprivileged namespaces of type |type| is supported
// (meaning that both CLONE_NEWUSER and type are are supported). |type| must
......
......@@ -45,11 +45,11 @@ SANDBOX_TEST(NamespaceUtils, WriteToIdMapFile) {
ASSERT_NE(-1, pid);
if (pid == 0) {
RAW_CHECK(getuid() != uid);
NamespaceUtils::WriteToIdMapFile("/proc/self/uid_map", uid);
RAW_CHECK(NamespaceUtils::WriteToIdMapFile("/proc/self/uid_map", uid));
RAW_CHECK(getuid() == uid);
RAW_CHECK(getgid() != gid);
NamespaceUtils::WriteToIdMapFile("/proc/self/gid_map", gid);
RAW_CHECK(NamespaceUtils::WriteToIdMapFile("/proc/self/gid_map", gid));
RAW_CHECK(getgid() == gid);
_exit(0);
......
......@@ -3,11 +3,14 @@
// found in the LICENSE file.
#include "base/at_exit.h"
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/test/test_suite.h"
#include "sandbox/linux/tests/test_utils.h"
#include "sandbox/linux/tests/unit_tests.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
namespace sandbox {
namespace {
......@@ -30,6 +33,15 @@ void UnitTestAssertHandler(const std::string& str) {
#endif
int main(int argc, char* argv[]) {
base::CommandLine::Init(argc, argv);
std::string client_func =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kTestChildProcess);
if (!client_func.empty()) {
base::AtExitManager exit_manager;
return multi_process_function_list::InvokeChildProcessTest(client_func);
}
#if defined(OS_ANDROID)
// The use of Callbacks requires an AtExitManager.
base::AtExitManager exit_manager;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment