Bug 1597427 - Check for recursive subframe loads in the parent process, r=kmag,necko-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D107311
This commit is contained in:
Anny Gakhokidze 2021-03-11 00:39:37 +00:00
Родитель 7016de7023
Коммит aa3895af8c
22 изменённых файлов: 337 добавлений и 52 удалений

Просмотреть файл

@ -0,0 +1,3 @@
iframe loaded inside of a srcdoc
<iframe id="static" srcdoc="Second nested srcdoc<iframe id='static' srcdoc='Third nested srcdoc'&gt;</iframe&gt;"></iframe>

Просмотреть файл

@ -0,0 +1,6 @@
<html>
<body>
Page 1
<iframe style="height: 100vh; width: 100%;" id="static" src="http://test1.mochi.test:8888/tests/docshell/test/navigation/frame_2_out_of_6.html"></iframe>
</body>
</html>

Просмотреть файл

@ -0,0 +1,6 @@
<html>
<body>
Page 2
<iframe style="height: 100vh; width: 100%;" id="static" src="http://sub1.test1.mochi.test:8888/tests/docshell/test/navigation/frame_3_out_of_6.html"></iframe>
</body>
</html>

Просмотреть файл

@ -0,0 +1,6 @@
<html>
<body>
Page 3
<iframe style="height: 100vh; width: 100%;" id="static" src="http://sub2.xn--lt-uia.mochi.test:8888/tests/docshell/test/navigation/frame_4_out_of_6.html"></iframe>
</body>
</html>

Просмотреть файл

@ -0,0 +1,6 @@
<html>
<body>
Page 4
<iframe style="height: 100vh; width: 100%;" id="static" src="http://test2.mochi.test:8888/tests/docshell/test/navigation/frame_5_out_of_6.html"></iframe>
</body>
</html>

Просмотреть файл

@ -0,0 +1,6 @@
<html>
<body>
Page 5
<iframe style="height: 100vh; width: 100%;" id="static" src="http://example.org:80/tests/docshell/test/navigation/frame_6_out_of_6.html"></iframe>
</body>
</html>

Просмотреть файл

@ -0,0 +1,6 @@
<html>
<body>
Page 6
<iframe style="height: 100vh; width: 100%;" id="static" src="http://example.com/tests/docshell/test/navigation/frame_1_out_of_6.html"></iframe>
</body>
</html>

Просмотреть файл

@ -0,0 +1,6 @@
<html>
<body>
example.com
<iframe style="height: 100vh; width:100%;" id="static" src="http://example.org/tests/docshell/test/navigation/frame_load_as_example_org.html"></iframe>
</body>
</html>

Просмотреть файл

@ -0,0 +1,6 @@
<html>
<body>
example.org
<iframe style="height: 100vh; width:100%;" id="static" src="http://example.com/tests/docshell/test/navigation/frame_load_as_example_com.html"></iframe>
</body>
</html>

Просмотреть файл

@ -0,0 +1,6 @@
<html>
<body>
Page 1
<iframe style="height: 100vh; width: 100%;" id="static" src="http://example.com/tests/docshell/test/navigation/frame_load_as_host2.html"></iframe>
</body>
</html>

Просмотреть файл

@ -0,0 +1,6 @@
<html>
<body>
Page 2
<iframe style="height: 100vh; width: 100%;" id="static" src="http://test1.mochi.test:8888/tests/docshell/test/navigation/frame_load_as_host3.html"></iframe>
</body>
</html>

Просмотреть файл

@ -0,0 +1,6 @@
<html>
<body>
Page 3
<iframe style="height: 100vh; width: 100%;" id="static" src="http://sub1.test1.mochi.test:8888/tests/docshell/test/navigation/frame_load_as_host1.html"></iframe>
</body>
</html>

Просмотреть файл

@ -0,0 +1,6 @@
<html>
<body>
example.com
<iframe style="height: 100vh; width:100%;" id="static" src="http://example.com/tests/docshell/test/navigation/frame_recursive.html"></iframe>
</body>
</html>

Просмотреть файл

