tor-browser

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

commit a548794c3bff22010945cd0a4f80fc05c9d689ba
parent 338cdae51c4db122cb9a9fcacce7f13efdd98940
Author: Asumu Takikawa <asumu@igalia.com>
Date:   Mon,  8 Dec 2025 16:02:52 +0000

Bug 1977854 - Wasm custom page sizes part 2: decoding of page sizes. r=rhunt,bvisness

Differential Revision: https://phabricator.services.mozilla.com/D257728

Diffstat:
Ajs/src/jit-test/tests/wasm/custom-page-sizes/decoding.js | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajs/src/jit-test/tests/wasm/custom-page-sizes/directives.txt | 1+
Mjs/src/vm/ArrayBufferObject.cpp | 5+++--
Mjs/src/vm/SharedArrayObject.cpp | 4++--
Mjs/src/wasm/WasmBCMemory.cpp | 2+-
Mjs/src/wasm/WasmConstants.h | 30++++++++++++++++++++++++------
Mjs/src/wasm/WasmInstance.cpp | 4++--
Mjs/src/wasm/WasmJS.cpp | 5+++--
Mjs/src/wasm/WasmMemory.cpp | 2+-
Mjs/src/wasm/WasmMemory.h | 21+++++++++++++++------
Mjs/src/wasm/WasmModuleTypes.h | 9+++++++--
Mjs/src/wasm/WasmValidate.cpp | 17++++++++++++++++-
12 files changed, 189 insertions(+), 25 deletions(-)

