Closed Bug 2053680 (CVE-2026-16410) Opened 3 months ago Closed 3 months ago

Assertion failure: Input shouldn't be negative zero., at js/src/jit/VMFunctions.cpp:3475 with --disable-main-thread-denormals

Categories

(Core :: JavaScript Engine: JIT, defect)

defect

Tracking

()

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

People

(Reporter: jandem, Assigned: jandem)

References

Details

(4 keywords, Whiteboard: [adv-main153+])

Attachments

(1 file, 2 external files)

This bug was reported to us by Amy Burnett at OpenAI. Thank you!

Summary

MConstant::computeRange() computes the range of a MIRType::Float32 constant by widening it to a C++ double and calling the binary64-oriented Range::setDouble() / NewDoubleSingletonRange(). setDouble decides whether the value can be signed zero using the binary64 smallest-normal threshold (2⁻¹⁰²²). A negative Float32 subnormal such as Math.fround(-1e-40) (bit pattern 0x800116c2) is far outside the binary64 subnormal band, so setDouble sets crossesZero=false and leaves canBeNegativeZero_ unset — the range asserts the value cannot be −0.

That is false for Float32 machine code running under x86 DAZ/FTZ: a binary32 subnormal's interval begins at 2⁻¹²⁶, so the value is subnormal in binary32 and, with DAZ enabled, is observed as −0. Firefox enables MXCSR FTZ+DAZ (0x8040) on the AudioWorklet / MediaTrackGraph processing thread (DenormalDisabler in MediaTrackGraphImpl::OneIterationImpl, dom/media/MediaTrackGraph.cpp / dom/media/webaudio/blink/DenormalDisabler.h), so an attacker-controlled worklet that computes this constant runs Ion code whose range says "not −0" while the instruction observes −0.

Root cause (code path)

  1. MConstant::computeRange() handles MIRType::Float32 via numberToDouble() → Range::NewDoubleSingletonRange() → Range::setDouble(double, double).
  2. Range::setDouble() builds its zero-crossing/subnormal threshold from the smallest positive binary64 normal (2⁻¹⁰²²).
  3. For Math.fround(-1e-40) the binary32 bit pattern is 0x800116c2, but its C++ double value is far more negative than -2⁻¹⁰²², so includesPositive=false, crossesZero=false, and canBeNegativeZero_ is not set.
  4. The Float32 subnormal interval actually begins at 2⁻¹²⁶, so the binary64 test is not conservative for a Float32 MIR value — and under DAZ the operand behaves as −0.

Violated invariant: Ion range metadata attached to a Float32 value must describe every value the generated binary32 operations may observe in the target environment; canBeNegativeZero() may be false only when −0 is provably unreachable. Since Firefox runs Float32 worklet code under DAZ/FTZ, Float32 subnormals must be treated as possibly −0.

Minimal test case

Both flags are required — --disable-main-thread-denormals supplies DAZ/FTZ (so the subnormal becomes −0), and --ion-check-range-analysis inserts the MAssertRangeF that observes the contradiction. Drop either and it exits clean. A bare constant does not trip it; the comparison + x ? x : 0 is what consumes the false range fact.

function f(x) {
  x = Math.fround(x);
  if (x >= Math.fround(-1e-40)) return x ? x : 0;   // consumes the false "can't be -0" range fact
  return 7;
}
for (let i = 0; i < 2000; i++) f(Math.fround(-1e-40));   // warm to Ion
print(f(Math.fround(-1e-40)));
js --disable-main-thread-denormals --ion-check-range-analysis \
   --ion-warmup-threshold=100 --ion-offthread-compile=on  ff1197-min.js
# => Assertion failure: Input shouldn't be negative zero., js/src/jit/VMFunctions.cpp:3475  (exit 133, 3/3)

Suggested fix

Compute Float32 constant ranges with binary32 semantics rather than routing them through Range::setDouble's binary64 thresholds. The DAZ/FTZ zero-crossing region for a Float32 value starts at the binary32 smallest-normal (2⁻¹²⁶), not 2⁻¹⁰²². Give Float32 constants a Float32-aware range path (a setFloat, or a float32 flag so the subnormal/zero-crossing threshold is 2⁻¹²⁶) so that any Float32 value with magnitude < 2⁻¹²⁶ is conservatively marked canBeNegativeZero (DAZ/FTZ can flush it to ±0).

Flags: needinfo?(jdemooij)
Keywords: sec-moderate
Keywords: sec-moderate → sec-low
Flags: needinfo?(jdemooij)
Attached file (secure) —
Assignee: nobody → jdemooij
Status: NEW → ASSIGNED

The denormals specializer you wrote apparently found this bug independently as well.

Pushed by jdemooij@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/cacd0898986e https://hg.mozilla.org/integration/autoland/rev/d59236b7c1d0 Make denormal handling in Range::setDouble more conservative to handle float32 values correctly. r=mgaudet
Group: javascript-core-security → core-security-release
Status: ASSIGNED → RESOLVED
Closed: 3 months ago
Resolution: --- → FIXED
Target Milestone: --- → 154 Branch
Duplicate of this bug: 2054222

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)
Flags: needinfo?(jdemooij)

firefox-beta Uplift Approval Request

  • User impact if declined/Reason for urgency: Small and safe patch that would be nice to have in ESR153.
  • 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: Makes code more conservative by using a wider range of values.
  • String changes made/needed?: N/A
  • Is Android affected?: yes
Attachment #9607196 - Flags: approval-mozilla-beta?
Attached file (secure) —
Attachment #9607196 - Flags: approval-mozilla-beta? → approval-mozilla-beta+
Whiteboard: [adv-main153+r]
Whiteboard: [adv-main153+r] → [adv-main153+]
Attached file advisory.txt —
Alias: CVE-2026-16410
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: