1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "components/nacl/loader/sandbox_linux/nacl_sandbox_linux.h" 6 7#include <errno.h> 8#include <fcntl.h> 9#include <sys/stat.h> 10#include <sys/types.h> 11#include <unistd.h> 12 13#include "base/basictypes.h" 14#include "base/callback.h" 15#include "base/command_line.h" 16#include "base/compiler_specific.h" 17#include "base/logging.h" 18#include "base/memory/scoped_ptr.h" 19#include "base/posix/eintr_wrapper.h" 20#include "build/build_config.h" 21#include "components/nacl/common/nacl_switches.h" 22#include "components/nacl/loader/nonsfi/nonsfi_sandbox.h" 23#include "components/nacl/loader/sandbox_linux/nacl_bpf_sandbox_linux.h" 24#include "sandbox/linux/services/credentials.h" 25#include "sandbox/linux/services/thread_helpers.h" 26#include "sandbox/linux/suid/client/setuid_sandbox_client.h" 27 28namespace nacl { 29 30namespace { 31 32// This is a poor man's check on whether we are sandboxed. 33bool IsSandboxed() { 34 int proc_fd = open("/proc/self/exe", O_RDONLY); 35 if (proc_fd >= 0) { 36 PCHECK(0 == IGNORE_EINTR(close(proc_fd))); 37 return false; 38 } 39 return true; 40} 41 42} // namespace 43 44NaClSandbox::NaClSandbox() 45 : layer_one_enabled_(false), 46 layer_one_sealed_(false), 47 layer_two_enabled_(false), 48 layer_two_is_nonsfi_(false), 49 proc_fd_(-1), 50 setuid_sandbox_client_(sandbox::SetuidSandboxClient::Create()) { 51 proc_fd_.reset( 52 HANDLE_EINTR(open("/proc", O_DIRECTORY | O_RDONLY | O_CLOEXEC))); 53 PCHECK(proc_fd_.is_valid()); 54} 55 56NaClSandbox::~NaClSandbox() { 57} 58 59bool NaClSandbox::IsSingleThreaded() { 60 CHECK(proc_fd_.is_valid()); 61 base::ScopedFD proc_self_task(HANDLE_EINTR(openat( 62 proc_fd_.get(), "self/task/", O_RDONLY | O_DIRECTORY | O_CLOEXEC))); 63 PCHECK(proc_self_task.is_valid()); 64 return sandbox::ThreadHelpers::IsSingleThreaded(proc_self_task.get()); 65} 66 67bool NaClSandbox::HasOpenDirectory() { 68 CHECK(proc_fd_.is_valid()); 69 sandbox::Credentials credentials; 70 return credentials.HasOpenDirectory(proc_fd_.get()); 71} 72 73void NaClSandbox::InitializeLayerOneSandbox() { 74 // Check that IsSandboxed() works. We should not be sandboxed at this point. 75 CHECK(!IsSandboxed()) << "Unexpectedly sandboxed!"; 76 77 if (setuid_sandbox_client_->IsSuidSandboxChild()) { 78 setuid_sandbox_client_->CloseDummyFile(); 79 80 // Make sure that no directory file descriptor is open, as it would bypass 81 // the setuid sandbox model. 82 CHECK(!HasOpenDirectory()); 83 84 // Get sandboxed. 85 CHECK(setuid_sandbox_client_->ChrootMe()); 86 CHECK(IsSandboxed()); 87 layer_one_enabled_ = true; 88 } 89} 90 91void NaClSandbox::CheckForExpectedNumberOfOpenFds() { 92 if (setuid_sandbox_client_->IsSuidSandboxChild()) { 93 // We expect to have the following FDs open: 94 // 1-3) stdin, stdout, stderr. 95 // 4) The /dev/urandom FD used by base::GetUrandomFD(). 96 // 5) A dummy pipe FD used to overwrite kSandboxIPCChannel. 97 // 6) The socket created by the SUID sandbox helper, used by ChrootMe(). 98 // After ChrootMe(), this is no longer connected to anything. 99 // (Only present when running under the SUID sandbox.) 100 // 7) The socket for the Chrome IPC channel that's connected to the 101 // browser process, kPrimaryIPCChannel. 102 // 103 // This sanity check ensures that dynamically loaded libraries don't 104 // leave any FDs open before we enable the sandbox. 105 sandbox::Credentials credentials; 106 CHECK_EQ(7, credentials.CountOpenFds(proc_fd_.get())); 107 } 108} 109 110void NaClSandbox::InitializeLayerTwoSandbox(bool uses_nonsfi_mode) { 111 // seccomp-bpf only applies to the current thread, so it's critical to only 112 // have a single thread running here. 113 DCHECK(!layer_one_sealed_); 114 CHECK(IsSingleThreaded()); 115 CheckForExpectedNumberOfOpenFds(); 116 117 if (uses_nonsfi_mode) { 118 layer_two_enabled_ = nacl::nonsfi::InitializeBPFSandbox(); 119 layer_two_is_nonsfi_ = true; 120 } else { 121 layer_two_enabled_ = nacl::InitializeBPFSandbox(); 122 } 123} 124 125void NaClSandbox::SealLayerOneSandbox() { 126 if (!layer_two_enabled_) { 127 // If nothing prevents us, check that there is no superfluous directory 128 // open. 129 CHECK(!HasOpenDirectory()); 130 } 131 proc_fd_.reset(); 132 layer_one_sealed_ = true; 133} 134 135void NaClSandbox::CheckSandboxingStateWithPolicy() { 136 static const char kItIsDangerousMsg[] = " this is dangerous."; 137 static const char kItIsNotAllowedMsg[] = 138 " this is not allowed in this configuration."; 139 140 const bool no_sandbox_for_nonsfi_ok = 141 CommandLine::ForCurrentProcess()->HasSwitch( 142 switches::kNaClDangerousNoSandboxNonSfi); 143 const bool can_be_no_sandbox = 144 !layer_two_is_nonsfi_ || no_sandbox_for_nonsfi_ok; 145 146 if (!layer_one_enabled_ || !layer_one_sealed_) { 147 static const char kNoSuidMsg[] = 148 "The SUID sandbox is not engaged for NaCl:"; 149 if (can_be_no_sandbox) 150 LOG(ERROR) << kNoSuidMsg << kItIsDangerousMsg; 151 else 152 LOG(FATAL) << kNoSuidMsg << kItIsNotAllowedMsg; 153 } 154 155 if (!layer_two_enabled_) { 156 static const char kNoBpfMsg[] = 157 "The seccomp-bpf sandbox is not engaged for NaCl:"; 158 if (can_be_no_sandbox) 159 LOG(ERROR) << kNoBpfMsg << kItIsDangerousMsg; 160 else 161 LOG(FATAL) << kNoBpfMsg << kItIsNotAllowedMsg; 162 } 163} 164 165} // namespace nacl 166