diff --git a/js/src/jit-test/tests/wasm/custom-page-sizes/decoding.js b/js/src/jit-test/tests/wasm/custom-page-sizes/decoding.js @@ -0,0 +1,114 @@ +// |jit-test| skip-if: !wasmCustomPageSizesEnabled() + +load(libdir + "wasm-binary.js"); + +wasmValidateText(`(module + (memory 0 1 (pagesize 1)) + ) +`); + +wasmValidateText(`(module + (memory 0 1 (pagesize 65536)) + ) +`); + +// Missing page size. +assertErrorMessage(() => wasmEvalBinary(moduleWithSections([ {name:memoryId, body:[0x01, 0x08, 0x00]} ])), WebAssembly.CompileError, /failed to decode custom page size/); + +// Too many bytes after page size. +assertErrorMessage(() => wasmEvalBinary(moduleWithSections([ {name:memoryId, body:[0x01, 0x08, 0x00, 0x10, 0x01]} ])), WebAssembly.CompileError, /byte size mismatch in memory section/); + +// Tables shouldn't have page sizes. +assertErrorMessage(() => wasmEvalBinary(moduleWithSections([ {name:tableId, body:[0x01, FuncRefCode, 0x08, 0x00, 0x10]} ])), WebAssembly.CompileError, /unexpected bits set in flags: 8/); + +function checkInvalidPageSize(pageSize) { + assertErrorMessage(() => wasmValidateText(`(module + (memory 0 1 (pagesize ${pageSize})) + )`), + WebAssembly.CompileError, /bad custom page size/); +} + +checkInvalidPageSize(8); +checkInvalidPageSize(128); +checkInvalidPageSize(131072); + +wasmValidateText(`(module + (memory 0 65536 (pagesize 1)) + ) +`); + +wasmValidateText(`(module + (memory i32 0 65536 (pagesize 65536)) + ) +`); + +wasmValidateText(`(module + (memory i32 0 4_294_967_295 (pagesize 1)) + ) +`); + +wasmValidateText(`(module + (memory i64 0 4_294_967_296 (pagesize 1)) + ) +`); + +wasmValidateText(`(module + (memory i64 0 9_007_199_254_740_991 (pagesize 1)) + ) +`); + +function maxPageCount(pageSize) { + if (pageSize === 1) + return 0xffffffff; + return 1 << (32 - Math.log2(pageSize)); +} + +function maxPageCount64(pageSize) { + if (pageSize === 1) + return BigInt(Number.MAX_SAFE_INTEGER); + return (1n << (53n - BigInt(Math.log2(pageSize)))) - 1n; +} + +function checkPageCount(pageSize, pageCount) { + function check() { + return wasmValidateText(`(module + (memory 0 ${pageCount} (pagesize ${pageSize})) + )`); + } + if (pageCount <= maxPageCount(pageSize)) + check(); + else if (pageCount > 0xffffffff) + // Unrepresentable in the text format as it's out of bounds + assertErrorMessage(check, SyntaxError, + /invalid u32 number/); + else + assertErrorMessage(check, WebAssembly.CompileError, + /maximum memory size too big/); +} + +function checkPageCount64(pageSize, pageCount) { + function check() { + return wasmValidateText(`(module + (memory i64 0 ${pageCount} (pagesize ${pageSize})) + )`); + } + if (pageCount <= maxPageCount64(pageSize)) + check(); + else { + assertErrorMessage(check, WebAssembly.CompileError, + /maximum memory size too big/); + } +} + +for (pageSize of [1, 65536]) + for (pageCount of [0, 1, 42, maxPageCount(pageSize)-1, + maxPageCount(pageSize), + maxPageCount(pageSize)+1, + 0xffffffff]) + checkPageCount(pageSize, pageCount); + +for (pageSize of [1, 65536]) + for (pageCount of [0n, 1n, 42n, maxPageCount64(pageSize)-1n, + maxPageCount64(pageSize), + maxPageCount64(pageSize)+1n]) + checkPageCount64(pageSize, pageCount); diff --git a/js/src/jit-test/tests/wasm/custom-page-sizes/directives.txt b/js/src/jit-test/tests/wasm/custom-page-sizes/directives.txt @@ -0,0 +1 @@ +|jit-test| test-also=-P wasm_custom_page_sizes; test-also=--wasm-compiler=baseline -P wasm_custom_page_sizes; test-also=--wasm-compiler=optimizing -P wasm_custom_page_sizes; test-also=--disable-wasm-huge-memory -P wasm_custom_page_sizes; include:wasm.js diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp @@ -554,8 +554,8 @@ bool ArrayBufferObject::maxByteLengthGetterImpl(JSContext* cx, Pages sourceMaxPages = buffer->wasmSourceMaxPages().value(); uint64_t sourceMaxBytes = sourceMaxPages.byteLength64(); - MOZ_ASSERT(sourceMaxBytes <= - wasm::StandardPageSizeBytes * wasm::MaxMemory64PagesValidation); + MOZ_ASSERT(sourceMaxBytes <= wasm::StandardPageSizeBytes * + wasm::MaxMemory64StandardPagesValidation); args.rval().setNumber(double(sourceMaxBytes)); return true; @@ -1797,6 +1797,7 @@ ArrayBufferObjectMaybeShared* js::CreateWasmBuffer( memory.initialPages() <= wasm::MaxMemoryPages(memory.addressType(), memory.pageSize())); MOZ_RELEASE_ASSERT(cx->wasm().haveSignalHandlers); + MOZ_ASSERT(memory.pageSize() == wasm::PageSize::Standard); if (memory.isShared()) { if (!cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()) { diff --git a/js/src/vm/SharedArrayObject.cpp b/js/src/vm/SharedArrayObject.cpp @@ -345,8 +345,8 @@ bool SharedArrayBufferObject::maxByteLengthGetterImpl(JSContext* cx, Pages sourceMaxPages = buffer->rawWasmBufferObject()->wasmSourceMaxPages(); uint64_t sourceMaxBytes = sourceMaxPages.byteLength64(); - MOZ_ASSERT(sourceMaxBytes <= - wasm::StandardPageSizeBytes * wasm::MaxMemory64PagesValidation); + MOZ_ASSERT(sourceMaxBytes <= wasm::StandardPageSizeBytes * + wasm::MaxMemory64StandardPagesValidation); args.rval().setNumber(double(sourceMaxBytes)); return true; diff --git a/js/src/wasm/WasmBCMemory.cpp b/js/src/wasm/WasmBCMemory.cpp @@ -147,7 +147,7 @@ RegI32 BaseCompiler::popConstMemoryAccess<RegI32>(MemoryAccessDesc* access, // Validation ensures that the offset is in 32-bit range, and the calculation // of the limit cannot overflow due to our choice of HugeOffsetGuardLimit. #ifdef WASM_SUPPORTS_HUGE_MEMORY - static_assert(MaxMemory32PagesValidation * StandardPageSizeBytes <= + static_assert(MaxMemory32StandardPagesValidation * StandardPageSizeBytes <= UINT64_MAX - HugeOffsetGuardLimit); #endif uint64_t ea = uint64_t(addr) + uint64_t(access->offset32()); diff --git a/js/src/wasm/WasmConstants.h b/js/src/wasm/WasmConstants.h @@ -225,12 +225,21 @@ enum class LimitsFlags { HasMaximum = 0x1, IsShared = 0x2, IsI64 = 0x4, +#ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES + HasCustomPageSize = 0x8, +#endif }; enum class LimitsMask { Table = uint8_t(LimitsFlags::HasMaximum) | uint8_t(LimitsFlags::IsI64), +#ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES + Memory = uint8_t(LimitsFlags::HasMaximum) | uint8_t(LimitsFlags::IsShared) | + uint8_t(LimitsFlags::IsI64) | + uint8_t(LimitsFlags::HasCustomPageSize), +#else Memory = uint8_t(LimitsFlags::HasMaximum) | uint8_t(LimitsFlags::IsShared) | uint8_t(LimitsFlags::IsI64), +#endif }; enum class DataSegmentKind { @@ -1130,10 +1139,14 @@ enum class FieldFlags { Mutable = 0x01, AllowedMask = 0x01 }; enum class FieldWideningOp { None, Signed, Unsigned }; -// The WebAssembly spec hard-codes the virtual page size to be 64KiB and -// requires the size of linear memory to always be a multiple of 64KiB. - -enum class PageSize { Standard = 16 }; +// The WebAssembly custom page sizes proposal allows for a virtual page size of +// either 64KiB, or 1 byte. We call these Standard and Tiny, respectively. +enum class PageSize { +#ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES + Tiny = 0, +#endif + Standard = 16 +}; // These limits are agreed upon with other engines for consistency. @@ -1158,8 +1171,13 @@ static const unsigned MaxLocals = 50000; static const unsigned MaxParams = 1000; static const unsigned MaxResults = 1000; static const unsigned MaxStructFields = 10000; -static const uint64_t MaxMemory32PagesValidation = uint64_t(1) << 16; -static const uint64_t MaxMemory64PagesValidation = (uint64_t(1) << 37) - 1; +#ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES +static const uint64_t MaxMemory32TinyPagesValidation = UINT32_MAX; +static const uint64_t MaxMemory64TinyPagesValidation = (uint64_t(1) << 53) - 1; +#endif +static const uint64_t MaxMemory32StandardPagesValidation = uint64_t(1) << 16; +static const uint64_t MaxMemory64StandardPagesValidation = + (uint64_t(1) << 37) - 1; static const unsigned MaxModuleBytes = 1024 * 1024 * 1024; static const unsigned MaxFunctionBytes = 7654321; static const unsigned MaxArrayNewFixedElements = 10000; diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp @@ -614,7 +614,7 @@ static int32_t PerformWake(Instance* instance, PtrT byteOffset, int32_t count, Pages pages = instance->memory(memoryIndex)->volatilePages(); #ifdef JS_64BIT // Ensure that the memory size is no more than 4GiB. - MOZ_ASSERT(pages <= Pages::fromPageCount(MaxMemory32PagesValidation, + MOZ_ASSERT(pages <= Pages::fromPageCount(MaxMemory32StandardPagesValidation, pages.pageSize())); #endif return uint32_t(pages.pageCount()); @@ -631,7 +631,7 @@ static int32_t PerformWake(Instance* instance, PtrT byteOffset, int32_t count, Pages pages = instance->memory(memoryIndex)->volatilePages(); #ifdef JS_64BIT - MOZ_ASSERT(pages <= Pages::fromPageCount(MaxMemory64PagesValidation, + MOZ_ASSERT(pages <= Pages::fromPageCount(MaxMemory64StandardPagesValidation, pages.pageSize())); #endif return pages.pageCount(); diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp @@ -2177,8 +2177,9 @@ bool WasmMemoryObject::construct(JSContext* cx, unsigned argc, Value* vp) { RootedObject obj(cx, &args[0].toObject()); Limits limits; if (!GetLimits(cx, obj, LimitsKind::Memory, &limits) || - !CheckLimits(cx, MaxMemoryPagesValidation(limits.addressType), - LimitsKind::Memory, &limits)) { + !CheckLimits( + cx, MaxMemoryPagesValidation(limits.addressType, limits.pageSize), + LimitsKind::Memory, &limits)) { return false; } diff --git a/js/src/wasm/WasmMemory.cpp b/js/src/wasm/WasmMemory.cpp @@ -316,7 +316,7 @@ static_assert(MaxInlineMemoryFillLength < MinOffsetGuardLimit, "precondition"); wasm::Pages wasm::MaxMemoryPages(AddressType t, PageSize pageSize) { #ifdef JS_64BIT MOZ_ASSERT_IF(t == AddressType::I64, !IsHugeMemoryEnabled(t)); - size_t desired = MaxMemoryPagesValidation(t); + size_t desired = MaxMemoryPagesValidation(t, pageSize); size_t actual = ArrayBufferObject::ByteLengthLimit / PageSizeInBytes(pageSize); return wasm::Pages::fromPageCount(std::min(desired, actual), pageSize); diff --git a/js/src/wasm/WasmMemory.h b/js/src/wasm/WasmMemory.h @@ -63,7 +63,7 @@ static_assert(StandardPageSizeBytes == 64 * 1024); // By spec, see // https://github.com/WebAssembly/spec/issues/1895#issuecomment-2895078022 -static_assert((StandardPageSizeBytes * MaxMemory64PagesValidation) <= +static_assert((StandardPageSizeBytes * MaxMemory64StandardPagesValidation) <= (uint64_t(1) << 53) - 1); // Pages is a typed unit representing a multiple of the page size, which @@ -166,11 +166,11 @@ struct Pages { constexpr auto operator<=>(const Pages& other) const { MOZ_RELEASE_ASSERT(other.pageSize_ == pageSize_); return pageCount_ <=> other.pageCount_; - }; + } constexpr auto operator==(const Pages& other) const { MOZ_RELEASE_ASSERT(other.pageSize_ == pageSize_); return pageCount_ == other.pageCount_; - }; + } }; // The largest number of pages the application can request. @@ -184,9 +184,18 @@ static inline size_t MaxMemoryBytes(AddressType t, PageSize pageSize) { // A value representing the largest valid value for boundsCheckLimit. extern size_t MaxMemoryBoundsCheckLimit(AddressType t, PageSize pageSize); -static inline uint64_t MaxMemoryPagesValidation(AddressType addressType) { - return addressType == AddressType::I32 ? MaxMemory32PagesValidation - : MaxMemory64PagesValidation; +static inline uint64_t MaxMemoryPagesValidation(AddressType addressType, + PageSize pageSize) { +#ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES + if (pageSize == PageSize::Tiny) { + return addressType == AddressType::I32 ? MaxMemory32TinyPagesValidation + : MaxMemory64TinyPagesValidation; + } +#endif + + MOZ_ASSERT(pageSize == PageSize::Standard); + return addressType == AddressType::I32 ? MaxMemory32StandardPagesValidation + : MaxMemory64StandardPagesValidation; } static inline uint64_t MaxTableElemsValidation(AddressType addressType) { diff --git a/js/src/wasm/WasmModuleTypes.h b/js/src/wasm/WasmModuleTypes.h @@ -855,7 +855,8 @@ struct MemoryDesc { // See static_assert after MemoryDesc for why this is safe for memory32. MOZ_ASSERT_IF(addressType() == AddressType::I64, limits.initial <= UINT64_MAX / PageSizeInBytes(pageSize())); - return initialPages().byteLength(); + return addressType() == AddressType::I64 ? initialPages().byteLength64() + : initialPages().byteLength(); } MemoryDesc() = default; @@ -869,7 +870,11 @@ using MemoryDescVector = Vector<MemoryDesc, 1, SystemAllocPolicy>; // We never need to worry about overflow with a Memory32 field when // using a uint64_t. -static_assert(MaxMemory32PagesValidation <= UINT64_MAX / StandardPageSizeBytes); +static_assert(MaxMemory32StandardPagesValidation <= + UINT64_MAX / StandardPageSizeBytes); +#ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES +static_assert(MaxMemory32TinyPagesValidation <= UINT64_MAX); +#endif struct TableDesc { Limits limits; diff --git a/js/src/wasm/WasmValidate.cpp b/js/src/wasm/WasmValidate.cpp @@ -2857,6 +2857,20 @@ static bool DecodeLimits(Decoder& d, LimitsKind kind, Limits* limits) { if (kind == LimitsKind::Memory) { limits->pageSize = PageSize::Standard; +#ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES + if (flags & uint8_t(LimitsFlags::HasCustomPageSize)) { + uint32_t customPageSize; + if (!d.readVarU32(&customPageSize)) { + return d.fail("failed to decode custom page size"); + } + + if (customPageSize == static_cast<uint32_t>(PageSize::Tiny)) { + limits->pageSize = PageSize::Tiny; + } else if (customPageSize != static_cast<uint32_t>(PageSize::Standard)) { + return d.fail("bad custom page size"); + } + } +#endif } return true; @@ -2962,7 +2976,8 @@ static bool DecodeMemoryTypeAndLimits(Decoder& d, CodeMetadata* codeMeta, return false; } - uint64_t maxField = MaxMemoryPagesValidation(limits.addressType); + uint64_t maxField = + MaxMemoryPagesValidation(limits.addressType, limits.pageSize); if (limits.initial > maxField) { return d.fail("initial memory size too big");