Skip to content

Commit 65071a6

Browse files
committed
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
1 parent 64dda35 commit 65071a6

File tree

7 files changed

+103
-1
lines changed

7 files changed

+103
-1
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
There should be no crash and no timeout.
2+
3+
The test stopped at depth 31.
4+
5+
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Test iframe depth limit</title>
5+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6+
<style>
7+
body, html {
8+
height: 100%;
9+
}
10+
iframe {
11+
width: 100%;
12+
height: 100%;
13+
display: block;
14+
}
15+
</style>
16+
</head>
17+
<body>
18+
<p>There should be no crash and no timeout.</p>
19+
<p id="result"></p>
20+
<iframe id="iframe" scrolling=no></iframe>
21+
<script>
22+
if (window.testRunner) {
23+
testRunner.dumpAsText();
24+
testRunner.waitUntilDone();
25+
}
26+
27+
iframe.src = "resources/self-referential-iframe.html?depth=1";
28+
29+
let finishTest = (depth) => {
30+
result.textContent = `The test stopped at depth ${depth}.`;
31+
if (window.testRunner)
32+
testRunner.notifyDone();
33+
};
34+
35+
addEventListener("load", () => {
36+
let delay = setTimeout(() => finishTest(0), 1000);
37+
addEventListener("message", ({ data }) => {
38+
clearTimeout(delay);
39+
delay = setTimeout(() => finishTest(data), 1000);
40+
});
41+
});
42+
</script>
43+
</body>
44+
</html>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<style>
5+
body, html {
6+
height: 100%;
7+
margin: 0;
8+
}
9+
.container {
10+
height: 100%;
11+
}
12+
iframe {
13+
display: block;
14+
border: 0.5px solid gray;
15+
width: 99.99%;
16+
height: 99.99%;
17+
}
18+
</style>
19+
<script>
20+
addEventListener("load", () => {
21+
let iframe = document.getElementsByTagName("iframe")[0];
22+
let params = new URL(location.href).searchParams;
23+
let currentDepth = params.has("depth") ? parseInt(params.get("depth")) : 0;
24+
iframe.src = `self-referential-iframe.html?depth=${currentDepth + 1}`;
25+
top.postMessage(currentDepth, "*");
26+
}, false);
27+
</script>
28+
</head>
29+
<body>
30+
<div class="container">
31+
<div class="container">
32+
<div class="container">
33+
<iframe scrolling="no"></iframe>
34+
</div>
35+
</div>
36+
</div>
37+
</body>
38+
</html>

Source/WebCore/loader/SubframeLoader.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,9 @@ RefPtr<Frame> FrameLoader::SubframeLoader::loadSubframe(HTMLFrameOwnerElement& o
268268
if (!m_frame.page() || m_frame.page()->subframeCount() >= Page::maxNumberOfFrames)
269269
return nullptr;
270270

271+
if (m_frame.tree().depth() >= Page::maxFrameDepth)
272+
return nullptr;
273+
271274
// Prevent initial empty document load from triggering load events.
272275
document->incrementLoadEventDelayCount();
273276

Source/WebCore/page/FrameTree.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,14 @@ AbstractFrame& FrameTree::top() const
491491
return *frame;
492492
}
493493

494+
unsigned FrameTree::depth() const
495+
{
496+
unsigned depth = 0;
497+
for (auto* parent = &m_thisFrame; parent; parent = parent->tree().parent())
498+
depth++;
499+
return depth;
500+
}
501+
494502
ASCIILiteral blankTargetFrameName()
495503
{
496504
return "_blank"_s;

Source/WebCore/page/FrameTree.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class FrameTree {
7575
WEBCORE_EXPORT unsigned childCount() const;
7676
unsigned descendantCount() const;
7777
WEBCORE_EXPORT AbstractFrame& top() const;
78+
unsigned depth() const;
7879

7980
WEBCORE_EXPORT AbstractFrame* scopedChild(unsigned index) const;
8081
WEBCORE_EXPORT AbstractFrame* scopedChild(const AtomString& name) const;

Source/WebCore/page/Page.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,10 @@ class Page : public Supplementable<Page>, public CanMakeWeakPtr<Page> {
694694
// This seems like a reasonable upper bound, and otherwise mutually
695695
// recursive frameset pages can quickly bring the program to its knees
696696
// with exponential growth in the number of frames.
697-
static const int maxNumberOfFrames = 1000;
697+
static constexpr int maxNumberOfFrames = 1000;
698+
699+
// Don't allow more than a certain frame depth to avoid stack exhaustion.
700+
static constexpr int maxFrameDepth = 32;
698701

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

0 commit comments

Comments
 (0)