@ -66,6 +66,20 @@ support-files =
file_scrollRestoration_part2_bfcache.html
file_scrollRestoration_part3_nobfcache.html
file_scrollRestoration_part3_nobfcache.html^headers^
frame_load_as_example_com.html
frame_load_as_example_org.html
frame_load_as_host1.html
frame_load_as_host2.html
frame_load_as_host3.html
frame_1_out_of_6.html
frame_2_out_of_6.html
frame_3_out_of_6.html
frame_4_out_of_6.html
frame_5_out_of_6.html
frame_6_out_of_6.html
frame_recursive.html
object_recursive_load.html
file_nested_srcdoc.html
[test_aboutblank_change_process.html]
[test_bug13871.html]
@ -133,3 +147,4 @@ skip-if = true # This was disabled for a few years now anyway, bug 1677544
[test_reload_large_postdata.html]
support-files =
file_reload_large_postdata.sjs
[test_recursive_frames.html]

Просмотреть файл

@ -0,0 +1,6 @@
<html>
<body width="400" height="300">
Frame 0
<object id="static" width="400" height="300" data="http://sub2.xn--lt-uia.mochi.test:8888/tests/docshell/test/navigation/object_recursive_load.html"></object>
</body>
</html>

Просмотреть файл

@ -0,0 +1,144 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Recursive Loads</title>
<meta charset="utf-8">
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1597427">Mozilla Bug 1597427</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
const TEST_CASES = [
{ // too many recursive iframes
frameId: "recursiveFrame",
expectedLocations: [
"http://example.com/tests/docshell/test/navigation/frame_recursive.html",
"http://example.com/tests/docshell/test/navigation/frame_recursive.html",
"about:blank",
],
},
{ // too many recursive iframes
frameId: "twoRecursiveIframes",
expectedLocations: [
"http://example.com/tests/docshell/test/navigation/frame_load_as_example_com.html",
"http://example.org/tests/docshell/test/navigation/frame_load_as_example_org.html",
"http://example.com/tests/docshell/test/navigation/frame_load_as_example_com.html",
"http://example.org/tests/docshell/test/navigation/frame_load_as_example_org.html",
"about:blank",
],
},
{ // too many recursive iframes
frameId: "threeRecursiveIframes",
expectedLocations: [
"http://sub1.test1.mochi.test:8888/tests/docshell/test/navigation/frame_load_as_host1.html",
"http://example.com/tests/docshell/test/navigation/frame_load_as_host2.html",
"http://test1.mochi.test:8888/tests/docshell/test/navigation/frame_load_as_host3.html",
"http://sub1.test1.mochi.test:8888/tests/docshell/test/navigation/frame_load_as_host1.html",
"http://example.com/tests/docshell/test/navigation/frame_load_as_host2.html",
"http://test1.mochi.test:8888/tests/docshell/test/navigation/frame_load_as_host3.html",
"about:blank",
],
},
{ // too many nested iframes
frameId: "sixRecursiveIframes",
expectedLocations: [
"http://example.com/tests/docshell/test/navigation/frame_1_out_of_6.html",
"http://test1.mochi.test:8888/tests/docshell/test/navigation/frame_2_out_of_6.html",
"http://sub1.test1.mochi.test:8888/tests/docshell/test/navigation/frame_3_out_of_6.html",
"http://sub2.xn--lt-uia.mochi.test:8888/tests/docshell/test/navigation/frame_4_out_of_6.html",
"http://test2.mochi.test:8888/tests/docshell/test/navigation/frame_5_out_of_6.html",
"http://example.org/tests/docshell/test/navigation/frame_6_out_of_6.html",
"http://example.com/tests/docshell/test/navigation/frame_1_out_of_6.html",
"http://test1.mochi.test:8888/tests/docshell/test/navigation/frame_2_out_of_6.html",
],
},
{ // too many recursive objects
frameId: "recursiveObject",
expectedLocations: [
"http://sub2.xn--lt-uia.mochi.test:8888/tests/docshell/test/navigation/object_recursive_load.html",
"http://sub2.xn--lt-uia.mochi.test:8888/tests/docshell/test/navigation/object_recursive_load.html",
],
},
{ // 3 nested srcdocs, should show all of them
frameId: "nestedSrcdoc",
expectedLocations: [
"about:srcdoc",
"http://example.com/tests/docshell/test/navigation/file_nested_srcdoc.html",
"about:srcdoc",
"about:srcdoc",
],
},
];
async function checkRecursiveLoad(level) {
let el = content.document.getElementById("static");
let documentURI = await SpecialPowers.spawn(
el,
[],
() => this.content.document.documentURI
);
if (documentURI == "about:blank") {
// If we had too many recursive frames, the most inner iframe's uri will be about:blank
return [documentURI];
}
if (documentURI == "about:srcdoc" && level == 3) {
// Check that we have the correct most inner srcdoc iframe
let innerText = await SpecialPowers.spawn(
el,
[],
() => this.content.document.body.innerText
);
is(innerText, "Third nested srcdoc", "correct most inner srcdoc iframe");
}
let nestedIfrOrObjectURI = [];
try {
// Throws an error when we have too many nested frames/objects, because we
// claim to have no content window for the inner most frame/object.
nestedIfrOrObjectURI = await SpecialPowers.spawn(
el,
[level + 1],
checkRecursiveLoad
);
} catch (err) {
info(
`Tried to spawn another task in the iframe/object, but got err: ${err}, must have had too many nested iframes/objects\n`
);
}
return [documentURI, ...nestedIfrOrObjectURI];
}
add_task(async () => {
for (const testCase of TEST_CASES) {
let el = document.getElementById(testCase.frameId);
let loc = await SpecialPowers.spawn(
el,
[],
() => this.content.location.href
);
let locations = await SpecialPowers.spawn(el, [1], checkRecursiveLoad);
isDeeply(
[loc, ...locations],
testCase.expectedLocations,
"iframes/object loaded in correct order"
);
}
});
</script>
</pre>
<div>
<iframe style="height: 100vh; width:25%;" id="recursiveFrame" src="http://example.com/tests/docshell/test/navigation/frame_recursive.html"></iframe>
<iframe style="height: 100vh; width:25%;" id="twoRecursiveIframes" src="http://example.com/tests/docshell/test/navigation/frame_load_as_example_com.html"></iframe>
<iframe style="height: 100vh; width:25%;" id="threeRecursiveIframes" src="http://sub1.test1.mochi.test:8888/tests/docshell/test/navigation/frame_load_as_host1.html"></iframe>
<iframe style="height: 100vh; width:25%;" id="sixRecursiveIframes" src="http://example.com/tests/docshell/test/navigation/frame_1_out_of_6.html"></iframe>
<object width="400" height="300" id="recursiveObject" data="http://sub2.xn--lt-uia.mochi.test:8888/tests/docshell/test/navigation/object_recursive_load.html"></object>
<iframe id="nestedSrcdoc" srcdoc="Srcdoc that will embed an iframe &lt;iframe id=&quot;static&quot; src=&quot;http://example.com/tests/docshell/test/navigation/file_nested_srcdoc.html&quot;&gt;&lt;/iframe&gt;"></iframe>
</div>
</body>
</html>

