1 // Copyright 2017 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 <limits> 6 7 #include "src/heap/heap-inl.h" 8 #include "src/objects-inl.h" 9 #include "src/objects/js-array-buffer-inl.h" 10 #include "src/wasm/wasm-engine.h" 11 #include "src/wasm/wasm-limits.h" 12 #include "src/wasm/wasm-memory.h" 13 #include "src/wasm/wasm-module.h" 14 15 namespace v8 { 16 namespace internal { 17 namespace wasm { 18 19 namespace { 20 21 constexpr size_t kNegativeGuardSize = 1u << 31; // 2GiB 22 23 void AddAllocationStatusSample(Isolate* isolate, 24 WasmMemoryTracker::AllocationStatus status) { 25 isolate->counters()->wasm_memory_allocation_result()->AddSample( 26 static_cast<int>(status)); 27 } 28 29 void* TryAllocateBackingStore(WasmMemoryTracker* memory_tracker, Heap* heap, 30 size_t size, bool require_full_guard_regions, 31 void** allocation_base, 32 size_t* allocation_length) { 33 using AllocationStatus = WasmMemoryTracker::AllocationStatus; 34 #if V8_TARGET_ARCH_32_BIT 35 DCHECK(!require_full_guard_regions); 36 #endif 37 // We always allocate the largest possible offset into the heap, so the 38 // addressable memory after the guard page can be made inaccessible. 39 // 40 // To protect against 32-bit integer overflow issues, we also protect the 2GiB 41 // before the valid part of the memory buffer. 42 // TODO(7881): do not use static_cast<uint32_t>() here 43 *allocation_length = 44 require_full_guard_regions 45 ? RoundUp(kWasmMaxHeapOffset + kNegativeGuardSize, CommitPageSize()) 46 : RoundUp( 47 base::bits::RoundUpToPowerOfTwo32(static_cast<uint32_t>(size)), 48 kWasmPageSize); 49 DCHECK_GE(*allocation_length, size); 50 DCHECK_GE(*allocation_length, kWasmPageSize); 51 52 // Let the WasmMemoryTracker know we are going to reserve a bunch of 53 // address space. 54 // Try up to three times; getting rid of dead JSArrayBuffer allocations might 55 // require two GCs because the first GC maybe incremental and may have 56 // floating garbage. 57 static constexpr int kAllocationRetries = 2; 58 bool did_retry = false; 59 for (int trial = 0;; ++trial) { 60 if (memory_tracker->ReserveAddressSpace(*allocation_length)) break; 61 did_retry = true; 62 // After first and second GC: retry. 63 if (trial == kAllocationRetries) { 64 // We are over the address space limit. Fail. 65 // 66 // When running under the correctness fuzzer (i.e. 67 // --abort-on-stack-or-string-length-overflow is preset), we crash instead 68 // so it is not incorrectly reported as a correctness violation. See 69 // https://crbug.com/828293#c4 70 if (FLAG_abort_on_stack_or_string_length_overflow) { 71 FATAL("could not allocate wasm memory"); 72 } 73 AddAllocationStatusSample( 74 heap->isolate(), AllocationStatus::kAddressSpaceLimitReachedFailure); 75 return nullptr; 76 } 77 // Collect garbage and retry. 78 heap->MemoryPressureNotification(MemoryPressureLevel::kCritical, true); 79 } 80 81 // The Reserve makes the whole region inaccessible by default. 82 DCHECK_NULL(*allocation_base); 83 for (int trial = 0;; ++trial) { 84 *allocation_base = AllocatePages(nullptr, *allocation_length, kWasmPageSize, 85 PageAllocator::kNoAccess); 86 if (*allocation_base != nullptr) break; 87 if (trial == kAllocationRetries) { 88 memory_tracker->ReleaseReservation(*allocation_length); 89 AddAllocationStatusSample(heap->isolate(), 90 AllocationStatus::kOtherFailure); 91 return nullptr; 92 } 93 heap->MemoryPressureNotification(MemoryPressureLevel::kCritical, true); 94 } 95 byte* memory = reinterpret_cast<byte*>(*allocation_base); 96 if (require_full_guard_regions) { 97 memory += kNegativeGuardSize; 98 } 99 100 // Make the part we care about accessible. 101 if (size > 0) { 102 bool result = SetPermissions(memory, RoundUp(size, kWasmPageSize), 103 PageAllocator::kReadWrite); 104 // SetPermissions commits the extra memory, which may put us over the 105 // process memory limit. If so, report this as an OOM. 106 if (!result) { 107 V8::FatalProcessOutOfMemory(nullptr, "TryAllocateBackingStore"); 108 } 109 } 110 111 memory_tracker->RegisterAllocation(heap->isolate(), *allocation_base, 112 *allocation_length, memory, size); 113 AddAllocationStatusSample(heap->isolate(), 114 did_retry ? AllocationStatus::kSuccessAfterRetry 115 : AllocationStatus::kSuccess); 116 return memory; 117 } 118 } // namespace 119 120 WasmMemoryTracker::~WasmMemoryTracker() { 121 // All reserved address space should be released before the allocation tracker 122 // is destroyed. 123 DCHECK_EQ(reserved_address_space_, 0u); 124 DCHECK_EQ(allocated_address_space_, 0u); 125 } 126 127 bool WasmMemoryTracker::ReserveAddressSpace(size_t num_bytes) { 128 // Address space reservations are currently only meaningful using guard 129 // regions, which is currently only supported on 64-bit systems. On other 130 // platforms, we always fall back on bounds checks. 131 #if V8_TARGET_ARCH_MIPS64 132 // MIPS64 has a user space of 2^40 bytes on most processors, 133 // address space limits needs to be smaller. 134 constexpr size_t kAddressSpaceLimit = 0x2100000000L; // 132 GiB 135 #elif V8_TARGET_ARCH_64_BIT 136 // We set the limit to 1 TiB + 4 GiB so that there is room for mini-guards 137 // once we fill everything up with full-sized guard regions. 138 constexpr size_t kAddressSpaceLimit = 0x10100000000L; // 1 TiB + 4 GiB 139 #else 140 constexpr size_t kAddressSpaceLimit = 0x90000000; // 2 GiB + 256 MiB 141 #endif 142 143 int retries = 5; // cmpxchng can fail, retry some number of times. 144 do { 145 size_t old_count = reserved_address_space_; 146 if ((kAddressSpaceLimit - old_count) < num_bytes) return false; 147 if (reserved_address_space_.compare_exchange_weak(old_count, 148 old_count + num_bytes)) { 149 return true; 150 } 151 } while (retries-- > 0); 152 153 return false; 154 } 155 156 void WasmMemoryTracker::ReleaseReservation(size_t num_bytes) { 157 size_t const old_reserved = reserved_address_space_.fetch_sub(num_bytes); 158 USE(old_reserved); 159 DCHECK_LE(num_bytes, old_reserved); 160 } 161 162 void WasmMemoryTracker::RegisterAllocation(Isolate* isolate, 163 void* allocation_base, 164 size_t allocation_length, 165 void* buffer_start, 166 size_t buffer_length) { 167 base::LockGuard<base::Mutex> scope_lock(&mutex_); 168 169 allocated_address_space_ += allocation_length; 170 AddAddressSpaceSample(isolate); 171 172 allocations_.emplace(buffer_start, 173 AllocationData{allocation_base, allocation_length, 174 buffer_start, buffer_length}); 175 } 176 177 WasmMemoryTracker::AllocationData WasmMemoryTracker::ReleaseAllocation( 178 Isolate* isolate, const void* buffer_start) { 179 base::LockGuard<base::Mutex> scope_lock(&mutex_); 180 181 auto find_result = allocations_.find(buffer_start); 182 CHECK_NE(find_result, allocations_.end()); 183 184 if (find_result != allocations_.end()) { 185 size_t num_bytes = find_result->second.allocation_length; 186 DCHECK_LE(num_bytes, reserved_address_space_); 187 DCHECK_LE(num_bytes, allocated_address_space_); 188 reserved_address_space_ -= num_bytes; 189 allocated_address_space_ -= num_bytes; 190 // ReleaseAllocation might be called with a nullptr as isolate if the 191 // embedder is releasing the allocation and not a specific isolate. This 192 // happens if the allocation was shared between multiple isolates (threads). 193 if (isolate) AddAddressSpaceSample(isolate); 194 195 AllocationData allocation_data = find_result->second; 196 allocations_.erase(find_result); 197 return allocation_data; 198 } 199 UNREACHABLE(); 200 } 201 202 const WasmMemoryTracker::AllocationData* WasmMemoryTracker::FindAllocationData( 203 const void* buffer_start) { 204 base::LockGuard<base::Mutex> scope_lock(&mutex_); 205 const auto& result = allocations_.find(buffer_start); 206 if (result != allocations_.end()) { 207 return &result->second; 208 } 209 return nullptr; 210 } 211 212 bool WasmMemoryTracker::IsWasmMemory(const void* buffer_start) { 213 base::LockGuard<base::Mutex> scope_lock(&mutex_); 214 return allocations_.find(buffer_start) != allocations_.end(); 215 } 216 217 bool WasmMemoryTracker::HasFullGuardRegions(const void* buffer_start) { 218 base::LockGuard<base::Mutex> scope_lock(&mutex_); 219 const auto allocation = allocations_.find(buffer_start); 220 221 if (allocation == allocations_.end()) { 222 return false; 223 } 224 225 Address start = reinterpret_cast<Address>(buffer_start); 226 Address limit = 227 reinterpret_cast<Address>(allocation->second.allocation_base) + 228 allocation->second.allocation_length; 229 return start + kWasmMaxHeapOffset < limit; 230 } 231 232 bool WasmMemoryTracker::FreeMemoryIfIsWasmMemory(Isolate* isolate, 233 const void* buffer_start) { 234 if (IsWasmMemory(buffer_start)) { 235 const AllocationData allocation = ReleaseAllocation(isolate, buffer_start); 236 CHECK(FreePages(allocation.allocation_base, allocation.allocation_length)); 237 return true; 238 } 239 return false; 240 } 241 242 void WasmMemoryTracker::AddAddressSpaceSample(Isolate* isolate) { 243 // Report address space usage in MiB so the full range fits in an int on all 244 // platforms. 245 isolate->counters()->wasm_address_space_usage_mb()->AddSample( 246 static_cast<int>(allocated_address_space_ >> 20)); 247 } 248 249 Handle<JSArrayBuffer> SetupArrayBuffer(Isolate* isolate, void* backing_store, 250 size_t size, bool is_external, 251 SharedFlag shared) { 252 Handle<JSArrayBuffer> buffer = 253 isolate->factory()->NewJSArrayBuffer(shared, TENURED); 254 constexpr bool is_wasm_memory = true; 255 JSArrayBuffer::Setup(buffer, isolate, is_external, backing_store, size, 256 shared, is_wasm_memory); 257 buffer->set_is_neuterable(false); 258 buffer->set_is_growable(true); 259 return buffer; 260 } 261 262 MaybeHandle<JSArrayBuffer> NewArrayBuffer(Isolate* isolate, size_t size, 263 SharedFlag shared) { 264 // Enforce engine-limited maximum allocation size. 265 if (size > kV8MaxWasmMemoryBytes) return {}; 266 // Enforce flag-limited maximum allocation size. 267 if (size > (FLAG_wasm_max_mem_pages * uint64_t{kWasmPageSize})) return {}; 268 269 WasmMemoryTracker* memory_tracker = isolate->wasm_engine()->memory_tracker(); 270 271 // Set by TryAllocateBackingStore or GetEmptyBackingStore 272 void* allocation_base = nullptr; 273 size_t allocation_length = 0; 274 275 #if V8_TARGET_ARCH_64_BIT 276 bool require_full_guard_regions = true; 277 #else 278 bool require_full_guard_regions = false; 279 #endif 280 void* memory = TryAllocateBackingStore(memory_tracker, isolate->heap(), size, 281 require_full_guard_regions, 282 &allocation_base, &allocation_length); 283 if (memory == nullptr && FLAG_wasm_trap_handler_fallback) { 284 // If we failed to allocate with full guard regions, fall back on 285 // mini-guards. 286 require_full_guard_regions = false; 287 memory = TryAllocateBackingStore(memory_tracker, isolate->heap(), size, 288 require_full_guard_regions, 289 &allocation_base, &allocation_length); 290 } 291 if (memory == nullptr) { 292 return {}; 293 } 294 295 #if DEBUG 296 // Double check the API allocator actually zero-initialized the memory. 297 const byte* bytes = reinterpret_cast<const byte*>(memory); 298 for (size_t i = 0; i < size; ++i) { 299 DCHECK_EQ(0, bytes[i]); 300 } 301 #endif 302 303 reinterpret_cast<v8::Isolate*>(isolate) 304 ->AdjustAmountOfExternalAllocatedMemory(size); 305 306 constexpr bool is_external = false; 307 return SetupArrayBuffer(isolate, memory, size, is_external, shared); 308 } 309 310 void DetachMemoryBuffer(Isolate* isolate, Handle<JSArrayBuffer> buffer, 311 bool free_memory) { 312 if (buffer->is_shared()) return; // Detaching shared buffers is impossible. 313 DCHECK(!buffer->is_neuterable()); 314 315 const bool is_external = buffer->is_external(); 316 DCHECK(!buffer->is_neuterable()); 317 if (!is_external) { 318 buffer->set_is_external(true); 319 isolate->heap()->UnregisterArrayBuffer(*buffer); 320 if (free_memory) { 321 // We need to free the memory before neutering the buffer because 322 // FreeBackingStore reads buffer->allocation_base(), which is nulled out 323 // by Neuter. This means there is a dangling pointer until we neuter the 324 // buffer. Since there is no way for the user to directly call 325 // FreeBackingStore, we can ensure this is safe. 326 buffer->FreeBackingStoreFromMainThread(); 327 } 328 } 329 330 DCHECK(buffer->is_external()); 331 buffer->set_is_wasm_memory(false); 332 buffer->set_is_neuterable(true); 333 buffer->Neuter(); 334 } 335 336 } // namespace wasm 337 } // namespace internal 338 } // namespace v8 339