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