Просмотреть файл

@ -141,14 +141,6 @@ typedef ScrollableLayerGuid::ViewID ViewID;
using PrintPreviewResolver = std::function<void(const PrintPreviewResultInfo&)>;
// Bug 136580: Limit to the number of nested content frames that can have the
// same URL. This is to stop content that is recursively loading
// itself. Note that "#foo" on the end of URL doesn't affect
// whether it's considered identical, but "?foo" or ";foo" are
// considered and compared.
// Limit this to 2, like chromium does.
#define MAX_SAME_URL_CONTENT_FRAMES 2
// Bug 8065: Limit content frame depth to some reasonable level. This
// does not count chrome frames when determining depth, nor does it
// prevent chrome recursion. Number is fairly arbitrary, but meant to
@ -2303,8 +2295,6 @@ void nsFrameLoader::GetURL(nsString& aURI, nsIPrincipal** aTriggeringPrincipal,
}
nsresult nsFrameLoader::CheckForRecursiveLoad(nsIURI* aURI) {
nsresult rv;
MOZ_ASSERT(!IsRemoteFrame(),
"Shouldn't call CheckForRecursiveLoad on remote frames.");
@ -2330,46 +2320,6 @@ nsresult nsFrameLoader::CheckForRecursiveLoad(nsIURI* aURI) {
}
}
// Bug 136580: Check for recursive frame loading excluding about:srcdoc URIs.
// srcdoc URIs require their contents to be specified inline, so it isn't
// possible for undesirable recursion to occur without the aid of a
// non-srcdoc URI, which this method will block normally.
// Besides, URI is not enough to guarantee uniqueness of srcdoc documents.
nsAutoCString buffer;
rv = aURI->GetScheme(buffer);
if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("about")) {
rv = aURI->GetPathQueryRef(buffer);
if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("srcdoc")) {
// Duplicates allowed up to depth limits
return NS_OK;
}
}
int32_t matchCount = 0;
for (BrowsingContext* bc = parentBC; bc; bc = bc->GetParent()) {
// Check the parent URI with the URI we're loading
if (auto* docShell = nsDocShell::Cast(bc->GetDocShell())) {
// Does the URI match the one we're about to load?
nsCOMPtr<nsIURI> parentURI;
docShell->GetCurrentURI(getter_AddRefs(parentURI));
if (parentURI) {
// Bug 98158/193011: We need to ignore data after the #
bool equal;
rv = aURI->EqualsExceptRef(parentURI, &equal);
NS_ENSURE_SUCCESS(rv, rv);
if (equal) {
matchCount++;
if (matchCount >= MAX_SAME_URL_CONTENT_FRAMES) {
NS_WARNING(
"Too many nested content frames have the same url (recursion?) "
"so giving up");
return NS_ERROR_UNEXPECTED;
}
}
}
}
}
return NS_OK;
}

