Skip to content

Commit

Permalink
Cherry-pick 47ed6aa. rdar://problem/110801928
Browse files Browse the repository at this point in the history
    Block sandboxed frames from navigating to javascript URLs without allow-scripts sandbox flag.
    https://bugs.webkit.org/show_bug.cgi?id=257824
    rdar://108462161

    Reviewed by Alex Christensen.

    Sandboxed iframes could execute script in a target frame by navigating
    the frame to a javascript: URL. For example, the top frame when the
    iframe has the sandbox flag "allow-top-navigation". This change checks to see if
    the "allow-scripts" flag is set before executing the URL in the target frame.

    * LayoutTests/http/tests/security/sandboxed-iframe-javascript-self-navigation-expected.txt: Added.
    * LayoutTests/http/tests/security/sandboxed-iframe-javascript-self-navigation.html: Added.
    * LayoutTests/http/tests/security/sandboxed-iframe-javascript-top-navigation-expected.txt: Added.
    * LayoutTests/http/tests/security/sandboxed-iframe-javascript-top-navigation.html: Added.
    * Source/WebCore/loader/FrameLoader.cpp:
    (WebCore::FrameLoader::executeJavaScriptURL):
    * Source/WebCore/loader/NavigationRequester.cpp:
    (WebCore::NavigationRequester::from):
    * Source/WebCore/loader/NavigationRequester.h:
    (WebCore::NavigationRequester::encode const):
    (WebCore::NavigationRequester::decode):

    Canonical link: https://commits.webkit.org/259548.813@safari-7615-branch

Identifier: 245886.897@safari-7613.4.1.0-branch
  • Loading branch information
rreno authored and MyahCobbs committed Jun 30, 2023
1 parent 8c68ef3 commit 2aed4a9
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CONSOLE MESSAGE: Blocked script execution in 'about:srcdoc' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.
Tests that an iframe without "allow-scripts" can not navigate itself to a javascript URL.


Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<script>
if (window.testRunner) {
testRunner.dumpAsText();
testRunner.waitUntilDone();
}
</script>
<body>
<p>
Tests that an iframe without "allow-scripts" can not navigate
itself to a javascript URL.
</p>
<iframe id="ifr" sandbox="allow-same-origin" srcdoc="<a href='javascript:alert(`FAIL`)'>Click Me</a>"></iframe>
<script>
ifr.addEventListener("load", () => {
ifr.contentDocument.getElementsByTagName("a")[0].click();
if (window.testRunner)
testRunner.notifyDone();
});
</script>
</body>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CONSOLE MESSAGE: Blocked script execution in 'about:srcdoc' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.
Tests that an iframe with "allow-top-navigation" but without "allow-scripts" can not navigate the top frame to a javascript URL.


Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<script>
if (window.testRunner) {
testRunner.dumpAsText();
testRunner.waitUntilDone();
}
</script>
<body>
<p>
Tests that an iframe with "allow-top-navigation" but without "allow-scripts"
can not navigate the top frame to a javascript URL.
</p>
<iframe id="ifr" sandbox="allow-same-origin allow-top-navigation" srcdoc="<a href='javascript:alert(`FAIL`)' target='_top'>Click Me</a>"></iframe>
<script>
ifr.addEventListener("load", () => {
ifr.contentDocument.getElementsByTagName("a")[0].click();
if (window.testRunner)
testRunner.notifyDone();
});
</script>
</body>
36 changes: 36 additions & 0 deletions Source/WebCore/loader/FrameLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3461,6 +3461,42 @@ bool FrameLoader::dispatchBeforeUnloadEvent(Chrome& chrome, FrameLoader* frameLo
return chrome.runBeforeUnloadConfirmPanel(text, m_frame);
}

