Cross-origin CSS rule disclosure and redirect URL leak via mOriginClean reset in StyleSheetInfo clone
Categories
(Core :: CSS Parsing and Computation, defect, P2)
Tracking
()
| Tracking | Status | |
|---|---|---|
| firefox-esr115 | --- | unaffected |
| firefox-esr140 | --- | unaffected |
| firefox148 | + | verified |
| firefox149 | + | verified |
| firefox150 | + | verified |
People
(Reporter: juny24602, Assigned: emilio)
References
(Regression)
Details
(5 keywords, Whiteboard: [client-bounty-form][adv-main148.0.2+])
Attachments
(4 files)
|
1.93 KB,
text/html
|
Details | |
|
48 bytes,
text/x-phabricator-request
|
pascalc|PTO
:
approval-mozilla-beta+
dmeehan
:
approval-mozilla-release+
dveditz
:
sec-approval+
|
Details | Review |
|
2.59 KB,
patch
|
Details | Diff | Splinter Review | |
|
48 bytes,
text/x-phabricator-request
|
Details | Review |
Cross-origin CSS rule disclosure and redirect URL leak via mOriginClean reset in StyleSheetInfo clone
Summary
A cross-origin stylesheet's origin-clean flag can be reset by triggering
StyleSheetInfo's copy constructor, which does not copy the mOriginClean field.
When two <link> elements load the same cross-origin CSS URL, the
SharedStyleSheetCache deduplicates them into a shared StyleSheetInfo inner.
Modifying the sheet's MediaList (e.g. via sheet.media.appendMedium()) calls
WillDirty() -> EnsureUniqueInner(), which clones the shared inner. The clone's
mOriginClean defaults to true (the field initializer), bypassing the CSSOM
origin-clean check. This allows reading cross-origin CSS rules and, when the
stylesheet was loaded through a redirect, leaking the redirect destination URL
via @import insertion.
This is a variant of CVE-2025-31205 (WebKit cross-site CSS rule and redirect
URL disclosure, https://project-zero.issues.chromium.org/issues/408172161),
affecting Firefox through a different mechanism but with overlapping impact.
Root Cause
StyleSheetInfo (layout/style/StyleSheetInfo.h) declares mOriginClean with a
default member initializer of true:
bool mOriginClean = true; // line 47
The copy constructor (layout/style/StyleSheet.cpp:349-367) copies mCORSMode,
mIntegrity, mSourceMapURL, and mContents, but does NOT copy mOriginClean:
StyleSheetInfo::StyleSheetInfo(StyleSheetInfo& aCopy, StyleSheet* aPrimarySheet)
: mCORSMode(aCopy.mCORSMode),
mIntegrity(aCopy.mIntegrity),
mSourceMapURL(aCopy.mSourceMapURL),
mContents(Servo_StyleSheet_Clone(...).Consume())
{
AddSheet(aPrimarySheet);
}
Since mOriginClean is absent from the initializer list, it takes its default
value of true in the cloned inner, regardless of the original's value.
Attack Chain
-
Load a cross-origin stylesheet via <link> (no CORS). The loader sets
mOriginClean = false on the sheet's StyleSheetInfo inner (Loader.cpp:669). -
Load the same URL via a second <link>. SharedStyleSheetCache deduplicates:
CreateSheet() finds a cache hit and returns Clone(), which shares the same
StyleSheetInfo inner. The inner's mSheets now has length > 1, so
HasUniqueInner() returns false. -
Modify the sheet's media attribute: sheet.media.appendMedium('screen').
MediaList::DoMediaChange() (MediaList.cpp:50) calls mStyleSheet->WillDirty()
with no origin check. WillDirty() calls EnsureUniqueInner()
(StyleSheet.cpp:491). Since the inner is shared, CloneFor() creates a new
StyleSheetInfo via the copy constructor -- mOriginClean is NOT copied and
defaults to true. -
The sheet now has a unique inner with mOriginClean = true.
AreRulesAvailable() (StyleSheet.cpp:928) passes, granting full CSSOM access:
sheet.cssRules, insertRule(), deleteRule() all become available.
Suggested Fix
Copy mOriginClean in the StyleSheetInfo copy constructor:
StyleSheetInfo::StyleSheetInfo(StyleSheetInfo& aCopy, StyleSheet* aPrimarySheet)
: mCORSMode(aCopy.mCORSMode),
mIntegrity(aCopy.mIntegrity),
+ mOriginClean(aCopy.mOriginClean),
mSourceMapURL(aCopy.mSourceMapURL),
mContents(Servo_StyleSheet_Clone(...).Consume())
Tested Version
Firefox 147.0.4 (release), source analysis on mozilla-central commit f8345be3cabc.
| Comment hidden (obsolete) |
Updated•5 months ago
|
Updated•5 months ago
|
| Assignee | ||
Comment 3•5 months ago
|
||
Gah, this is a dumb bug indeed, good find.
I'm not sure of the severity of this. Andrew do you know? Just in terms of holding off landing a test or what not.
| Assignee | ||
Comment 4•5 months ago
|
||
Updated•5 months ago
|
Comment 5•5 months ago
|
||
It doesn't sound good. I guess a cross-origin data leak sounds like a sec-high to me. Dan, what do you think? This is a bit of an unusual issue so a second opinion is probably a good idea.
| Comment hidden (obsolete) |
(Sorry for the wrong format in previous comment, I don't know how to mark it as 'obsolete')
Attached a unittest (test.patch).
Before the (mOriginClean) patch:
Error Summary
-------------
layout/style/test/test_origin_clean_clone.html
FAIL layout/style/test/test_origin_clean_clone.html - cssRules access must still be blocked after inner clone
SimpleTest.ok@SimpleTest/SimpleTest.js:427:16
link1.onload/link2.onload@layout/style/test/test_origin_clean_clone.html:38:7
EventHandlerNonNull*link1.onload@layout/style/test/test_origin_clean_clone.html:28:3
EventHandlerNonNull*@layout/style/test/test_origin_clean_clone.html:22:1
FAIL layout/style/test/test_origin_clean_clone.html - Finished in 576ms
After patch:
Overall Summary
===============
mochitest-plain
~~~~~~~~~~~~~~~
Ran 3 checks (1 asserts, 1 subtests, 1 tests)
Expected results: 3
Unexpected results: 0
OK
| Assignee | ||
Comment 9•5 months ago
|
||
Thanks! I have a test written locally which is pretty similar to that, but it's a web platform test (so, shared with other browsers and reusing a bit the existing test).
| Assignee | ||
Comment 10•5 months ago
|
||
| Assignee | ||
Comment 11•5 months ago
|
||
Comment on attachment 9547044 [details]
(secure)
Security Approval Request
- How easily could an exploit be constructed based on the patch?: not terribly hard, it's a one liner so relatively easy to figure out if you know browser internals.
- 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 1995282
- Do you have backports for the affected branches?: Yes
- If not, how different, hard to create, and risky will they be?: Not hard, one liners.
- How likely is this patch to cause regressions; how much testing does it need?: not very, one liner for well tested code-path
- Is the patch ready to land after security approval is given?: Yes
- Is Android affected?: Yes
Comment 12•5 months ago
|
||
I'll mark it sec-high for now and Dan can adjust it as appropriate.
Comment 13•5 months ago
|
||
sec-high is standard for a web SOP violation. Even if the information is usually not that secret, in specific cases it might reveal information private to a logged-in user or similar.
| Assignee | ||
Comment 14•5 months ago
|
||
Comment on attachment 9547044 [details]
(secure)
Beta/Release Uplift Approval Request
- User impact if declined/Reason for urgency: SOP bypass
- Is this code covered by automated tests?: Yes
- Has the fix been verified in Nightly?: No
- Needs manual test from QE?: Yes
- If yes, steps to reproduce: comment 0
- List of other uplifts needed: none
- Risk to taking this patch: Low
- Why is the change risky/not risky? (and alternatives if risky): One-liner.
- String changes made/needed: none
- Is Android affected?: Yes
| Assignee | ||
Updated•5 months ago
|
Comment 15•5 months ago
|
||
The severity field for this bug is set to S3. However, the bug is flagged with the sec-high keyword.
:emilio, could you consider increasing the severity of this security bug?
For more information, please visit BugBot documentation.
Comment 17•5 months ago
|
||
Because this doesn't affect ESR it is a good candidate for the mid-cycle 148 point release if release managers approve it.
Comment 18•5 months ago
•
|
||
Comment on attachment 9547044 [details]
(secure)
Please remove the mention of cloning from the commit message; given the simplicity of the patch maybe just mention the bug number and r=. We don't want to give a head start to people who don't already know browser internals. Please don't land the test until after we release the fix. If we can get it into a 148 point-release then after April 1 is good, but if this doesn't ship until 149 then wait until May. I'll set the reminder for the later date but feel free to modify it if we do uplift sooner.
sec-approval+ to land in central and request uplifts after you redact the commit message
Updated•5 months ago
|
Comment 19•5 months ago
|
||
Comment 20•5 months ago
|
||
The bug is marked as tracked for firefox148 (release), tracked for firefox149 (beta) and tracked for firefox150 (nightly). However, the bug still has low priority.
:fgriffith, could you please increase the priority for this tracked bug? Given that it is a regression and we know the cause, we could also simply backout the regressor. If you disagree with the tracking decision, please talk with the release managers.
For more information, please visit BugBot documentation.
| Assignee | ||
Updated•5 months ago
|
Comment 21•5 months ago
|
||
Updated•5 months ago
|
Comment 22•5 months ago
|
||
| uplift | ||
Updated•5 months ago
|
Updated•5 months ago
|
Updated•5 months ago
|
Comment 23•5 months ago
|
||
Verified as fixed on the latest Firefox for Android Nigthly 150.0a1 from 3/3, and on Firefox for Android Beta 149.0b3 with a Pixel 9 Pro (Android 16).
Tapping on the link from Comment 0, on "Disclose rules" or "Disclose redirect URL" are not providing any results (the buttons present different behaviors with and without the patch applied).
Comment 24•5 months ago
|
||
Reproduced the leak on Firefox 148 by clicking Disclose rules and Disclose redirect URL on Windows 11.
Verified as fixed using the latest Nightly 150.0a1 and Firefox 149.0b3 on Windows 11, Ubuntu 24.04, and macOS 15 — no leak occurs when clicking the Disclose buttons.
@emilio - is there anything else we should manually verify?
Updated•5 months ago
|
Comment 26•5 months ago
|
||
Comment on attachment 9547044 [details]
(secure)
Approved for 148.0.2
Comment 27•5 months ago
|
||
| uplift | ||
Updated•5 months ago
|
Comment 28•5 months ago
|
||
Tom, would this be similar to your work for LoadInfo?
Updated•5 months ago
|
Comment 29•5 months ago
|
||
Verified as fixed using Firefox 148.0.8 - tested on Windows 11, Ubuntu 24.04 and macOS 15 - no leak occurs when clicking the Disclose buttons.
Comment 30•3 months ago
|
||
2 months ago, dveditz placed a reminder on the bug using the whiteboard tag [reminder-test 2026-04-29] .
emilio, please refer to the original comment to better understand the reason for the reminder.
Comment 32•3 months ago
|
||
Comment 33•3 months ago
|
||
Updated•2 months ago
|
Description
•