Просмотреть файл

@ -6,6 +6,7 @@
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
test_nestediframe body
<script>
SimpleTest.waitForExplicitFinish();
@ -40,16 +41,16 @@ localStorage.recursion = "" + recursion;
var ifr = document.createElement('iframe');
ifr.src = location.href.split("#")[0] + "#" + recursion;
document.body.appendChild(ifr);
ifr.onload = function() {
reportState("OK " + recursion);
}
ifr.onerror = function() {
reportState("KO " + recursion);
}
document.body.appendChild(ifr);
</script>
</body>
</html>

Просмотреть файл

@ -17,6 +17,9 @@
#include "nsIObjectLoadingContent.h"
#include "nsIXULRuntime.h"
#include "nsIWritablePropertyBag.h"
#include "nsFrameLoader.h"
#include "nsFrameLoaderOwner.h"
#include "nsQueryObject.h"
using namespace mozilla::dom;
using namespace mozilla::ipc;
@ -44,6 +47,7 @@ DocumentChannelChild::DocumentChannelChild(nsDocShellLoadState* aLoadState,
bool aUriModified, bool aIsXFOError)
: DocumentChannel(aLoadState, aLoadInfo, aLoadFlags, aCacheKey,
aUriModified, aIsXFOError) {
mLoadingContext = nullptr;
LOG(("DocumentChannelChild ctor [this=%p, uri=%s]", this,
aLoadState->URI()->GetSpecOrDefault().get()));
}
@ -98,6 +102,7 @@ DocumentChannelChild::AsyncOpen(nsIStreamListener* aListener) {
if (!loadingContext || loadingContext->IsDiscarded()) {
return NS_ERROR_FAILURE;
}
mLoadingContext = loadingContext;
DocumentChannelCreationArgs args;
@ -165,6 +170,21 @@ DocumentChannelChild::AsyncOpen(nsIStreamListener* aListener) {
IPCResult DocumentChannelChild::RecvFailedAsyncOpen(
const nsresult& aStatusCode) {
if (aStatusCode == NS_ERROR_RECURSIVE_DOCUMENT_LOAD) {
// This exists so that we are able to fire an error event
// for when there are too many recursive iframe or object loads.
// This is an incomplete solution, because right now we don't have a unified
// way of firing error events due to errors in document channel.
// This should be fixed in bug 1629201.
MOZ_DIAGNOSTIC_ASSERT(mLoadingContext);
if (RefPtr<Element> embedder = mLoadingContext->GetEmbedderElement()) {
if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(embedder)) {
if (RefPtr<nsFrameLoader> fl = flo->GetFrameLoader()) {
fl->FireErrorEvent();
}
}
}
}
ShutdownListeners(aStatusCode);
return IPC_OK();
}

Просмотреть файл

@ -64,6 +64,7 @@ class DocumentChannelChild final : public DocumentChannel,
RedirectToRealChannelResolver mRedirectResolver;
nsTArray<Endpoint<extensions::PStreamFilterParent>> mStreamFilterEndpoints;
BrowsingContext* mLoadingContext;
};
} // namespace net

