tor-browser

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

WasmMemory.h (10904B)


      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 #ifndef wasm_memory_h
     20 #define wasm_memory_h
     21 
     22 #include "mozilla/CheckedInt.h"
     23 #include "mozilla/Maybe.h"
     24 
     25 #include <compare>  // std::strong_ordering
     26 #include <stdint.h>
     27 
     28 #include "js/Value.h"
     29 #include "vm/NativeObject.h"
     30 #include "wasm/WasmConstants.h"
     31 #include "wasm/WasmValType.h"
     32 
     33 namespace js {
     34 namespace wasm {
     35 
     36 // Limits are parameterized by an AddressType which is used to index the
     37 // underlying resource (either a Memory or a Table). Tables are restricted to
     38 // I32, while memories may use I64 when memory64 is enabled.
     39 
     40 enum class AddressType : uint8_t { I32, I64 };
     41 
     42 inline ValType ToValType(AddressType at) {
     43  return at == AddressType::I64 ? ValType::I64 : ValType::I32;
     44 }
     45 
     46 inline AddressType MinAddressType(AddressType a, AddressType b) {
     47  return (a == AddressType::I32 || b == AddressType::I32) ? AddressType::I32
     48                                                          : AddressType::I64;
     49 }
     50 
     51 extern bool ToAddressType(JSContext* cx, HandleValue value,
     52                          AddressType* addressType);
     53 
     54 extern const char* ToString(AddressType addressType);
     55 
     56 static constexpr unsigned PageSizeInBytes(PageSize sz) {
     57  return 1U << static_cast<uint8_t>(sz);
     58 }
     59 
     60 static constexpr unsigned StandardPageSizeBytes =
     61    PageSizeInBytes(PageSize::Standard);
     62 static_assert(StandardPageSizeBytes == 64 * 1024);
     63 
     64 // By spec, see
     65 // https://github.com/WebAssembly/spec/issues/1895#issuecomment-2895078022
     66 static_assert((StandardPageSizeBytes * MaxMemory64StandardPagesValidation) <=
     67              (uint64_t(1) << 53) - 1);
     68 
     69 // Pages is a typed unit representing a multiple of the page size, which
     70 // defaults to wasm::StandardPageSizeBytes. The page size can be customized
     71 // only if the custom page sizes proposal is enabled.
     72 //
     73 // We generally use pages as the unit of length when representing linear memory
     74 // lengths so as to avoid overflow when the specified initial or maximum pages
     75 // would overflow the native word size.
     76 //
     77 // Modules may specify pages up to 2^48 (or 2^64 - 1 with tiny pages) inclusive
     78 // and so Pages is 64-bit on all platforms.
     79 //
     80 // We represent byte lengths using the native word size, as it is assumed that
     81 // consumers of this API will only need byte lengths once it is time to
     82 // allocate memory, at which point the pages will be checked against the
     83 // implementation limits `MaxMemoryPages()` and will then be guaranteed to
     84 // fit in a native word.
     85 struct Pages {
     86 private:
     87  // Pages are specified by limit fields, which in general may be up to 2^48,
     88  // so we must use uint64_t here.
     89  uint64_t pageCount_;
     90  PageSize pageSize_;
     91 
     92  constexpr Pages(uint64_t pageCount, PageSize pageSize)
     93      : pageCount_(pageCount), pageSize_(pageSize) {}
     94 
     95 public:
     96  static constexpr Pages fromPageCount(uint64_t pageCount, PageSize pageSize) {
     97    return Pages(pageCount, pageSize);
     98  }
     99 
    100  static constexpr Pages forPageSize(PageSize pageSize) {
    101    return Pages(0, pageSize);
    102  }
    103 
    104  static constexpr bool byteLengthIsMultipleOfPageSize(size_t byteLength,
    105                                                       PageSize pageSize) {
    106    return byteLength % PageSizeInBytes(pageSize) == 0;
    107  }
    108 
    109  // Converts from a byte length to pages, assuming that the length is an
    110  // exact multiple of the page size.
    111  static constexpr Pages fromByteLengthExact(size_t byteLength,
    112                                             PageSize pageSize) {
    113    MOZ_RELEASE_ASSERT(byteLengthIsMultipleOfPageSize(byteLength, pageSize));
    114    return Pages(byteLength / PageSizeInBytes(pageSize), pageSize);
    115  }
    116 
    117  Pages& operator=(const Pages& other) {
    118    MOZ_RELEASE_ASSERT(other.pageSize_ == pageSize_);
    119    pageCount_ = other.pageCount_;
    120    return *this;
    121  }
    122 
    123  // Get the wrapped page count and size. Only use this if you must, prefer to
    124  // use or add new APIs to Page.
    125  uint64_t pageCount() const { return pageCount_; }
    126  PageSize pageSize() const { return pageSize_; }
    127 
    128  // Return whether the page length may overflow when converted to a byte
    129  // length in the native word size.
    130  bool hasByteLength() const {
    131    mozilla::CheckedInt<size_t> length(pageCount_);
    132    length *= PageSizeInBytes(pageSize_);
    133    return length.isValid();
    134  }
    135 
    136  // Converts from pages to byte length in the native word size. Users must
    137  // check for overflow, or be assured else-how that overflow cannot happen.
    138  size_t byteLength() const {
    139    mozilla::CheckedInt<size_t> length(pageCount_);
    140    length *= PageSizeInBytes(pageSize_);
    141    return length.value();
    142  }
    143 
    144  // Return the byteLength for a 64-bits memory.
    145  uint64_t byteLength64() const {
    146    mozilla::CheckedInt<uint64_t> length(pageCount_);
    147    length *= PageSizeInBytes(pageSize_);
    148    return length.value();
    149  }
    150 
    151  // Increment this pages by delta and return whether the resulting value
    152  // did not overflow. If there is no overflow, then this is set to the
    153  // resulting value.
    154  bool checkedIncrement(uint64_t delta) {
    155    mozilla::CheckedInt<uint64_t> newValue = pageCount_;
    156    newValue += delta;
    157    if (!newValue.isValid()) {
    158      return false;
    159    }
    160    pageCount_ = newValue.value();
    161    return true;
    162  }
    163 
    164  // Implement pass-through comparison operators so that Pages can be compared.
    165 
    166  constexpr auto operator<=>(const Pages& other) const {
    167    MOZ_RELEASE_ASSERT(other.pageSize_ == pageSize_);
    168    return pageCount_ <=> other.pageCount_;
    169  }
    170  constexpr auto operator==(const Pages& other) const {
    171    MOZ_RELEASE_ASSERT(other.pageSize_ == pageSize_);
    172    return pageCount_ == other.pageCount_;
    173  }
    174 };
    175 
    176 // The largest number of pages the application can request.
    177 extern Pages MaxMemoryPages(AddressType t, PageSize pageSize);
    178 
    179 // The byte value of MaxMemoryPages(t).
    180 static inline size_t MaxMemoryBytes(AddressType t, PageSize pageSize) {
    181  return MaxMemoryPages(t, pageSize).byteLength();
    182 }
    183 
    184 // A value representing the largest valid value for boundsCheckLimit.
    185 extern size_t MaxMemoryBoundsCheckLimit(AddressType t, PageSize pageSize);
    186 
    187 static inline uint64_t MaxMemoryPagesValidation(AddressType addressType,
    188                                                PageSize pageSize) {
    189 #ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES
    190  if (pageSize == PageSize::Tiny) {
    191    return addressType == AddressType::I32 ? MaxMemory32TinyPagesValidation
    192                                           : MaxMemory64TinyPagesValidation;
    193  }
    194 #endif
    195 
    196  MOZ_ASSERT(pageSize == PageSize::Standard);
    197  return addressType == AddressType::I32 ? MaxMemory32StandardPagesValidation
    198                                         : MaxMemory64StandardPagesValidation;
    199 }
    200 
    201 static inline uint64_t MaxTableElemsValidation(AddressType addressType) {
    202  return addressType == AddressType::I32 ? MaxTable32ElemsValidation
    203                                         : MaxTable64ElemsValidation;
    204 }
    205 
    206 // Compute the 'clamped' maximum size of a memory. See
    207 // 'WASM Linear Memory structure' in ArrayBufferObject.cpp for background.
    208 extern Pages ClampedMaxPages(AddressType t, Pages initialPages,
    209                             const mozilla::Maybe<Pages>& sourceMaxPages,
    210                             bool useHugeMemory);
    211 
    212 // For a given WebAssembly/asm.js 'clamped' max pages, return the number of
    213 // bytes to map which will necessarily be a multiple of the system page size and
    214 // greater than clampedMaxPages in bytes.  See "Wasm Linear Memory Structure" in
    215 // vm/ArrayBufferObject.cpp.
    216 extern size_t ComputeMappedSize(Pages clampedMaxPages);
    217 
    218 extern uint64_t GetMaxOffsetGuardLimit(bool hugeMemory, PageSize sz);
    219 
    220 // Return the next higher valid immediate that satisfies the constraints of the
    221 // platform.
    222 extern uint64_t RoundUpToNextValidBoundsCheckImmediate(uint64_t i);
    223 
    224 #ifdef WASM_SUPPORTS_HUGE_MEMORY
    225 // On WASM_SUPPORTS_HUGE_MEMORY platforms, every asm.js or WebAssembly 32-bit
    226 // memory unconditionally allocates a huge region of virtual memory of size
    227 // wasm::HugeMappedSize. This allows all memory resizing to work without
    228 // reallocation and provides enough guard space for most offsets to be folded
    229 // into memory accesses.  See "Linear memory addresses and bounds checking" in
    230 // wasm/WasmMemory.cpp for more information.
    231 
    232 // Reserve 4GiB to support any i32 index.
    233 static const uint64_t HugeIndexRange = uint64_t(UINT32_MAX) + 1;
    234 // Reserve 32MiB to support most offset immediates. Any immediate that is over
    235 // this will require a bounds check to be emitted. 32MiB was chosen to
    236 // generously cover the max offset immediate, 20MiB, found in a corpus of wasm
    237 // modules.
    238 static const uint64_t HugeOffsetGuardLimit = 1 << 25;
    239 // Reserve a wasm page (64KiB) to support slop on unaligned accesses.
    240 static const uint64_t HugeUnalignedGuardPage = StandardPageSizeBytes;
    241 
    242 // Compute the total memory reservation.
    243 static const uint64_t HugeMappedSize =
    244    HugeIndexRange + HugeOffsetGuardLimit + HugeUnalignedGuardPage;
    245 
    246 // Try to keep the memory reservation aligned to the wasm page size. This
    247 // ensures that it's aligned to the system page size.
    248 static_assert(HugeMappedSize % StandardPageSizeBytes == 0);
    249 
    250 #endif
    251 
    252 // The size of the guard page for non huge-memories.
    253 static const size_t GuardSize = StandardPageSizeBytes;
    254 
    255 // The size of the guard page that included NULL pointer. Reserve a smallest
    256 // range for typical hardware, to catch near NULL pointer accesses, e.g.
    257 // for a structure fields operations.
    258 static const size_t NullPtrGuardSize = 4096;
    259 
    260 // Check if a range of wasm memory is within bounds, specified as byte offset
    261 // and length (using 32-bit indices). Omits one check by converting from
    262 // uint32_t to uint64_t, at which point overflow cannot occur.
    263 static inline bool MemoryBoundsCheck(uint32_t offset, uint32_t len,
    264                                     size_t memLen) {
    265  uint64_t offsetLimit = uint64_t(offset) + uint64_t(len);
    266  return offsetLimit <= memLen;
    267 }
    268 
    269 // Check if a range of wasm memory is within bounds, specified as byte offset
    270 // and length (using 64-bit indices).
    271 static inline bool MemoryBoundsCheck(uint64_t offset, uint64_t len,
    272                                     size_t memLen) {
    273  uint64_t offsetLimit = offset + len;
    274  bool didOverflow = offsetLimit < offset;
    275  bool tooLong = memLen < offsetLimit;
    276  return !didOverflow && !tooLong;
    277 }
    278 
    279 }  // namespace wasm
    280 }  // namespace js
    281 
    282 #endif  // wasm_memory_h