xref: /aosp_15_r20/external/cronet/components/nacl/loader/sandbox_linux/nacl_sandbox_linux.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2014 The Chromium Authors
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 <stdint.h>
10 #include <sys/prctl.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13 #include <unistd.h>
14 
15 #include <limits>
16 #include <memory>
17 #include <utility>
18 
19 #include "base/command_line.h"
20 #include "base/compiler_specific.h"
21 #include "base/files/scoped_file.h"
22 #include "base/functional/callback.h"
23 #include "base/logging.h"
24 #include "base/posix/eintr_wrapper.h"
25 #include "base/rand_util.h"
26 #include "build/build_config.h"
27 #include "components/nacl/common/nacl_switches.h"
28 #include "components/nacl/loader/sandbox_linux/nacl_bpf_sandbox_linux.h"
29 #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
30 #include "sandbox/linux/services/credentials.h"
31 #include "sandbox/linux/services/namespace_sandbox.h"
32 #include "sandbox/linux/services/proc_util.h"
33 #include "sandbox/linux/services/resource_limits.h"
34 #include "sandbox/linux/services/thread_helpers.h"
35 #include "sandbox/linux/suid/client/setuid_sandbox_client.h"
36 #include "sandbox/policy/switches.h"
37 
38 namespace nacl {
39 
40 namespace {
41 
42 // This is a simplistic check of whether we are sandboxed.
IsSandboxed()43 bool IsSandboxed() {
44   int proc_fd = open("/proc/self/exe", O_RDONLY);
45   if (proc_fd >= 0) {
46     PCHECK(0 == IGNORE_EINTR(close(proc_fd)));
47     return false;
48   }
49   return true;
50 }
51 
MaybeSetProcessNonDumpable()52 bool MaybeSetProcessNonDumpable() {
53   const base::CommandLine& command_line =
54       *base::CommandLine::ForCurrentProcess();
55   if (command_line.HasSwitch(
56           sandbox::policy::switches::kAllowSandboxDebugging)) {
57     return true;
58   }
59 
60   if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) != 0) {
61     PLOG(ERROR) << "Failed to set non-dumpable flag";
62     return false;
63   }
64 
65   return prctl(PR_GET_DUMPABLE) == 0;
66 }
67 
RestrictAddressSpaceUsage()68 void RestrictAddressSpaceUsage() {
69   // Sanitizers need to reserve huge chunks of the address space.
70 #if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && \
71     !defined(THREAD_SANITIZER)
72   // Add a limit to the brk() heap that would prevent allocations that can't be
73   // indexed by an int. This helps working around typical security bugs.
74   // This could almost certainly be set to zero. GLibc's allocator and others
75   // would fall-back to mmap if brk() fails.
76   const rlim_t kNewDataSegmentMaxSize = std::numeric_limits<int>::max();
77   CHECK_EQ(0,
78            sandbox::ResourceLimits::Lower(RLIMIT_DATA, kNewDataSegmentMaxSize));
79 
80 #if defined(ARCH_CPU_64_BITS)
81   // NaCl's x86-64 sandbox allocated 88GB address of space during startup:
82   // - The main sandbox is 4GB
83   // - There are two guard regions of 40GB each.
84   // - 4GB are allocated extra to have a 4GB-aligned address.
85   // See https://crbug.com/455839
86   //
87   // Set the limit to 128 GB and have some margin.
88   const rlim_t kNewAddressSpaceLimit = 1UL << 37;
89 #else
90   // Some architectures such as X86 allow 32 bits processes to switch to 64
91   // bits when running under 64 bits kernels. Set a limit in case this happens.
92   const rlim_t kNewAddressSpaceLimit = std::numeric_limits<uint32_t>::max();
93 #endif
94   CHECK_EQ(0, sandbox::ResourceLimits::Lower(RLIMIT_AS, kNewAddressSpaceLimit));
95 #endif
96 }
97 
98 }  // namespace
99 
NaClSandbox()100 NaClSandbox::NaClSandbox()
101     : layer_one_enabled_(false),
102       layer_one_sealed_(false),
103       layer_two_enabled_(false),
104       proc_fd_(-1),
105       setuid_sandbox_client_(sandbox::SetuidSandboxClient::Create()) {
106   proc_fd_.reset(
107       HANDLE_EINTR(open("/proc", O_DIRECTORY | O_RDONLY | O_CLOEXEC)));
108   PCHECK(proc_fd_.is_valid());
109 }
110 
~NaClSandbox()111 NaClSandbox::~NaClSandbox() {
112 }
113 
IsSingleThreaded()114 bool NaClSandbox::IsSingleThreaded() {
115   CHECK(proc_fd_.is_valid());
116   return sandbox::ThreadHelpers::IsSingleThreaded(proc_fd_.get());
117 }
118 
HasOpenDirectory()119 bool NaClSandbox::HasOpenDirectory() {
120   CHECK(proc_fd_.is_valid());
121   return sandbox::ProcUtil::HasOpenDirectory(proc_fd_.get());
122 }
123 
InitializeLayerOneSandbox()124 void NaClSandbox::InitializeLayerOneSandbox() {
125   // Check that IsSandboxed() works. We should not be sandboxed at this point.
126   CHECK(!IsSandboxed()) << "Unexpectedly sandboxed!";
127 
128   // Open /dev/urandom while we can. This enables `base::RandBytes` to work. We
129   // don't need to store the resulting file descriptor; it's a singleton and
130   // subsequent calls to `GetUrandomFD` will return it.
131   CHECK_GE(base::GetUrandomFD(), 0);
132 
133   if (setuid_sandbox_client_->IsSuidSandboxChild()) {
134     setuid_sandbox_client_->CloseDummyFile();
135 
136     // Make sure that no directory file descriptor is open, as it would bypass
137     // the setuid sandbox model.
138     CHECK(!HasOpenDirectory());
139 
140     // Get sandboxed.
141     CHECK(setuid_sandbox_client_->ChrootMe());
142     CHECK(MaybeSetProcessNonDumpable());
143     CHECK(IsSandboxed());
144     layer_one_enabled_ = true;
145   } else if (sandbox::NamespaceSandbox::InNewUserNamespace()) {
146     CHECK(sandbox::Credentials::MoveToNewUserNS());
147     CHECK(sandbox::Credentials::DropFileSystemAccess(proc_fd_.get()));
148 
149     // We do not drop CAP_SYS_ADMIN because we need it to place each child
150     // process in its own PID namespace later on.
151     std::vector<sandbox::Credentials::Capability> caps;
152     caps.push_back(sandbox::Credentials::Capability::SYS_ADMIN);
153     CHECK(sandbox::Credentials::SetCapabilities(proc_fd_.get(), caps));
154 
155     CHECK(IsSandboxed());
156     layer_one_enabled_ = true;
157   }
158 }
159 
CheckForExpectedNumberOfOpenFds()160 void NaClSandbox::CheckForExpectedNumberOfOpenFds() {
161   // We expect to have the following FDs open:
162   //  1-3) stdin, stdout, stderr.
163   //  4) The /dev/urandom FD used by base::GetUrandomFD().
164   //  5) A dummy pipe FD used to overwrite kSandboxIPCChannel.
165   //  6) The socket for the Chrome IPC channel that's connected to the
166   //     browser process, kPrimaryIPCChannel.
167   // We also have an fd for /proc (proc_fd_), but CountOpenFds excludes this.
168   //
169   // This sanity check ensures that dynamically loaded libraries don't
170   // leave any FDs open before we enable the sandbox.
171   int expected_num_fds = 6;
172   if (setuid_sandbox_client_->IsSuidSandboxChild()) {
173     // When using the setuid sandbox, there is one additional socket used for
174     // ChrootMe(). After ChrootMe(), it is no longer connected to anything.
175     ++expected_num_fds;
176   }
177 
178   CHECK_EQ(expected_num_fds, sandbox::ProcUtil::CountOpenFds(proc_fd_.get()));
179 }
180 
InitializeLayerTwoSandbox()181 void NaClSandbox::InitializeLayerTwoSandbox() {
182   // seccomp-bpf only applies to the current thread, so it's critical to only
183   // have a single thread running here.
184   DCHECK(!layer_one_sealed_);
185   CHECK(IsSingleThreaded());
186   CheckForExpectedNumberOfOpenFds();
187 
188   RestrictAddressSpaceUsage();
189 
190   // Pass proc_fd_ ownership to the BPF sandbox, which guarantees it will
191   // be closed. There is no point in keeping it around since the BPF policy
192   // will prevent its usage.
193   layer_two_enabled_ = nacl::InitializeBPFSandbox(std::move(proc_fd_));
194 }
195 
SealLayerOneSandbox()196 void NaClSandbox::SealLayerOneSandbox() {
197   if (proc_fd_.is_valid() && !layer_two_enabled_) {
198     // If nothing prevents us, check that there is no superfluous directory
199     // open.
200     CHECK(!HasOpenDirectory());
201   }
202   proc_fd_.reset();
203   layer_one_sealed_ = true;
204 }
205 
CheckSandboxingStateWithPolicy()206 void NaClSandbox::CheckSandboxingStateWithPolicy() {
207   LOG_IF(ERROR, !layer_one_enabled_ || !layer_one_sealed_)
208       << "The SUID sandbox is not engaged for NaCl: this is dangerous.";
209   LOG_IF(ERROR, !layer_two_enabled_)
210       << "The seccomp-bpf sandbox is not engaged for NaCl: this is dangerous.";
211 }
212 
213 }  // namespace nacl
214