Skip to content

Commit

Permalink
Namespace sandbox: add important security checks
Browse files Browse the repository at this point in the history
When engaging the namespace sandbox, add important checks that the process
is single threaded and has no directory file descriptor open.

As part of this change, move the function engaging the namespace
sandbox from the Zygote to the LinuxSandbox class.

BUG=457377, 312380

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

Cr-Commit-Position: refs/heads/master@{#315932}
  • Loading branch information
jln authored and Commit bot committed Feb 12, 2015
1 parent cffa416 commit b94f681
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 74 deletions.
3 changes: 3 additions & 0 deletions components/nacl/loader/sandbox_linux/nacl_sandbox_linux.cc
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,10 @@ void NaClSandbox::InitializeLayerOneSandbox() {
layer_one_enabled_ = true;
} else if (sandbox::NamespaceSandbox::InNewUserNamespace()) {
CHECK(sandbox::Credentials::MoveToNewUserNS());
// This relies on SealLayerOneSandbox() to be called later.
CHECK(!HasOpenDirectory());
CHECK(sandbox::Credentials::DropFileSystemAccess());
CHECK(IsSingleThreaded());
CHECK(sandbox::Credentials::DropAllCapabilities());
CHECK(IsSandboxed());
layer_one_enabled_ = true;
Expand Down
79 changes: 79 additions & 0 deletions content/common/sandbox_linux/sandbox_debug_handling_linux.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// 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 "content/common/sandbox_linux/sandbox_debug_handling_linux.h"

#include <errno.h>
#include <signal.h>
#include <sys/prctl.h>
#include <unistd.h>

#include "base/command_line.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/safe_sprintf.h"
#include "content/public/common/content_switches.h"

namespace content {

namespace {

void DoChrootSignalHandler(int) {
const int old_errno = errno;
const char kFirstMessage[] = "Chroot signal handler called.\n";
ignore_result(write(STDERR_FILENO, kFirstMessage, sizeof(kFirstMessage) - 1));

const int chroot_ret = chroot("/");

char kSecondMessage[100];
const ssize_t printed = base::strings::SafeSPrintf(
kSecondMessage, "chroot() returned %d. Errno is %d.\n", chroot_ret,
errno);
if (printed > 0 && printed < static_cast<ssize_t>(sizeof(kSecondMessage))) {
ignore_result(write(STDERR_FILENO, kSecondMessage, printed));
}
errno = old_errno;
}

// This is a quick hack to allow testing sandbox crash reports in production
// binaries.
// This installs a signal handler for SIGUSR2 that performs a chroot().
// In most of our BPF policies, it is a "watched" system call which will
// trigger a SIGSYS signal whose handler will crash.
// This has been added during the investigation of https://crbug.com/415842.
void InstallCrashTestHandler() {
struct sigaction act = {};
act.sa_handler = DoChrootSignalHandler;
CHECK_EQ(0, sigemptyset(&act.sa_mask));
act.sa_flags = 0;

PCHECK(0 == sigaction(SIGUSR2, &act, NULL));
}

bool IsSandboxDebuggingEnabled() {
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
return command_line.HasSwitch(switches::kAllowSandboxDebugging);
}

} // namespace

// static
bool SandboxDebugHandling::SetDumpableStatusAndHandlers() {
if (IsSandboxDebuggingEnabled()) {
// If sandbox debugging is allowed, install a handler for sandbox-related
// crash testing.
InstallCrashTestHandler();
return true;
}

if (prctl(PR_SET_DUMPABLE, 0) != 0) {
PLOG(ERROR) << "Failed to set non-dumpable flag";
return false;
}

return prctl(PR_GET_DUMPABLE) == 0;
}

} // namespace content
25 changes: 25 additions & 0 deletions content/common/sandbox_linux/sandbox_debug_handling_linux.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// 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 CONTENT_COMMON_SANDBOX_LINUX_SANDBOX_DEBUG_HANDLING_LINUX_H_
#define CONTENT_COMMON_SANDBOX_LINUX_SANDBOX_DEBUG_HANDLING_LINUX_H_

#include "base/macros.h"

namespace content {

class SandboxDebugHandling {
public:
// Depending on the command line, set the current process as
// non dumpable. Also set any signal handlers for sandbox
// debugging.
static bool SetDumpableStatusAndHandlers();

private:
DISALLOW_IMPLICIT_CONSTRUCTORS(SandboxDebugHandling);
};

} // namespace content

#endif // CONTENT_COMMON_SANDBOX_LINUX_SANDBOX_DEBUG_HANDLING_LINUX_H_
23 changes: 23 additions & 0 deletions content/common/sandbox_linux/sandbox_linux.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@
#include "base/sys_info.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/common/sandbox_linux/sandbox_debug_handling_linux.h"
#include "content/common/sandbox_linux/sandbox_linux.h"
#include "content/common/sandbox_linux/sandbox_seccomp_bpf_linux.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/sandbox_linux.h"
#include "sandbox/linux/services/credentials.h"
#include "sandbox/linux/services/namespace_sandbox.h"
#include "sandbox/linux/services/proc_util.h"
#include "sandbox/linux/services/thread_helpers.h"
Expand Down Expand Up @@ -182,6 +184,27 @@ void LinuxSandbox::PreinitializeSandbox() {
pre_initialized_ = true;
}

void LinuxSandbox::EngageNamespaceSandbox() {
CHECK(pre_initialized_);
// Check being in a new PID namespace created by the namespace sandbox and
// being the init process.
CHECK(sandbox::NamespaceSandbox::InNewPidNamespace());
const pid_t pid = getpid();
CHECK_EQ(1, pid);

CHECK(sandbox::Credentials::MoveToNewUserNS());
// Note: this requires SealSandbox() to be called later in this process to be
// safe, as this class is keeping a file descriptor to /proc.
CHECK(!HasOpenDirectories());
CHECK(sandbox::Credentials::DropFileSystemAccess());
CHECK(IsSingleThreaded());
CHECK(sandbox::Credentials::DropAllCapabilities());

// This needs to happen after moving to a new user NS, since doing so involves
// writing the UID/GID map.
CHECK(SandboxDebugHandling::SetDumpableStatusAndHandlers());
}

std::vector<int> LinuxSandbox::GetFileDescriptorsToClose() {
std::vector<int> fds;
if (proc_fd_ >= 0) {
Expand Down
25 changes: 21 additions & 4 deletions content/common/sandbox_linux/sandbox_linux.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ namespace content {

// A singleton class to represent and change our sandboxing state for the
// three main Linux sandboxes.
// The sandboxing model allows using two layers of sandboxing. The first layer
// can be implemented either with unprivileged namespaces or with the setuid
// sandbox. This class provides a way to engage the namespace sandbox, but does
// not deal with the legacy setuid sandbox directly.
// The second layer is mainly based on seccomp-bpf and is engaged with
// InitializeSandbox(). InitializeSandbox() is also responsible for "sealing"
// the first layer of sandboxing. That is, InitializeSandbox must always be
// called to have any meaningful sandboxing at all.
class LinuxSandbox {
public:
// This is a list of sandbox IPC methods which the renderer may send to the
Expand Down Expand Up @@ -58,15 +66,24 @@ class LinuxSandbox {
// a fork().
void PreinitializeSandbox();

// Check that the current process is the init process of a new PID
// namespace and then proceed to drop access to the file system by using
// a new unprivileged namespace. This is a layer-1 sandbox.
// In order for this sandbox to be effective, it must be "sealed" by calling
// InitializeSandbox().
void EngageNamespaceSandbox();

// Return a list of file descriptors to close if PreinitializeSandbox() ran
// but InitializeSandbox() won't. Avoid using.
// TODO(jln): get rid of this hack.
std::vector<int> GetFileDescriptorsToClose();

// Initialize the sandbox with the given pre-built configuration. Currently
// seccomp-bpf and address space limitations (the setuid sandbox works
// differently and is set-up in the Zygote). This will instantiate the
// LinuxSandbox singleton if it doesn't already exist.
// Seal an eventual layer-1 sandbox and initialize the layer-2 sandbox with
// an adequate policy depending on the process type and command line
// arguments.
// Currently the layer-2 sandbox is composed of seccomp-bpf and address space
// limitations. This will instantiate the LinuxSandbox singleton if it
// doesn't already exist.
// This function should only be called without any thread running.
static bool InitializeSandbox();

Expand Down
2 changes: 2 additions & 0 deletions content/content_common.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,8 @@
'common/sandbox_linux/bpf_utility_policy_linux.h',
'common/sandbox_linux/sandbox_bpf_base_policy_linux.cc',
'common/sandbox_linux/sandbox_bpf_base_policy_linux.h',
'common/sandbox_linux/sandbox_debug_handling_linux.cc',
'common/sandbox_linux/sandbox_debug_handling_linux.h',
'common/sandbox_linux/sandbox_init_linux.cc',
'common/sandbox_linux/sandbox_linux.cc',
'common/sandbox_linux/sandbox_linux.h',
Expand Down
76 changes: 7 additions & 69 deletions content/zygote/zygote_main_linux.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "build/build_config.h"
#include "content/common/child_process_sandbox_support_impl_linux.h"
#include "content/common/font_config_ipc_linux.h"
#include "content/common/sandbox_linux/sandbox_debug_handling_linux.h"
#include "content/common/sandbox_linux/sandbox_linux.h"
#include "content/common/zygote_commands_linux.h"
#include "content/public/common/content_switches.h"
Expand All @@ -39,7 +40,6 @@
#include "content/public/common/zygote_fork_delegate_linux.h"
#include "content/zygote/zygote_linux.h"
#include "crypto/nss_util.h"
#include "sandbox/linux/services/credentials.h"
#include "sandbox/linux/services/init_process_reaper.h"
#include "sandbox/linux/services/libc_urandom_override.h"
#include "sandbox/linux/services/namespace_sandbox.h"
Expand Down Expand Up @@ -72,40 +72,6 @@ namespace content {

namespace {

void DoChrootSignalHandler(int) {
const int old_errno = errno;
const char kFirstMessage[] = "Chroot signal handler called.\n";
ignore_result(write(STDERR_FILENO, kFirstMessage, sizeof(kFirstMessage) - 1));

const int chroot_ret = chroot("/");

char kSecondMessage[100];
const ssize_t printed =
base::strings::SafeSPrintf(kSecondMessage,
"chroot() returned %d. Errno is %d.\n",
chroot_ret,
errno);
if (printed > 0 && printed < static_cast<ssize_t>(sizeof(kSecondMessage))) {
ignore_result(write(STDERR_FILENO, kSecondMessage, printed));
}
errno = old_errno;
}

// This is a quick hack to allow testing sandbox crash reports in production
// binaries.
// This installs a signal handler for SIGUSR2 that performs a chroot().
// In most of our BPF policies, it is a "watched" system call which will
// trigger a SIGSYS signal whose handler will crash.
// This has been added during the investigation of https://crbug.com/415842.
void InstallSandboxCrashTestHandler() {
struct sigaction act = {};
act.sa_handler = DoChrootSignalHandler;
CHECK_EQ(0, sigemptyset(&act.sa_mask));
act.sa_flags = 0;

PCHECK(0 == sigaction(SIGUSR2, &act, NULL));
}

void CloseFds(const std::vector<int>& fds) {
for (const auto& it : fds) {
PCHECK(0 == IGNORE_EINTR(close(it)));
Expand Down Expand Up @@ -401,24 +367,6 @@ static bool CreateInitProcessReaper(base::Closure* post_fork_parent_callback) {
return true;
}

static bool MaybeSetProcessNonDumpable() {
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
if (command_line.HasSwitch(switches::kAllowSandboxDebugging)) {
// If sandbox debugging is allowed, install a handler for sandbox-related
// crash testing.
InstallSandboxCrashTestHandler();
return true;
}

if (prctl(PR_SET_DUMPABLE, 0) != 0) {
PLOG(ERROR) << "Failed to set non-dumpable flag";
return false;
}

return prctl(PR_GET_DUMPABLE) == 0;
}

// Enter the setuid sandbox. This requires the current process to have been
// created through the setuid sandbox.
static bool EnterSuidSandbox(sandbox::SetuidSandboxClient* setuid_sandbox,
Expand Down Expand Up @@ -453,25 +401,15 @@ static bool EnterSuidSandbox(sandbox::SetuidSandboxClient* setuid_sandbox,
CHECK(CreateInitProcessReaper(post_fork_parent_callback));
}

CHECK(MaybeSetProcessNonDumpable());
CHECK(SandboxDebugHandling::SetDumpableStatusAndHandlers());
return true;
}

static void EnterNamespaceSandbox(base::Closure* post_fork_parent_callback) {
pid_t pid = getpid();
if (sandbox::NamespaceSandbox::InNewPidNamespace()) {
CHECK_EQ(1, pid);
}

CHECK(sandbox::Credentials::MoveToNewUserNS());
CHECK(sandbox::Credentials::DropFileSystemAccess());
CHECK(sandbox::Credentials::DropAllCapabilities());
static void EnterNamespaceSandbox(LinuxSandbox* linux_sandbox,
base::Closure* post_fork_parent_callback) {
linux_sandbox->EngageNamespaceSandbox();

// This needs to happen after moving to a new user NS, since doing so involves
// writing the UID/GID map.
CHECK(MaybeSetProcessNonDumpable());

if (pid == 1) {
if (getpid() == 1) {
CHECK(CreateInitProcessReaper(post_fork_parent_callback));
}
}
Expand Down Expand Up @@ -550,7 +488,7 @@ static void EnterLayerOneSandbox(LinuxSandbox* linux_sandbox,
CHECK(EnterSuidSandbox(setuid_sandbox, post_fork_parent_callback))
<< "Failed to enter setuid sandbox";
} else if (sandbox::NamespaceSandbox::InNewUserNamespace()) {
EnterNamespaceSandbox(post_fork_parent_callback);
EnterNamespaceSandbox(linux_sandbox, post_fork_parent_callback);
} else {
CHECK(!using_layer1_sandbox);
}
Expand Down
4 changes: 3 additions & 1 deletion sandbox/linux/services/credentials.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ namespace sandbox {
class SANDBOX_EXPORT Credentials {
public:
// Drop all capabilities in the effective, inheritable and permitted sets for
// the current process.
// the current process. For security reasons, since capabilities are
// per-thread, the caller is responsible for ensuring it is single-threaded
// when calling this API.
static bool DropAllCapabilities() WARN_UNUSED_RESULT;
// Return true iff there is any capability in any of the capabilities sets
// of the current process.
Expand Down

0 comments on commit b94f681

Please sign in to comment.