CVE-2025-31205
Overview
Background
- Origin-clean / CORS-same-origin stylesheet
- A stylesheet is origin-clean if it is same-origin or fetched with CORS and CORS-same-origin; only then may script read its rules.
- canAccessRules()
- The CSSOM guard that decides whether script may read/modify a stylesheet’s rules based on origin.
- CSSOM wrapper
- The CSSStyleSheet script object wrapping engine StyleSheetContents; created for link, @import and xml-stylesheet sheets.
- Same-origin policy (SOP)
- The rule that script may not read cross-origin resource contents, including cross-origin CSS rule text.
Root Cause Analysis
CSSOM access to a stylesheet’s rules is gated by CSSStyleSheet::canAccessRules(), which must return false for a cross-origin (not origin-clean) sheet so script cannot read or mutate cross-origin CSS. Two defects made that gate leaky. First, canAccessRules() returned true when ownerDocument() was null: Document* document = ownerDocument(); if (!document) return true;. A CSSStyleSheet wrapper can outlive its owner node / be detached from the document (e.g. the owning element is removed), at which point ownerDocument() is null and the check fell open, granting access to a sheet whose origin can no longer be verified. Second, the origin-clean flag was not propagated correctly when constructing CSSStyleSheet CSSOM wrappers for imported/linked sheets: CSSImportRule::styleSheet(), ProcessingInstruction::setCSSStyleSheet() and HTMLLinkElement::initializeStyleSheet() created wrappers without threading the real CachedCSSStyleSheet::isCORSSameOrigin() value (HTMLLinkElement even had a FIXME and only set originClean when fetch mode was CORS), so a cross-origin sheet could be treated as origin-clean.
The fix (a) makes canAccessRules() return false when there is no owner document, (b) adds explicit canAccessRules() checks to insertRule()/deleteRule() that throw SecurityError for cross-origin sheets, and (c) plumbs the true isCORSSameOrigin/origin-clean flag into CSSStyleSheet::create for import rules, processing instructions and link elements.
The restored invariant is that rule read/write on a stylesheet is permitted only when the sheet is provably same-origin (or CORS-clean), including after it is detached from its document. The added layout test reads a cross-origin sheet after removing it from the document.
Attack Path
- Load a cross-origin stylesheet Reference a stylesheet from another origin without CORS (e.g. <link rel=stylesheet> or @import to a cross-origin URL).
- Get its CSSOM wrapper Obtain the CSSStyleSheet object (document.styleSheets / the link’s .sheet), which pre-patch may be wrapped without the correct origin-clean flag.
- Detach it from the document Remove the owning link/element so ownerDocument() becomes null, making canAccessRules() fall open (return true).
- Read/modify cross-origin rules Access cssRules/insertRule/deleteRule to read the cross-origin CSS text (which can encode sensitive, per-user data) or mutate it — a same-origin-policy bypass / information disclosure.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
CSSStyleSheet::canAccessRulesSource/WebCore/css/CSSStyleSheet.cpp |
modified | Returns false (was true) when ownerDocument() is null, so a detached sheet can no longer be read cross-origin. |
CSSStyleSheet::insertRule / deleteRuleSource/WebCore/css/CSSStyleSheet.cpp |
modified | Now call canAccessRules() and throw SecurityError for cross-origin sheets before mutating rules. |
CSSStyleSheet::create / constructor (+ m_isOriginClean)Source/WebCore/css/CSSStyleSheet.cpp |
modified | Accept and store an isOriginClean flag for CSSImportRule-owned sheets so the origin-clean state is set correctly. |
CSSImportRule::styleSheetSource/WebCore/css/CSSImportRule.cpp |
modified | Derives isOriginClean from cachedCSSStyleSheet()->isCORSSameOrigin() and passes it into the wrapper. |
ProcessingInstruction::setCSSStyleSheet / HTMLLinkElement::initializeStyleSheetSource/WebCore/dom/ProcessingInstruction.cpp |
modified | Pass the CachedCSSStyleSheet::isCORSSameOrigin() value into CSSStyleSheet::create, removing the incomplete CORS-only origin-clean logic (FIXME) in HTMLLinkElement. |
Files Changed
LayoutTests/http/tests/security/access-cssstylesheet-after-removing-from-document-expected.txtLayoutTests/http/tests/security/access-cssstylesheet-after-removing-from-document.htmlLayoutTests/http/tests/security/access-imported-cssstylesheet-after-removing-from-document-expected.txtLayoutTests/http/tests/security/access-imported-cssstylesheet-after-removing-from-document.htmlLayoutTests/http/tests/security/cannot-read-cssrules-redirect-expected.txtLayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/fetch-request-css-cross-origin.https-expected.txtSource/WebCore/css/CSSImportRule.cppSource/WebCore/css/CSSStyleSheet.cppSource/WebCore/css/CSSStyleSheet.hSource/WebCore/dom/ProcessingInstruction.cppSource/WebCore/html/HTMLLinkElement.cpp
Audit Directions
- Other fall-open origin checksgrep WebCore for security predicates that
return trueon a null document/frame/origin (canAccessRules, canRequest wrappers), where the safe default is deny. - Origin-clean propagationAudit every CSSStyleSheet::create call site (import, link, xml-stylesheet, constructed sheets) to confirm isOriginClean/isCORSSameOrigin is threaded correctly.
- Detached-object accessReview CSSOM/DOM objects that can outlive their owner node for security checks that depend on the now-null owner (ownerDocument/ownerNode).