ChromeOnly event interfaces become exposed to web content when chromeEventHandler listener callbacks run
Categories
(Core :: DOM: Core & HTML, defect)
Tracking
()
People
(Reporter: gregp, Assigned: peterv)
Details
(Keywords: csectype-priv-escalation, reporter-external, sec-moderate, Whiteboard: [adv-main130+][adv-esr128.2+][adv-esr115.15+])
Attachments
(6 files)
|
591 bytes,
text/html
|
Details | |
|
48 bytes,
text/x-phabricator-request
|
RyanVM
:
approval-mozilla-beta+
RyanVM
:
approval-mozilla-esr128+
|
Details | Review |
|
48 bytes,
text/x-phabricator-request
|
Details | Review | |
|
4.18 MB,
patch
|
Details | Diff | Splinter Review | |
|
48 bytes,
text/x-phabricator-request
|
RyanVM
:
approval-mozilla-esr115+
|
Details | Review |
|
497 bytes,
text/plain
|
Details |
Steps to reproduce:
- Navigate to https://www.google.com/
- Open DevTools console
- Run
typeof NotifyPaintEvent
Actual results:
type is function
Expected results:
type is undefined
This is surprising given that this interface has been ChromeOnly since bug 1334957
In a local build, commenting out this MozAfterPaint listener in AboutReaderChild results in NotifyPaintEvent not being present in web content
https://searchfox.org/mozilla-central/rev/6936c4c3fc9bee166912fce10104fbe0417d77d3/browser/actors/AboutReaderChild.sys.mjs#199-202
Another example is StyleSheetApplicableStateChangeEvent. This one is fun because StyleSheetApplicableStateChanged events only fire when DevTools are open
https://searchfox.org/mozilla-central/rev/a5bcdc6afbcfccf9f293ace0fddf11307e5bc22c/devtools/server/actors/utils/stylesheets-manager.js#252-256
So, it's possible to construct a test case that detects when DevTools are opened by looking for the existence of this interface.
Steps to reproduce:
- Navigate to the attached test case
- Open DevTools
Actual results:
Test case says devtools opened
Expected results:
Test case doesn't say that
Comment 1•1 year ago
|
||
Olli, I wonder if there's fundamental problem here, this looks surprising
Comment 2•1 year ago
|
||
I can't quite tell from comment 0 - does this only work from the devtools console?
Updated•1 year ago
|
Comment 3•1 year ago
|
||
(In reply to :Gijs (he/him) from comment #2)
I can't quite tell from comment 0 - does this only work from the devtools console?
To be clear - from comment 0:
(In reply to Gregory Pappas [:gregp] from comment #0)
Steps to reproduce:
- Navigate to https://www.google.com/
- Open DevTools console
- Run
typeof NotifyPaintEvent
Does executing the typeof from outside devtools (ie directly from web content) produce undefined or function, if run after the AboutReader MozAfterPaint stuff has run?
| Reporter | ||
Comment 4•1 year ago
|
||
Does executing the typeof from outside devtools (ie directly from web content) produce undefined or function, if run after the AboutReader MozAfterPaint stuff has run?
function. Test case: data:text/html,<script>requestIdleCallback(()=>requestIdleCallback(()=>alert(typeof NotifyPaintEvent)))</script>
Comment 5•1 year ago
|
||
This broke at some point, on Jan 1 2021 this returned undefined.
Comment 6•1 year ago
|
||
mozregression says this regressed with https://hg.mozilla.org/mozilla-central/pushloghtml?fromchange=a8d37f94a6364103b7f373b9938ce7a62d9db9e6&tochange=5b0962239a45c0cdae5d495e8ee3786a7c5a07f3 though I wonder if the requestIdleCallback based testcase is racy.
Also I don't see anything obvious in that pushlog. :-(
Providing web content access to ChromeOnly interfaces is worse than S3.
In some further testing I'm struggling to consistently reproduce things - is the trick here that things are only exposed once some chrome actor accesses the thing in question, or something? (that is, I'm trying to use e.g. typeof StyleSheetApplicableStateChangeEvent and that still produces undefined in most cases, but the attached testcase does notice when I open devtools. How does that work, exactly? Debugging this at this time of night is not really working well for me...)
Updated•1 year ago
|
| Reporter | ||
Comment 8•1 year ago
•
|
||
(In reply to :Gijs (he/him) from comment #6)
mozregression says this regressed with https://hg.mozilla.org/mozilla-central/pushloghtml?fromchange=a8d37f94a6364103b7f373b9938ce7a62d9db9e6&tochange=5b0962239a45c0cdae5d495e8ee3786a7c5a07f3 though I wonder if the
requestIdleCallbackbased testcase is racy.Also I don't see anything obvious in that pushlog. :-(
The data uri test case is very racy and is not useful for bisecting
In some further testing I'm struggling to consistently reproduce things - is the trick here that things are only exposed once some chrome actor accesses the thing in question, or something?
In testing locally, using something like function(){} as the event listener callback still results in the interface being exposed to web content. Removing the addEventListener call results in the interface not being exposed to web content.
that is, I'm trying to use e.g. typeof StyleSheetApplicableStateChangeEvent and that still produces undefined in most cases, but the attached testcase does notice when I open devtools. How does that work, exactly?
StyleSheetApplicableStateChangedevents fire when stylesheets are constructed by JS
https://searchfox.org/mozilla-central/rev/4a78812619a63c4ace10ff4269432f73b64d5d67/layout/style/StyleSheet.cpp#139
https://searchfox.org/mozilla-central/rev/4a78812619a63c4ace10ff4269432f73b64d5d67/layout/style/StyleSheet.cpp#297-353- DevTools is the only consumer of these events
StyleSheetApplicableStateChangeEventgets exposed to web content when this callback runs at least once
https://searchfox.org/mozilla-central/rev/4a78812619a63c4ace10ff4269432f73b64d5d67/devtools/server/actors/utils/stylesheets-manager.js#104-107
So, the test case constructs a stylesheet and checks "StyleSheetApplicableStateChangeEvent" in window. This ends up being a reliable way to detect that DevTools has been opened.
| Reporter | ||
Comment 9•1 year ago
|
||
FWIW I can reproduce this as far as back as Nightly 42.0a1 (2015-07-23). Anything older instantly crashes on my machine.
STR for Nightly 42.0a1 (2015-07-23):
- Launch the ancient Nightly:
mozregression --launch '2015-07-23' --preferences 'prefs.json'// prefs.json { "browser.tabs.remote.autostart.2": false, "devtools.debugger.remote-enabled": true, "devtools.chrome.enabled": true } - Navigate to https://www.google.com/
- Open DevTools console
- Run
typeof StyleSheetApplicableStateChangeEvent - Confirm that it is
undefined - Open browser toolbox console
- Run
gBrowser.selectedBrowser.contentDocument.styleSheetChangeEventsEnabled = true; gBrowser.selectedBrowser.docShell.chromeEventHandler.addEventListener("StyleSheetApplicableStateChanged", function(){}); - Go back to DevTools console
- Run
typeof StyleSheetApplicableStateChangeEvent - Confirm it is still
undefined - Run
Array.from(document.styleSheets).forEach(sheet => sheet.disabled = true) - Run
typeof StyleSheetApplicableStateChangeEvent
Actual results:
Type is function
Expected results:
Type is undefined
It looks like DevTools started using this event in 2017 (bug 1224558), so a test case like attachment 9411638 [details] would not work in these very old builds.
Updated•1 year ago
|
Comment 10•1 year ago
|
||
Providing web content access to ChromeOnly interfaces is worse than S3.
can the web page use those interfaces, or can it only see that they exist? That is, is the ChromeOnly attribute still enforced even though the object itself is weirdly exposed?
| Reporter | ||
Comment 11•1 year ago
|
||
can the web page use those interfaces?
Yes. For example, new StyleSheetApplicableStateChangeEvent({}) does work in web content.
Comment 12•1 year ago
•
|
||
Web page can create such event, but it will be an untrusted event.
| Assignee | ||
Updated•1 year ago
|
Comment 13•1 year ago
|
||
Based on comment #6, this bug contains a bisection range found by mozregression. However, the Regressed by field is still not filled.
:peterv, if possible, could you fill the Regressed by field and investigate this regression?
For more information, please visit BugBot documentation.
| Assignee | ||
Comment 14•1 year ago
•
|
||
I don't think this is really a regression, the WebIDL bindings have always worked like this and I don't remember that pre-WebIDL bindings had a mechanism for chrome-only interfaces (pretty sure we just exposed all nsIDOM* interfaces).
(In reply to :Gijs (he/him) from comment #6)
In some further testing I'm struggling to consistently reproduce things - is the trick here that things are only exposed once some chrome actor accesses the thing in question, or something?
The specific conditions are that chrome code creates a WebIDL object in the web content global's scope but for a WebIDL interface that's marked [ChromeOnly]. For example chrome code doing webContentWindow.createAChromeOnlyFoo(), or when we're calling an event handler in chrome code for an event created in the web content global and fired on an object from the web content global's scope. That will wrap the WebIDL object from the web content scope in an Xray before passing it to the chrome code. If at that point the chrome code has not accessed the WebIDL interface object for that interface (eg by enumerating the properties on the web content global or something), then we'll define a property on the web content global for that [ChromeOnly] interface. We do have to create the WebIDL interface object, so that we can wrap it in an Xray too (and put it on the prototype chain for the Xray for the WebIDL object), but we might be able to skip defining the property.
| Assignee | ||
Comment 15•1 year ago
|
||
(In reply to Daniel Veditz [:dveditz] from comment #10)
can the web page use those interfaces, or can it only see that they exist? That is, is the ChromeOnly attribute still enforced even though the object itself is weirdly exposed?
It can use the interface objects to create objects (new Foo()), the objects will be created in the web content scope. It can also access any static WebIDL properties on them (Foo.staticWebIDLMethod()).
| Assignee | ||
Comment 16•1 year ago
|
||
| Assignee | ||
Comment 17•1 year ago
|
||
Updated•1 year ago
|
Comment 18•1 year ago
|
||
This is only a sec-moderate because it does require devtools doing something, but it feels like there's potentially a lot of surface area exposed here, and the problem seems to be understood, so I'm going to mark this S2.
| Reporter | ||
Comment 19•1 year ago
|
||
Found another one
ChromeOnly CaretStateChangedEvent becomes exposed to web content when the user selects text because of this mozcaretstatechanged listener
https://searchfox.org/mozilla-central/rev/8c6edfe25c094e032a27722ef30f69555f556bf8/mobile/shared/chrome/geckoview/geckoview.js#672-676
(Happens only on Android; Desktop doesn't have any mozcaretstatechange listeners)
Updated•1 year ago
|
Updated•1 year ago
|
| Assignee | ||
Comment 20•1 year ago
|
||
Updated•1 year ago
|
Comment 21•1 year ago
|
||
Comment 22•1 year ago
|
||
Backed out for failing browser_aboutNetError.js:
https://hg.mozilla.org/integration/autoland/rev/5184e66f8e3be21678ce937d2be3bdae848e5a7c
Push with failures
Failure log
[task 2024-07-26T15:54:09.881Z] 15:54:09 INFO - Entering test bound resetToDefaultConfig
[task 2024-07-26T15:54:09.881Z] 15:54:09 INFO - Change TLS config to cause page load to fail, check that reset button is shown and that it works
[task 2024-07-26T15:54:09.888Z] 15:54:09 INFO - Loading and waiting for the net error
[task 2024-07-26T15:54:09.889Z] 15:54:09 INFO - Buffered messages logged at 15:52:40
[task 2024-07-26T15:54:09.889Z] 15:54:09 INFO - Console message: [JavaScript Error: "ReferenceError: Localization is not defined" {file: "chrome://global/content/aboutNetError.mjs" line: 856}]
[task 2024-07-26T15:54:09.891Z] 15:54:09 INFO - setNetErrorMessageFromCode@chrome://global/content/aboutNetError.mjs:856:20
[task 2024-07-26T15:54:09.892Z] 15:54:09 INFO - initPage@chrome://global/content/aboutNetError.mjs:625:3
[task 2024-07-26T15:54:09.892Z] 15:54:09 INFO - @chrome://global/content/aboutNetError.mjs:1567:1
[task 2024-07-26T15:54:09.893Z] 15:54:09 INFO -
[task 2024-07-26T15:54:09.893Z] 15:54:09 INFO - Buffered messages finished
[task 2024-07-26T15:54:09.894Z] 15:54:09 INFO - TEST-UNEXPECTED-FAIL | browser/base/content/test/about/browser_aboutNetError.js | Test timed out -
Comment 23•1 year ago
|
||
TEST-UNEXPECTED-FAIL | browser/base/content/test/siteIdentity/browser_check_identity_state.js | Test timed out -
| Assignee | ||
Comment 24•1 year ago
•
|
||
We have code that creates a Localization object in a global where it shouldn't be exposed, by relying on the bug that I'm trying to fix: https://searchfox.org/mozilla-central/rev/dd421ae14997e3bebb9c08634633a4a3e3edeffc/toolkit/content/aboutNetError.mjs#856
Looking at the l10n interfaces and properties there is some weirdness. document.l10n is marked as [Func="Document::DocumentSupportsL10n"]. Its interface is DocumentL10n, which is not exposed because it's marked [LegacyNoInterfaceObject] and it doesn't have a constructor, so it's really only accessible if Document::DocumentSupportsL10n is true. The interfaces on its prototype chain are DOMLocalization and Localization, these are both marked [Func="IsChromeOrUAWidget"]. [Func="IsChromeOrUAWidget"] is different than [Func="Document::DocumentSupportsL10n"], but if the intention is to avoid exposing DOMLocalization and Localization to globals that return true for IsChromeOrUAWidget but false for Document::DocumentSupportsL10n then that fails because of the prototype chain (Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Object.getPrototypeOf(document.l10n)), "constructor").value returns the interface object for DOMLocalization). What is the intention for these interfaces? Is marking DOMLocalization and Localization as [Func="Document::DocumentSupportsL10n"] the right fix, or do we need to try to restrict access to them? I don't know in detail what these expose, so hard to judge for me.
Comment 25•1 year ago
|
||
(In reply to Peter Van der Beken [:peterv] from comment #24)
[...] The interfaces on its prototype chain are
DOMLocalizationandLocalization, these are both marked[Func="IsChromeOrUAWidget"].[Func="IsChromeOrUAWidget"]is different than[Func="Document::DocumentSupportsL10n"], but if the intention is to avoid exposingDOMLocalizationandLocalizationto globals that returntrueforIsChromeOrUAWidgetbutfalseforDocument::DocumentSupportsL10nthen that fails because [...] What is the intention for these interfaces?
These interfaces and their restrictions are from before my time, and I'm not completely sure why they're different. However, I'm not aware of any situation where IsChromeOrUAWidget is false while Document::DocumentSupportsL10n is true. In practice, I would be surprised by being able to use document.l10n but not Localization or DOMLocalization from the same code.
Is marking
DOMLocalizationandLocalizationas[Func="Document::DocumentSupportsL10n"]the right fix, or do we need to try to restrict access to them? I don't know in detail what these expose, so hard to judge for me.
If that works with our current usage, then it should be fine. For instance, in the aboutNetError code, the Localization is used directly instead of document.l10n in order to format messages synchronously, which the latter does not allow.
Comment 26•1 year ago
|
||
I would be surprised by being able to use document.l10n but not Localization or DOMLocalization from the same code.
I don't think this would be even possible, nor should it. document.l10n exposes DocumentL10n which is an implementation of DOMLocalization [0].
For instance, in the aboutNetError code, the Localization is used directly instead of document.l10n in order to format messages synchronously, which the latter does not allow.
That's just about sync vs async. Localization is a dual interface (sync+async), DOMLocalization and DocumentL10n are async only. The intention behind this decision was to move everything to async, but we had to leave a sync-escape-hatch for cases like the one you just described. (and at the time of Fluent introduction we had WAY more of since Properties API is sync).
The intention behind it was to move away from sync over time as we migrate Properties to use DOMLocalization, l10n-id etc.
What is the intention for these interfaces?
I'm a bit fuzzy on the details here, but IIRC the intention here is to keep document.l10n+DocumentL10n+DOMLocalization+Localization bound to the same surface area. What the area is may change. We started with Chrome, but we talked about exposing it to extensions and we have a bunch of insecure documents that we have to localize - about* documents are such an example. I think we closely followed what DTD was exposing as we target replacing that.
It may very well be that DTD was exposing too much and the bounds were the subject of this bug as well.
The question is then, how can we expose localization API to about* documents without exposing it to the Web Content.
[0] https://searchfox.org/mozilla-central/source/dom/l10n/DocumentL10n.h#44
| Assignee | ||
Comment 27•1 year ago
|
||
(In reply to Eemeli Aro [:eemeli] from comment #25)
However, I'm not aware of any situation where
IsChromeOrUAWidgetisfalsewhileDocument::DocumentSupportsL10nistrue.
about:* error pages are one example.
Comment 28•1 year ago
|
||
Drat, sorry, I got lost in the booleans and meant to say the opposite: I'm not aware of any situation where IsChromeOrUAWidget is true while Document::DocumentSupportsL10n is false.
| Assignee | ||
Comment 29•1 year ago
|
||
(In reply to Zibi Braniecki [:zbraniecki][:gandalf] from comment #26)
What is the intention for these interfaces?
I'm a bit fuzzy on the details here, but IIRC the intention here is to keep
document.l10n+DocumentL10n+DOMLocalization+Localization bound to the same surface area. What the area is may change. We started with Chrome, but we talked about exposing it to extensions and we have a bunch of insecure documents that we have to localize - about* documents are such an example. I think we closely followed what DTD was exposing as we target replacing that.
To be clear, loading a DTD and exposing a JS api is vastly different in terms of the security-sensitive surface. I'll expose DOMLocalization and Localization when Document::DocumentSupportsL10n is true. I'm assuming that you all have determined that that's safe, since you've put them on the prototype chain of DocumentL10n.
Comment 30•1 year ago
|
||
I'm assuming that you all have determined that that's safe, since you've put them on the prototype chain of DocumentL10n
We've done security review when we landed the API and it has not changed since the landing (yay!).
I'll expose DOMLocalization and Localization when Document::DocumentSupportsL10n is true.
It may be prudent to verify that the heuristics of DocumentSupportsL10n are sane. This method has moved quite a bit and Gecko's principal model evolved since I wrote all of this.
Comment 31•1 year ago
|
||
I reported that L10N is exposed to all extensions around 4 years ago in bug 1696755. I haven't tested if this is still the case.
Comment 32•1 year ago
|
||
Comment 33•1 year ago
|
||
Comment 34•1 year ago
|
||
Backed out for various failures
Push with failures
https://treeherder.mozilla.org/logviewer?job_id=469239944&repo=autoland&lineNumber=2502
https://treeherder.mozilla.org/logviewer?job_id=469239458&repo=autoland&lineNumber=4642
https://treeherder.mozilla.org/logviewer?job_id=469239633&repo=autoland&lineNumber=2341
https://treeherder.mozilla.org/logviewer?job_id=469238479&repo=autoland&lineNumber=2153
https://treeherder.mozilla.org/logviewer?job_id=469242163&repo=autoland&lineNumber=4267
https://treeherder.mozilla.org/logviewer?job_id=469242330&repo=autoland&lineNumber=2376
https://treeherder.mozilla.org/logviewer?job_id=469237688&repo=autoland&lineNumber=8142
https://treeherder.mozilla.org/logviewer?job_id=469242410&repo=autoland&lineNumber=14349
https://treeherder.mozilla.org/logviewer?job_id=469238529&repo=autoland&lineNumber=3049
https://treeherder.mozilla.org/logviewer?job_id=469238727&repo=autoland&lineNumber=1635
| Reporter | ||
Updated•1 year ago
|
Comment 35•1 year ago
|
||
Comment 36•1 year ago
|
||
Comment 37•1 year ago
|
||
Backed for hazard failure in intl/l10n/Localization.cpp
https://hg.mozilla.org/integration/autoland/rev/520eea124117a4edac931d5d099b28902778737f
Push with failures
Failure log
[task 2024-08-06T21:53:29.391Z] TEST-UNEXPECTED-FAIL | hazards | unrooted 'aObject' of type 'JSObject*' live across GC call at intl/l10n/Localization.cpp:155
[task 2024-08-06T21:53:29.391Z] TEST-UNEXPECTED-FAIL | hazards | 1 rooting hazards detected
Comment 38•1 year ago
|
||
Comment 39•1 year ago
|
||
| Reporter | ||
Updated•1 year ago
|
Updated•1 year ago
|
Updated•1 year ago
|
Comment 40•1 year ago
|
||
Please nominate this for Beta, ESR128, and ESR115 approval. It grafts cleanly to Beta and ESR128. It'll need some rebasing for ESR115, however.
Updated•1 year ago
|
Comment 41•1 year ago
|
||
gregp, this is a truly great find. We're currently seeing this as a sec-moderate and will award it as such. but we were wondering if there's a way to do something of a higher severity with this kind of bug.
If you want, you can continue working on this and see if there's a high severity security bug lurking underneath. At that point, we would happily re-evaluate our bug bounty. Thank you.
| Assignee | ||
Comment 42•1 year ago
|
||
Comment on attachment 9412971 [details]
Bug 1906744 - Check if constructor is enabled before installing named property. r?mccr8!
Beta/Release Uplift Approval Request
- User impact if declined: Allows access from web content to DOM objects that should only be exposed to chrome code. We're unsure that this is exploitable.
- Is this code covered by automated tests?: Yes
- Has the fix been verified in Nightly?: Yes
- Needs manual test from QE?: Yes
- If yes, steps to reproduce: Automated testcase in https://phabricator.services.mozilla.com/D216672 hasn't landed yet, but steps from comment 0 could be used.
- List of other uplifts needed: None
- Risk to taking this patch: Medium
- Why is the change risky/not risky? (and alternatives if risky): There might be subtle breakage when derived WebIDL interfaces are inconsistent in their exposure wrt their parent interfaces. We caught one of these through tests. It does seem unlikely that there are more.
- String changes made/needed:
- Is Android affected?: Yes
| Assignee | ||
Updated•1 year ago
|
| Assignee | ||
Comment 43•1 year ago
|
||
Comment on attachment 9412971 [details]
Bug 1906744 - Check if constructor is enabled before installing named property. r?mccr8!
ESR Uplift Approval Request
- If this is not a sec:{high,crit} bug, please state case for ESR consideration: We're unsure that this is exploitable, but if it is it could raise the security rating.
- User impact if declined: Allows access from web content to DOM objects that should only be exposed to chrome code.
- Fix Landed on Version: 131
- Risk to taking this patch: Medium
- Why is the change risky/not risky? (and alternatives if risky): There might be subtle breakage when derived WebIDL interfaces are inconsistent in their exposure wrt their parent interfaces. We caught one of these through tests. It does seem unlikely that there are more.
| Assignee | ||
Comment 44•1 year ago
|
||
Comment 45•1 year ago
|
||
Comment on attachment 9412971 [details]
Bug 1906744 - Check if constructor is enabled before installing named property. r?mccr8!
Approved for 130.0b9.
Updated•1 year ago
|
Comment 46•1 year ago
|
||
| uplift | ||
Comment 47•1 year ago
|
||
A patch has been attached on this bug, which was already closed. Filing a separate bug will ensure better tracking. If this was not by mistake and further action is needed, please alert the appropriate party. (Or: if the patch doesn't change behavior -- e.g. landing a test case, or fixing a typo -- then feel free to disregard this message)
Updated•1 year ago
|
Comment 48•1 year ago
|
||
Comment on attachment 9412971 [details]
Bug 1906744 - Check if constructor is enabled before installing named property. r?mccr8!
Approved for 128.2esr and 115.15esr.
Updated•1 year ago
|
Updated•1 year ago
|
Comment 49•1 year ago
|
||
| uplift | ||
Updated•1 year ago
|
Comment 50•1 year ago
|
||
| uplift | ||
Updated•1 year ago
|
Comment 51•1 year ago
|
||
Reproduced the initial issue described in comment 0 using an old Nightly build from 2024-07-08. Verified that using latest Nightly build 131.0a1, Firefox 130.0RC, Firefox 128.2.0esr and Firefox 115.15.0esr the issues is not reproducible anymore across platforms. (Windows 11, macOS 13.6 and Ubuntu 22.04)
Updated•1 year ago
|
Comment 52•1 year ago
|
||
Updated•1 year ago
|
Updated•9 months ago
|
Comment 53•7 months ago
|
||
Comment 54•7 months ago
|
||
| bugherder | ||
Description
•