blob: 9fd81bcfae958dd44beee4ba363bbb604060917e [file] [log] [blame]
// Copyright 2014 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 "components/nacl/loader/sandbox_linux/nacl_sandbox_linux.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <limits>
#include "base/basictypes.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/posix/eintr_wrapper.h"
#include "build/build_config.h"
#include "components/nacl/common/nacl_switches.h"
#include "components/nacl/loader/nonsfi/nonsfi_sandbox.h"
#include "components/nacl/loader/sandbox_linux/nacl_bpf_sandbox_linux.h"
#include "content/public/common/content_switches.h"
#include "sandbox/linux/seccomp-bpf/sandbox_bpf.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/resource_limits.h"
#include "sandbox/linux/services/thread_helpers.h"
#include "sandbox/linux/suid/client/setuid_sandbox_client.h"
namespace nacl {
namespace {
// This is a poor man's check on whether we are sandboxed.
bool IsSandboxed() {
int proc_fd = open("/proc/self/exe", O_RDONLY);
if (proc_fd >= 0) {
PCHECK(0 == IGNORE_EINTR(close(proc_fd)));
return false;
}
return true;
}
bool MaybeSetProcessNonDumpable() {
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
if (command_line.HasSwitch(switches::kAllowSandboxDebugging)) {
return true;
}
if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) != 0) {
PLOG(ERROR) << "Failed to set non-dumpable flag";
return false;
}
return prctl(PR_GET_DUMPABLE) == 0;
}
void RestrictAddressSpaceUsage() {
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
defined(THREAD_SANITIZER)
// Sanitizers need to reserve huge chunks of the address space.
return;
#endif
// Add a limit to the brk() heap that would prevent allocations that can't be
// indexed by an int. This helps working around typical security bugs.
// This could almost certainly be set to zero. GLibc's allocator and others
// would fall-back to mmap if brk() fails.
const rlim_t kNewDataSegmentMaxSize = std::numeric_limits<int>::max();
CHECK(sandbox::ResourceLimits::Lower(RLIMIT_DATA, kNewDataSegmentMaxSize));
#if defined(ARCH_CPU_64_BITS)
// NaCl's x86-64 sandbox allocated 88GB address of space during startup:
// - The main sandbox is 4GB
// - There are two guard regions of 40GB each.
// - 4GB are allocated extra to have a 4GB-aligned address.
// See https://crbug.com/455839
//
// Set the limit to 128 GB and have some margin.
const rlim_t kNewAddressSpaceLimit = 1UL << 37;
#else
// Some architectures such as X86 allow 32 bits processes to switch to 64
// bits when running under 64 bits kernels. Set a limit in case this happens.
const rlim_t kNewAddressSpaceLimit = std::numeric_limits<uint32_t>::max();
#endif
CHECK(sandbox::ResourceLimits::Lower(RLIMIT_AS, kNewAddressSpaceLimit));
}
} // namespace
NaClSandbox::NaClSandbox()
: layer_one_enabled_(false),
layer_one_sealed_(false),
layer_two_enabled_(false),
layer_two_is_nonsfi_(false),
proc_fd_(-1),
setuid_sandbox_client_(sandbox::SetuidSandboxClient::Create()) {
proc_fd_.reset(
HANDLE_EINTR(open("/proc", O_DIRECTORY | O_RDONLY | O_CLOEXEC)));
PCHECK(proc_fd_.is_valid());
}
NaClSandbox::~NaClSandbox() {
}
bool NaClSandbox::IsSingleThreaded() {
CHECK(proc_fd_.is_valid());
return sandbox::ThreadHelpers::IsSingleThreaded(proc_fd_.get());
}
bool NaClSandbox::HasOpenDirectory() {
CHECK(proc_fd_.is_valid());
return sandbox::ProcUtil::HasOpenDirectory(proc_fd_.get());
}
void NaClSandbox::InitializeLayerOneSandbox() {
// Check that IsSandboxed() works. We should not be sandboxed at this point.
CHECK(!IsSandboxed()) << "Unexpectedly sandboxed!";
if (setuid_sandbox_client_->IsSuidSandboxChild()) {
setuid_sandbox_client_->CloseDummyFile();
// Make sure that no directory file descriptor is open, as it would bypass
// the setuid sandbox model.
CHECK(!HasOpenDirectory());
// Get sandboxed.
CHECK(setuid_sandbox_client_->ChrootMe());
CHECK(MaybeSetProcessNonDumpable());
CHECK(IsSandboxed());
layer_one_enabled_ = true;
} else if (sandbox::NamespaceSandbox::InNewUserNamespace()) {
CHECK(sandbox::Credentials::MoveToNewUserNS());
// This relies on SealLayerOneSandbox() to be called later since this
// class is keeping a file descriptor to /proc/.
CHECK(sandbox::Credentials::DropFileSystemAccess(proc_fd_.get()));
CHECK(sandbox::Credentials::DropAllCapabilities(proc_fd_.get()));
CHECK(IsSandboxed());
layer_one_enabled_ = true;
}
}
void NaClSandbox::CheckForExpectedNumberOfOpenFds() {
// We expect to have the following FDs open:
// 1-3) stdin, stdout, stderr.
// 4) The /dev/urandom FD used by base::GetUrandomFD().
// 5) A dummy pipe FD used to overwrite kSandboxIPCChannel.
// 6) The socket for the Chrome IPC channel that's connected to the
// browser process, kPrimaryIPCChannel.
// We also have an fd for /proc (proc_fd_), but CountOpenFds excludes this.
//
// This sanity check ensures that dynamically loaded libraries don't
// leave any FDs open before we enable the sandbox.
int expected_num_fds = 6;
if (setuid_sandbox_client_->IsSuidSandboxChild()) {
// When using the setuid sandbox, there is one additional socket used for
// ChrootMe(). After ChrootMe(), it is no longer connected to anything.
++expected_num_fds;
}
CHECK_EQ(expected_num_fds, sandbox::ProcUtil::CountOpenFds(proc_fd_.get()));
}
void NaClSandbox::InitializeLayerTwoSandbox(bool uses_nonsfi_mode) {
// seccomp-bpf only applies to the current thread, so it's critical to only
// have a single thread running here.
DCHECK(!layer_one_sealed_);
CHECK(IsSingleThreaded());
CheckForExpectedNumberOfOpenFds();
RestrictAddressSpaceUsage();
// Pass proc_fd_ ownership to the BPF sandbox, which guarantees it will
// be closed. There is no point in keeping it around since the BPF policy
// will prevent its usage.
if (uses_nonsfi_mode) {
layer_two_enabled_ = nacl::nonsfi::InitializeBPFSandbox(proc_fd_.Pass());
layer_two_is_nonsfi_ = true;
} else {
layer_two_enabled_ = nacl::InitializeBPFSandbox(proc_fd_.Pass());
}
}
void NaClSandbox::SealLayerOneSandbox() {
if (proc_fd_.is_valid() && !layer_two_enabled_) {
// If nothing prevents us, check that there is no superfluous directory
// open.
CHECK(!HasOpenDirectory());
}
proc_fd_.reset();
layer_one_sealed_ = true;
}
void NaClSandbox::CheckSandboxingStateWithPolicy() {
static const char kItIsDangerousMsg[] = " this is dangerous.";
static const char kItIsNotAllowedMsg[] =
" this is not allowed in this configuration.";
const bool no_sandbox_for_nonsfi_ok =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kNaClDangerousNoSandboxNonSfi);
const bool can_be_no_sandbox =
!layer_two_is_nonsfi_ || no_sandbox_for_nonsfi_ok;
if (!layer_one_enabled_ || !layer_one_sealed_) {
static const char kNoSuidMsg[] =
"The SUID sandbox is not engaged for NaCl:";
if (can_be_no_sandbox)
LOG(ERROR) << kNoSuidMsg << kItIsDangerousMsg;
else
LOG(FATAL) << kNoSuidMsg << kItIsNotAllowedMsg;
}
if (!layer_two_enabled_) {
static const char kNoBpfMsg[] =
"The seccomp-bpf sandbox is not engaged for NaCl:";
if (can_be_no_sandbox)
LOG(ERROR) << kNoBpfMsg << kItIsDangerousMsg;
else
LOG(FATAL) << kNoBpfMsg << kItIsNotAllowedMsg;
}
}
} // namespace nacl