tor-browser

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

commit 5c24f7dba833e96b28ecd6f00587e6aff19daeea
parent c47d8476b9d3c4c8876005a24977498820610119
Author: André Bargull <andre.bargull@gmail.com>
Date:   Wed, 29 Oct 2025 10:44:07 +0000

Bug 1996770: Align memory before reinterpreting as uint32. r=iain

Align the memory to avoid an assertion failure in `SharedMem::cast`.

Unaligned memory reads are always allowed in x86 and arm64, and often also in
arm32. `ARMv7` added support for unaligned memory loads and stores (`ldr` and
`str` instructions), except when SCTLR.A is set to 1, in which case an
alignment fault occurs.

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

Diffstat:
Ajs/src/tests/non262/TypedArray/base64-and-hex-unaligned-buffer.js | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mjs/src/vm/TypedArrayObject.cpp | 65++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
2 files changed, 141 insertions(+), 9 deletions(-)

diff --git a/js/src/tests/non262/TypedArray/base64-and-hex-unaligned-buffer.js b/js/src/tests/non262/TypedArray/base64-and-hex-unaligned-buffer.js @@ -0,0 +1,85 @@ +function toBase64(Buffer) { + for (var length = 1; length <= 32; ++length) { + var buffer = new Buffer(length); + for (var offset = 0; offset < length; ++offset) { + var u8 = new Uint8Array(buffer, offset); + assertEq(u8.length > 0, true); + + var str = u8.toBase64(); + assertEq(str.startsWith("A"), true); + assertEq(str.endsWith("A") || str.endsWith("="), true); + } + } +} +toBase64(ArrayBuffer); +if (typeof SharedArrayBuffer === "function"); + toBase64(SharedArrayBuffer); + +function toHex(Buffer) { + for (var length = 1; length <= 32; ++length) { + var buffer = new Buffer(length); + for (var offset = 0; offset < length; ++offset) { + var u8 = new Uint8Array(buffer, offset); + assertEq(u8.length > 0, true); + + var str = u8.toHex(); + assertEq(str.startsWith("00"), true); + assertEq(str.endsWith("00"), true); + } + } +} +toHex(ArrayBuffer); +if (typeof SharedArrayBuffer === "function"); + toHex(SharedArrayBuffer); + +function setFromHex(Buffer) { + var input = "aabbccddeeff00112233445566778899"; + + for (var length = 1; length <= 32; ++length) { + var buffer = new Buffer(length); + for (var offset = 0; offset < length; ++offset) { + var u8 = new Uint8Array(buffer, offset); + assertEq(u8.length > 0, true); + + for (var str = input; str.length; str = str.slice(0, -2)) { + u8.setFromHex(str); + assertEq(u8[0], 0xaa); + } + } + } +} +setFromHex(ArrayBuffer); +if (typeof SharedArrayBuffer === "function"); + setFromHex(SharedArrayBuffer); + +function setFromBase64(Buffer) { + var input = "AAA".repeat(16); + + for (var length = 1; length <= 32; ++length) { + var buffer = new Buffer(length); + for (var offset = 0; offset < length; ++offset) { + var u8 = new Uint8Array(buffer, offset); + assertEq(u8.length > 0, true); + + for (var str = input; str.length; str = str.slice(0, -4)) { + u8.setFromBase64(str); + assertEq(u8[0], 0); + + u8.setFromBase64(str.slice(0, -1) + "="); + assertEq(u8[0], 0); + + u8.setFromBase64(str.slice(0, -2) + "=="); + assertEq(u8[0], 0); + + u8.setFromBase64(str.slice(0, -3), {lastChunkHandling: "stop-before-partial"}); + assertEq(u8[0], 0); + } + } + } +} +setFromBase64(ArrayBuffer); +if (typeof SharedArrayBuffer === "function"); + setFromBase64(SharedArrayBuffer); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp @@ -4380,6 +4380,10 @@ static size_t FromHex(const CharT* chars, size_t length, } }; + auto decode2Chars = [&](const CharT* chars) { + return (decodeChar(chars[0]) << 4) | (decodeChar(chars[1]) << 0); + }; + auto decode4Chars = [&](const CharT* chars) { return (decodeChar(chars[2]) << 12) | (decodeChar(chars[3]) << 8) | (decodeChar(chars[0]) << 4) | (decodeChar(chars[1]) << 0); @@ -4392,12 +4396,34 @@ static size_t FromHex(const CharT* chars, size_t length, MOZ_ASSERT(length % 2 == 0); // Process eight characters per loop iteration. - size_t alignedLength = length & ~7; - if (index < alignedLength) { + if (length >= 8) { + // Align |data| to uint32_t. + if (MOZ_UNLIKELY(data.unwrapValue() & 3)) { + // Performs at most three iterations until |data| is aligned, reading up + // to six characters. + while (data.unwrapValue() & 3) { + // Step 6.a and 6.d. + uint32_t byte = decode2Chars(chars + index); + + // Step 6.b. + if (MOZ_UNLIKELY(int32_t(byte) < 0)) { + return index; + } + MOZ_ASSERT(byte <= 0xff); + + // Step 6.c. + index += 2; + + // Step 6.e. + Ops::store(data++, uint8_t(byte)); + } + } + auto data32 = data.template cast<uint32_t*>(); // Step 6. - while (index < alignedLength) { + size_t lastValidIndex = length - 8; + while (index <= lastValidIndex) { // Steps 6.a and 6.d. uint32_t word1 = decode4Chars(chars + index); @@ -4433,12 +4459,8 @@ static size_t FromHex(const CharT* chars, size_t length, // Step 6. while (index < length) { - // Step 6.a. - auto c0 = chars[index + 0]; - auto c1 = chars[index + 1]; - - // Step 6.d. - uint32_t byte = (decodeChar(c0) << 4) | (decodeChar(c1) << 0); + // Step 6.a and 6.d. + uint32_t byte = decode2Chars(chars + index); // Step 6.b. if (MOZ_UNLIKELY(int32_t(byte) < 0)) { @@ -5506,6 +5528,31 @@ static void ToBase64(TypedArrayObject* tarray, size_t length, Alphabet alphabet, auto toRead = length; if (toRead >= 12) { + // Align |data| to uint32_t. + if (MOZ_UNLIKELY(data.unwrapValue() & 3)) { + // Performs at most three iterations until |data| is aligned, reading up + // to nine bytes. + while (data.unwrapValue() & 3) { + // Combine three input bytes into a single uint24 value. + auto byte0 = Ops::load(data++); + auto byte1 = Ops::load(data++); + auto byte2 = Ops::load(data++); + auto u24 = (uint32_t(byte0) << 16) | (uint32_t(byte1) << 8) | byte2; + + // Encode the uint24 value as base64. + char chars[] = { + encode(u24 >> 18), + encode(u24 >> 12), + encode(u24 >> 6), + encode(u24 >> 0), + }; + sb.infallibleAppend(chars, sizeof(chars)); + + MOZ_ASSERT(toRead >= 3); + toRead -= 3; + } + } + auto data32 = data.template cast<uint32_t*>(); for (; toRead >= 12; toRead -= 12) { // Read three 32-bit words.