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