WKUserScripts injecting into all frames seem to violate app-bound domains
https://bugs.webkit.org/show_bug.cgi?id=213816
<rdar://problem/64872296>
Reviewed by Brady Eidson.
Source/WebKit:
Patch to make app-bound domain checks on a per-frame basis instead
of per-page-based-on-main-frame. This will prevent WKUserScripts from
being injected in subframes when the main frame is app-bound. Now, all
ancestors of a frame must be app-bound in order for a frame to
evaluate user script.
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::setIsNavigatingToAppBoundDomainAndCheckIfPermitted):
Move 'isMainFrame' check to be inside of the check for
m_limitsNavigationsToAppBoundDomains. The navigation should fail if
the main frame is not app-bound and the WKWebView is marked with the
limitsNavigationToAppBoundDomains flag, but non-app-bound subframe
loads are allowed to continue.
(WebKit::WebPageProxy::decidePolicyForNavigationAction):
* UIProcess/WebPageProxy.h:
(WebKit::WebPageProxy::isTopFrameNavigatingToAppBoundDomain const):
* UIProcess/WebProcessPool.cpp:
(WebKit::WebProcessPool::download):
* WebProcess/Network/WebLoaderStrategy.cpp:
(WebKit::WebLoaderStrategy::scheduleLoadFromNetworkProcess):
(WebKit::WebLoaderStrategy::loadResourceSynchronously):
(WebKit::WebLoaderStrategy::startPingLoad):
(WebKit::WebLoaderStrategy::preconnectTo):
For reporting loads to the Network Process, we only care if the main
frame is app bound, so we need to store that info in the WebPage and
WebPageProxy.
* WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp:
(WebKit::WebFrameLoaderClient::shouldEnableInAppBrowserPrivacyProtections const):
* WebProcess/WebPage/WebFrame.cpp:
(WebKit::WebFrame::startDownload):
(WebKit::WebFrame::convertMainResourceLoadToDownload):
(WebKit::WebFrame::shouldEnableInAppBrowserPrivacyProtections):
(WebKit::WebFrame::isTopFrameNavigatingToAppBoundDomain const):
* WebProcess/WebPage/WebFrame.h:
(WebKit::WebFrame::setIsNavigatingToAppBoundDomain):
(WebKit::WebFrame::isNavigatingToAppBoundDomain const):
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::loadRequest):
(WebKit::WebPage::loadDataImpl):
(WebKit::WebPage::didReceivePolicyDecision):
(WebKit::WebPage::runJavaScript):
(WebKit::WebPage::setIsNavigatingToAppBoundDomain):
(WebKit::WebPage::shouldEnableInAppBrowserPrivacyProtections): Deleted.
* WebProcess/WebPage/WebPage.h:
(WebKit::WebPage::needsInAppBrowserPrivacyQuirks):
(WebKit::WebPage::isNavigatingToAppBoundDomain const): Deleted.
Move all app-bound domain logic from the WebPage to the WebFrame now
that checks are on a per-frame basis.
Tools:
Added API test coverage. Sets up a tree like so: [app-bound domain,
non-app-bound domain, app-bound domain]. Script injection should only
be successful in the top frame. Since message handlers are disabled for non-
app-bound domains, this test injects script which loads an image using a
custom url scheme. If the load succeeds, we increment a counter, and
expect only one successful script load (the main frame).
* TestWebKitAPI/Tests/WebKitCocoa/InAppBrowserPrivacy.mm:
(-[InAppBrowserSchemeHandler webView:startURLSchemeTask:]):
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@263806 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog
index d4759ed..34bcf1c 100644
--- a/Source/WebKit/ChangeLog
+++ b/Source/WebKit/ChangeLog
@@ -1,3 +1,62 @@
+2020-07-01 Kate Cheney <katherine_cheney@apple.com>
+
+ WKUserScripts injecting into all frames seem to violate app-bound domains
+ https://bugs.webkit.org/show_bug.cgi?id=213816
+ <rdar://problem/64872296>
+
+ Reviewed by Brady Eidson.
+
+ Patch to make app-bound domain checks on a per-frame basis instead
+ of per-page-based-on-main-frame. This will prevent WKUserScripts from
+ being injected in subframes when the main frame is app-bound. Now, all
+ ancestors of a frame must be app-bound in order for a frame to
+ evaluate user script.
+
+ * UIProcess/WebPageProxy.cpp:
+ (WebKit::WebPageProxy::setIsNavigatingToAppBoundDomainAndCheckIfPermitted):
+ Move 'isMainFrame' check to be inside of the check for
+ m_limitsNavigationsToAppBoundDomains. The navigation should fail if
+ the main frame is not app-bound and the WKWebView is marked with the
+ limitsNavigationToAppBoundDomains flag, but non-app-bound subframe
+ loads are allowed to continue.
+
+ (WebKit::WebPageProxy::decidePolicyForNavigationAction):
+ * UIProcess/WebPageProxy.h:
+ (WebKit::WebPageProxy::isTopFrameNavigatingToAppBoundDomain const):
+ * UIProcess/WebProcessPool.cpp:
+ (WebKit::WebProcessPool::download):
+ * WebProcess/Network/WebLoaderStrategy.cpp:
+ (WebKit::WebLoaderStrategy::scheduleLoadFromNetworkProcess):
+ (WebKit::WebLoaderStrategy::loadResourceSynchronously):
+ (WebKit::WebLoaderStrategy::startPingLoad):
+ (WebKit::WebLoaderStrategy::preconnectTo):
+ For reporting loads to the Network Process, we only care if the main
+ frame is app bound, so we need to store that info in the WebPage and
+ WebPageProxy.
+
+ * WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp:
+ (WebKit::WebFrameLoaderClient::shouldEnableInAppBrowserPrivacyProtections const):
+ * WebProcess/WebPage/WebFrame.cpp:
+ (WebKit::WebFrame::startDownload):
+ (WebKit::WebFrame::convertMainResourceLoadToDownload):
+ (WebKit::WebFrame::shouldEnableInAppBrowserPrivacyProtections):
+ (WebKit::WebFrame::isTopFrameNavigatingToAppBoundDomain const):
+ * WebProcess/WebPage/WebFrame.h:
+ (WebKit::WebFrame::setIsNavigatingToAppBoundDomain):
+ (WebKit::WebFrame::isNavigatingToAppBoundDomain const):
+ * WebProcess/WebPage/WebPage.cpp:
+ (WebKit::WebPage::loadRequest):
+ (WebKit::WebPage::loadDataImpl):
+ (WebKit::WebPage::didReceivePolicyDecision):
+ (WebKit::WebPage::runJavaScript):
+ (WebKit::WebPage::setIsNavigatingToAppBoundDomain):
+ (WebKit::WebPage::shouldEnableInAppBrowserPrivacyProtections): Deleted.
+ * WebProcess/WebPage/WebPage.h:
+ (WebKit::WebPage::needsInAppBrowserPrivacyQuirks):
+ (WebKit::WebPage::isNavigatingToAppBoundDomain const): Deleted.
+ Move all app-bound domain logic from the WebPage to the WebFrame now
+ that checks are on a per-frame basis.
+
2020-07-01 Youenn Fablet <youenn@apple.com>
Make NetworkSendQueue use CString instead of String for UTF-8 data
diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp
index 89f4a8dd..6b400dc 100644
--- a/Source/WebKit/UIProcess/WebPageProxy.cpp
+++ b/Source/WebKit/UIProcess/WebPageProxy.cpp
@@ -3143,31 +3143,34 @@
bool WebPageProxy::setIsNavigatingToAppBoundDomainAndCheckIfPermitted(bool isMainFrame, const URL& requestURL, Optional<NavigatingToAppBoundDomain> isNavigatingToAppBoundDomain)
{
#if PLATFORM(IOS_FAMILY)
- if (isMainFrame) {
- if (WEB_PAGE_PROXY_ADDITIONS_SETISNAVIGATINGTOAPPBOUNDDOMAIN)
- return true;
- if (!isNavigatingToAppBoundDomain) {
- m_isNavigatingToAppBoundDomain = WTF::nullopt;
- return true;
- }
- if (m_ignoresAppBoundDomains)
- return true;
-
- if (shouldTreatURLProtocolAsAppBound(requestURL)) {
- isNavigatingToAppBoundDomain = NavigatingToAppBoundDomain::Yes;
- m_limitsNavigationsToAppBoundDomains = true;
- }
- if (m_limitsNavigationsToAppBoundDomains) {
- if (*isNavigatingToAppBoundDomain == NavigatingToAppBoundDomain::No)
- return false;
- m_configuration->setWebViewCategory(WebViewCategory::AppBoundDomain);
- m_isNavigatingToAppBoundDomain = NavigatingToAppBoundDomain::Yes;
- } else {
- if (m_hasExecutedAppBoundBehaviorBeforeNavigation)
+ if (WEB_PAGE_PROXY_ADDITIONS_SETISNAVIGATINGTOAPPBOUNDDOMAIN)
+ return true;
+ if (!isNavigatingToAppBoundDomain) {
+ m_isNavigatingToAppBoundDomain = WTF::nullopt;
+ return true;
+ }
+ if (m_ignoresAppBoundDomains)
+ return true;
+
+ if (shouldTreatURLProtocolAsAppBound(requestURL)) {
+ isNavigatingToAppBoundDomain = NavigatingToAppBoundDomain::Yes;
+ m_limitsNavigationsToAppBoundDomains = true;
+ }
+ if (m_limitsNavigationsToAppBoundDomains) {
+ if (*isNavigatingToAppBoundDomain == NavigatingToAppBoundDomain::No) {
+ if (isMainFrame)
return false;
m_configuration->setWebViewCategory(WebViewCategory::InAppBrowser);
m_isNavigatingToAppBoundDomain = NavigatingToAppBoundDomain::No;
+ return true;
}
+ m_configuration->setWebViewCategory(WebViewCategory::AppBoundDomain);
+ m_isNavigatingToAppBoundDomain = NavigatingToAppBoundDomain::Yes;
+ } else {
+ if (m_hasExecutedAppBoundBehaviorBeforeNavigation)
+ return false;
+ m_configuration->setWebViewCategory(WebViewCategory::InAppBrowser);
+ m_isNavigatingToAppBoundDomain = NavigatingToAppBoundDomain::No;
}
#else
UNUSED_PARAM(isMainFrame);
@@ -5214,6 +5217,8 @@
completionHandler(PolicyAction::Ignore);
return;
}
+ if (frame->isMainFrame())
+ m_isTopFrameNavigatingToAppBoundDomain = m_isNavigatingToAppBoundDomain;
}
#endif
diff --git a/Source/WebKit/UIProcess/WebPageProxy.h b/Source/WebKit/UIProcess/WebPageProxy.h
index 3c35aea..f8ce508 100644
--- a/Source/WebKit/UIProcess/WebPageProxy.h
+++ b/Source/WebKit/UIProcess/WebPageProxy.h
@@ -1759,6 +1759,7 @@
void isForcedIntoAppBoundModeTesting(CompletionHandler<void(bool)>&&);
Optional<NavigatingToAppBoundDomain> isNavigatingToAppBoundDomain() const { return m_isNavigatingToAppBoundDomain; }
+ Optional<NavigatingToAppBoundDomain> isTopFrameNavigatingToAppBoundDomain() const { return m_isTopFrameNavigatingToAppBoundDomain; }
#if PLATFORM(COCOA)
WebCore::ResourceError errorForUnpermittedAppBoundDomainNavigation(const URL&);
@@ -2837,6 +2838,7 @@
#endif
Optional<NavigatingToAppBoundDomain> m_isNavigatingToAppBoundDomain;
+ Optional<NavigatingToAppBoundDomain> m_isTopFrameNavigatingToAppBoundDomain;
bool m_ignoresAppBoundDomains { false };
bool m_userScriptsNotified { false };
bool m_limitsNavigationsToAppBoundDomains { false };
diff --git a/Source/WebKit/UIProcess/WebProcessPool.cpp b/Source/WebKit/UIProcess/WebProcessPool.cpp
index 2b27694..20cf926 100644
--- a/Source/WebKit/UIProcess/WebProcessPool.cpp
+++ b/Source/WebKit/UIProcess/WebProcessPool.cpp
@@ -1389,7 +1389,7 @@
Optional<NavigatingToAppBoundDomain> isAppBound = NavigatingToAppBoundDomain::No;
if (initiatingPage) {
initiatingPage->handleDownloadRequest(downloadProxy);
- isAppBound = initiatingPage->isNavigatingToAppBoundDomain();
+ isAppBound = initiatingPage->isTopFrameNavigatingToAppBoundDomain();
}
if (networkProcess()) {
diff --git a/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp b/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp
index 6a7eca6..1f0e5e9 100644
--- a/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp
+++ b/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp
@@ -337,8 +337,8 @@
auto* webFrameLoaderClient = frame ? toWebFrameLoaderClient(frame->loader().client()) : nullptr;
auto* webFrame = webFrameLoaderClient ? &webFrameLoaderClient->webFrame() : nullptr;
auto* webPage = webFrame ? webFrame->page() : nullptr;
- if (webPage)
- loadParameters.isNavigatingToAppBoundDomain = webPage->isNavigatingToAppBoundDomain();
+ if (webFrame)
+ loadParameters.isNavigatingToAppBoundDomain = webFrame->isTopFrameNavigatingToAppBoundDomain();
#if ENABLE(CONTENT_EXTENSIONS)
if (document) {
@@ -603,9 +603,8 @@
}
loadParameters.originalRequestHeaders = originalRequestHeaders;
- if (webPage)
- loadParameters.isNavigatingToAppBoundDomain = webPage->isNavigatingToAppBoundDomain();
-
+ if (webFrame)
+ loadParameters.isNavigatingToAppBoundDomain = webFrame->isTopFrameNavigatingToAppBoundDomain();
addParametersShared(webFrame->coreFrame(), loadParameters);
data.shrink(0);
@@ -685,7 +684,7 @@
}
addParametersShared(&frame, loadParameters);
- loadParameters.isNavigatingToAppBoundDomain = webPage->isNavigatingToAppBoundDomain();
+ loadParameters.isNavigatingToAppBoundDomain = webFrame->isTopFrameNavigatingToAppBoundDomain();
#if ENABLE(CONTENT_EXTENSIONS)
loadParameters.mainDocumentURL = document->topDocument().url();
@@ -753,7 +752,7 @@
parameters.shouldRestrictHTTPResponseAccess = shouldPerformSecurityChecks();
// FIXME: Use the proper destination once all fetch options are passed.
parameters.options.destination = FetchOptions::Destination::EmptyString;
- parameters.isNavigatingToAppBoundDomain = webPage.isNavigatingToAppBoundDomain();
+ parameters.isNavigatingToAppBoundDomain = webFrame.isTopFrameNavigatingToAppBoundDomain();
WebProcess::singleton().ensureNetworkProcessConnection().connection().send(Messages::NetworkConnectionToWebProcess::PreconnectTo(preconnectionIdentifier, WTFMove(parameters)), 0);
}
diff --git a/Source/WebKit/WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp b/Source/WebKit/WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp
index 0587c38..c975bf1 100644
--- a/Source/WebKit/WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp
+++ b/Source/WebKit/WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp
@@ -1912,14 +1912,7 @@
bool WebFrameLoaderClient::shouldEnableInAppBrowserPrivacyProtections() const
{
- if (!m_frame->isMainFrame())
- return false;
-
- auto* webPage = m_frame->page();
- if (!webPage)
- return false;
-
- return webPage->shouldEnableInAppBrowserPrivacyProtections();
+ return m_frame->shouldEnableInAppBrowserPrivacyProtections();
}
void WebFrameLoaderClient::notifyPageOfAppBoundBehavior()
diff --git a/Source/WebKit/WebProcess/WebPage/WebFrame.cpp b/Source/WebKit/WebProcess/WebPage/WebFrame.cpp
index 9d76794..4b7a77b 100644
--- a/Source/WebKit/WebProcess/WebPage/WebFrame.cpp
+++ b/Source/WebKit/WebProcess/WebPage/WebFrame.cpp
@@ -288,9 +288,7 @@
m_policyDownloadID = { };
Optional<NavigatingToAppBoundDomain> isAppBound = NavigatingToAppBoundDomain::No;
- if (page())
- isAppBound = page()->isNavigatingToAppBoundDomain();
-
+ isAppBound = m_isNavigatingToAppBoundDomain;
WebProcess::singleton().ensureNetworkProcessConnection().connection().send(Messages::NetworkConnectionToWebProcess::StartDownload(policyDownloadID, request, isAppBound, suggestedName), 0);
}
@@ -314,9 +312,7 @@
mainResourceLoadIdentifier = 0;
Optional<NavigatingToAppBoundDomain> isAppBound = NavigatingToAppBoundDomain::No;
- if (page())
- isAppBound = page()->isNavigatingToAppBoundDomain();
-
+ isAppBound = m_isNavigatingToAppBoundDomain;
webProcess.ensureNetworkProcessConnection().connection().send(Messages::NetworkConnectionToWebProcess::ConvertMainResourceLoadToDownload(mainResourceLoadIdentifier, policyDownloadID, request, response, isAppBound), 0);
}
@@ -858,5 +854,28 @@
return sharedSnapshot;
}
+
+bool WebFrame::shouldEnableInAppBrowserPrivacyProtections()
+{
+ if (page() && page()->needsInAppBrowserPrivacyQuirks())
+ return false;
+
+ bool treeHasNonAppBoundFrame = m_isNavigatingToAppBoundDomain && m_isNavigatingToAppBoundDomain == NavigatingToAppBoundDomain::No;
+ if (!treeHasNonAppBoundFrame) {
+ for (WebFrame* frame = this; !frame->isMainFrame(); frame = frame->parentFrame()) {
+ if (frame->isNavigatingToAppBoundDomain() && frame->isNavigatingToAppBoundDomain() == NavigatingToAppBoundDomain::No) {
+ treeHasNonAppBoundFrame = true;
+ break;
+ }
+ }
+ }
+ return treeHasNonAppBoundFrame;
+}
+
+Optional<NavigatingToAppBoundDomain> WebFrame::isTopFrameNavigatingToAppBoundDomain() const
+{
+ return fromCoreFrame(m_coreFrame->mainFrame())->isNavigatingToAppBoundDomain();
+}
+
} // namespace WebKit
diff --git a/Source/WebKit/WebProcess/WebPage/WebFrame.h b/Source/WebKit/WebProcess/WebPage/WebFrame.h
index da02921..498a665 100644
--- a/Source/WebKit/WebProcess/WebPage/WebFrame.h
+++ b/Source/WebKit/WebProcess/WebPage/WebFrame.h
@@ -172,6 +172,10 @@
#endif
WebFrameLoaderClient* frameLoaderClient() const;
+ bool shouldEnableInAppBrowserPrivacyProtections();
+ void setIsNavigatingToAppBoundDomain(Optional<NavigatingToAppBoundDomain> isNavigatingToAppBoundDomain) { m_isNavigatingToAppBoundDomain = isNavigatingToAppBoundDomain; };
+ Optional<NavigatingToAppBoundDomain> isNavigatingToAppBoundDomain() const { return m_isNavigatingToAppBoundDomain; }
+ Optional<NavigatingToAppBoundDomain> isTopFrameNavigatingToAppBoundDomain() const;
private:
WebFrame();
@@ -192,6 +196,8 @@
#if PLATFORM(IOS_FAMILY)
TransactionID m_firstLayerTreeTransactionIDAfterDidCommitLoad;
#endif
+ Optional<NavigatingToAppBoundDomain> m_isNavigatingToAppBoundDomain;
+
};
} // namespace WebKit
diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.cpp b/Source/WebKit/WebProcess/WebPage/WebPage.cpp
index 779668b..488006f 100644
--- a/Source/WebKit/WebProcess/WebPage/WebPage.cpp
+++ b/Source/WebKit/WebProcess/WebPage/WebPage.cpp
@@ -1534,7 +1534,7 @@
void WebPage::loadRequest(LoadParameters&& loadParameters)
{
- setIsNavigatingToAppBoundDomain(loadParameters.isNavigatingToAppBoundDomain);
+ setIsNavigatingToAppBoundDomain(loadParameters.isNavigatingToAppBoundDomain, &m_mainFrame.get());
SendStopResponsivenessTimer stopper;
@@ -1572,7 +1572,7 @@
void WebPage::loadDataImpl(uint64_t navigationID, bool shouldTreatAsContinuingLoad, Optional<WebsitePoliciesData>&& websitePolicies, Ref<SharedBuffer>&& sharedBuffer, const String& MIMEType, const String& encodingName, const URL& baseURL, const URL& unreachableURL, const UserData& userData, Optional<NavigatingToAppBoundDomain> isNavigatingToAppBoundDomain, ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy)
{
- setIsNavigatingToAppBoundDomain(isNavigatingToAppBoundDomain);
+ setIsNavigatingToAppBoundDomain(isNavigatingToAppBoundDomain, &m_mainFrame.get());
SendStopResponsivenessTimer stopper;
@@ -3284,11 +3284,11 @@
void WebPage::didReceivePolicyDecision(FrameIdentifier frameID, uint64_t listenerID, PolicyDecision&& policyDecision)
{
- setIsNavigatingToAppBoundDomain(policyDecision.isNavigatingToAppBoundDomain);
-
WebFrame* frame = WebProcess::singleton().webFrame(frameID);
if (!frame)
return;
+
+ setIsNavigatingToAppBoundDomain(policyDecision.isNavigatingToAppBoundDomain, frame);
frame->didReceivePolicyDecision(listenerID, WTFMove(policyDecision));
}
@@ -3457,7 +3457,7 @@
send(Messages::WebPageProxy::ScriptValueCallback(dataReference, details, callbackID));
};
- if (shouldEnableInAppBrowserPrivacyProtections()) {
+ if (frame->shouldEnableInAppBrowserPrivacyProtections()) {
send(Messages::WebPageProxy::ScriptValueCallback({ }, ExceptionDetails { "Unable to execute JavaScript in a frame that is not in an app-bound domain"_s, 0, 0, ExceptionDetails::Type::AppBoundDomain }, callbackID));
if (auto* document = m_page->mainFrame().document())
document->addConsoleMessage(MessageSource::Security, MessageLevel::Warning, "Ignoring user script injection for non-app bound domain.");
@@ -7162,9 +7162,9 @@
#endif
-void WebPage::setIsNavigatingToAppBoundDomain(Optional<NavigatingToAppBoundDomain> isNavigatingToAppBoundDomain)
+void WebPage::setIsNavigatingToAppBoundDomain(Optional<NavigatingToAppBoundDomain> isNavigatingToAppBoundDomain, WebFrame* frame)
{
- m_isNavigatingToAppBoundDomain = isNavigatingToAppBoundDomain;
+ frame->setIsNavigatingToAppBoundDomain(isNavigatingToAppBoundDomain);
m_navigationHasOccured = true;
}
@@ -7175,14 +7175,6 @@
send(Messages::WebPageProxy::SetHasExecutedAppBoundBehaviorBeforeNavigation());
}
-bool WebPage::shouldEnableInAppBrowserPrivacyProtections()
-{
- if (m_needsInAppBrowserPrivacyQuirks)
- return false;
-
- return isNavigatingToAppBoundDomain() && isNavigatingToAppBoundDomain() == NavigatingToAppBoundDomain::No;
-}
-
} // namespace WebKit
#undef RELEASE_LOG_IF_ALLOWED
diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.h b/Source/WebKit/WebProcess/WebPage/WebPage.h
index f94acaaf..9204186 100644
--- a/Source/WebKit/WebProcess/WebPage/WebPage.h
+++ b/Source/WebKit/WebProcess/WebPage/WebPage.h
@@ -1326,10 +1326,9 @@
void getAllFrames(CompletionHandler<void(FrameTreeNodeData&&)>&&);
void notifyPageOfAppBoundBehavior();
- bool shouldEnableInAppBrowserPrivacyProtections();
- void setIsNavigatingToAppBoundDomain(Optional<NavigatingToAppBoundDomain>);
- Optional<NavigatingToAppBoundDomain> isNavigatingToAppBoundDomain() const { return m_isNavigatingToAppBoundDomain; }
-
+ void setIsNavigatingToAppBoundDomain(Optional<NavigatingToAppBoundDomain>, WebFrame*);
+ bool needsInAppBrowserPrivacyQuirks() { return m_needsInAppBrowserPrivacyQuirks; }
+
bool shouldUseRemoteRenderingFor(WebCore::RenderingPurpose);
#if ENABLE(MEDIA_USAGE)
@@ -2130,7 +2129,6 @@
String m_themeName;
#endif
- Optional<NavigatingToAppBoundDomain> m_isNavigatingToAppBoundDomain;
bool m_limitsNavigationsToAppBoundDomains { false };
bool m_navigationHasOccured { false };
};
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index 256c75d..111a114 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -1,3 +1,21 @@
+2020-07-01 Kate Cheney <katherine_cheney@apple.com>
+
+ WKUserScripts injecting into all frames seem to violate app-bound domains
+ https://bugs.webkit.org/show_bug.cgi?id=213816
+ <rdar://problem/64872296>
+
+ Reviewed by Brady Eidson.
+
+ Added API test coverage. Sets up a tree like so: [app-bound domain,
+ non-app-bound domain, app-bound domain]. Script injection should only
+ be successful in the top frame. Since message handlers are disabled for non-
+ app-bound domains, this test injects script which loads an image using a
+ custom url scheme. If the load succeeds, we increment a counter, and
+ expect only one successful script load (the main frame).
+
+ * TestWebKitAPI/Tests/WebKitCocoa/InAppBrowserPrivacy.mm:
+ (-[InAppBrowserSchemeHandler webView:startURLSchemeTask:]):
+
2020-06-30 Sam Weinig <weinig@apple.com>
Split Color serialization out of Color classes
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/InAppBrowserPrivacy.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/InAppBrowserPrivacy.mm
index 44c3b36..5c0c1eb 100644
--- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/InAppBrowserPrivacy.mm
+++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/InAppBrowserPrivacy.mm
@@ -34,6 +34,7 @@
#import <WebCore/RuntimeApplicationChecks.h>
#import <WebKit/WKHTTPCookieStorePrivate.h>
#import <WebKit/WKPreferencesPrivate.h>
+#import <WebKit/WKURLSchemeTaskPrivate.h>
#import <WebKit/WKUserContentControllerPrivate.h>
#import <WebKit/WKWebsiteDataStorePrivate.h>
#import <WebKit/_WKUserContentWorld.h>
@@ -83,6 +84,8 @@
@end
static NSString * const userScriptSource = @"window.wkUserScriptInjected = true";
+static bool mainFrameReceivedScriptSource = false;
+static bool subFrameReceivedScriptSource = false;
@interface InAppBrowserSchemeHandler : NSObject <WKURLSchemeHandler>
@end
@@ -93,7 +96,7 @@
{
NSString *response = nil;
if ([task.request.URL.path isEqualToString:@"/in-app-browser-privacy-test-user-script"])
- response = @"<script>window.wkUserScriptInjected = false;</script>";
+ response = @"<body id = 'body'><script>window.wkUserScriptInjected = false;</script>";
else if ([task.request.URL.path isEqualToString:@"/in-app-browser-privacy-test-user-agent-script"])
response = @"<script> window.wkUserScriptInjected = true; </script>";
else if ([task.request.URL.path isEqualToString:@"/in-app-browser-privacy-test-user-style-sheets"])
@@ -104,6 +107,14 @@
response = @"<body style='background-color: green;'></body><script>if (window.webkit.messageHandlers)\nwindow.webkit.messageHandlers.testHandler.postMessage('Failed'); \nelse \n document.body.style.background = 'red';</script>";
else if ([task.request.URL.path isEqualToString:@"/app-bound-domain-load"])
response = @"<body></body>";
+ else if ([task.request.URL.path isEqualToString:@"/in-app-browser-privacy-test-user-script-iframe"])
+ response = @"<body id = 'body'></body><iframe src='in-app-browser://apple.com/in-app-browser-privacy-test-user-script' id='nestedFrame'></iframe></body>";
+ else if ([task.request.URL.path isEqualToString:@"/in-app-browser-privacy-test-user-script-nested-iframe"])
+ response = @"<body id = 'body'></body><iframe src='in-app-browser://nonAppBoundDomain/in-app-browser-privacy-test-user-script-iframe' id='iframe'></iframe></body>";
+ else if (((id<WKURLSchemeTaskPrivate>)task)._frame.isMainFrame && [task.request.URL.path isEqualToString:@"/should-load-for-main-frame-only"])
+ mainFrameReceivedScriptSource = true;
+ else if ([task.request.URL.path isEqualToString:@"/should-load-for-main-frame-only"])
+ subFrameReceivedScriptSource = true;
[task didReceiveResponse:[[[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:response.length textEncodingName:nil] autorelease]];
[task didReceiveData:[response dataUsingEncoding:NSUTF8StringEncoding]];
@@ -1057,6 +1068,115 @@
EXPECT_EQ(error.code, WKErrorNavigationAppBoundDomain);
}
+TEST(InAppBrowserPrivacy, InjectScriptInNonAppBoundSubframeAppBoundMainframeFails)
+{
+ isDone = false;
+ initializeInAppBrowserPrivacyTestSettings();
+
+ auto userScript = adoptNS([[WKUserScript alloc] initWithSource:@"var img = document.createElement('img'); img.src = 'in-app-browser:///should-load-for-main-frame-only'; document.getElementById('body').appendChild(img);" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]);
+
+ WKWebViewConfiguration *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+ auto schemeHandler = adoptNS([[InAppBrowserSchemeHandler alloc] init]);
+ [configuration setURLSchemeHandler:schemeHandler.get() forURLScheme:@"in-app-browser"];
+ [[configuration preferences] _setNeedsInAppBrowserPrivacyQuirks:NO];
+ [configuration setLimitsNavigationsToAppBoundDomains:YES];
+ [[configuration userContentController] addUserScript:userScript.get()];
+
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
+ auto delegate = adoptNS([AppBoundDomainDelegate new]);
+ [webView setNavigationDelegate:delegate.get()];
+
+ // Load an app-bound domain with an iframe that is not app-bound.
+ NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"in-app-browser://apple.com/in-app-browser-privacy-test-user-script-nested-iframe"]];
+ [webView loadRequest:request];
+ [delegate waitForDidFinishNavigation];
+
+ EXPECT_TRUE(mainFrameReceivedScriptSource);
+ EXPECT_FALSE(subFrameReceivedScriptSource);
+ cleanUpInAppBrowserPrivacyTestSettings();
+}
+
+static NSMutableSet<WKFrameInfo *> *allFrames;
+
+TEST(InAppBrowserPrivacy, JavaScriptInNonAppBoundFrameFails)
+{
+ allFrames = [[NSMutableSet<WKFrameInfo *> alloc] init];
+
+ isDone = false;
+ initializeInAppBrowserPrivacyTestSettings();
+
+ auto userScript = adoptNS([[WKUserScript alloc] initWithSource:@"var img = document.createElement('img'); img.src = 'in-app-browser:///should-load-for-main-frame-only'; document.getElementById('body').appendChild(img);" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]);
+
+ WKWebViewConfiguration *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+ auto schemeHandler = adoptNS([[InAppBrowserSchemeHandler alloc] init]);
+ [configuration setURLSchemeHandler:schemeHandler.get() forURLScheme:@"in-app-browser"];
+ [[configuration preferences] _setNeedsInAppBrowserPrivacyQuirks:NO];
+ [configuration setLimitsNavigationsToAppBoundDomains:YES];
+ [[configuration userContentController] addUserScript:userScript.get()];
+
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
+ auto navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]);
+
+ __block bool didFinishNavigation = false;
+ [navigationDelegate setDidFinishNavigation:^(WKWebView *, WKNavigation *) {
+ didFinishNavigation = true;
+ }];
+
+ [navigationDelegate setDecidePolicyForNavigationAction:[&] (WKNavigationAction *action, void (^decisionHandler)(WKNavigationActionPolicy)) {
+ if (action.targetFrame)
+ [allFrames addObject:action.targetFrame];
+
+ decisionHandler(WKNavigationActionPolicyAllow);
+ }];
+
+ [webView setNavigationDelegate:navigationDelegate.get()];
+
+ // Load an app-bound domain with an iframe that is not app-bound.
+ NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"in-app-browser://apple.com/in-app-browser-privacy-test-user-script-nested-iframe"]];
+ [webView loadRequest:request];
+
+ TestWebKitAPI::Util::run(&didFinishNavigation);
+
+ EXPECT_EQ(allFrames.count, 3u);
+
+ static size_t finishedFrames = 0;
+ static bool isDone = false;
+
+ for (WKFrameInfo *frame in allFrames) {
+ bool isMainFrame = frame.isMainFrame;
+ [webView callAsyncJavaScript:@"return location.href;" arguments:nil inFrame:frame inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[isMainFrame] (id result, NSError *error) {
+ if (isMainFrame) {
+ EXPECT_TRUE([result isKindOfClass:[NSString class]]);
+ EXPECT_FALSE(!!error);
+ EXPECT_TRUE([result isEqualToString:@"in-app-browser://apple.com/in-app-browser-privacy-test-user-script-nested-iframe"]);
+ } else {
+ EXPECT_TRUE(!!error);
+ EXPECT_EQ(error.code, WKErrorJavaScriptAppBoundDomain);
+ }
+
+ if (++finishedFrames == allFrames.count * 2)
+ isDone = true;
+ }];
+
+
+ [webView evaluateJavaScript:@"location.href;" inFrame:frame inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[isMainFrame] (id result, NSError *error) {
+ if (isMainFrame) {
+ EXPECT_TRUE([result isKindOfClass:[NSString class]]);
+ EXPECT_FALSE(!!error);
+ EXPECT_TRUE([result isEqualToString:@"in-app-browser://apple.com/in-app-browser-privacy-test-user-script-nested-iframe"]);
+ } else {
+ EXPECT_TRUE(!!error);
+ EXPECT_EQ(error.code, WKErrorJavaScriptAppBoundDomain);
+ }
+
+ if (++finishedFrames == allFrames.count * 2)
+ isDone = true;
+ }];
+ }
+
+ TestWebKitAPI::Util::run(&isDone);
+}
+
TEST(InAppBrowserPrivacy, WebViewCategory)
{
initializeInAppBrowserPrivacyTestSettings();