Diff
Modified: trunk/LayoutTests/ChangeLog (239741 => 239742)
--- trunk/LayoutTests/ChangeLog 2019-01-08 21:24:01 UTC (rev 239741)
+++ trunk/LayoutTests/ChangeLog 2019-01-08 21:28:53 UTC (rev 239742)
@@ -1,3 +1,28 @@
+2019-01-08 Chris Dumez <cdu...@apple.com>
+
+ Prevent cross-site top-level navigations from third-party iframes
+ https://bugs.webkit.org/show_bug.cgi?id=193076
+ <rdar://problem/36074736>
+
+ Reviewed by Alex Christensen.
+
+ Add layout test coverage.
+
+ * http/tests/security/allow-top-level-navigations-by-third-party-iframes-to-same-origin-expected.txt: Added.
+ * http/tests/security/allow-top-level-navigations-by-third-party-iframes-to-same-origin.html: Added.
+ * http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-previous-user-activation-expected.txt: Added.
+ * http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-previous-user-activation.html: Added.
+ * http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-user-activation-expected.txt: Added.
+ * http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-user-activation.html: Added.
+ * http/tests/security/block-top-level-navigations-by-third-party-iframes-expected.txt: Added.
+ * http/tests/security/block-top-level-navigations-by-third-party-iframes.html: Added.
+ * http/tests/security/resources/navigate-top-level-frame-to-failure-page.html: Added.
+ * http/tests/security/resources/navigate-top-level-frame-to-success-page-same-origin.html: Added.
+ * http/tests/security/resources/navigate-top-level-frame-to-success-page-with-previous-user-gesture.html: Added.
+ * http/tests/security/resources/navigate-top-level-frame-to-success-page-with-user-gesture.html: Added.
+ * http/tests/security/resources/should-have-loaded.html: Added.
+ * http/tests/security/resources/should-not-have-loaded.html: Added.
+
2019-01-08 Truitt Savell <tsav...@apple.com>
Revert expectation changes to pointerevents in iOS after https://trac.webkit.org/changeset/239704/webkit
Modified: trunk/LayoutTests/http/tests/cookies/same-site/resources/click-hyperlink.php (239741 => 239742)
--- trunk/LayoutTests/http/tests/cookies/same-site/resources/click-hyperlink.php 2019-01-08 21:24:01 UTC (rev 239741)
+++ trunk/LayoutTests/http/tests/cookies/same-site/resources/click-hyperlink.php 2019-01-08 21:28:53 UTC (rev 239742)
@@ -13,6 +13,10 @@
$targetAttribute = 'target="' . $_GET["target"] . '"';
?>
<a href="" echo $_GET['href']; ?>" <?php echo $targetAttribute; ?>>Click</a>
-<script>document.querySelector("a").click()</script>
+<script>
+internals.withUserGesture(() => {
+ document.querySelector("a").click();
+});
+</script>
</body>
</html>
Added: trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-to-same-origin-expected.txt (0 => 239742)
--- trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-to-same-origin-expected.txt (rev 0)
+++ trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-to-same-origin-expected.txt 2019-01-08 21:28:53 UTC (rev 239742)
@@ -0,0 +1,5 @@
+PASS Did the load
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-to-same-origin.html (0 => 239742)
--- trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-to-same-origin.html (rev 0)
+++ trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-to-same-origin.html 2019-01-08 21:28:53 UTC (rev 239742)
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src=""
+<script>
+description("Test that top-level navigations by a third-party iframe are allowed if the destination URL is same-origin");
+jsTestIsAsync = true;
+_onload_ = () => {
+ setTimeout(() => {
+ document.getElementById('testFrame').src = ""
+ setTimeout(() => {
+ testFailed("Navigation by subframe should not have been blocked");
+ finishJSTest();
+ }, 5000);
+ }, 10);
+}
+</script>
+<iframe id="testFrame"></iframe>
+</body>
+</html>
Added: trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-previous-user-activation-expected.txt (0 => 239742)
--- trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-previous-user-activation-expected.txt (rev 0)
+++ trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-previous-user-activation-expected.txt 2019-01-08 21:28:53 UTC (rev 239742)
@@ -0,0 +1,5 @@
+PASS Did the load
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-previous-user-activation.html (0 => 239742)
--- trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-previous-user-activation.html (rev 0)
+++ trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-previous-user-activation.html 2019-01-08 21:28:53 UTC (rev 239742)
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src=""
+<script>
+description("Test that top-level navigations by a third-party iframe are allowed with a previous user gesture.");
+jsTestIsAsync = true;
+_onload_ = () => {
+ setTimeout(() => {
+ document.getElementById('testFrame').src = ""
+ setTimeout(() => {
+ testFailed("Navigation by subframe should not have been blocked");
+ finishJSTest();
+ }, 5000);
+ }, 10);
+}
+</script>
+<iframe id="testFrame"></iframe>
+</body>
+</html>
Added: trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-user-activation-expected.txt (0 => 239742)
--- trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-user-activation-expected.txt (rev 0)
+++ trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-user-activation-expected.txt 2019-01-08 21:28:53 UTC (rev 239742)
@@ -0,0 +1,5 @@
+PASS Did the load
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-user-activation.html (0 => 239742)
--- trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-user-activation.html (rev 0)
+++ trunk/LayoutTests/http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-user-activation.html 2019-01-08 21:28:53 UTC (rev 239742)
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src=""
+<script>
+description("Test that top-level navigations by a third-party iframe are allowed with a user gesture.");
+jsTestIsAsync = true;
+_onload_ = () => {
+ setTimeout(() => {
+ document.getElementById('testFrame').src = ""
+ setTimeout(() => {
+ testFailed("Navigation by subframe should not have been blocked");
+ finishJSTest();
+ }, 5000);
+ }, 10);
+}
+</script>
+<iframe id="testFrame"></iframe>
+</body>
+</html>
Added: trunk/LayoutTests/http/tests/security/block-top-level-navigations-by-third-party-iframes-expected.txt (0 => 239742)
--- trunk/LayoutTests/http/tests/security/block-top-level-navigations-by-third-party-iframes-expected.txt (rev 0)
+++ trunk/LayoutTests/http/tests/security/block-top-level-navigations-by-third-party-iframes-expected.txt 2019-01-08 21:28:53 UTC (rev 239742)
@@ -0,0 +1,16 @@
+CONSOLE MESSAGE: line 6: Unsafe _javascript_ attempt to initiate navigation for frame with URL 'http://127.0.0.1:8000/security/block-top-level-navigations-by-third-party-iframes.html' from frame with URL 'http://localhost:8000/security/resources/navigate-top-level-frame-to-failure-page.html'. The frame attempting navigation of the top-level window is cross-origin and the user has never interacted with the frame.
+
+CONSOLE MESSAGE: line 6: SecurityError: The operation is insecure.
+CONSOLE MESSAGE: line 6: Unsafe _javascript_ attempt to initiate navigation for frame with URL 'http://127.0.0.1:8000/security/block-top-level-navigations-by-third-party-iframes.html' from frame with URL 'http://localhost:8000/security/resources/navigate-top-level-frame-to-failure-page.html'. The frame attempting navigation of the top-level window is cross-origin and the user has never interacted with the frame.
+
+CONSOLE MESSAGE: line 6: SecurityError: The operation is insecure.
+Test blocking of suspicious top-level navigations by a third-party iframe
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS All navigations by subframes have been blocked
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/http/tests/security/block-top-level-navigations-by-third-party-iframes.html (0 => 239742)
--- trunk/LayoutTests/http/tests/security/block-top-level-navigations-by-third-party-iframes.html (rev 0)
+++ trunk/LayoutTests/http/tests/security/block-top-level-navigations-by-third-party-iframes.html 2019-01-08 21:28:53 UTC (rev 239742)
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src=""
+<script>
+description("Test blocking of suspicious top-level navigations by a third-party iframe");
+jsTestIsAsync = true;
+_onload_ = () => {
+ setTimeout(() => {
+ document.getElementById('testFrame').src = ""
+ setTimeout(() => {
+ testPassed("All navigations by subframes have been blocked");
+ finishJSTest();
+ }, 100);
+ }, 10);
+}
+</script>
+<iframe src=""
+<iframe id="testFrame"></iframe>
+</body>
+</html>
Modified: trunk/LayoutTests/http/tests/security/frameNavigation/resources/iframe-that-performs-parent-navigation.html (239741 => 239742)
--- trunk/LayoutTests/http/tests/security/frameNavigation/resources/iframe-that-performs-parent-navigation.html 2019-01-08 21:24:01 UTC (rev 239741)
+++ trunk/LayoutTests/http/tests/security/frameNavigation/resources/iframe-that-performs-parent-navigation.html 2019-01-08 21:28:53 UTC (rev 239742)
@@ -11,7 +11,9 @@
function performTest()
{
- parent.location = "http://localhost:8000/security/frameNavigation/resources/navigation-changed-iframe.html";
+ internals.withUserGesture(() => {
+ parent.location = "http://localhost:8000/security/frameNavigation/resources/navigation-changed-iframe.html";
+ });
}
</script>
</head>
Added: trunk/LayoutTests/http/tests/security/resources/navigate-top-level-frame-to-failure-page.html (0 => 239742)
--- trunk/LayoutTests/http/tests/security/resources/navigate-top-level-frame-to-failure-page.html (rev 0)
+++ trunk/LayoutTests/http/tests/security/resources/navigate-top-level-frame-to-failure-page.html 2019-01-08 21:28:53 UTC (rev 239742)
@@ -0,0 +1,10 @@
+<html>
+<body>
+Success! The navigation was blocked
+<script>
+window.addEventListener("load", e => {
+ top.location = "http://localhost:8000/security/resources/should-not-have-loaded.html";
+});
+</script>
+</body>
+</html>
Added: trunk/LayoutTests/http/tests/security/resources/navigate-top-level-frame-to-success-page-same-origin.html (0 => 239742)
--- trunk/LayoutTests/http/tests/security/resources/navigate-top-level-frame-to-success-page-same-origin.html (rev 0)
+++ trunk/LayoutTests/http/tests/security/resources/navigate-top-level-frame-to-success-page-same-origin.html 2019-01-08 21:28:53 UTC (rev 239742)
@@ -0,0 +1,10 @@
+<html>
+<body>
+Failure! The navigation should not have been blocked
+<script>
+window.addEventListener("load", e => {
+ top.location = "http://127.0.0.1:8000/security/resources/should-have-loaded.html";
+});
+</script>
+</body>
+</html>
Added: trunk/LayoutTests/http/tests/security/resources/navigate-top-level-frame-to-success-page-with-previous-user-gesture.html (0 => 239742)
--- trunk/LayoutTests/http/tests/security/resources/navigate-top-level-frame-to-success-page-with-previous-user-gesture.html (rev 0)
+++ trunk/LayoutTests/http/tests/security/resources/navigate-top-level-frame-to-success-page-with-previous-user-gesture.html 2019-01-08 21:28:53 UTC (rev 239742)
@@ -0,0 +1,14 @@
+<html>
+<body>
+Failure! The navigation should not have been blocked
+<script>
+window.addEventListener("load", e => {
+ internals.withUserGesture(() => {
+ setTimeout(() => {
+ top.location = "http://localhost:8000/security/resources/should-have-loaded.html";
+ }, 0);
+ });
+});
+</script>
+</body>
+</html>
Added: trunk/LayoutTests/http/tests/security/resources/navigate-top-level-frame-to-success-page-with-user-gesture.html (0 => 239742)
--- trunk/LayoutTests/http/tests/security/resources/navigate-top-level-frame-to-success-page-with-user-gesture.html (rev 0)
+++ trunk/LayoutTests/http/tests/security/resources/navigate-top-level-frame-to-success-page-with-user-gesture.html 2019-01-08 21:28:53 UTC (rev 239742)
@@ -0,0 +1,12 @@
+<html>
+<body>
+Failure! The navigation should not have been blocked
+<script>
+window.addEventListener("load", e => {
+ internals.withUserGesture(() => {
+ top.location = "http://localhost:8000/security/resources/should-have-loaded.html";
+ });
+});
+</script>
+</body>
+</html>
Added: trunk/LayoutTests/http/tests/security/resources/should-have-loaded.html (0 => 239742)
--- trunk/LayoutTests/http/tests/security/resources/should-have-loaded.html (rev 0)
+++ trunk/LayoutTests/http/tests/security/resources/should-have-loaded.html 2019-01-08 21:28:53 UTC (rev 239742)
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<script src=""
+<body>
+<script>
+jsTestIsAsync = true;
+testPassed("Did the load");
+finishJSTest();
+</script>
+</body>
+</html>
Added: trunk/LayoutTests/http/tests/security/resources/should-not-have-loaded.html (0 => 239742)
--- trunk/LayoutTests/http/tests/security/resources/should-not-have-loaded.html (rev 0)
+++ trunk/LayoutTests/http/tests/security/resources/should-not-have-loaded.html 2019-01-08 21:28:53 UTC (rev 239742)
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<script src=""
+<body>
+<script>
+jsTestIsAsync = true;
+testFailed("Should not have loaded");
+finishJSTest();
+</script>
+</body>
+</html>
Modified: trunk/LayoutTests/http/tests/security/resources/xss-DENIED-window-open-parent-attacker.html (239741 => 239742)
--- trunk/LayoutTests/http/tests/security/resources/xss-DENIED-window-open-parent-attacker.html 2019-01-08 21:24:01 UTC (rev 239741)
+++ trunk/LayoutTests/http/tests/security/resources/xss-DENIED-window-open-parent-attacker.html 2019-01-08 21:28:53 UTC (rev 239742)
@@ -1,4 +1,6 @@
<script>
-open("_javascript_:alert('failed')", "_top");
-parent.postMessage("", "*");
+internals.withUserGesture(() => {
+ open("_javascript_:alert('failed')", "_top");
+ parent.postMessage("", "*");
+});
</script>
Modified: trunk/LayoutTests/http/tests/security/xss-DENIED-window-open-parent-expected.txt (239741 => 239742)
--- trunk/LayoutTests/http/tests/security/xss-DENIED-window-open-parent-expected.txt 2019-01-08 21:24:01 UTC (rev 239741)
+++ trunk/LayoutTests/http/tests/security/xss-DENIED-window-open-parent-expected.txt 2019-01-08 21:28:53 UTC (rev 239742)
@@ -1,3 +1,3 @@
-CONSOLE MESSAGE: line 2: Blocked a frame with origin "http://localhost:8080" from accessing a frame with origin "http://127.0.0.1:8000". Protocols, domains, and ports must match.
+CONSOLE MESSAGE: line 3: Blocked a frame with origin "http://localhost:8080" from accessing a frame with origin "http://127.0.0.1:8000". Protocols, domains, and ports must match.
This test passes if there is no alert dialog.
Modified: trunk/Source/WebCore/ChangeLog (239741 => 239742)
--- trunk/Source/WebCore/ChangeLog 2019-01-08 21:24:01 UTC (rev 239741)
+++ trunk/Source/WebCore/ChangeLog 2019-01-08 21:28:53 UTC (rev 239742)
@@ -1,3 +1,39 @@
+2019-01-08 Chris Dumez <cdu...@apple.com>
+
+ Prevent cross-site top-level navigations from third-party iframes
+ https://bugs.webkit.org/show_bug.cgi?id=193076
+ <rdar://problem/36074736>
+
+ Reviewed by Alex Christensen.
+
+ Prevent cross-site top-level navigations from third-party iframes if the following conditions are met:
+ 1. Its tries to navigate the top-level page cross-site (different eTDL+1)
+ 2. The user has never interacted with the third-party iframe or any of its subframes
+
+ This experiment's intent is to block suspicious main-frame navigations by third-party content. The feature
+ is behind a runtime experimental feature flag, on by default.
+
+ Tests: http/tests/security/allow-top-level-navigations-by-third-party-iframes-to-same-origin.html
+ http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-previous-user-activation.html
+ http/tests/security/allow-top-level-navigations-by-third-party-iframes-with-user-activation.html
+ http/tests/security/block-top-level-navigations-by-third-party-iframes.html
+
+ * dom/Document.cpp:
+ (WebCore::printNavigationErrorMessage):
+ (WebCore::Document::canNavigate):
+ (WebCore::Document::canNavigateInternal):
+ (WebCore::Document::isNavigationBlockedByThirdPartyIFrameRedirectBlocking):
+ * dom/Document.h:
+ * dom/UserGestureIndicator.cpp:
+ * page/DOMWindow.cpp:
+ (WebCore::DOMWindow::setLocation):
+ * page/DOMWindow.h:
+ * page/Frame.h:
+ * page/Location.cpp:
+ (WebCore::Location::replace):
+ (WebCore::Location::setLocation):
+ * page/Settings.yaml:
+
2019-01-08 Alex Christensen <achristen...@webkit.org>
Stop using NetworkStorageSession in WebProcess
Modified: trunk/Source/WebCore/dom/Document.cpp (239741 => 239742)
--- trunk/Source/WebCore/dom/Document.cpp 2019-01-08 21:24:01 UTC (rev 239741)
+++ trunk/Source/WebCore/dom/Document.cpp 2019-01-08 21:28:53 UTC (rev 239742)
@@ -456,12 +456,12 @@
return false;
}
-static void printNavigationErrorMessage(Frame* frame, const URL& activeURL, const char* reason)
+static void printNavigationErrorMessage(Frame& frame, const URL& activeURL, const char* reason)
{
- String message = "Unsafe _javascript_ attempt to initiate navigation for frame with URL '" + frame->document()->url().string() + "' from frame with URL '" + activeURL.string() + "'. " + reason + "\n";
+ String message = "Unsafe _javascript_ attempt to initiate navigation for frame with URL '" + frame.document()->url().string() + "' from frame with URL '" + activeURL.string() + "'. " + reason + "\n";
// FIXME: should we print to the console of the document performing the navigation instead?
- frame->document()->domWindow()->printErrorMessage(message);
+ frame.document()->domWindow()->printErrorMessage(message);
}
uint64_t Document::s_globalTreeVersion = 0;
@@ -3337,7 +3337,7 @@
return m_socketProvider.get();
}
-bool Document::canNavigate(Frame* targetFrame)
+bool Document::canNavigate(Frame* targetFrame, const URL& destinationURL)
{
if (!m_frame)
return false;
@@ -3348,30 +3348,45 @@
if (!targetFrame)
return true;
+ if (!canNavigateInternal(*targetFrame))
+ return false;
+
+ if (isNavigationBlockedByThirdPartyIFrameRedirectBlocking(*targetFrame, destinationURL)) {
+ printNavigationErrorMessage(*targetFrame, url(), "The frame attempting navigation of the top-level window is cross-origin and the user has never interacted with the frame."_s);
+ return false;
+ }
+
+ return true;
+}
+
+bool Document::canNavigateInternal(Frame& targetFrame)
+{
+ ASSERT(m_frame);
+
// Cases (i), (ii) and (iii) pass the tests from the specifications but might not pass the "security origin" tests.
// i. A frame can navigate its top ancestor when its 'allow-top-navigation' flag is set (sometimes known as 'frame-busting').
- if (!isSandboxed(SandboxTopNavigation) && targetFrame == &m_frame->tree().top())
+ if (!isSandboxed(SandboxTopNavigation) && &targetFrame == &m_frame->tree().top())
return true;
// ii. A frame can navigate its top ancestor when its 'allow-top-navigation-by-user-activation' flag is set and navigation is triggered by user activation.
- if (!isSandboxed(SandboxTopNavigationByUserActivation) && UserGestureIndicator::processingUserGesture() && targetFrame == &m_frame->tree().top())
+ if (!isSandboxed(SandboxTopNavigationByUserActivation) && UserGestureIndicator::processingUserGesture() && &targetFrame == &m_frame->tree().top())
return true;
// iii. A sandboxed frame can always navigate its descendants.
- if (isSandboxed(SandboxNavigation) && targetFrame->tree().isDescendantOf(m_frame))
+ if (isSandboxed(SandboxNavigation) && targetFrame.tree().isDescendantOf(m_frame))
return true;
// From https://html.spec.whatwg.org/multipage/browsers.html#allowed-to-navigate.
// 1. If A is not the same browsing context as B, and A is not one of the ancestor browsing contexts of B, and B is not a top-level browsing context, and A's active document's active sandboxing
// flag set has its sandboxed navigation browsing context flag set, then abort these steps negatively.
- if (m_frame != targetFrame && isSandboxed(SandboxNavigation) && targetFrame->tree().parent() && !targetFrame->tree().isDescendantOf(m_frame)) {
+ if (m_frame != &targetFrame && isSandboxed(SandboxNavigation) && targetFrame.tree().parent() && !targetFrame.tree().isDescendantOf(m_frame)) {
printNavigationErrorMessage(targetFrame, url(), "The frame attempting navigation is sandboxed, and is therefore disallowed from navigating its ancestors."_s);
return false;
}
// 2. Otherwise, if B is a top-level browsing context, and is one of the ancestor browsing contexts of A, then:
- if (m_frame != targetFrame && targetFrame == &m_frame->tree().top()) {
+ if (m_frame != &targetFrame && &targetFrame == &m_frame->tree().top()) {
bool triggeredByUserActivation = UserGestureIndicator::processingUserGesture();
// 1. If this algorithm is triggered by user activation and A's active document's active sandboxing flag set has its sandboxed top-level navigation with user activation browsing context flag set, then abort these steps negatively.
if (triggeredByUserActivation && isSandboxed(SandboxTopNavigationByUserActivation)) {
@@ -3387,7 +3402,7 @@
// 3. Otherwise, if B is a top-level browsing context, and is neither A nor one of the ancestor browsing contexts of A, and A's Document's active sandboxing flag set has its
// sandboxed navigation browsing context flag set, and A is not the one permitted sandboxed navigator of B, then abort these steps negatively.
- if (!targetFrame->tree().parent() && m_frame != targetFrame && targetFrame != &m_frame->tree().top() && isSandboxed(SandboxNavigation) && targetFrame->loader().opener() != m_frame) {
+ if (!targetFrame.tree().parent() && m_frame != &targetFrame && &targetFrame != &m_frame->tree().top() && isSandboxed(SandboxNavigation) && targetFrame.loader().opener() != m_frame) {
printNavigationErrorMessage(targetFrame, url(), "The frame attempting navigation is sandboxed, and is not allowed to navigate this popup."_s);
return false;
}
@@ -3401,7 +3416,7 @@
//
// See http://www.adambarth.com/papers/2008/barth-jackson-mitchell.pdf for
// historical information about this security check.
- if (canAccessAncestor(securityOrigin(), targetFrame))
+ if (canAccessAncestor(securityOrigin(), &targetFrame))
return true;
// Top-level frames are easier to navigate than other frames because they
@@ -3415,11 +3430,11 @@
// some way related to the frame being navigate (e.g., by the "opener"
// and/or "parent" relation). Requiring some sort of relation prevents a
// document from navigating arbitrary, unrelated top-level frames.
- if (!targetFrame->tree().parent()) {
- if (targetFrame == m_frame->loader().opener())
+ if (!targetFrame.tree().parent()) {
+ if (&targetFrame == m_frame->loader().opener())
return true;
- if (canAccessAncestor(securityOrigin(), targetFrame->loader().opener()))
+ if (canAccessAncestor(securityOrigin(), targetFrame.loader().opener()))
return true;
}
@@ -3427,6 +3442,37 @@
return false;
}
+// Prevent cross-site top-level redirects from third-party iframes unless the user has ever interacted with the frame.
+bool Document::isNavigationBlockedByThirdPartyIFrameRedirectBlocking(Frame& targetFrame, const URL& destinationURL)
+{
+ if (!settings().thirdPartyIframeRedirectBlockingEnabled())
+ return false;
+
+ // Only prevent top frame navigations by subframes.
+ if (m_frame == &targetFrame || &targetFrame != &m_frame->tree().top())
+ return false;
+
+ // Only prevent navigations by subframes that the user has not interacted with.
+ if (m_frame->hasHadUserInteraction())
+ return false;
+
+ // Only prevent navigations by unsandboxed iframes. Such navigations by unsandboxed iframes would have already been blocked unless
+ // "allow-top-navigation" / "allow-top-navigation-by-user-activation" was explicitly specified.
+ if (sandboxFlags() != SandboxNone)
+ return false;
+
+ // Only prevent navigations by third-party iframes.
+ if (canAccessAncestor(securityOrigin(), &targetFrame))
+ return false;
+
+ // Only prevent cross-site navigations.
+ auto* targetDocument = targetFrame.document();
+ if (targetDocument && (targetDocument->securityOrigin().canAccess(SecurityOrigin::create(destinationURL)) || registrableDomainsAreEqual(targetDocument->url(), destinationURL)))
+ return false;
+
+ return true;
+}
+
void Document::didRemoveAllPendingStylesheet()
{
if (auto* parser = scriptableDocumentParser())
Modified: trunk/Source/WebCore/dom/Document.h (239741 => 239742)
--- trunk/Source/WebCore/dom/Document.h 2019-01-08 21:24:01 UTC (rev 239741)
+++ trunk/Source/WebCore/dom/Document.h 2019-01-08 21:28:53 UTC (rev 239742)
@@ -706,7 +706,7 @@
#endif
SocketProvider* socketProvider() final;
- bool canNavigate(Frame* targetFrame);
+ bool canNavigate(Frame* targetFrame, const URL& destinationURL = URL());
bool usesStyleBasedEditability() const;
void setHasElementUsingStyleBasedEditability();
@@ -1644,6 +1644,9 @@
void checkViewportDependentPictures();
void checkAppearanceDependentPictures();
+ bool canNavigateInternal(Frame& targetFrame);
+ bool isNavigationBlockedByThirdPartyIFrameRedirectBlocking(Frame& targetFrame, const URL& destinationURL);
+
#if ENABLE(INTERSECTION_OBSERVER)
void notifyIntersectionObserversTimerFired();
#endif
Modified: trunk/Source/WebCore/dom/UserGestureIndicator.cpp (239741 => 239742)
--- trunk/Source/WebCore/dom/UserGestureIndicator.cpp 2019-01-08 21:24:01 UTC (rev 239741)
+++ trunk/Source/WebCore/dom/UserGestureIndicator.cpp 2019-01-08 21:28:53 UTC (rev 239742)
@@ -27,6 +27,7 @@
#include "UserGestureIndicator.h"
#include "Document.h"
+#include "Frame.h"
#include "ResourceLoadObserver.h"
#include <wtf/MainThread.h>
#include <wtf/NeverDestroyed.h>
@@ -59,6 +60,12 @@
if (processInteractionStyle == ProcessInteractionStyle::Immediate)
ResourceLoadObserver::shared().logUserInteractionWithReducedTimeResolution(document->topDocument());
document->topDocument().setUserDidInteractWithPage(true);
+ if (auto* frame = document->frame()) {
+ if (!frame->hasHadUserInteraction()) {
+ for (; frame; frame = frame->tree().parent())
+ frame->setHasHadUserInteraction();
+ }
+ }
}
}
Modified: trunk/Source/WebCore/page/DOMWindow.cpp (239741 => 239742)
--- trunk/Source/WebCore/page/DOMWindow.cpp 2019-01-08 21:24:01 UTC (rev 239741)
+++ trunk/Source/WebCore/page/DOMWindow.cpp 2019-01-08 21:28:53 UTC (rev 239742)
@@ -2103,7 +2103,7 @@
}
}
-void DOMWindow::setLocation(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& urlString, SetLocationLocking locking)
+void DOMWindow::setLocation(DOMWindow& activeWindow, const URL& completedURL, SetLocationLocking locking)
{
if (!isCurrentlyDisplayedInFrame())
return;
@@ -2113,17 +2113,9 @@
return;
auto* frame = this->frame();
- if (!activeDocument->canNavigate(frame))
+ if (!activeDocument->canNavigate(frame, completedURL))
return;
- Frame* firstFrame = firstWindow.frame();
- if (!firstFrame)
- return;
-
- URL completedURL = firstFrame->document()->completeURL(urlString);
- if (completedURL.isNull())
- return;
-
if (isInsecureScriptAccess(activeWindow, completedURL))
return;
Modified: trunk/Source/WebCore/page/DOMWindow.h (239741 => 239742)
--- trunk/Source/WebCore/page/DOMWindow.h 2019-01-08 21:24:01 UTC (rev 239741)
+++ trunk/Source/WebCore/page/DOMWindow.h 2019-01-08 21:28:53 UTC (rev 239742)
@@ -145,7 +145,7 @@
Navigator& clientInformation() { return navigator(); }
Location& location();
- void setLocation(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& location, SetLocationLocking = LockHistoryBasedOnGestureState);
+ void setLocation(DOMWindow& activeWindow, const URL& completedURL, SetLocationLocking = LockHistoryBasedOnGestureState);
DOMSelection* getSelection();
Modified: trunk/Source/WebCore/page/Frame.h (239741 => 239742)
--- trunk/Source/WebCore/page/Frame.h 2019-01-08 21:24:01 UTC (rev 239741)
+++ trunk/Source/WebCore/page/Frame.h 2019-01-08 21:28:53 UTC (rev 239742)
@@ -172,6 +172,9 @@
bool documentIsBeingReplaced() const { return m_documentIsBeingReplaced; }
+ bool hasHadUserInteraction() const { return m_hasHadUserInteraction; }
+ void setHasHadUserInteraction() { m_hasHadUserInteraction = true; }
+
// ======== All public functions below this point are candidates to move out of Frame into another class. ========
WEBCORE_EXPORT void injectUserScripts(UserScriptInjectionTime);
@@ -348,6 +351,7 @@
bool m_documentIsBeingReplaced { false };
unsigned m_navigationDisableCount { 0 };
unsigned m_selfOnlyRefCount { 0 };
+ bool m_hasHadUserInteraction { false };
protected:
UniqueRef<EventHandler> m_eventHandler;
Modified: trunk/Source/WebCore/page/Location.cpp (239741 => 239742)
--- trunk/Source/WebCore/page/Location.cpp 2019-01-08 21:24:01 UTC (rev 239741)
+++ trunk/Source/WebCore/page/Location.cpp 2019-01-08 21:28:53 UTC (rev 239742)
@@ -225,7 +225,7 @@
return setLocation(activeWindow, firstWindow, url);
}
-void Location::replace(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& url)
+void Location::replace(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& urlString)
{
auto* frame = this->frame();
if (!frame)
@@ -232,8 +232,18 @@
return;
ASSERT(frame->document());
ASSERT(frame->document()->domWindow());
+
+ Frame* firstFrame = firstWindow.frame();
+ if (!firstFrame || !firstFrame->document())
+ return;
+
+ URL completedURL = firstFrame->document()->completeURL(urlString);
+ // FIXME: The specification says to throw a SyntaxError if the URL is not valid.
+ if (completedURL.isNull())
+ return;
+
// We call DOMWindow::setLocation directly here because replace() always operates on the current frame.
- frame->document()->domWindow()->setLocation(activeWindow, firstWindow, url, LockHistoryAndBackForwardList);
+ frame->document()->domWindow()->setLocation(activeWindow, completedURL, LockHistoryAndBackForwardList);
}
void Location::reload(DOMWindow& activeWindow)
@@ -264,15 +274,26 @@
frame->navigationScheduler().scheduleRefresh(activeDocument);
}
-ExceptionOr<void> Location::setLocation(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& url)
+ExceptionOr<void> Location::setLocation(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& urlString)
{
auto* frame = this->frame();
ASSERT(frame);
- if (!activeWindow.document()->canNavigate(frame))
+
+ Frame* firstFrame = firstWindow.frame();
+ if (!firstFrame || !firstFrame->document())
+ return { };
+
+ URL completedURL = firstFrame->document()->completeURL(urlString);
+ // FIXME: The specification says to throw a SyntaxError if the URL is not valid.
+ if (completedURL.isNull())
+ return { };
+
+ if (!activeWindow.document()->canNavigate(frame, completedURL))
return Exception { SecurityError };
+
ASSERT(frame->document());
ASSERT(frame->document()->domWindow());
- frame->document()->domWindow()->setLocation(activeWindow, firstWindow, url);
+ frame->document()->domWindow()->setLocation(activeWindow, completedURL);
return { };
}
Modified: trunk/Source/WebCore/page/Settings.yaml (239741 => 239742)
--- trunk/Source/WebCore/page/Settings.yaml 2019-01-08 21:24:01 UTC (rev 239741)
+++ trunk/Source/WebCore/page/Settings.yaml 2019-01-08 21:28:53 UTC (rev 239742)
@@ -332,6 +332,9 @@
HTTPSUpgradeEnabled:
initial: false
+thirdPartyIframeRedirectBlockingEnabled:
+ initial: true
+
cookieEnabled:
initial: true
mediaEnabled:
Modified: trunk/Source/WebCore/platform/network/ResourceRequestBase.h (239741 => 239742)
--- trunk/Source/WebCore/platform/network/ResourceRequestBase.h 2019-01-08 21:24:01 UTC (rev 239741)
+++ trunk/Source/WebCore/platform/network/ResourceRequestBase.h 2019-01-08 21:28:53 UTC (rev 239742)
@@ -260,7 +260,10 @@
// FIXME: Find a better place for these functions.
inline String toRegistrableDomain(const URL& a)
{
- return ResourceRequestBase::partitionName(a.host().toString());
+ auto host = a.host().toString();
+ auto registrableDomain = ResourceRequestBase::partitionName(host);
+ // Fall back to the host if we cannot determine the registrable domain.
+ return registrableDomain.isEmpty() ? host : registrableDomain;
}
inline bool registrableDomainsAreEqual(const URL& a, const URL& b)
Modified: trunk/Source/WebKit/ChangeLog (239741 => 239742)
--- trunk/Source/WebKit/ChangeLog 2019-01-08 21:24:01 UTC (rev 239741)
+++ trunk/Source/WebKit/ChangeLog 2019-01-08 21:28:53 UTC (rev 239742)
@@ -1,3 +1,15 @@
+2019-01-08 Chris Dumez <cdu...@apple.com>
+
+ Prevent cross-site top-level navigations from third-party iframes
+ https://bugs.webkit.org/show_bug.cgi?id=193076
+ <rdar://problem/36074736>
+
+ Reviewed by Alex Christensen.
+
+ Add experimental feature flag, on by default.
+
+ * Shared/WebPreferences.yaml:
+
2019-01-08 Alex Christensen <achristen...@webkit.org>
Remove more use of NetworkProcess::singleton
Modified: trunk/Source/WebKit/Shared/WebPreferences.yaml (239741 => 239742)
--- trunk/Source/WebKit/Shared/WebPreferences.yaml 2019-01-08 21:24:01 UTC (rev 239741)
+++ trunk/Source/WebKit/Shared/WebPreferences.yaml 2019-01-08 21:28:53 UTC (rev 239742)
@@ -42,6 +42,13 @@
humanReadableDescription: "Automatic HTTPS upgrade for known supported sites"
category: experimental
+ThirdPartyIframeRedirectBlockingEnabled:
+ type: bool
+ defaultValue: true
+ humanReadableName: "Block top-level redirects by third-party iframes"
+ humanReadableDescription: "Block top-level redirects by third-party iframes"
+ category: experimental
+
JavaEnabled:
type: bool
defaultValue: false