Contents can paint outside of an iframe by using <dialog>, position:fixed and transform
Categories
(Core :: Web Painting, defect, P1)
Tracking
()
People
(Reporter: Oriol, Assigned: emilio)
References
Details
(4 keywords, Whiteboard: [adv-main121+])
Attachments
(7 files, 2 obsolete files)
Load this testcase:
<!DOCTYPE html>
<iframe sandbox="allow-scripts" srcdoc='
<main style="transform: scale(1)">
<dialog>
<div style="position: fixed; background: cyan; padding: 10px;">Painting outside iframe!</div>
</dialog>
</main>
<script>document.querySelector("dialog").showModal();</script>
'></iframe>
The div is painted outside the sandboxed iframe!!!
Luckily, the parts outside the iframe don't seem to react to hit-testing, but it still seems problematic from a security perspective.
I can reproduce the problem at least since 2021-06-09 8508c35e49793fbe402ad2a706a57fabd1c2b0c4. Earlier builds instacrash any tab before I can try the testcase.
Reporter | ||
Comment 1•2 years ago
|
||
Reporter | ||
Comment 2•2 years ago
|
||
I imagine this could be taken advantage like in this example.
The user loads a trusted website that has legitimate content, but it also embeds something like a video from a third party.
If the third party turns evil, it can paint all the page in red, and place a scary message at the top of the page.
Then the user thinks the message comes from the trusted website, and calls the scammers.
Updated•2 years ago
|
Comment 3•2 years ago
•
|
||
(In reply to Oriol Brufau [:Oriol] from comment #0)
Earlier builds instacrash any tab before I can try the testcase.
If you're on linux, this is likely due to the fact that there's a range of Nightlies whose sandboxing code doesn't play well with current OS libraries (or something to that effect). Assuming you're running into this instacrash on mozregression, the workaround is to invoke mozregression like so, to disable the sandbox:
mozregression --pref "security.sandbox.content.level:0"
Comment 4•2 years ago
|
||
Regression range:
https://hg.mozilla.org/mozilla-central/pushloghtml?fromchange=840349cabe2f7e724539ab1b7c50a992f77af0d5&tochange=7765d979d044be1fb03054013474a22e0b9ea022
In there, this probably would have been from:
https://hg.mozilla.org/mozilla-central/rev/5ee2649eac6153f5b4349c1d3621357892917a3a
Sean Feng — Bug 1675857 - Update <dialog> internal styles for spec alignment r=emilio
Comment 5•2 years ago
•
|
||
In particular: that commit removed a transform
decl on dialog:-moz-modal-dialog
, which maybe we were accidentally relying on to make the dialog a container for fixed-pos elements...
Comment 6•2 years ago
|
||
Yeah -- probably not a real fix but if I add either transform: translateY(0%);
or contain: paint;
to that dialog:modal
rule, then we seem to aggressively clip (to the bounds of the dialog itself) in the attached testcases. So the transform
removal from html.css is what made these testcases start rendering "bad".
That transform
styling wasn't strictly enforced, though, so I can repro it further back if I adjust the "possible attack" attachment to use <dialog style="transform: none !important">
.
Updated•2 years ago
|
Updated•2 years ago
|
Comment 7•2 years ago
|
||
Comment 8•2 years ago
|
||
er, just resetting transform
takes me back to bug 1651089 where we switched from translate
to transform
in the relevant html.css rule.
If I reset both transform
and translate
per this rule, I can repro further back..
Comment 9•2 years ago
|
||
testcase 3 repro's the issue back further than when dialog was default-enabled on Nightly.
After enabling the pref, mozregression gives me this range for testcase 3:
https://hg.mozilla.org/mozilla-central/pushloghtml?fromchange=d6abd35b54ad39fa030217716b172fa9883bf3c8&tochange=9a2f741cef6ae4ae568a17fd3ffe52599f0a574f
That points to this commit:
https://hg.mozilla.org/mozilla-central/rev/fcfe734c80312372987f142c64747cccf95e511a
sefeng — Bug 1637307 - Push/Pop dialog to top layer when needed r=smaug,emilio
In "bad" builds just after that range, I do have to interact with the page -- e.g. mousewheel-scroll -- before the red overlay appears. But then it does appear and covers up the whole viewport.
In "good" builds before that range, the red content seems to be properly clipped to the iframe's bounds, just as it is in Chrome. (Minor difference: the dialog's clipped portions don't create scrollable overflow in the iframe in current Chrome -- i.e. no scrollbars show up there -- whereas it does create scrollable overflow in the iframe, in pre-regression "good" Firefox builds).
Comment 10•2 years ago
•
|
||
This does seem to be mitigated (i.e. I can't repro) if the iframe is truly cross-origin and hence out-of-process, FWIW.
e.g.
(1) if I take the srdoc=...
attribute contents of testcase 3 and put them in a separate file called iframe.html
and serve them over HTTP using e.g. python2 -m SimpleHTTPServer
and then load the testcase as a file:/// URL (but with <iframe src="http://localhost:8000/iframe.html">
), then the whole iframe ends up red, but none of the red leaks out into the outer page.
(2) If I repeat the process with both pages served over HTTP from different hosts, then I similarly don't see any red leaking out beyond the frame.
(3) If I serve both pages over http from the same host using different port numbers, then the red leaks out. (I had assumed it wouldn't since I thought port-number-differences made something cross-origin, but there's probably some subtlety I'm forgetting there... Maybe we share the same process for cross-origin hosts that only differ by port number, or something?)
Comment 11•2 years ago
|
||
Things can be same-site but not same-origin, in which case they have to be in the same process due to document domain or some such thing. That happens if they have the same host but different ports, or if some other parts of the URI don't match but some do.
Comment 12•2 years ago
|
||
Thanks, that matches what I'm seeing I think. I suppose that describes the attached testcases, which have the sandbox
attribute, which IIUC means we should be treating the iframe as cross-origin. So while it's a nice bonus that this doesn't repro in an OOP iframe, but not a full mitigation since "same-process-but-still-cross-origin" scenarios do seem to be affected.
I've captured this bug in rr using the attached testcase 3
(which I believe is in the "same-process but cross-origin" category), and I've submitted it to pernosco, in case it's handy. I added a line of logging to note the moment (and PID) when we enter HTMLDialogElement::ShowModal
-- it looks like this in the trace:
[Child 805041, Main Thread] WARNING: ****Now entering HTMLDialogElement::ShowModal: file /scratch/work/builds/mozilla-central/mozilla/dom/html/HTMLDialogElement.cpp:118
I'll share the pernosco URL once I've got it.
Comment 13•2 years ago
|
||
pernosco trace, at the moment showModal is called for testcase 3:
https://pernos.co/debug/pSxD0iNwjhfsT5A6YhMegQ/index.html#f{m[AoCL,AA_,t[xQ,DEix_,f{e[AoCI,A3z5rA_,s{af7IujJAA,bCik,uCihxoA,oCjKAww___/
Updated•2 years ago
|
Assignee | ||
Comment 14•2 years ago
|
||
I wonder if bug 1799216 or so helps here fwiw (that fixes a clipping issue that could be relevant here).
Assignee | ||
Comment 15•2 years ago
|
||
It doesn't seem to. This does seem to properly fix it but I'm not sure why the current code doesn't quite work:
diff --git a/layout/generic/nsSubDocumentFrame.cpp b/layout/generic/nsSubDocumentFrame.cpp
index 5ebe2aab907db..fc7e6eed5a1fd 100644
--- a/layout/generic/nsSubDocumentFrame.cpp
+++ b/layout/generic/nsSubDocumentFrame.cpp
@@ -434,7 +434,8 @@ void nsSubDocumentFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
}
DisplayListClipState::AutoSaveRestore clipState(aBuilder);
- clipState.ClipContainingBlockDescendantsToContentBox(aBuilder, this);
+ clipState.ClipContentDescendants(GetContentRectRelativeToSelf() +
+ aBuilder->ToReferenceFrame(this));
nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
bool constructZoomItem = subdocRootFrame && parentAPD != subdocAPD;
Assignee | ||
Comment 16•2 years ago
|
||
I'm not sure if the flattening here is worth it, probably not. If we can
flatten both the subdoc/zoom items, we might ignore the clip
incorrectly.
This simplifies the code, though see the bug comments, there might be a
better fix?
Updated•2 years ago
|
Updated•2 years ago
|
Updated•2 years ago
|
Updated•2 years ago
|
Reporter | ||
Comment 17•2 years ago
|
||
FYI, https://github.com/w3c/csswg-drafts/issues/8040 asks whether the dialog should be the containing block for fixed contents.
Basically the opposite of comment 5, so it would be a workaround for this problem.
Comment 18•2 years ago
|
||
Based on comment #9, this bug contains a bisection range found by mozregression. However, the Regressed by
field is still not filled.
:emilio, if possible, could you fill the Regressed by
field and investigate this regression?
For more information, please visit auto_nag documentation.
Assignee | ||
Updated•2 years ago
|
Assignee | ||
Comment 19•2 years ago
|
||
(It's a bit more complicated, see above)
Updated•2 years ago
|
Updated•2 years ago
|
Comment 20•2 years ago
|
||
So the relevant frame structure here looks like
block (transformed)
-placeholder for dialog (true fixed pos)
-absolute list
--the "fixed" div
true fixed list
-dialog
--placeholder for the "fixed" div
Note that the placeholder for the "fixed" div is not in the containing block for its positioned frame. Our painting code is not able to handle situations like this, and it seems like it would be hard to make it work. Specifically when we are building display items for the transformed div at
we clear the clip because the containing transform item will clip it's contents. So when we save the clip for the "fixed" div when we call MarkAbsoluteFramesForDisplayList there is no clip. We don't traverse into placeholders to top layer items in the normal display list building pass, instead they are handled after building the rest of the display list in the root scroll frame. At this point we follow the placeholder for the "fixed" div and we restore the empty clip and build the display items that are expecting to be inside the transform item that we built before, but they aren't inside the transform item now so they are unclipped.
Even with Emilio's patch applied we are still going to render testcases like this incorrectly (no matter how we define correctly). For example if one makes the iframe in testcase 1 scrollable the "painting outside iframe" content will not draw outside the iframe but it will appear fixed but disappear if you scroll far enough. (Safari also renders this testcases buggily).
So it seems the best course here is to get the spec updated and update our code to follow the spec so we can avoid problematic frame tree structures like this.
Comment 21•2 years ago
|
||
The other potential good thing about changing the frame tree here is that I believe (from my memory) a lot of the testcases that trip ASR related asserts involve fixed pos and dialog so that might fix those too.
Updated•2 years ago
|
Updated•2 years ago
|
Comment 22•2 years ago
|
||
Some previous similar bugs were rated sec-low (i.e. bug 1792643, bug 1735265) - is sec-high correct for this specific instance?
Comment 23•2 years ago
•
|
||
Yeah, I think this is essentially the same attack scenario as those other bugs (with the added comfort -- maybe also present in those bugs -- that fission provides a relatively-effictive mitigation as I indirectly noted in comment 10).
bug 1735265 comment 17 essentially describes the attack scenario here, I think, so it seems fine to apply the same security rating that we settled on there.
Updated•2 years ago
|
Assignee | ||
Comment 24•1 year ago
|
||
This is the most reasonable behavior to preserve our layout invariants.
Once https://github.com/w3c/csswg-drafts/issues/8040 gets resolved we
can reconsider this.
I think at the very least we should promote fixed-pos elements inside
the top layer to the top layer, but that has its own set of
implications.
Updated•1 year ago
|
Comment 25•1 year ago
|
||
Comment 26•1 year ago
|
||
Comment 27•1 year ago
|
||
Backed out for causing wpt/mochitest failures on top-layer-stacking.tentative.html / test_fullscreen-api.html
Backout link: https://hg.mozilla.org/integration/autoland/rev/77c3d7a4bf46f3dd8622ff35e6763d02e801a0e6
Assignee | ||
Updated•1 year ago
|
Comment 28•1 year ago
|
||
Comment 29•1 year ago
|
||
Comment 30•1 year ago
|
||
The patch landed in nightly and beta is affected.
:emilio, is this bug important enough to require an uplift?
- If yes, please nominate the patch for beta approval.
- If no, please set
status-firefox119
towontfix
.
For more information, please visit BugBot documentation.
Assignee | ||
Comment 31•1 year ago
|
||
There's some potential compat impact so I think this should ride the trains.
Updated•1 year ago
|
Updated•1 year ago
|
Updated•1 year ago
|
Comment 32•1 year ago
|
||
backout uplift |
Comment 33•1 year ago
|
||
Backed out of 120 for causing bug 1863402
https://hg.mozilla.org/releases/mozilla-beta/rev/b0f04f13b25453822484a6a53bc6b2b39bba4ae7
Backout push
This was also backed out of Fx121 in bug 1863402
https://hg.mozilla.org/mozilla-central/rev/820ffb716b9d
Assignee | ||
Comment 34•1 year ago
|
||
Well, yeah, but bug 1863402 also fixed this.
Updated•1 year ago
|
Updated•1 year ago
|
Comment 35•1 year ago
|
||
Comment 36•1 year ago
|
||
Updated•1 year ago
|
Comment 37•9 months ago
|
||
Bulk-unhiding security bugs fixed in Firefox 119-121 (Fall 2023). Use "moo-doctrine-subsidy" to filter
Comment 38•8 months ago
|
||
Sorry for the burst of bugspam: filter on tinkling-glitter-filtrate
Adding reporter-external
keyword to security bugs found by non-employees for accounting reasons
Description
•