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 
26 namespace v8 {
27 namespace 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.
DetermineAddressSpaceLimit()33 static 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 
Initialize(v8::VirtualAddressSpace* vas)84 bool 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 
Initialize(v8::VirtualAddressSpace* vas, size_t size, bool use_guard_regions)161 bool 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 
InitializeAsPartiallyReservedSandbox(v8::VirtualAddressSpace* vas, size_t size, size_t size_to_reserve)233 bool 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 
InitializeConstants()298 void 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 
TearDown()306 void 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
328 DEFINE_LAZY_LEAKY_OBJECT_GETTER(Sandbox, GetProcessWideSandbox)
329 #endif
330 
331 }  // namespace internal
332 }  // namespace v8
333