Closed Bug 2036983 (CVE-2026-8389) Opened 4 months ago Closed 4 months ago

Baseline RetAddrEntry PC Offset Truncation

Categories

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

defect

Tracking

()

RESOLVED FIXED
152 Branch
Tracking Status
firefox-esr115 --- unaffected
firefox-esr140 --- unaffected
firefox150 + fixed
firefox151 + fixed
firefox152 + fixed

People

(Reporter: gaddofpwn, Assigned: jandem)

References

(Blocks 2 open bugs, Regression)

Details

(5 keywords, Whiteboard: [client-bounty-form][pp1][adv-main150.0.3+])

Attachments

(8 files)

Attached file poc.zip —

Baseline RetAddrEntry PC Offset Truncation

Summary

The Baseline JIT return-address table stores the bytecode PC offset in the 28-bit RetAddrEntry::pcOffset_ bitfield. The normal Baseline entry path rejects oversized scripts on x86-64 with BaselineMaxScriptLength == 0x0fffffff, matching that 28-bit storage limit. The eager Baseline compilation path used during stencil instantiation does not apply the same length check.

As a result, content can first seed a JitHint and then load a larger script with the same script key. If the larger script places an IC at a bytecode offset above the 28-bit limit, the off-thread eager Baseline compiler attempts to store that oversized PC offset in RetAddrEntry. Debug builds abort on pcOffset_ == pcOffset. Release builds truncate the high bits in the bitfield, producing incorrect BaselineScript return-address metadata.

Affected Component

  • SpiderMonkey Baseline JIT
  • off-thread eager Baseline compilation
  • JitHints-based eager compilation
  • BaselineScript return-address table

Impact

Confirmed impact:

  • Firefox content process assertion failure at js/src/jit/BaselineJIT.h:150
  • The content process exits with signal 11 after the assertion.
  • The symbolized stack enters RetAddrEntry::RetAddrEntry from BaselineCodeGen::emitNextIC, emit_GetGName, and BaselineCompileTask::runTask.

Security relevance:

  • This is not only a debug assertion. pcOffset_ is a 28-bit bitfield, so release builds store a truncated PC offset.
  • The Baseline return-address table maps native return addresses back to bytecode PCs for ICs, VM calls, exception handling, stack walking, and related runtime operations.
  • A truncated PC offset can make those operations use a bytecode location different from the actual execution site.
  • In exception handling, this can select the wrong try-note or stack depth and cause Baseline frame stack slots to be interpreted incorrectly.
  • A controlled memory-corruption primitive has not been established in this report.

Suggested severity: high, pending exploitability assessment.

Root Cause

RetAddrEntry stores the bytecode PC offset in a 28-bit bitfield.

Relevant source:

  • js/src/jit/BaselineJIT.h:100-164

The failing checks are debug-only:

uint32_t pcOffset_ : 28;

MOZ_ASSERT(pcOffset_ == pcOffset);
MOZ_ASSERT(pcOffset <= BaselineMaxScriptLength);

The normal x86-64 Baseline script length limit is aligned with the 28-bit bound.

  • js/src/jit/BaselineJIT.h:66-74

The eager Baseline compilation path queues scripts during stencil instantiation without checking BaselineMaxScriptLength.

Relevant source:

  • js/src/frontend/Stencil.cpp:2720-2798
  • js/src/frontend/Stencil.cpp:2819-2826

emitNextIC creates an IC return-address entry using the current bytecode pcOffset.

  • js/src/jit/BaselineCodeGen.cpp:783-788

A high-offset GetGName IC reaches this path.

  • js/src/jit/BaselineCodeGen.cpp:3683-3690

Firefox default settings enable JitHints-based eager Baseline compilation.

  • modules/libpref/init/StaticPrefList.yaml:9073-9142
  • dom/script/ScriptLoader.cpp:2742-2766

Reproducer

poc.html
payload.js
server.py

PoC structure:

  • poc.html loads payload.js twice from the same URL.
  • The first payload.js response is a small seed script that creates a JitHint.
  • The second payload.js response is a large script from the same URL, placing a GetGName IC after a bytecode offset above the 28-bit limit.
  • server.py serves different responses for the first and second requests to the same /payload.js URL.
  • No shell-only function or preference change is required.

Default-Settings Reproduction

Command:

<firefox> --headless --no-remote --profile <empty-profile> <poc-url>

Result:

firefox_status=11
reason=debug-assertion-after-done
done=1
assertion=1

Assertion:

Assertion failure: pcOffset_ == pcOffset, at js/src/jit/BaselineJIT.h:150
Program firefox received signal 11.

Symbolized stack:

js::jit::RetAddrEntry::RetAddrEntry
mozilla::Vector<js::jit::RetAddrEntry>::emplaceBack
js::jit::BaselineCodeGen<js::jit::BaselineCompilerHandler>::emitNextIC
js::jit::BaselineCodeGen<js::jit::BaselineCompilerHandler>::emit_GetGName
js::jit::BaselineCompiler::emitBody
js::jit::BaselineCompiler::compileImpl
js::jit::BaselineCompileTask::runTask
js::jit::BaselineCompileTask::runHelperThreadTask
JS::RunHelperThreadTask

Expected Behavior

The Baseline compiler should do one of the following:

  • enforce script->length() <= BaselineMaxScriptLength in the eager Baseline path as well;
  • safely fail Baseline compilation for scripts whose PC offsets cannot be represented in RetAddrEntry;
  • widen or change return-address metadata encoding so PC offsets are not truncated.

