WasmMemory.cpp (18149B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * 4 * Copyright 2021 Mozilla Foundation 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 #include "wasm/WasmMemory.h" 20 21 #include "mozilla/MathAlgorithms.h" 22 23 #include "js/Conversions.h" 24 #include "js/ErrorReport.h" 25 #include "vm/ArrayBufferObject.h" 26 #include "wasm/WasmCodegenTypes.h" 27 #include "wasm/WasmProcess.h" 28 29 using mozilla::IsPowerOfTwo; 30 31 using namespace js; 32 using namespace js::wasm; 33 34 const char* wasm::ToString(AddressType addressType) { 35 switch (addressType) { 36 case AddressType::I32: 37 return "i32"; 38 case AddressType::I64: 39 return "i64"; 40 default: 41 MOZ_CRASH(); 42 } 43 } 44 45 bool wasm::ToAddressType(JSContext* cx, HandleValue value, 46 AddressType* addressType) { 47 RootedString typeStr(cx, ToString(cx, value)); 48 if (!typeStr) { 49 return false; 50 } 51 52 Rooted<JSLinearString*> typeLinearStr(cx, typeStr->ensureLinear(cx)); 53 if (!typeLinearStr) { 54 return false; 55 } 56 57 if (StringEqualsLiteral(typeLinearStr, "i32")) { 58 *addressType = AddressType::I32; 59 } else if (StringEqualsLiteral(typeLinearStr, "i64")) { 60 *addressType = AddressType::I64; 61 } else { 62 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 63 JSMSG_WASM_BAD_STRING_ADDR_TYPE); 64 return false; 65 } 66 return true; 67 } 68 69 /* 70 * [SMDOC] Linear memory addresses and bounds checking 71 * 72 * (Also see "WASM Linear Memory structure" in vm/ArrayBufferObject.cpp) 73 * 74 * 75 * ## Memory addresses 76 * 77 * A memory address in an access instruction has three components, the "memory 78 * base", the "address", and the "offset". The "memory base" (the HeapReg on 79 * most platforms and a value loaded from the instance on x86) is a native 80 * pointer to the start of the linear memory array; we'll ignore the memory base 81 * in the following. The "address" is the i32 or i64 address into linear memory 82 * from the WebAssembly program; it is usually variable but can be constant. The 83 * "offset" is a constant immediate to the access instruction. For example, 84 * consider the following instructions: 85 * 86 * i32.const 128 87 * f32.load offset=8 88 * 89 * The address is 128; the offset is 8. The memory base is not observable to 90 * wasm. Note that the address comes from wasm value stack, but the offset is an 91 * immediate. 92 * 93 * The "effective address" (EA) is the non-overflowed sum of the address and the 94 * offset. (If the sum overflows, the program traps.) For the above, the 95 * effective address is 136. 96 * 97 * An access has an "access size", which is the number of bytes that are 98 * accessed - currently up to 16 (for V128). The highest-addressed byte to be 99 * accessed is thus the byte at (address + offset + access_size - 1). Note that 100 * (offset + access_size - 1) can be evaluated at compile time. 101 * 102 * Bounds checking ensures that the entire access is in bounds, i.e. that the 103 * highest-addressed byte is within the memory's current byteLength. 104 * 105 * 106 * ## Bounds check avoidance 107 * 108 * To avoid performing an addition with overflow check and a compare-and-branch 109 * bounds check for every memory access, we use some tricks: 110 * 111 * - We allocate an access-protected guard region of size R at the end of each 112 * memory to trap out-of-bounds offsets in the range 0..R-access_size. Thus, 113 * the offset and the access size can be omitted from the bounds check, saving 114 * the add and overflow check. For example, given the following module: 115 * 116 * (memory 1) ;; 1 page, 65536 bytes 117 * (func 118 * (f64.load offset=8 (i32.const 65528)) 119 * ) 120 * 121 * As long as the address itself is bounds checked, the offset will at worst 122 * cause the access to land in the guard region and trap via signal handling: 123 * 124 * Memory │ Guard Region 125 * ─ ─ ──────────┼────────┬──────── ─ ─ 126 * │ access │ 127 * ─ ─ ─┬────────┼────────┴──────── ─ ─ 128 * 65528 65536 129 * 130 * Therefore, after bounds checking the address, the offset can be added into 131 * the address without an overflow check, either directly before the access or 132 * in the access instruction itself (depending on the ISA). 133 * 134 * This is the second part of the "SLOP" region as defined in "WASM Linear 135 * Memory structure" in ArrayBufferObject.cpp. 136 * 137 * - For 32-bit memories on 64-bit systems where we determine there is plenty of 138 * virtual memory space, we use "huge memories", in which we reserve 4GiB + R 139 * bytes of memory regardless of the memory's byteLength. Since the address 140 * itself has a 4GiB range, this allows us to skip bounds checks on the 141 * address as well. The extra R bytes of guard pages protect against 142 * out-of-bounds offsets as above. 143 * 144 * The offset can be added into the pointer (using 64-bit arithmetic) either 145 * directly before the access or in the access instruction. 146 * 147 * In both cases, accesses with offsets greater than R-access_size must be 148 * explicitly bounds checked in full, with an overflow check, since we cannot 149 * rely on the guard region. 150 * 151 * The value of R may vary depending on the memory allocation strategy and the 152 * amount of address space we can freely reserve. We do not document it here 153 * lest it be absurdly out of date. Search for "OffsetGuardLimit" if you wish. 154 * 155 * All memories in a process use the same strategy, selected at process startup. 156 * This is because the machine code embeds the strategy it's been compiled with, 157 * and may later be exposed to memories originating from different modules or 158 * directly from JS. If the memories did not all use the same strategy, we would 159 * have to recompile the code for each case. 160 * 161 * 162 * ## The boundsCheckLimit and the byteLength 163 * 164 * One would expect the boundsCheckLimit to always equal the memory's current 165 * byteLength. However, because the memory can grow, this means each bounds 166 * check must first load the boundsCheckLimit from the instance. 167 * 168 * We can sometimes avoid this load by observing that, even for non-huge 169 * memories, the signal handler is the final source of truth. In any case where 170 * we make a single memory reservation up front, we can set the boundsCheckLimit 171 * to the maximum possible byteLength. (For example, huge memories and memories 172 * with a max - anything that will NOT move on grow.) 173 * 174 * 175 * b.c. pass b.c. pass b.c. fail 176 * s.h. pass s.h. fail s.h. n/a 177 * ─ ─ ─────────────────┼─────────────────┼────────────── ─ ─ 178 * 179 * ─ ─ ─────────────────────────────────────────────────────┐ 180 * ─ ─ ─────────────────│─────────────────│─────────────────│ 181 * byteLength boundsCheckLimit mappedSize 182 * 183 * ─ ─ ─────────────────┘ 184 * COMMITTED 185 * └─────────────────┴─────────────────┘ 186 * SLOP 187 * 188 * 189 * Note that this works even if byteLength later grows: 190 * 191 * 192 * b.c. pass b.c. fail 193 * s.h. pass s.h. n/a 194 * ─ ─ ───────────────────────────────────┼────────────── ─ ─ 195 * 196 * ─ ─ ─────────────────────────────────────────────────────┐ 197 * ─ ─ ───────────────────────────────────│─────────────────│ 198 * byteLength mappedSize 199 * boundsCheckLimit 200 * 201 * ─ ─ ───────────────────────────────────┘ 202 * COMMITTED 203 * └─────────────────┘ 204 * SLOP 205 * 206 * 207 * Therefore, the boundsCheckLimit need only be greater than byteLength, not 208 * equal to byteLength, and the boundsCheckLimit need only be loaded once. This 209 * is the first part of the "SLOP" region as defined in "WASM Linear Memory 210 * structure" in ArrayBufferObject.cpp. 211 * 212 * 213 * ## Size of the boundsCheckLimit 214 * 215 * The boundsCheckLimit that is stored in the instance is always valid and is 216 * always a 64-bit value, and it is always correct to load it and use it as a 217 * 64-bit value. However, in situations when the 32 upper bits are known to be 218 * zero, it is also correct to load just the low 32 bits, and use that value as 219 * the limit. (This does not require a different address, since the limit is 220 * always little-endian when a JIT is enabled) 221 * 222 * On x86 and arm32 (and on any other 32-bit platform, should there ever be 223 * one), we always use explicit bounds checks, and the boundsCheckLimit can 224 * always be treated as a 32-bit quantity. 225 * 226 * On all 64-bit platforms, we may use explicit bounds checking or huge memories 227 * for memory32, but must always use explicit bounds checking for memory64. If 228 * the heap has a known maximum size that is less than 4GiB, then the 229 * boundsCheckLimit can be treated as a 32-bit quantity; otherwise it must be 230 * treated as a 64-bit quantity. 231 * 232 * Asm.js memories are limited to 2GB even on 64-bit platforms, and we can 233 * therefore always assume a 32-bit bounds check limit for asm.js. 234 * 235 * 236 * ## Constant pointers 237 * 238 * If the pointer is constant then the EA can be computed at compile time, and 239 * if (EA + access_size) is below the initial memory size, then the bounds check 240 * can always be elided. 241 * 242 * 243 * ## Alignment checks 244 * 245 * On all platforms, some accesses (currently atomics) require an alignment 246 * check: the EA must be naturally aligned for the datum being accessed. 247 * However, we do not need to compute the EA properly, we care only about the 248 * low bits - a cheap, overflowing add is fine, and if the offset is known to be 249 * aligned, only the address need be checked. 250 */ 251 252 // Bounds checks always compare the base of the memory access with the bounds 253 // check limit. If the memory access is unaligned, this means that, even if the 254 // bounds check succeeds, a few bytes of the access can extend past the end of 255 // memory. To guard against this, extra space is included in the guard region to 256 // catch the overflow. MaxMemoryAccessSize is a conservative approximation of 257 // the maximum guard space needed to catch all unaligned overflows. 258 // 259 // Also see "Linear memory addresses and bounds checking" above. 260 261 static const unsigned MaxMemoryAccessSize = LitVal::sizeofLargestValue(); 262 263 // All plausible targets must be able to do at least IEEE754 double 264 // loads/stores, hence the lower limit of 8. Some Intel processors support 265 // AVX-512 loads/stores, hence the upper limit of 64. 266 static_assert(MaxMemoryAccessSize >= 8, "MaxMemoryAccessSize too low"); 267 static_assert(MaxMemoryAccessSize <= 64, "MaxMemoryAccessSize too high"); 268 static_assert((MaxMemoryAccessSize & (MaxMemoryAccessSize - 1)) == 0, 269 "MaxMemoryAccessSize is not a power of two"); 270 271 #ifdef WASM_SUPPORTS_HUGE_MEMORY 272 273 static_assert(MaxMemoryAccessSize <= HugeUnalignedGuardPage, 274 "rounded up to static page size"); 275 static_assert(HugeOffsetGuardLimit < UINT32_MAX, 276 "checking for overflow against OffsetGuardLimit is enough."); 277 278 // We have only tested huge memory on x64, arm64 and riscv64. 279 # if !(defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64) || \ 280 defined(JS_CODEGEN_RISCV64)) 281 # error "Not an expected configuration" 282 # endif 283 284 #endif 285 286 // On !WASM_SUPPORTS_HUGE_MEMORY platforms: 287 // - To avoid OOM in ArrayBuffer::prepareForAsmJS, asm.js continues to use the 288 // original ArrayBuffer allocation which has no guard region at all. 289 // - For WebAssembly memories, an additional GuardSize is mapped after the 290 // accessible region of the memory to catch folded (base+offset) accesses 291 // where `offset < OffsetGuardLimit` as well as the overflow from unaligned 292 // accesses, as described above for MaxMemoryAccessSize. 293 294 static const size_t OffsetGuardLimit = 295 StandardPageSizeBytes - MaxMemoryAccessSize; 296 297 static_assert(MaxMemoryAccessSize < GuardSize, 298 "Guard page handles partial out-of-bounds"); 299 static_assert(OffsetGuardLimit < UINT32_MAX, 300 "checking for overflow against OffsetGuardLimit is enough."); 301 302 uint64_t wasm::GetMaxOffsetGuardLimit(bool hugeMemory, PageSize sz) { 303 #ifndef ENABLE_WASM_CUSTOM_PAGE_SIZES 304 MOZ_ASSERT(sz == PageSize::Standard); 305 #endif 306 307 uint64_t guardLimit = sz == PageSize::Standard ? OffsetGuardLimit : 0; 308 #ifdef WASM_SUPPORTS_HUGE_MEMORY 309 return hugeMemory ? HugeOffsetGuardLimit : guardLimit; 310 #else 311 return guardLimit; 312 #endif 313 } 314 315 // Assert that our minimum offset guard limit covers our inline 316 // memory.copy/fill optimizations. 317 static const size_t MinOffsetGuardLimit = OffsetGuardLimit; 318 static_assert(MaxInlineMemoryCopyLength < MinOffsetGuardLimit, "precondition"); 319 static_assert(MaxInlineMemoryFillLength < MinOffsetGuardLimit, "precondition"); 320 321 wasm::Pages wasm::MaxMemoryPages(AddressType t, PageSize pageSize) { 322 #ifdef JS_64BIT 323 MOZ_ASSERT_IF(t == AddressType::I64, !IsHugeMemoryEnabled(t, pageSize)); 324 size_t desired = MaxMemoryPagesValidation(t, pageSize); 325 size_t actual = 326 ArrayBufferObject::ByteLengthLimit / PageSizeInBytes(pageSize); 327 return wasm::Pages::fromPageCount(std::min(desired, actual), pageSize); 328 #else 329 // On 32-bit systems, the heap limit must be representable in the nonnegative 330 // range of an int32_t, which means the maximum heap size as observed by wasm 331 // code is one wasm page less than 2GB. 332 MOZ_ASSERT(ArrayBufferObject::ByteLengthLimit >= 333 INT32_MAX / PageSizeInBytes(pageSize)); 334 return wasm::Pages::fromPageCount(INT32_MAX / PageSizeInBytes(pageSize), 335 pageSize); 336 #endif 337 } 338 339 size_t wasm::MaxMemoryBoundsCheckLimit(AddressType t, PageSize pageSize) { 340 return MaxMemoryBytes(t, pageSize); 341 } 342 343 Pages wasm::ClampedMaxPages(AddressType t, Pages initialPages, 344 const mozilla::Maybe<Pages>& sourceMaxPages, 345 bool useHugeMemory) { 346 PageSize pageSize = initialPages.pageSize(); 347 Pages clampedMaxPages = Pages::forPageSize(pageSize); 348 349 if (sourceMaxPages.isSome()) { 350 // There is a specified maximum, clamp it to the implementation limit of 351 // maximum pages 352 clampedMaxPages = 353 std::min(*sourceMaxPages, wasm::MaxMemoryPages(t, pageSize)); 354 355 #ifndef JS_64BIT 356 static_assert(sizeof(uintptr_t) == 4, "assuming not 64 bit implies 32 bit"); 357 358 // On 32-bit platforms, prevent applications specifying a large max (like 359 // MaxMemoryPages()) from unintentially OOMing the browser: they just want 360 // "a lot of memory". Maintain the invariant that initialPages <= 361 // clampedMaxPages. 362 static const uint64_t OneGib = 1 << 30; 363 const Pages OneGibPages = Pages::fromByteLengthExact(OneGib, pageSize); 364 365 Pages clampedPages = std::max(OneGibPages, initialPages); 366 clampedMaxPages = std::min(clampedPages, clampedMaxPages); 367 #endif 368 } else { 369 // There is not a specified maximum, fill it in with the implementation 370 // limit of maximum pages 371 clampedMaxPages = wasm::MaxMemoryPages(t, pageSize); 372 } 373 374 // Double-check our invariants 375 MOZ_RELEASE_ASSERT(sourceMaxPages.isNothing() || 376 clampedMaxPages <= *sourceMaxPages); 377 MOZ_RELEASE_ASSERT(clampedMaxPages <= wasm::MaxMemoryPages(t, pageSize)); 378 MOZ_RELEASE_ASSERT(initialPages <= clampedMaxPages); 379 380 return clampedMaxPages; 381 } 382 383 size_t wasm::ComputeMappedSize(wasm::Pages clampedMaxPages) { 384 // Caller is responsible to ensure that clampedMaxPages has been clamped to 385 // implementation limits. 386 size_t maxSize = clampedMaxPages.byteLength(); 387 388 // For tiny page sizes, round up the mapped size to a multiple of the 389 // system page size after clamping. 390 #ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES 391 if (clampedMaxPages.pageSize() == wasm::PageSize::Tiny) { 392 mozilla::CheckedInt<size_t> length(maxSize); 393 394 if (length.value() % gc::SystemPageSize() != 0) { 395 length += ComputeByteAlignment(length.value(), gc::SystemPageSize()); 396 // This should be valid because of previous clamping. 397 MOZ_RELEASE_ASSERT(length.isValid()); 398 MOZ_ASSERT(length.value() % gc::SystemPageSize() == 0); 399 maxSize = length.value(); 400 } 401 402 MOZ_ASSERT(maxSize <= clampedMaxPages.byteLength() + GuardSize); 403 } 404 #endif 405 406 MOZ_ASSERT(maxSize % gc::SystemPageSize() == 0); 407 MOZ_ASSERT(GuardSize % gc::SystemPageSize() == 0); 408 if (clampedMaxPages.pageSize() == PageSize::Standard) { 409 maxSize += GuardSize; 410 } else { 411 #ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES 412 // In the case of a tiny page, we omit the guard page size 413 // because we can't use guard pages for tiny page bounds checks. 414 MOZ_ASSERT(clampedMaxPages.pageSize() == PageSize::Tiny); 415 #else 416 MOZ_CRASH(); 417 #endif 418 } 419 420 return maxSize; 421 }