Medium CVSS 9.8 firefox Uninitialized Memory 🔧 Commit mapped

Overview

Medium
Severity
9.8
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionWhen String.toUpperCase() caused a string to get longer it was possible for uninitialized memory to be incorporated into the result string
ComponentSpiderMonkey
Bug ClassUninitialized Memory
Tracker1947139
Fix commitf3ce96184239 (firefox) +1/-1
CWECWE-908
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CISA KEVNot listed
Creditedanbu
Disclosed2025-03-04

Background

`StringChars<CharT>`
A SpiderMonkey helper that builds up string character data, starting with an inline buffer (inlineChars_) and, when the content outgrows it, reallocating into a heap-owned buffer.
`CharT`
The character type parameter: Latin1Char (1 byte) or char16_t (2 bytes). A two-byte string has sizeof(CharT) == 2.
`maybeRealloc`
The method that, on growth, allocates a larger buffer and copies the existing inline characters over before the new characters are written.
`String.prototype.toUpperCase`
Case conversion that can make a string longer (some code points upcase to multiple code units), forcing exactly the grow/realloc path here.

Root Cause Analysis

In js/src/vm/StringType.cpp, StringChars<CharT>::maybeRealloc copied the existing inline characters into the freshly allocated buffer with std::memcpy(chars.data(), inlineChars_, InlineLength);. memcpy’s third argument is a byte count, but InlineLength is a count of characters. For CharT == char16_t (two-byte strings), each character is 2 bytes, so this copied only InlineLength bytes — half of the InlineLength * 2 bytes actually occupied by the inline characters. The upper half of the inline region in the new buffer was therefore left with whatever uninitialized heap bytes the allocation returned. When toUpperCase() lengthened the string and subsequent characters were written past that region, the stale uninitialized bytes remained embedded in the final string contents, matching the advisory: “uninitialized memory to be incorporated into the result string.”

The patch changes the copy length to InlineLength * sizeof(CharT), so the full byte extent of the inline characters is copied for both Latin1Char and char16_t, leaving no uninitialized gap. This is the correct byte count for a memcpy of InlineLength elements of size sizeof(CharT).

Key insight
memcpy was given a character count where it needed a byte count, so for two-byte strings only half the inline characters were copied into the reallocated buffer and the remainder stayed uninitialized heap memory that then leaked into the resulting string; multiplying by sizeof(CharT) copies the full extent.

Attack Path

  1. Attacker script Runs JavaScript on a page (web-reachable, no privilege).
  2. Trigger growth Calls String.prototype.toUpperCase() on a two-byte (char16_t) string whose uppercase form is longer, forcing StringChars<char16_t>::maybeRealloc down the grow path.
  3. Short copy The reallocated buffer receives only InlineLength bytes of the InlineLength*2-byte inline data, leaving uninitialized heap bytes in the gap.
  4. Leak into result Those uninitialized bytes become part of the returned uppercase string, readable by script.
  5. Primitive Information disclosure: attacker-readable uninitialized heap memory in the content process, usable to defeat ASLR or leak adjacent allocation contents.

Impact Assessment

This is a content-process (renderer) uninitialized-memory read reachable by any web page with no privilege and no user interaction beyond running script (the CVSS vector is 9.8 AV:N/PR:N/UI:N/C:H/I:H/A:H, though Mozilla rates practical impact moderate). The substantiated primitive is an info-leak: uninitialized heap bytes surfaced inside a JS string the attacker can read, a strong read oracle for heap contents. There is no write/corruption primitive shown by the diff; its severity comes from the quality of the disclosure oracle rather than direct memory corruption.

Changed Functions

FunctionChangeNotes
if
js/src/vm/StringType.cpp
modified

Files Changed

  • js/src/vm/StringType.cpp

Audit Directions

  • `memcpy`/`memmove` with element counts
    Audit StringType.cpp and other CharT-templated string code for copies that pass a character count where a byte count is required (sizeof(CharT) missing).
  • Other grow/realloc paths
    Review every place inline buffers are promoted to heap buffers in the string/StringBuffer code for partial-copy gaps.
  • Two-byte vs Latin1 asymmetry
    Look for length arithmetic that is correct for Latin1Char but wrong for char16_t, the exact asymmetry exploited here.
diff --git a/js/src/vm/StringType.cpp b/js/src/vm/StringType.cpp
index 2bff1d4aa4b..f368399b875 100644
--- a/js/src/vm/StringType.cpp
+++ b/js/src/vm/StringType.cpp
@@ -2894,7 +2894,7 @@ bool js::StringChars<CharT>::maybeRealloc(JSContext* cx, size_t oldLength,
     if (!chars) {
       return false;
     }
-    std::memcpy(chars.data(), inlineChars_, InlineLength);
+    std::memcpy(chars.data(), inlineChars_, InlineLength * sizeof(CharT));
 
     ownedChars_.set(std::move(chars));
     return true;
Loading diff…