Release builds must not create BaselineScripts containing truncated bytecode PC offsets.

Suggested Fix

Minimum fix:

  • Before queueing a script for eager Baseline compilation, reject scripts with script->length() > BaselineMaxScriptLength using a release-enforced compile failure.
  • Share script length validation between the normal Baseline entry path and the eager Baseline path.

More robust fix:

  • Validate pcOffset <= BaselineMaxScriptLength in release builds before storing a RetAddrEntry.
  • Widen RetAddrEntry::pcOffset_ or move the kind field out of the packed value to eliminate truncation.
  • Add a regression test that reloads a large same-key script through JitHints-based eager Baseline compilation.
Flags: sec-bounty?
Group: firefox-core-security → javascript-core-security
Component: Security → JavaScript Engine: JIT
Keywords: ai-involved
Product: Firefox → Core

Please attach files individually and not in a zip. This aides triage. Thanks.

Flags: needinfo?(gaddofpwn)

Jan, would you be the right person to look at this issue?

Severity: -- → S2
Flags: needinfo?(jdemooij)
Priority: -- → P1

(In reply to Andrew McCreight [:mccr8] from comment #1)

Please attach files individually and not in a zip. This aides triage. Thanks.

The payload.js file is over 100 MB, so I’m unable to attach it.(In reply to Andrew McCreight [:mccr8] from comment #1)

Please attach files individually and not in a zip. This aides triage. Thanks.

The payload.js file is over 100 MB, so I’m unable to attach it.

Flags: needinfo?(gaddofpwn)

(In reply to ggwhyp from comment #3)

The payload.js file is over 100 MB, so I’m unable to attach it.

Ah, okay. Well, you can attach the other two files then. Thanks.

Attached file poc.html —
Attached file server.py —
Attached file payload.js.zip —
Keywords: regression
Regressed by: 1980830

The eager baseline compilation mode added in bug 1980830 wasn't hooked up to the JS shell, that's probably why we didn't find this sooner.

Assignee: nobody → jdemooij
Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true

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

Attached file (secure) —

The test doesn't generate a huge function because it makes the test very slow,
but it has all the scaffolding for a fuzzer to mutate.

Flags: needinfo?(jdemooij)
Keywords: sec-high

Comment on attachment 9583793 [details]
(secure)

Security Approval Request

  • How easily could an exploit be constructed based on the patch?: Not very easily but it's doable.
  • Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?: No
  • Which branches (beta, release, and/or ESR) are affected by this flaw, and do the release status flags reflect this affected/unaffected state correctly?: beta, release
  • If not all supported branches, which bug introduced the flaw?: Bug 1980830
  • Do you have backports for the affected branches?: Yes
  • If not, how different, hard to create, and risky will they be?: Patch should apply or should be easy to backport.
  • How likely is this patch to cause regressions; how much testing does it need?: Unlikely.
  • Is the patch ready to land after security approval is given?: Yes
  • Is Android affected?: Yes
Attachment #9583793 - Flags: sec-approval?
Attachment #9583793 - Flags: sec-approval? → sec-approval+

(In reply to Jan de Mooij [:jandem] from comment #8)

The eager baseline compilation mode added in bug 1980830 was never hooked up to the JS shell, that's probably why we didn't find this sooner.

That looks like useful follow-up work. Is it tracked in a bug elsewhere?

Flags: needinfo?(jdemooij)

(In reply to Frederik Braun [:freddy] from comment #14)

That looks like useful follow-up work. Is it tracked in a bug elsewhere?

The follow-up patch here adds a new testing function option + a shell test that uses it :)

Flags: needinfo?(jdemooij)
Whiteboard: [client-bounty-form] → [client-bounty-form][pp1]

https://hg-edge.mozilla.org/mozilla-central/rev/f40113af5ca5

Please add Beta and Release uplift nominations for this when you get chance.

Group: javascript-core-security → core-security-release
Status: ASSIGNED → RESOLVED
Closed: 4 months ago
Resolution: --- → FIXED
Target Milestone: --- → 152 Branch

firefox-beta Uplift Approval Request

  • User impact if declined/Reason for urgency: Security bug.
  • 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: Pretty small and safe patch moving some checks into a shared function.
  • String changes made/needed?: N/A
  • Is Android affected?: yes
Attachment #9584781 - Flags: approval-mozilla-beta?
Attached file (secure) —

firefox-release Uplift Approval Request

  • User impact if declined/Reason for urgency: Security bug.
  • 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: Pretty small and safe patch moving some checks into a shared function.
  • String changes made/needed?: N/A
  • Is Android affected?: yes
Attachment #9584783 - Flags: approval-mozilla-release?
Attached file (secure) —
Attachment #9584781 - Flags: approval-mozilla-beta? → approval-mozilla-beta+
Attachment #9584783 - Flags: approval-mozilla-release? → approval-mozilla-release+
QA Whiteboard: [sec] [uplift] [qa-triage-done-c152/b151]
Whiteboard: [client-bounty-form][pp1] → [client-bounty-form][pp1][adv-main150.0.3+]
Alias: CVE-2026-8393
Alias: CVE-2026-8393 → CVE-2026-8389
Flags: sec-bounty? → sec-bounty+
Group: core-security-release
Attachment #9583794 - Attachment description: (secure) → Bug 2036983 - Add testing option for EagerBaselineStrategy compile option. r?iain!
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: