tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }