xref: /third_party/node/deps/v8/src/sandbox/sandbox.cc (revision 1cb0ef41)
1// Copyright 2021 the V8 project 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 "src/sandbox/sandbox.h"
6
7#include "include/v8-internal.h"
8#include "src/base/bits.h"
9#include "src/base/bounded-page-allocator.h"
10#include "src/base/cpu.h"
11#include "src/base/emulated-virtual-address-subspace.h"
12#include "src/base/lazy-instance.h"
13#include "src/base/utils/random-number-generator.h"
14#include "src/base/virtual-address-space-page-allocator.h"
15#include "src/base/virtual-address-space.h"
16#include "src/flags/flags.h"
17#include "src/sandbox/sandboxed-pointer.h"
18#include "src/utils/allocation.h"
19
20#if defined(V8_OS_WIN)
21#include <windows.h>
22// This has to come after windows.h.
23#include <versionhelpers.h>  // For IsWindows8Point1OrGreater().
24#endif
25
26namespace v8 {
27namespace internal {
28
29#ifdef V8_SANDBOX_IS_AVAILABLE
30
31// Best-effort helper function to determine the size of the userspace virtual
32// address space. Used to determine appropriate sandbox size and placement.
33static Address DetermineAddressSpaceLimit() {
34#ifndef V8_TARGET_ARCH_64_BIT
35#error Unsupported target architecture.
36#endif
37
38  // Assume 48 bits by default, which seems to be the most common configuration.
39  constexpr unsigned kDefaultVirtualAddressBits = 48;
40  // 36 bits should realistically be the lowest value we could ever see.
41  constexpr unsigned kMinVirtualAddressBits = 36;
42  constexpr unsigned kMaxVirtualAddressBits = 64;
43
44  constexpr size_t kMinVirtualAddressSpaceSize = 1ULL << kMinVirtualAddressBits;
45  static_assert(kMinVirtualAddressSpaceSize >= kSandboxMinimumSize,
46                "The minimum sandbox size should be smaller or equal to the "
47                "smallest possible userspace address space. Otherwise, large "
48                "parts of the sandbox will not be usable on those platforms.");
49
50#ifdef V8_TARGET_ARCH_X64
51  base::CPU cpu;
52  Address virtual_address_bits = kDefaultVirtualAddressBits;
53  if (cpu.exposes_num_virtual_address_bits()) {
54    virtual_address_bits = cpu.num_virtual_address_bits();
55  }
56#else
57  // TODO(saelo) support ARM and possibly other CPUs as well.
58  Address virtual_address_bits = kDefaultVirtualAddressBits;
59#endif
60
61  // Guard against nonsensical values.
62  if (virtual_address_bits < kMinVirtualAddressBits ||
63      virtual_address_bits > kMaxVirtualAddressBits) {
64    virtual_address_bits = kDefaultVirtualAddressBits;
65  }
66
67  // Assume virtual address space is split 50/50 between userspace and kernel.
68  Address userspace_virtual_address_bits = virtual_address_bits - 1;
69  Address address_space_limit = 1ULL << userspace_virtual_address_bits;
70
71#if defined(V8_OS_WIN_X64)
72  if (!IsWindows8Point1OrGreater()) {
73    // On Windows pre 8.1 userspace is limited to 8TB on X64. See
74    // https://docs.microsoft.com/en-us/windows/win32/memory/memory-limits-for-windows-releases
75    address_space_limit = 8ULL * TB;
76  }
77#endif  // V8_OS_WIN_X64
78
79  // TODO(saelo) we could try allocating memory in the upper half of the address
80  // space to see if it is really usable.
81  return address_space_limit;
82}
83
84bool Sandbox::Initialize(v8::VirtualAddressSpace* vas) {
85  // Take the number of virtual address bits into account when determining the
86  // size of the sandbox. For example, if there are only 39 bits available,
87  // split evenly between userspace and kernel, then userspace can only address
88  // 256GB and so we use a quarter of that, 64GB, as maximum size.
89  Address address_space_limit = DetermineAddressSpaceLimit();
90  size_t max_sandbox_size = address_space_limit / 4;
91  size_t sandbox_size = std::min(kSandboxSize, max_sandbox_size);
92  size_t size_to_reserve = sandbox_size;
93
94  // If the size is less than the minimum sandbox size though, we fall back to
95  // creating a partially reserved sandbox, as that allows covering more virtual
96  // address space. This happens for CPUs with only 36 virtual address bits, in
97  // which case the sandbox size would end up being only 8GB.
98  bool partially_reserve = false;
99  if (sandbox_size < kSandboxMinimumSize) {
100    static_assert(
101        (8ULL * GB) >= kSandboxMinimumReservationSize,
102        "Minimum reservation size for a partially reserved sandbox must be at "
103        "most 8GB to support CPUs with only 36 virtual address bits");
104    size_to_reserve = sandbox_size;
105    sandbox_size = kSandboxMinimumSize;
106    partially_reserve = true;
107  }
108
109#if defined(V8_OS_WIN)
110  if (!IsWindows8Point1OrGreater()) {
111    // On Windows pre 8.1, reserving virtual memory is an expensive operation,
112    // apparently because the OS already charges for the memory required for
113    // all page table entries. For example, a 1TB reservation increases private
114    // memory usage by 2GB. As such, it is not possible to create a proper
115    // sandbox there and so a partially reserved sandbox is created which
116    // doesn't reserve most of the virtual memory, and so doesn't incur the
117    // cost, but also doesn't provide the desired security benefits.
118    size_to_reserve = kSandboxMinimumReservationSize;
119    partially_reserve = true;
120  }
121#endif  // V8_OS_WIN
122
123  if (!vas->CanAllocateSubspaces()) {
124    // If we cannot create virtual memory subspaces, we also need to fall back
125    // to creating a partially reserved sandbox. In practice, this should only
126    // happen on Windows version before Windows 10, maybe including early
127    // Windows 10 releases, where the necessary memory management APIs, in
128    // particular, VirtualAlloc2, are not available. This check should also in
129    // practice subsume the preceeding one for Windows 8 and earlier, but we'll
130    // keep both just to be sure since there the partially reserved sandbox is
131    // technically required for a different reason (large virtual memory
132    // reservations being too expensive).
133    size_to_reserve = kSandboxMinimumReservationSize;
134    partially_reserve = true;
135  }
136
137  // In any case, the sandbox must be at most as large as our address space.
138  DCHECK_LE(sandbox_size, address_space_limit);
139
140  if (partially_reserve) {
141    return InitializeAsPartiallyReservedSandbox(vas, sandbox_size,
142                                                size_to_reserve);
143  } else {
144    const bool use_guard_regions = true;
145    bool success = Initialize(vas, sandbox_size, use_guard_regions);
146#ifdef V8_SANDBOXED_POINTERS
147    // If sandboxed pointers are enabled, we need the sandbox to be initialized,
148    // so fall back to creating a partially reserved sandbox.
149    if (!success) {
150      // Instead of going for the minimum reservation size directly, we could
151      // also first try a couple of larger reservation sizes if that is deemed
152      // sensible in the future.
153      success = InitializeAsPartiallyReservedSandbox(
154          vas, sandbox_size, kSandboxMinimumReservationSize);
155    }
156#endif  // V8_SANDBOXED_POINTERS
157    return success;
158  }
159}
160
161bool Sandbox::Initialize(v8::VirtualAddressSpace* vas, size_t size,
162                         bool use_guard_regions) {
163  CHECK(!initialized_);
164  CHECK(!disabled_);
165  CHECK(base::bits::IsPowerOfTwo(size));
166  CHECK_GE(size, kSandboxMinimumSize);
167  CHECK(vas->CanAllocateSubspaces());
168
169  // Currently, we allow the sandbox to be smaller than the requested size.
170  // This way, we can gracefully handle address space reservation failures
171  // during the initial rollout and can collect data on how often these occur.
172  // In the future, we will likely either require the sandbox to always have a
173  // fixed size or will design SandboxedPointers (pointers that are guaranteed
174  // to point into the sandbox) in a way that doesn't reduce the sandbox's
175  // security properties if it has a smaller size.  Which of these options is
176  // ultimately taken likey depends on how frequently sandbox reservation
177  // failures occur in practice.
178  size_t reservation_size;
179  while (!address_space_ && size >= kSandboxMinimumSize) {
180    reservation_size = size;
181    if (use_guard_regions) {
182      reservation_size += 2 * kSandboxGuardRegionSize;
183    }
184
185    Address hint = RoundDown(vas->RandomPageAddress(), kSandboxAlignment);
186
187    // There should be no executable pages mapped inside the sandbox since
188    // those could be corrupted by an attacker and therefore pose a security
189    // risk. Furthermore, allowing executable mappings in the sandbox requires
190    // MAP_JIT on macOS, which causes fork() to become excessively slow
191    // (multiple seconds or even minutes for a 1TB sandbox on macOS 12.X), in
192    // turn causing tests to time out. As such, the maximum page permission
193    // inside the sandbox should be read + write.
194    address_space_ = vas->AllocateSubspace(
195        hint, reservation_size, kSandboxAlignment, PagePermissions::kReadWrite);
196    if (!address_space_) {
197      size /= 2;
198    }
199  }
200
201  if (!address_space_) return false;
202
203  reservation_base_ = address_space_->base();
204  base_ = reservation_base_;
205  if (use_guard_regions) {
206    base_ += kSandboxGuardRegionSize;
207  }
208
209  size_ = size;
210  end_ = base_ + size_;
211  reservation_size_ = reservation_size;
212
213  if (use_guard_regions) {
214    Address front = reservation_base_;
215    Address back = end_;
216    // These must succeed since nothing was allocated in the subspace yet.
217    CHECK(address_space_->AllocateGuardRegion(front, kSandboxGuardRegionSize));
218    CHECK(address_space_->AllocateGuardRegion(back, kSandboxGuardRegionSize));
219  }
220
221  sandbox_page_allocator_ =
222      std::make_unique<base::VirtualAddressSpacePageAllocator>(
223          address_space_.get());
224
225  initialized_ = true;
226  is_partially_reserved_ = false;
227
228  InitializeConstants();
229
230  return true;
231}
232
233bool Sandbox::InitializeAsPartiallyReservedSandbox(v8::VirtualAddressSpace* vas,
234                                                   size_t size,
235                                                   size_t size_to_reserve) {
236  CHECK(!initialized_);
237  CHECK(!disabled_);
238  CHECK(base::bits::IsPowerOfTwo(size));
239  CHECK(base::bits::IsPowerOfTwo(size_to_reserve));
240  CHECK_GE(size, kSandboxMinimumSize);
241  CHECK_LT(size_to_reserve, size);
242
243  // Use a custom random number generator here to ensure that we get uniformly
244  // distributed random numbers. We figure out the available address space
245  // ourselves, and so are potentially better positioned to determine a good
246  // base address for the sandbox than the embedder.
247  base::RandomNumberGenerator rng;
248  if (FLAG_random_seed != 0) {
249    rng.SetSeed(FLAG_random_seed);
250  }
251
252  // We try to ensure that base + size is still (mostly) within the process'
253  // address space, even though we only reserve a fraction of the memory. For
254  // that, we attempt to map the sandbox into the first half of the usable
255  // address space. This keeps the implementation simple and should, In any
256  // realistic scenario, leave plenty of space after the actual reservation.
257  Address address_space_end = DetermineAddressSpaceLimit();
258  Address highest_allowed_address = address_space_end / 2;
259  DCHECK(base::bits::IsPowerOfTwo(highest_allowed_address));
260  constexpr int kMaxAttempts = 10;
261  for (int i = 1; i <= kMaxAttempts; i++) {
262    Address hint = rng.NextInt64() % highest_allowed_address;
263    hint = RoundDown(hint, kSandboxAlignment);
264
265    reservation_base_ = vas->AllocatePages(
266        hint, size_to_reserve, kSandboxAlignment, PagePermissions::kNoAccess);
267
268    if (!reservation_base_) return false;
269
270    // Take this base if it meets the requirements or if this is the last
271    // attempt.
272    if (reservation_base_ <= highest_allowed_address || i == kMaxAttempts)
273      break;
274
275    // Can't use this base, so free the reservation and try again
276    vas->FreePages(reservation_base_, size_to_reserve);
277    reservation_base_ = kNullAddress;
278  }
279  DCHECK(reservation_base_);
280
281  base_ = reservation_base_;
282  size_ = size;
283  end_ = base_ + size_;
284  reservation_size_ = size_to_reserve;
285  initialized_ = true;
286  is_partially_reserved_ = true;
287  address_space_ = std::make_unique<base::EmulatedVirtualAddressSubspace>(
288      vas, reservation_base_, reservation_size_, size_);
289  sandbox_page_allocator_ =
290      std::make_unique<base::VirtualAddressSpacePageAllocator>(
291          address_space_.get());
292
293  InitializeConstants();
294
295  return true;
296}
297
298void Sandbox::InitializeConstants() {
299#ifdef V8_SANDBOXED_POINTERS
300  // Place the empty backing store buffer at the end of the sandbox, so that any
301  // accidental access to it will most likely hit a guard page.
302  constants_.set_empty_backing_store_buffer(base_ + size_ - 1);
303#endif
304}
305
306void Sandbox::TearDown() {
307  if (initialized_) {
308    // This destroys the sub space and frees the underlying reservation.
309    address_space_.reset();
310    sandbox_page_allocator_.reset();
311    base_ = kNullAddress;
312    end_ = kNullAddress;
313    size_ = 0;
314    reservation_base_ = kNullAddress;
315    reservation_size_ = 0;
316    initialized_ = false;
317    is_partially_reserved_ = false;
318#ifdef V8_SANDBOXED_POINTERS
319    constants_.Reset();
320#endif
321  }
322  disabled_ = false;
323}
324
325#endif  // V8_SANDBOX_IS_AVAILABLE
326
327#ifdef V8_SANDBOX
328DEFINE_LAZY_LEAKY_OBJECT_GETTER(Sandbox, GetProcessWideSandbox)
329#endif
330
331}  // namespace internal
332}  // namespace v8
333