void FrameLoader::executeJavaScriptURL(const URL& url, const NavigationAction& action)
{
ASSERT(url.protocolIsJavaScript());

bool isFirstNavigationInFrame = false;
if (!m_stateMachine.committedFirstRealDocumentLoad()) {
m_stateMachine.advanceTo(FrameLoaderStateMachine::DisplayingInitialEmptyDocumentPostCommit);
isFirstNavigationInFrame = true;
}

RefPtr ownerDocument = m_frame.ownerElement() ? &m_frame.ownerElement()->document() : nullptr;
if (ownerDocument)
ownerDocument->incrementLoadEventDelayCount();

bool didReplaceDocument = false;
bool requesterSandboxedFromScripts = action.requester() ? (action.requester()->sandboxFlags & SandboxScripts) : false;
if (requesterSandboxedFromScripts) {
// FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
// This message is identical to the message in ScriptController::canExecuteScripts.
if (auto* document = m_frame.document())
document->addConsoleMessage(MessageSource::Security, MessageLevel::Error, "Blocked script execution in '" + action.requester()->url.stringCenterEllipsizedToLength() + "' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.");
} else
m_frame.script().executeJavaScriptURL(url, action.requester() ? action.requester()->securityOrigin.ptr() : nullptr, action.shouldReplaceDocumentIfJavaScriptURL(), didReplaceDocument);

// We need to communicate that a load happened, even if the JavaScript URL execution didn't end up replacing the document.
if (auto* document = m_frame.document(); isFirstNavigationInFrame && !didReplaceDocument)
document->dispatchWindowLoadEvent();

checkCompleted();

if (ownerDocument)
ownerDocument->decrementLoadEventDelayCount();

m_quickRedirectComing = false;
}

void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest& request, FormState* formState, NavigationPolicyDecision navigationPolicyDecision, AllowNavigationToInvalidURL allowNavigationToInvalidURL)
{
// If we loaded an alternate page to replace an unreachableURL, we'll get in here with a
Expand Down
6 changes: 4 additions & 2 deletions Source/WebCore/loader/NavigationRequester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ NavigationRequester NavigationRequester::from(Document& document)
document.url(),
document.securityOrigin(),
document.topOrigin(),
document.crossOriginOpenerPolicy(),
document.identifier(),
createGlobalFrameIdentifier(document)
document.policyContainer(),
document.identifier(),
createGlobalFrameIdentifier(document),
document.sandboxFlags()
};
}

Expand Down
13 changes: 10 additions & 3 deletions Source/WebCore/loader/NavigationRequester.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@

#include "CrossOriginOpenerPolicy.h"
#include "GlobalFrameIdentifier.h"
#include "ScriptExecutionContextIdentifier.h"
#include "PolicyContainer.h"
#include "SecurityContext.h"
#include "SecurityOrigin.h"

namespace WebCore {
Expand All @@ -43,6 +44,7 @@ struct NavigationRequester {
CrossOriginOpenerPolicy crossOriginOpenerPolicy;
ScriptExecutionContextIdentifier documentIdentifier;
std::optional<GlobalFrameIdentifier> globalFrameIdentifier;
SandboxFlags sandboxFlags;

template<class Encoder> void encode(Encoder&) const;
template<class Decoder> static std::optional<NavigationRequester> decode(Decoder&);
Expand All @@ -51,7 +53,7 @@ struct NavigationRequester {
template<class Encoder>
void NavigationRequester::encode(Encoder& encoder) const
{
encoder << url << securityOrigin.get() << topOrigin.get() << crossOriginOpenerPolicy << documentIdentifier << globalFrameIdentifier;
encoder << url << securityOrigin.get() << topOrigin.get() << policyContainer << documentIdentifier << globalFrameIdentifier << sandboxFlags;
}

template<class Decoder>
Expand Down Expand Up @@ -85,7 +87,12 @@ std::optional<NavigationRequester> NavigationRequester::decode(Decoder& decoder)
if (!globalFrameIdentifier)
return std::nullopt;

return NavigationRequester { WTFMove(*url), securityOrigin.releaseNonNull(), topOrigin.releaseNonNull(), WTFMove(*crossOriginOpenerPolicy), *documentIdentifier, *globalFrameIdentifier };
std::optional<SandboxFlags> sandboxFlags;
decoder >> sandboxFlags;
if (!sandboxFlags)
return std::nullopt;

return NavigationRequester { WTFMove(*url), WTFMove(*securityOrigin), WTFMove(*topOrigin), WTFMove(*policyContainer), *documentIdentifier, *globalFrameIdentifier, *sandboxFlags };
}

} // namespace WebCore

0 comments on commit 2aed4a9

Please sign in to comment.