Просмотреть файл

@ -67,6 +67,14 @@ mozilla::LazyLogModule gDocumentChannelLog("DocumentChannel");
extern mozilla::LazyLogModule gSHIPBFCacheLog;
// Bug 136580: Limit to the number of nested content frames that can have the
// same URL. This is to stop content that is recursively loading
// itself. Note that "#foo" on the end of URL doesn't affect
// whether it's considered identical, but "?foo" or ";foo" are
// considered and compared.
// Limit this to 2, like chromium does.
static constexpr int kMaxSameURLContentFrames = 2;
using namespace mozilla::dom;
namespace mozilla {
@ -450,6 +458,57 @@ WindowGlobalParent* DocumentLoadListener::GetParentWindowContext() const {
return mParentWindowContext;
}
bool CheckRecursiveLoad(CanonicalBrowsingContext* aLoadingContext,
nsDocShellLoadState* aLoadState,
DocumentLoadListener* aDLL, bool aIsDocumentLoad,
LoadInfo* aLoadInfo) {
// Bug 136580: Check for recursive frame loading excluding about:srcdoc URIs.
// srcdoc URIs require their contents to be specified inline, so it isn't
// possible for undesirable recursion to occur without the aid of a
// non-srcdoc URI, which this method will block normally.
// Besides, URI is not enough to guarantee uniqueness of srcdoc documents.
nsAutoCString buffer;
if (aLoadState->URI()->SchemeIs("about")) {
nsresult rv = aLoadState->URI()->GetPathQueryRef(buffer);
if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("srcdoc")) {
// Duplicates allowed up to depth limits
return true;
}
}
RefPtr<WindowGlobalParent> parent;
if (!aIsDocumentLoad) { // object load
parent = aLoadingContext->GetCurrentWindowGlobal();
} else {
parent = aLoadingContext->GetParentWindowContext();
}
int matchCount = 0;
CanonicalBrowsingContext* ancestorBC;
for (WindowGlobalParent* ancestorWGP = parent; ancestorWGP;
ancestorWGP = ancestorBC->GetParentWindowContext()) {
ancestorBC = ancestorWGP->BrowsingContext();
MOZ_ASSERT(ancestorBC);
if (nsCOMPtr<nsIURI> parentURI = ancestorWGP->GetDocumentURI()) {
bool equal;
nsresult rv = aLoadState->URI()->EqualsExceptRef(parentURI, &equal);
NS_ENSURE_SUCCESS(rv, false);
if (equal) {
matchCount++;
if (matchCount >= kMaxSameURLContentFrames) {
NS_WARNING(
"Too many nested content frames/objects have the same url "
"(recursion?) "
"so giving up");
return false;
}
}
}
}
return true;
}
auto DocumentLoadListener::Open(nsDocShellLoadState* aLoadState,
LoadInfo* aLoadInfo, nsLoadFlags aLoadFlags,
uint32_t aCacheKey,
@ -469,6 +528,16 @@ auto DocumentLoadListener::Open(nsDocShellLoadState* aLoadState,
mLoadIdentifier = aLoadState->GetLoadIdentifier();
// Check for infinite recursive object or iframe loads
if (aLoadState->OriginalFrameSrc() || !mIsDocumentLoad) {
if (!CheckRecursiveLoad(loadingContext, aLoadState, this, mIsDocumentLoad,
aLoadInfo)) {
*aRv = NS_ERROR_RECURSIVE_DOCUMENT_LOAD;
mParentChannelListener = nullptr;
return nullptr;
}
}
if (!nsDocShell::CreateAndConfigureRealChannelForLoadState(
loadingContext, aLoadState, aLoadInfo, mParentChannelListener,
nullptr, attrs, aLoadFlags, aCacheKey, *aRv,

Просмотреть файл

@ -785,6 +785,10 @@ with modules["DOM"]:
# context.
errors["NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI"] = FAILURE(1037)
# The request failed because there are too many recursive iframes or
# objects being loaded.
errors["NS_ERROR_RECURSIVE_DOCUMENT_LOAD"] = FAILURE(1038)
# May be used to indicate when e.g. setting a property value didn't
# actually change the value, like for obj.foo = "bar"; obj.foo = "bar";
# the second assignment throws NS_SUCCESS_DOM_NO_OPERATION.