Skip to content
Permalink
Browse files
Add iframe depth limit
https://bugs.webkit.org/show_bug.cgi?id=67940
rdar://101560112

Reviewed by Darin Adler and Alex Christensen.

* LayoutTests/fast/frames/frame-depth-limit-expected.txt: Added.
* LayoutTests/fast/frames/frame-depth-limit.html: Added.
* LayoutTests/fast/frames/resources/self-referential-iframe.html: Added.
* Source/WebCore/loader/SubframeLoader.cpp:
(WebCore::FrameLoader::SubframeLoader::loadSubframe):
* Source/WebCore/page/FrameTree.cpp:
(WebCore::FrameTree::depth const):
* Source/WebCore/page/FrameTree.h:
* Source/WebCore/page/Page.h:

Canonical link: https://commits.webkit.org/257550@main
  • Loading branch information
nt1m committed Dec 8, 2022
1 parent 64dda35 commit 65071a674a05a195850647ffccfb78b36fc22000
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 1 deletion.
@@ -0,0 +1,5 @@
There should be no crash and no timeout.

The test stopped at depth 31.


@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<title>Test iframe depth limit</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<style>
body, html {
height: 100%;
}
iframe {
width: 100%;
height: 100%;
display: block;
}
</style>
</head>
<body>
<p>There should be no crash and no timeout.</p>
<p id="result"></p>
<iframe id="iframe" scrolling=no></iframe>
<script>
if (window.testRunner) {
testRunner.dumpAsText();
testRunner.waitUntilDone();
}

iframe.src = "resources/self-referential-iframe.html?depth=1";

let finishTest = (depth) => {
result.textContent = `The test stopped at depth ${depth}.`;
if (window.testRunner)
testRunner.notifyDone();
};

addEventListener("load", () => {
let delay = setTimeout(() => finishTest(0), 1000);
addEventListener("message", ({ data }) => {
clearTimeout(delay);
delay = setTimeout(() => finishTest(data), 1000);
});
});
</script>
</body>
</html>
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<style>
body, html {
height: 100%;
margin: 0;
}
.container {
height: 100%;
}
iframe {
display: block;
border: 0.5px solid gray;
width: 99.99%;
height: 99.99%;
}
</style>
<script>
addEventListener("load", () => {
let iframe = document.getElementsByTagName("iframe")[0];
let params = new URL(location.href).searchParams;
let currentDepth = params.has("depth") ? parseInt(params.get("depth")) : 0;
iframe.src = `self-referential-iframe.html?depth=${currentDepth + 1}`;
top.postMessage(currentDepth, "*");
}, false);
</script>
</head>
<body>
<div class="container">
<div class="container">
<div class="container">
<iframe scrolling="no"></iframe>
</div>
</div>
</div>
</body>
</html>
@@ -268,6 +268,9 @@ RefPtr<Frame> FrameLoader::SubframeLoader::loadSubframe(HTMLFrameOwnerElement& o
if (!m_frame.page() || m_frame.page()->subframeCount() >= Page::maxNumberOfFrames)
return nullptr;

if (m_frame.tree().depth() >= Page::maxFrameDepth)
return nullptr;

// Prevent initial empty document load from triggering load events.
document->incrementLoadEventDelayCount();

@@ -491,6 +491,14 @@ AbstractFrame& FrameTree::top() const
return *frame;
}

unsigned FrameTree::depth() const
{
unsigned depth = 0;
for (auto* parent = &m_thisFrame; parent; parent = parent->tree().parent())
depth++;
return depth;
}

ASCIILiteral blankTargetFrameName()
{
return "_blank"_s;
@@ -75,6 +75,7 @@ class FrameTree {
WEBCORE_EXPORT unsigned childCount() const;
unsigned descendantCount() const;
WEBCORE_EXPORT AbstractFrame& top() const;
unsigned depth() const;

WEBCORE_EXPORT AbstractFrame* scopedChild(unsigned index) const;
WEBCORE_EXPORT AbstractFrame* scopedChild(const AtomString& name) const;
@@ -694,7 +694,10 @@ class Page : public Supplementable<Page>, public CanMakeWeakPtr<Page> {
// This seems like a reasonable upper bound, and otherwise mutually
// recursive frameset pages can quickly bring the program to its knees
// with exponential growth in the number of frames.
static const int maxNumberOfFrames = 1000;
static constexpr int maxNumberOfFrames = 1000;

// Don't allow more than a certain frame depth to avoid stack exhaustion.
static constexpr int maxFrameDepth = 32;

void setEditable(bool isEditable) { m_isEditable = isEditable; }
bool isEditable() const { return m_isEditable; }

0 comments on commit 65071a6

Please sign in to comment.