CVE-2025-31273
Overview
Impact:
Description: Processing maliciously crafted web content may lead to memory corruption
Researchers: Yuhao Hu, Yan Kang, Chenggang Wu, and Xiaojie Wei
Key insight. The add path cached storage capacity and the new-entry index and bumped the live count before normalizeMapKey, which can reallocate the table — so the write landed at a stale index; reordering the bookkeeping after the side effect fixes it.
Patch Details
diff --git a/Source/JavaScriptCore/runtime/OrderedHashTableHelper.h b/Source/JavaScriptCore/runtime/OrderedHashTableHelper.h
index 8c9190faebc2..ed3de117de16 100644
--- a/Source/JavaScriptCore/runtime/OrderedHashTableHelper.h
+++ b/Source/JavaScriptCore/runtime/OrderedHashTableHelper.h
@@ -439,12 +439,6 @@ public:
Storage* candidate = expandIfNeeded(globalObject, owner, base);
RETURN_IF_EXCEPTION(scope, void());
- Storage& candidateRef = *candidate;
- TableSize capacity = Helper::capacity(candidateRef);
- Entry newEntry = usedCapacity(candidateRef);
- TableIndex newEntryKeyIndex = entryDataStartIndex(dataTableStartIndex(capacity), newEntry);
- incrementAliveEntryCount(candidateRef);
-
bool firstAliveEntry = result.normalizedKey.isEmpty();
if (firstAliveEntry) [[unlikely]] {
result.normalizedKey = normalizeMapKey(key);
@@ -452,6 +446,12 @@ public:
RETURN_IF_EXCEPTION(scope, void());
}
+ Storage& candidateRef = *candidate;
+ TableSize capacity = Helper::capacity(candidateRef);
+ Entry newEntry = usedCapacity(candidateRef);
+ TableIndex newEntryKeyIndex = entryDataStartIndex(dataTableStartIndex(capacity), newEntry);
+ incrementAliveEntryCount(candidateRef);
+
bool rehashed = &base != candidate;
if (rehashed || firstAliveEntry) [[unlikely]]
result.bucketIndex = bucketIndex(capacity, result.hash);
Background
Advisory: Apple Security Advisory
| Attribute | Value |
|---|---|
| CVE | CVE-2025-31273 |
| Bugzilla | 293579 |
| Component | JSC |
| Bug Class | OOB |
| Severity | medium |
| Commit | a05032c6961001c2… |
| Advisory | Apple Advisory |
Terminology
- OrderedHashTable — The insertion-ordered hash table backing JavaScript Map and Set in JSC.
- normalizeMapKey — Normalizes a key (e.g. -0 to 0); for object/proxy keys it can invoke user code with side effects.
- Rehash / reallocation — Growing the table moves its storage; indices captured before a reallocation become stale.
Root Cause Analysis
This fixes a memory-corruption bug in JavaScriptCore’s OrderedHashTable (backing Map/Set) where storage-derived indices were computed before a JS-observable side effect that can reallocate the storage. In add(), after expandIfNeeded returns the candidate storage, the pre-patch code immediately captured capacity, newEntry = usedCapacity(candidate), the new entry’s key index, and called incrementAliveEntryCount(candidate) — and only THEN, for the first alive entry, called normalizeMapKey(key). normalizeMapKey can run arbitrary JS (via valueOf/toString or a proxy) which may re-enter the engine, allocate, trigger GC, or cause the table to rehash/reallocate, invalidating the previously captured candidate/capacity/newEntry index. The subsequent write then uses a stale index into freed or moved storage — memory corruption. The fix reorders the operations: it performs the firstAliveEntry normalizeMapKey step first, and only afterward computes capacity/newEntry/newEntryKeyIndex and increments the alive count from the current candidate state. The restored invariant is that storage-derived indices and counters are computed after any step that can reallocate the storage.
Attack Path
1. Insert a key with side effects
Add to a Map/Set a key whose normalization (valueOf/toString or proxy) runs attacker JS.
2. Reallocate during normalization
From that callback, cause the table to rehash/reallocate (or trigger GC), invalidating the pre-computed candidate storage and indices.
3. Write at a stale index
The add continues using the captured capacity/newEntry index into the now-freed/moved storage.
4. Corrupt memory
The stale-index write corrupts heap memory in the WebContent process.
Impact Assessment
A memory-corruption primitive in the WebContent process reachable from ordinary Map/Set usage with a side-effecting key. Stale-index writes into reallocated hash-table storage are controllable with grooming; the advisory rates it memory corruption.
Audit Directions
Same file: order of side effects
Audit OrderedHashTableHelper for other places that capture storage pointers/indices before a call that can rehash, GC, or run user code.
Side-effecting key normalization
Grep Map/Set/WeakMap operations for cached candidate/capacity values used across normalizeMapKey / hashValue calls that may re-enter.
Changed Functions
| Function | File | Change | Note |
|---|---|---|---|
OrderedHashTable add (index bookkeeping) |
Source/JavaScriptCore/runtime/OrderedHashTableHelper.h |
modified | Moves capacity/newEntry/newEntryKeyIndex computation and incrementAliveEntryCount to AFTER the normalizeMapKey side effect, so indices reflect the possibly-reallocated storage. |
Files Changed
Source Files
Source/JavaScriptCore/runtime/OrderedHashTableHelper.h