Released December 11, 2024

CVE-2024-54505

CVSS 8.8 severity high CWE-843 vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

Overview

Impact:

Description: Processing maliciously crafted web content may lead to memory corruption

Researchers: Gary Kwong

Key insight. ToString/CallStringConstructor unconditionally marked their operand as UsesAsOther even when it could not be an ‘other’ type, feeding inconsistent usage flags into representation choices; gating the flag on SpecOther fixes the miscompilation.

Patch Details

diff --git a/JSTests/stress/string-add-conversion-unused.js b/JSTests/stress/string-add-conversion-unused.js
new file mode 100644
index 000000000000..96e1377859a0
--- /dev/null
+++ b/JSTests/stress/string-add-conversion-unused.js
@@ -0,0 +1,14 @@
+var x = [, 9999999999];
+var count = 0;
+for (
+  y = 10;
+  (function () {
+    z = y;
+    " " + x[y];
+    return 9;
+  })();
+  y--
+) {
+  if (count >= 99) break;
+  count++;
+}
diff --git a/Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp b/Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp
index d1c2540344cd..e2eb6b334eaf 100644
--- a/Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp
@@ -528,7 +528,9 @@ private:
             
         case ToString:
         case CallStringConstructor: {
-            node->child1()->mergeFlags(NodeBytecodeUsesAsNumber | NodeBytecodeUsesAsOther | NodeBytecodeNeedsNaNOrInfinity);
+            if (typeFilterFor(node->child1().useKind()) & SpecOther)
+                node->child1()->mergeFlags(NodeBytecodeUsesAsOther);
+            node->child1()->mergeFlags(NodeBytecodeUsesAsNumber | NodeBytecodeNeedsNaNOrInfinity);
             break;
         }
             

Background

Advisory: Apple Security Advisory

Attribute Value
CVE CVE-2024-54505
Bugzilla 282661
Component JSC
Bug Class TypeConfusion
Severity medium
Commit 2d5e29d47324834c…
Advisory Apple Advisory

Terminology

  • Backwards propagation / usage flags — A DFG phase that records how values are used (NodeBytecodeUsesAsNumber/AsOther/…) to guide later representation and conversion choices.
  • SpecOther — The speculated type for ‘other’ primitives (null/undefined); UsesAsOther is only meaningful when the operand can be SpecOther.
  • ToString / CallStringConstructor — DFG nodes for String() coercion whose operand-usage flags feed downstream optimization.

Root Cause Analysis

This fixes an incorrect backward-propagation of usage flags for ToString/CallStringConstructor in the DFG that could mislead value-representation decisions into a type confusion. DFGBackwardsPropagationPhase walks the graph backward to record how each value is used (NodeBytecodeUses* flags), which downstream phases consult to pick number/other representations and conversions. For ToString and CallStringConstructor, the pre-patch code unconditionally merged NodeBytecodeUsesAsNumber | NodeBytecodeUsesAsOther | NodeBytecodeNeedsNaNOrInfinity onto the operand (child1). Marking the operand as UsesAsOther even when its use kind cannot actually be an ‘other’ (null/undefined, SpecOther) type feeds an inconsistent usage signal into later optimization: the representation/conversion chosen for the operand based on these flags becomes inconsistent with how the value is really produced/consumed, and that mismatch is exploitable as a type confusion (a value handled as the wrong representation), which the advisory rates as memory corruption. The fix gates the UsesAsOther flag on the operand actually being able to be SpecOther: if (typeFilterFor(node->child1().useKind()) & SpecOther) node->child1()->mergeFlags(NodeBytecodeUsesAsOther); and always merges UsesAsNumber | NeedsNaNOrInfinity. The restored invariant is that the UsesAsOther usage flag is propagated only when the operand’s use kind admits an ‘other’ type, so downstream representation choices are consistent. INFERENCE: the precise downstream miscompilation that turns the wrong flag into a corrupting type confusion is not contained in this one-hunk diff; the patch establishes the incorrect unconditional UsesAsOther propagation as the root cause.

Attack Path

1. Reach the DFG

Run JS with a ToString/String() over a value whose DFG use kind cannot be an ‘other’ type.

2. Mis-propagate UsesAsOther

Backward propagation unconditionally marks the operand UsesAsOther, an inconsistent usage signal.

3. Choose a wrong representation

A later phase picks a value representation/conversion based on the incorrect flags.

4. Type confusion

The value is handled as the wrong representation, a memory-corruption primitive in the WebContent process (inferred escalation).

Impact Assessment

A DFG usage-flag miscompilation in the WebContent process that the advisory rates as memory corruption via type confusion. The shown change corrects the flag propagation; the concrete corrupting path downstream is not in this diff, so the demonstrated defect is an incorrect optimization input rather than a fully-shown write primitive, though the class (JSC type confusion) is high-value.

Audit Directions

Unconditional usage-flag merges

Audit DFGBackwardsPropagationPhase for other nodes that merge NodeBytecodeUsesAsOther/AsNumber unconditionally regardless of the operand’s use kind / possible types.

Flag-driven representation

Trace consumers of NodeBytecodeUsesAsOther to confirm they tolerate (or the producers correctly gate) the flag on SpecOther.

Changed Functions

Function File Change Note
BackwardsPropagationPhase (ToString / CallStringConstructor) Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp modified Merges NodeBytecodeUsesAsOther only when typeFilterFor(child1 useKind) & SpecOther, instead of unconditionally, so the usage flag matches the operand’s possible types.

Files Changed

Source Files

  • Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp

Test Files

  • JSTests/stress/string-add-conversion-unused.js

Regression Test

The fix ships this proof-of-concept / regression test:

diff --git a/JSTests/stress/string-add-conversion-unused.js b/JSTests/stress/string-add-conversion-unused.js
new file mode 100644
index 000000000000..96e1377859a0
--- /dev/null
+++ b/JSTests/stress/string-add-conversion-unused.js
@@ -0,0 +1,14 @@
+var x = [, 9999999999];
+var count = 0;
+for (
+  y = 10;
+  (function () {
+    z = y;
+    " " + x[y];
+    return 9;
+  })();
+  y--
+) {
+  if (count >= 99) break;
+  count++;
+}