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)
Tracking
()
| 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)
|
26 bytes,
text/plain
|
Details |
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)
MConstant::computeRange()handlesMIRType::Float32vianumberToDouble()→Range::NewDoubleSingletonRange()→Range::setDouble(double, double).Range::setDouble()builds its zero-crossing/subnormal threshold from the smallest positive binary64 normal (2⁻¹⁰²²).- For
Math.fround(-1e-40)the binary32 bit pattern is0x800116c2, but its C++doublevalue is far more negative than-2⁻¹⁰²², soincludesPositive=false,crossesZero=false, andcanBeNegativeZero_is not set. - 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).
| Assignee | ||
Updated•3 months ago
|
| Assignee | ||
Updated•3 months ago
|
| Assignee | ||
Updated•3 months ago
|
| Assignee | ||
Updated•3 months ago
|
| Assignee | ||
Comment 1•3 months ago
|
||
Updated•3 months ago
|
Comment 2•3 months ago
|
||
The denormals specializer you wrote apparently found this bug independently as well.
Comment 4•3 months ago
|
||
Updated•3 months ago
|
Comment 6•3 months ago
|
||
The patch landed in nightly and beta is affected.
:jandem, is this bug important enough to require an uplift?
- If yes, please nominate the patch for beta approval.
- See https://wiki.mozilla.org/Release_Management/Requesting_an_Uplift for documentation on how to request an uplift.
- If no, please set
status-firefox153towontfix.
For more information, please visit BugBot documentation.
| Assignee | ||
Updated•3 months ago
|
Comment 7•3 months ago
|
||
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
| Assignee | ||
Comment 8•3 months ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D311277
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Comment 10•3 months ago
|
||
Updated•3 months ago
|
Updated•3 months ago
|
Updated•1 month ago
|
Description
•