Reland: service worker: Don't control a subframe of an insecure context
We must check isSecureContext when creating the network provider to
adhere to https://w3c.github.io/webappsec/specs/powerfulfeatures/#settings-privileged.
We already did this for getRegistration(), register(), unregister() but must
also do this when deciding whether to control an in-scope document.
BUG=607543
CQ_INCLUDE_TRYBOTS=tryserver.chromium.linux:linux_site_isolation
TBR=reviewers from the original review
Original review: https://codereview.chromium.org/2009453002
Review-Url: https://codereview.chromium.org/2071433003
Cr-Commit-Position: refs/heads/master@{#400093}
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 1448be55..f46641d 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -104,6 +104,7 @@
#include "chrome/common/pepper_permission_util.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/render_messages.h"
+#include "chrome/common/secure_origin_whitelist.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/installer/util/google_update_settings.h"
@@ -2520,6 +2521,11 @@
}
}
+void ChromeContentBrowserClient::GetSchemesBypassingSecureContextCheckWhitelist(
+ std::set<std::string>* schemes) {
+ return ::GetSchemesBypassingSecureContextCheckWhitelist(schemes);
+}
+
void ChromeContentBrowserClient::GetURLRequestAutoMountHandlers(
std::vector<storage::URLRequestAutoMountHandler>* handlers) {
for (size_t i = 0; i < extra_parts_.size(); ++i)
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 2de739b..7a835bbf 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -232,6 +232,8 @@
content::WebContents* web_contents) override;
void GetAdditionalAllowedSchemesForFileSystem(
std::vector<std::string>* additional_schemes) override;
+ void GetSchemesBypassingSecureContextCheckWhitelist(
+ std::set<std::string>* schemes) override;
void GetURLRequestAutoMountHandlers(
std::vector<storage::URLRequestAutoMountHandler>* handlers) override;
void GetAdditionalFileSystemBackends(
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index e434593..2c9e0eef1 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -25,6 +25,7 @@
#include "content/public/browser/permission_type.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
+#include "content/public/common/origin_util.h"
#include "content/public/common/page_type.h"
#include "content/public/test/background_sync_test_util.h"
#include "content/public/test/browser_test_utils.h"
@@ -33,6 +34,7 @@
#include "extensions/browser/process_manager.h"
#include "extensions/test/background_page_watcher.h"
#include "extensions/test/extension_test_message_listener.h"
+#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
namespace extensions {
@@ -634,9 +636,24 @@
kFlagNone);
ASSERT_TRUE(extension);
ASSERT_TRUE(StartEmbeddedTestServer());
- GURL page_url = embedded_test_server()->GetURL(
- "/extensions/api_test/service_worker/web_accessible_resources/"
- "webpage.html");
+
+ // Service workers can only control secure contexts
+ // (https://w3c.github.io/webappsec-secure-contexts/). For documents, this
+ // typically means the document must have a secure origin AND all its ancestor
+ // frames must have documents with secure origins. However, extension pages
+ // are considered secure, even if they have an ancestor document that is an
+ // insecure context (see GetSchemesBypassingSecureContextCheckWhitelist). So
+ // extension service workers must be able to control an extension page
+ // embedded in an insecure context. To test this, set up an insecure
+ // (non-localhost, non-https) URL for the web page. This page will create
+ // iframes that load extension pages that must be controllable by service
+ // worker.
+ host_resolver()->AddRule("a.com", "127.0.0.1");
+ GURL page_url =
+ embedded_test_server()->GetURL("a.com",
+ "/extensions/api_test/service_worker/"
+ "web_accessible_resources/webpage.html");
+ EXPECT_FALSE(content::IsOriginSecure(page_url));
content::WebContents* web_contents = AddTab(browser(), page_url);
std::string result;
diff --git a/content/browser/service_worker/service_worker_browsertest.cc b/content/browser/service_worker/service_worker_browsertest.cc
index ee5ecb3..fae510d 100644
--- a/content/browser/service_worker/service_worker_browsertest.cc
+++ b/content/browser/service_worker/service_worker_browsertest.cc
@@ -529,11 +529,12 @@
void AddControlleeOnIOThread() {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
std::unique_ptr<ServiceWorkerProviderHost> host(
- new ServiceWorkerProviderHost(33 /* dummy render process id */,
- MSG_ROUTING_NONE /* render_frame_id */,
- 1 /* dummy provider_id */,
- SERVICE_WORKER_PROVIDER_FOR_WINDOW,
- wrapper()->context()->AsWeakPtr(), NULL));
+ new ServiceWorkerProviderHost(
+ 33 /* dummy render process id */,
+ MSG_ROUTING_NONE /* render_frame_id */, 1 /* dummy provider_id */,
+ SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+ wrapper()->context()->AsWeakPtr(), NULL));
host->SetDocumentUrl(
embedded_test_server()->GetURL("/service_worker/host"));
host->AssociateRegistration(registration_.get(),
diff --git a/content/browser/service_worker/service_worker_context_core.cc b/content/browser/service_worker/service_worker_context_core.cc
index 6bb3ee1e..2db34643 100644
--- a/content/browser/service_worker/service_worker_context_core.cc
+++ b/content/browser/service_worker/service_worker_context_core.cc
@@ -632,13 +632,13 @@
int provider_id) {
ProviderMap* map = GetProviderMapForProcess(process_id);
ServiceWorkerProviderHost* transferee = map->Lookup(provider_id);
- ServiceWorkerProviderHost* replacement =
- new ServiceWorkerProviderHost(process_id,
- transferee->frame_id(),
- provider_id,
- transferee->provider_type(),
- AsWeakPtr(),
- transferee->dispatcher_host());
+ ServiceWorkerProviderHost* replacement = new ServiceWorkerProviderHost(
+ process_id, transferee->frame_id(), provider_id,
+ transferee->provider_type(),
+ transferee->is_parent_frame_secure()
+ ? ServiceWorkerProviderHost::FrameSecurityLevel::SECURE
+ : ServiceWorkerProviderHost::FrameSecurityLevel::INSECURE,
+ AsWeakPtr(), transferee->dispatcher_host());
map->Replace(provider_id, replacement);
transferee->PrepareForCrossSiteTransfer();
return base::WrapUnique(transferee);
diff --git a/content/browser/service_worker/service_worker_context_request_handler_unittest.cc b/content/browser/service_worker/service_worker_context_request_handler_unittest.cc
index 22296bc..dbc170c 100644
--- a/content/browser/service_worker/service_worker_context_request_handler_unittest.cc
+++ b/content/browser/service_worker/service_worker_context_request_handler_unittest.cc
@@ -49,11 +49,12 @@
// An empty host.
std::unique_ptr<ServiceWorkerProviderHost> host(
- new ServiceWorkerProviderHost(helper_->mock_render_process_id(),
- MSG_ROUTING_NONE /* render_frame_id */,
- 1 /* provider_id */,
- SERVICE_WORKER_PROVIDER_FOR_WINDOW,
- context()->AsWeakPtr(), nullptr));
+ new ServiceWorkerProviderHost(
+ helper_->mock_render_process_id(),
+ MSG_ROUTING_NONE /* render_frame_id */, 1 /* provider_id */,
+ SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+ context()->AsWeakPtr(), nullptr));
provider_host_ = host->AsWeakPtr();
context()->AddProviderHost(std::move(host));
diff --git a/content/browser/service_worker/service_worker_context_unittest.cc b/content/browser/service_worker/service_worker_context_unittest.cc
index d4de332..05409c8 100644
--- a/content/browser/service_worker/service_worker_context_unittest.cc
+++ b/content/browser/service_worker/service_worker_context_unittest.cc
@@ -532,25 +532,33 @@
// Host1 (provider_id=1): process_id=1, origin1.
ServiceWorkerProviderHost* host1(new ServiceWorkerProviderHost(
kRenderProcessId1, MSG_ROUTING_NONE, provider_id++,
- SERVICE_WORKER_PROVIDER_FOR_WINDOW, context()->AsWeakPtr(), nullptr));
+ SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+ context()->AsWeakPtr(), nullptr));
host1->SetDocumentUrl(kOrigin1);
// Host2 (provider_id=2): process_id=2, origin2.
ServiceWorkerProviderHost* host2(new ServiceWorkerProviderHost(
kRenderProcessId2, MSG_ROUTING_NONE, provider_id++,
- SERVICE_WORKER_PROVIDER_FOR_WINDOW, context()->AsWeakPtr(), nullptr));
+ SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+ context()->AsWeakPtr(), nullptr));
host2->SetDocumentUrl(kOrigin2);
// Host3 (provider_id=3): process_id=2, origin1.
ServiceWorkerProviderHost* host3(new ServiceWorkerProviderHost(
kRenderProcessId2, MSG_ROUTING_NONE, provider_id++,
- SERVICE_WORKER_PROVIDER_FOR_WINDOW, context()->AsWeakPtr(), nullptr));
+ SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+ context()->AsWeakPtr(), nullptr));
host3->SetDocumentUrl(kOrigin1);
// Host4 (provider_id=4): process_id=2, origin2, for ServiceWorker.
ServiceWorkerProviderHost* host4(new ServiceWorkerProviderHost(
kRenderProcessId2, MSG_ROUTING_NONE, provider_id++,
- SERVICE_WORKER_PROVIDER_FOR_CONTROLLER, context()->AsWeakPtr(), nullptr));
+ SERVICE_WORKER_PROVIDER_FOR_CONTROLLER,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+ context()->AsWeakPtr(), nullptr));
host4->SetDocumentUrl(kOrigin2);
context()->AddProviderHost(base::WrapUnique(host1));
diff --git a/content/browser/service_worker/service_worker_controllee_request_handler.cc b/content/browser/service_worker/service_worker_controllee_request_handler.cc
index 74b8e80..c6652dc 100644
--- a/content/browser/service_worker/service_worker_controllee_request_handler.cc
+++ b/content/browser/service_worker/service_worker_controllee_request_handler.cc
@@ -5,6 +5,7 @@
#include "content/browser/service_worker/service_worker_controllee_request_handler.h"
#include <memory>
+#include <set>
#include <string>
#include "base/trace_event/trace_event.h"
@@ -213,6 +214,17 @@
return;
}
+ if (!provider_host_->IsContextSecureForServiceWorker()) {
+ // TODO(falken): Figure out a way to surface in the page's DevTools
+ // console that the service worker was blocked for security.
+ job_->FallbackToNetwork();
+ TRACE_EVENT_ASYNC_END1(
+ "ServiceWorker",
+ "ServiceWorkerControlleeRequestHandler::PrepareForMainResource",
+ job_.get(), "Info", "Insecure context");
+ return;
+ }
+
if (need_to_update) {
force_update_started_ = true;
context_->UpdateServiceWorker(
diff --git a/content/browser/service_worker/service_worker_controllee_request_handler_unittest.cc b/content/browser/service_worker/service_worker_controllee_request_handler_unittest.cc
index c31d4ba..918627f 100644
--- a/content/browser/service_worker/service_worker_controllee_request_handler_unittest.cc
+++ b/content/browser/service_worker/service_worker_controllee_request_handler_unittest.cc
@@ -105,8 +105,8 @@
helper_.reset(helper);
// A new unstored registration/version.
- scope_ = GURL("http://host/scope/");
- script_url_ = GURL("http://host/script.js");
+ scope_ = GURL("https://host/scope/");
+ script_url_ = GURL("https://host/script.js");
registration_ = new ServiceWorkerRegistration(
scope_, 1L, context()->AsWeakPtr());
version_ = new ServiceWorkerVersion(
@@ -119,10 +119,11 @@
// An empty host.
std::unique_ptr<ServiceWorkerProviderHost> host(
- new ServiceWorkerProviderHost(helper_->mock_render_process_id(),
- MSG_ROUTING_NONE, kMockProviderId,
- SERVICE_WORKER_PROVIDER_FOR_WINDOW,
- context()->AsWeakPtr(), NULL));
+ new ServiceWorkerProviderHost(
+ helper_->mock_render_process_id(), MSG_ROUTING_NONE,
+ kMockProviderId, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+ context()->AsWeakPtr(), NULL));
provider_host_ = host->AsWeakPtr();
context()->AddProviderHost(std::move(host));
@@ -180,7 +181,7 @@
// Conduct a main resource load.
ServiceWorkerRequestTestResources test_resources(
- this, GURL("http://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
+ this, GURL("https://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
ServiceWorkerURLRequestJob* sw_job = test_resources.MaybeCreateJob();
EXPECT_FALSE(sw_job->ShouldFallbackToNetwork());
@@ -209,7 +210,7 @@
// Conduct a main resource load.
ServiceWorkerRequestTestResources test_resources(
- this, GURL("http://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
+ this, GURL("https://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
ServiceWorkerURLRequestJob* sw_job = test_resources.MaybeCreateJob();
EXPECT_FALSE(sw_job->ShouldFallbackToNetwork());
@@ -239,7 +240,7 @@
// Conduct a main resource load.
ServiceWorkerRequestTestResources test_resources(
- this, GURL("http://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
+ this, GURL("https://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
ServiceWorkerURLRequestJob* job = test_resources.MaybeCreateJob();
base::RunLoop().RunUntilIdle();
@@ -271,7 +272,7 @@
// Conduct a main resource load.
ServiceWorkerRequestTestResources test_resources(
- this, GURL("http://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
+ this, GURL("https://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
ServiceWorkerURLRequestJob* sw_job = test_resources.MaybeCreateJob();
EXPECT_FALSE(sw_job->ShouldFallbackToNetwork());
@@ -297,7 +298,7 @@
base::RunLoop().RunUntilIdle();
ServiceWorkerRequestTestResources main_test_resources(
- this, GURL("http://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
+ this, GURL("https://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
ServiceWorkerURLRequestJob* main_job = main_test_resources.MaybeCreateJob();
EXPECT_FALSE(main_job->ShouldFallbackToNetwork());
@@ -312,7 +313,7 @@
EXPECT_EQ(version_, provider_host_->controlling_version());
ServiceWorkerRequestTestResources sub_test_resources(
- this, GURL("http://host/scope/doc/subresource"), RESOURCE_TYPE_IMAGE);
+ this, GURL("https://host/scope/doc/subresource"), RESOURCE_TYPE_IMAGE);
ServiceWorkerURLRequestJob* sub_job = sub_test_resources.MaybeCreateJob();
// This job shouldn't be created because this worker doesn't have fetch
diff --git a/content/browser/service_worker/service_worker_dispatcher_host.cc b/content/browser/service_worker/service_worker_dispatcher_host.cc
index e352979e9..034e0feb 100644
--- a/content/browser/service_worker/service_worker_dispatcher_host.cc
+++ b/content/browser/service_worker/service_worker_dispatcher_host.cc
@@ -747,7 +747,8 @@
void ServiceWorkerDispatcherHost::OnProviderCreated(
int provider_id,
int route_id,
- ServiceWorkerProviderType provider_type) {
+ ServiceWorkerProviderType provider_type,
+ bool is_parent_frame_secure) {
// TODO(pkasting): Remove ScopedTracker below once crbug.com/477117 is fixed.
tracked_objects::ScopedTracker tracking_profile(
FROM_HERE_WITH_EXPLICIT_FUNCTION(
@@ -769,8 +770,10 @@
// Retrieve the provider host previously created for navigation requests.
ServiceWorkerNavigationHandleCore* navigation_handle_core =
GetContext()->GetNavigationHandleCore(provider_id);
- if (navigation_handle_core != nullptr)
+ if (navigation_handle_core != nullptr) {
provider_host = navigation_handle_core->RetrievePreCreatedHost();
+ provider_host->set_parent_frame_secure(is_parent_frame_secure);
+ }
// If no host is found, the navigation has been cancelled in the meantime.
// Just return as the navigation will be stopped in the renderer as well.
@@ -785,10 +788,14 @@
this, bad_message::SWDH_PROVIDER_CREATED_NO_HOST);
return;
}
+ ServiceWorkerProviderHost::FrameSecurityLevel parent_frame_security_level =
+ is_parent_frame_secure
+ ? ServiceWorkerProviderHost::FrameSecurityLevel::SECURE
+ : ServiceWorkerProviderHost::FrameSecurityLevel::INSECURE;
provider_host = std::unique_ptr<ServiceWorkerProviderHost>(
- new ServiceWorkerProviderHost(render_process_id_, route_id, provider_id,
- provider_type, GetContext()->AsWeakPtr(),
- this));
+ new ServiceWorkerProviderHost(
+ render_process_id_, route_id, provider_id, provider_type,
+ parent_frame_security_level, GetContext()->AsWeakPtr(), this));
}
GetContext()->AddProviderHost(std::move(provider_host));
}
diff --git a/content/browser/service_worker/service_worker_dispatcher_host.h b/content/browser/service_worker/service_worker_dispatcher_host.h
index 0907ec94..95330b4 100644
--- a/content/browser/service_worker/service_worker_dispatcher_host.h
+++ b/content/browser/service_worker/service_worker_dispatcher_host.h
@@ -116,7 +116,8 @@
int provider_id);
void OnProviderCreated(int provider_id,
int route_id,
- ServiceWorkerProviderType provider_type);
+ ServiceWorkerProviderType provider_type,
+ bool is_parent_frame_secure);
void OnProviderDestroyed(int provider_id);
void OnSetHostedVersionId(int provider_id, int64_t version_id);
void OnWorkerReadyForInspection(int embedded_worker_id);
diff --git a/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc b/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc
index 93613af4..1dc9254d 100644
--- a/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc
+++ b/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc
@@ -151,7 +151,8 @@
const GURL& pattern) {
const int64_t kProviderId = 99;
dispatcher_host_->OnMessageReceived(ServiceWorkerHostMsg_ProviderCreated(
- kProviderId, MSG_ROUTING_NONE, type));
+ kProviderId, MSG_ROUTING_NONE, type,
+ true /* is_parent_frame_secure */));
helper_->SimulateAddProcessToPattern(pattern,
helper_->mock_render_process_id());
provider_host_ = context()->GetProviderHost(
@@ -235,8 +236,9 @@
ServiceWorkerProviderHost* CreateServiceWorkerProviderHost(int provider_id) {
return new ServiceWorkerProviderHost(
helper_->mock_render_process_id(), kRenderFrameId, provider_id,
- SERVICE_WORKER_PROVIDER_FOR_WINDOW, context()->AsWeakPtr(),
- dispatcher_host_.get());
+ SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+ context()->AsWeakPtr(), dispatcher_host_.get());
}
TestBrowserThreadBundle browser_thread_bundle_;
@@ -497,12 +499,14 @@
int process_id = helper_->mock_render_process_id();
dispatcher_host_->OnMessageReceived(ServiceWorkerHostMsg_ProviderCreated(
- kProviderId, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW));
+ kProviderId, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ true /* is_parent_frame_secure */));
EXPECT_TRUE(context()->GetProviderHost(process_id, kProviderId));
// Two with the same ID should be seen as a bad message.
dispatcher_host_->OnMessageReceived(ServiceWorkerHostMsg_ProviderCreated(
- kProviderId, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW));
+ kProviderId, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ true /* is_parent_frame_secure */));
EXPECT_EQ(1, dispatcher_host_->bad_messages_received_count_);
dispatcher_host_->OnMessageReceived(
@@ -517,7 +521,8 @@
// Deletion of the dispatcher_host should cause providers for that
// process to get deleted as well.
dispatcher_host_->OnMessageReceived(ServiceWorkerHostMsg_ProviderCreated(
- kProviderId, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW));
+ kProviderId, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ true /* is_parent_frame_secure */));
EXPECT_TRUE(context()->GetProviderHost(process_id, kProviderId));
EXPECT_TRUE(dispatcher_host_->HasOneRef());
dispatcher_host_ = NULL;
@@ -653,7 +658,8 @@
// the old dispatcher cleaned up the old provider host, the new one won't
// complain.
new_dispatcher_host->OnMessageReceived(ServiceWorkerHostMsg_ProviderCreated(
- provider_id, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW));
+ provider_id, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ true /* is_parent_frame_secure */));
EXPECT_EQ(0, new_dispatcher_host->bad_messages_received_count_);
}
diff --git a/content/browser/service_worker/service_worker_handle_unittest.cc b/content/browser/service_worker/service_worker_handle_unittest.cc
index 1483da4..d5a2109 100644
--- a/content/browser/service_worker/service_worker_handle_unittest.cc
+++ b/content/browser/service_worker/service_worker_handle_unittest.cc
@@ -107,8 +107,9 @@
provider_host_.reset(new ServiceWorkerProviderHost(
helper_->mock_render_process_id(), kRenderFrameId, 1,
- SERVICE_WORKER_PROVIDER_FOR_WINDOW, helper_->context()->AsWeakPtr(),
- dispatcher_host_.get()));
+ SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+ helper_->context()->AsWeakPtr(), dispatcher_host_.get()));
helper_->SimulateAddProcessToPattern(pattern,
helper_->mock_render_process_id());
diff --git a/content/browser/service_worker/service_worker_job_unittest.cc b/content/browser/service_worker/service_worker_job_unittest.cc
index fc8cd15..d06955f 100644
--- a/content/browser/service_worker/service_worker_job_unittest.cc
+++ b/content/browser/service_worker/service_worker_job_unittest.cc
@@ -186,11 +186,12 @@
std::unique_ptr<ServiceWorkerProviderHost>
ServiceWorkerJobTest::CreateControllee() {
return std::unique_ptr<ServiceWorkerProviderHost>(
- new ServiceWorkerProviderHost(33 /* dummy render_process id */,
- MSG_ROUTING_NONE /* render_frame_id */,
- 1 /* dummy provider_id */,
- SERVICE_WORKER_PROVIDER_FOR_WINDOW,
- helper_->context()->AsWeakPtr(), NULL));
+ new ServiceWorkerProviderHost(
+ 33 /* dummy render_process id */,
+ MSG_ROUTING_NONE /* render_frame_id */, 1 /* dummy provider_id */,
+ SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+ helper_->context()->AsWeakPtr(), NULL));
}
TEST_F(ServiceWorkerJobTest, SameDocumentSameRegistration) {
diff --git a/content/browser/service_worker/service_worker_provider_host.cc b/content/browser/service_worker/service_worker_provider_host.cc
index a06b7969..5666813 100644
--- a/content/browser/service_worker/service_worker_provider_host.cc
+++ b/content/browser/service_worker/service_worker_provider_host.cc
@@ -22,8 +22,11 @@
#include "content/common/service_worker/service_worker_messages.h"
#include "content/common/service_worker/service_worker_types.h"
#include "content/common/service_worker/service_worker_utils.h"
+#include "content/public/browser/content_browser_client.h"
#include "content/public/common/browser_side_navigation_policy.h"
#include "content/public/common/child_process_host.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/origin_util.h"
namespace content {
@@ -55,7 +58,8 @@
return std::unique_ptr<ServiceWorkerProviderHost>(
new ServiceWorkerProviderHost(
ChildProcessHost::kInvalidUniqueID, MSG_ROUTING_NONE, provider_id,
- SERVICE_WORKER_PROVIDER_FOR_WINDOW, context, nullptr));
+ SERVICE_WORKER_PROVIDER_FOR_WINDOW, FrameSecurityLevel::UNINITIALIZED,
+ context, nullptr));
}
ServiceWorkerProviderHost::ServiceWorkerProviderHost(
@@ -63,6 +67,7 @@
int route_id,
int provider_id,
ServiceWorkerProviderType provider_type,
+ FrameSecurityLevel parent_frame_security_level,
base::WeakPtr<ServiceWorkerContextCore> context,
ServiceWorkerDispatcherHost* dispatcher_host)
: client_uuid_(base::GenerateGUID()),
@@ -71,6 +76,7 @@
render_thread_id_(kDocumentMainThreadId),
provider_id_(provider_id),
provider_type_(provider_type),
+ parent_frame_security_level_(parent_frame_security_level),
context_(context),
dispatcher_host_(dispatcher_host),
allow_association_(true) {
@@ -112,6 +118,20 @@
return MSG_ROUTING_NONE;
}
+bool ServiceWorkerProviderHost::IsContextSecureForServiceWorker() const {
+ DCHECK(document_url_.is_valid());
+ if (!OriginCanAccessServiceWorkers(document_url_))
+ return false;
+
+ if (is_parent_frame_secure())
+ return true;
+
+ std::set<std::string> schemes;
+ GetContentClient()->browser()->GetSchemesBypassingSecureContextCheckWhitelist(
+ &schemes);
+ return schemes.find(document_url().scheme()) != schemes.end();
+}
+
void ServiceWorkerProviderHost::OnVersionAttributesChanged(
ServiceWorkerRegistration* registration,
ChangedVersionAttributesMask changed_mask,
@@ -166,6 +186,7 @@
void ServiceWorkerProviderHost::SetControllerVersionAttribute(
ServiceWorkerVersion* version,
bool notify_controllerchange) {
+ CHECK(!version || IsContextSecureForServiceWorker());
if (version == controlling_version_.get())
return;
@@ -239,6 +260,7 @@
void ServiceWorkerProviderHost::AssociateRegistration(
ServiceWorkerRegistration* registration,
bool notify_controllerchange) {
+ CHECK(IsContextSecureForServiceWorker());
DCHECK(CanAssociateRegistration(registration));
associated_registration_ = registration;
AddMatchingRegistration(registration);
@@ -267,6 +289,8 @@
ServiceWorkerRegistration* registration) {
DCHECK(ServiceWorkerUtils::ScopeMatches(
registration->pattern(), document_url_));
+ if (!IsContextSecureForServiceWorker())
+ return;
size_t key = registration->pattern().spec().size();
if (ContainsKey(matching_registrations_, key))
return;
diff --git a/content/browser/service_worker/service_worker_provider_host.h b/content/browser/service_worker/service_worker_provider_host.h
index 0372b11..4d33c59e 100644
--- a/content/browser/service_worker/service_worker_provider_host.h
+++ b/content/browser/service_worker/service_worker_provider_host.h
@@ -61,6 +61,8 @@
static std::unique_ptr<ServiceWorkerProviderHost> PreCreateNavigationHost(
base::WeakPtr<ServiceWorkerContextCore> context);
+ enum class FrameSecurityLevel { UNINITIALIZED, INSECURE, SECURE };
+
// When this provider host is for a Service Worker context, |route_id| is
// MSG_ROUTING_NONE. When this provider host is for a Document,
// |route_id| is the frame ID of the Document. When this provider host is for
@@ -72,6 +74,7 @@
int route_id,
int provider_id,
ServiceWorkerProviderType provider_type,
+ FrameSecurityLevel parent_frame_security_level,
base::WeakPtr<ServiceWorkerContextCore> context,
ServiceWorkerDispatcherHost* dispatcher_host);
virtual ~ServiceWorkerProviderHost();
@@ -82,6 +85,26 @@
int frame_id() const;
int route_id() const { return route_id_; }
+ bool is_parent_frame_secure() const {
+ return parent_frame_security_level_ == FrameSecurityLevel::SECURE;
+ }
+ void set_parent_frame_secure(bool is_parent_frame_secure) {
+ CHECK_EQ(parent_frame_security_level_, FrameSecurityLevel::UNINITIALIZED);
+ parent_frame_security_level_ = is_parent_frame_secure
+ ? FrameSecurityLevel::SECURE
+ : FrameSecurityLevel::INSECURE;
+ }
+
+ // Returns whether this provider host is secure enough to have a service
+ // worker controller.
+ // Analogous to Blink's Document::isSecureContext. Because of how service
+ // worker intercepts main resource requests, this check must be done
+ // browser-side once the URL is known (see comments in
+ // ServiceWorkerNetworkProvider::CreateForNavigation). This function uses
+ // |document_url_| and |is_parent_frame_secure_| to determine context
+ // security, so they must be set properly before calling this function.
+ bool IsContextSecureForServiceWorker() const;
+
bool IsHostToRunningServiceWorker() {
return running_hosted_version_.get() != NULL;
}
@@ -259,6 +282,7 @@
UpdateForceBypassCache);
FRIEND_TEST_ALL_PREFIXES(ServiceWorkerContextRequestHandlerTest,
ServiceWorkerDataRequestAnnotation);
+ FRIEND_TEST_ALL_PREFIXES(ServiceWorkerProviderHostTest, ContextSecurity);
struct OneShotGetReadyCallback {
GetRegistrationForReadyCallback callback;
@@ -307,6 +331,7 @@
int render_thread_id_;
int provider_id_;
ServiceWorkerProviderType provider_type_;
+ FrameSecurityLevel parent_frame_security_level_;
GURL document_url_;
GURL topmost_frame_url_;
@@ -316,8 +341,9 @@
// Keyed by registration scope URL length.
typedef std::map<size_t, scoped_refptr<ServiceWorkerRegistration>>
ServiceWorkerRegistrationMap;
- // Contains all living registrations which has pattern this document's
- // URL starts with.
+ // Contains all living registrations whose pattern this document's URL
+ // starts with. It is empty if IsContextSecureForServiceWorker() is
+ // false.
ServiceWorkerRegistrationMap matching_registrations_;
std::unique_ptr<OneShotGetReadyCallback> get_ready_callback_;
diff --git a/content/browser/service_worker/service_worker_provider_host_unittest.cc b/content/browser/service_worker/service_worker_provider_host_unittest.cc
index cd478f0a..a9bec57a 100644
--- a/content/browser/service_worker/service_worker_provider_host_unittest.cc
+++ b/content/browser/service_worker/service_worker_provider_host_unittest.cc
@@ -13,39 +13,58 @@
#include "content/browser/service_worker/service_worker_register_job.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/browser/service_worker/service_worker_version.h"
+#include "content/public/common/origin_util.h"
#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/test/test_content_browser_client.h"
+#include "content/test/test_content_client.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
+const char kServiceWorkerScheme[] = "i-can-use-service-worker";
+
+class ServiceWorkerTestContentClient : public TestContentClient {
+ public:
+ void AddServiceWorkerSchemes(std::set<std::string>* schemes) override {
+ schemes->insert(kServiceWorkerScheme);
+ }
+};
+
class ServiceWorkerProviderHostTest : public testing::Test {
protected:
ServiceWorkerProviderHostTest()
- : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {}
+ : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {
+ SetContentClient(&test_content_client_);
+ }
~ServiceWorkerProviderHostTest() override {}
void SetUp() override {
+ old_content_browser_client_ =
+ SetBrowserClientForTesting(&test_content_browser_client_);
+
helper_.reset(new EmbeddedWorkerTestHelper(base::FilePath()));
context_ = helper_->context();
- script_url_ = GURL("http://www.example.com/service_worker.js");
+ script_url_ = GURL("https://www.example.com/service_worker.js");
registration1_ = new ServiceWorkerRegistration(
- GURL("http://www.example.com/"), 1L, context_->AsWeakPtr());
+ GURL("https://www.example.com/"), 1L, context_->AsWeakPtr());
registration2_ = new ServiceWorkerRegistration(
- GURL("http://www.example.com/example"), 2L, context_->AsWeakPtr());
+ GURL("https://www.example.com/example"), 2L, context_->AsWeakPtr());
// Prepare provider hosts (for the same process).
std::unique_ptr<ServiceWorkerProviderHost> host1(
- new ServiceWorkerProviderHost(helper_->mock_render_process_id(),
- MSG_ROUTING_NONE, 1 /* provider_id */,
- SERVICE_WORKER_PROVIDER_FOR_WINDOW,
- context_->AsWeakPtr(), NULL));
- host1->SetDocumentUrl(GURL("http://www.example.com/example1.html"));
+ new ServiceWorkerProviderHost(
+ helper_->mock_render_process_id(), MSG_ROUTING_NONE,
+ 1 /* provider_id */, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+ context_->AsWeakPtr(), NULL));
+ host1->SetDocumentUrl(GURL("https://www.example.com/example1.html"));
std::unique_ptr<ServiceWorkerProviderHost> host2(
- new ServiceWorkerProviderHost(helper_->mock_render_process_id(),
- MSG_ROUTING_NONE, 2 /* provider_id */,
- SERVICE_WORKER_PROVIDER_FOR_WINDOW,
- context_->AsWeakPtr(), NULL));
- host2->SetDocumentUrl(GURL("http://www.example.com/example2.html"));
+ new ServiceWorkerProviderHost(
+ helper_->mock_render_process_id(), MSG_ROUTING_NONE,
+ 2 /* provider_id */, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+ context_->AsWeakPtr(), NULL));
+ host2->SetDocumentUrl(GURL("https://www.example.com/example2.html"));
provider_host1_ = host1->AsWeakPtr();
provider_host2_ = host2->AsWeakPtr();
context_->AddProviderHost(base::WrapUnique(host1.release()));
@@ -56,6 +75,7 @@
registration1_ = 0;
registration2_ = 0;
helper_.reset();
+ SetBrowserClientForTesting(old_content_browser_client_);
}
bool PatternHasProcessToRun(const GURL& pattern) const {
@@ -70,6 +90,9 @@
base::WeakPtr<ServiceWorkerProviderHost> provider_host1_;
base::WeakPtr<ServiceWorkerProviderHost> provider_host2_;
GURL script_url_;
+ ServiceWorkerTestContentClient test_content_client_;
+ TestContentBrowserClient test_content_browser_client_;
+ ContentBrowserClient* old_content_browser_client_;
private:
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerProviderHostTest);
@@ -128,4 +151,37 @@
ASSERT_EQ(provider_host1_->MatchRegistration(), nullptr);
}
+TEST_F(ServiceWorkerProviderHostTest, ContextSecurity) {
+ using FrameSecurityLevel = ServiceWorkerProviderHost::FrameSecurityLevel;
+ content::ResetSchemesAndOriginsWhitelistForTesting();
+
+ // Insecure document URL.
+ provider_host1_->SetDocumentUrl(GURL("http://host"));
+ provider_host1_->parent_frame_security_level_ = FrameSecurityLevel::SECURE;
+ EXPECT_FALSE(provider_host1_->IsContextSecureForServiceWorker());
+
+ // Insecure parent frame.
+ provider_host1_->SetDocumentUrl(GURL("https://host"));
+ provider_host1_->parent_frame_security_level_ = FrameSecurityLevel::INSECURE;
+ EXPECT_FALSE(provider_host1_->IsContextSecureForServiceWorker());
+
+ // Secure URL and parent frame.
+ provider_host1_->SetDocumentUrl(GURL("https://host"));
+ provider_host1_->parent_frame_security_level_ = FrameSecurityLevel::SECURE;
+ EXPECT_TRUE(provider_host1_->IsContextSecureForServiceWorker());
+
+ // Exceptional service worker scheme.
+ GURL url(std::string(kServiceWorkerScheme) + "://host");
+ EXPECT_TRUE(url.is_valid());
+ provider_host1_->SetDocumentUrl(url);
+ provider_host1_->parent_frame_security_level_ = FrameSecurityLevel::SECURE;
+ EXPECT_FALSE(IsOriginSecure(url));
+ EXPECT_TRUE(OriginCanAccessServiceWorkers(url));
+ EXPECT_TRUE(provider_host1_->IsContextSecureForServiceWorker());
+
+ // Exceptional service worker scheme with insecure parent frame.
+ provider_host1_->parent_frame_security_level_ = FrameSecurityLevel::INSECURE;
+ EXPECT_FALSE(provider_host1_->IsContextSecureForServiceWorker());
+}
+
} // namespace content
diff --git a/content/browser/service_worker/service_worker_registration.cc b/content/browser/service_worker/service_worker_registration.cc
index d7fb0b1..bad99207 100644
--- a/content/browser/service_worker/service_worker_registration.cc
+++ b/content/browser/service_worker/service_worker_registration.cc
@@ -201,6 +201,8 @@
continue;
if (host->controlling_version() == active_version())
continue;
+ if (!host->IsContextSecureForServiceWorker())
+ continue;
if (host->MatchRegistration() == this)
host->ClaimedByRegistration(this);
}
diff --git a/content/browser/service_worker/service_worker_request_handler_unittest.cc b/content/browser/service_worker/service_worker_request_handler_unittest.cc
index 56dcac6..7316c91 100644
--- a/content/browser/service_worker/service_worker_request_handler_unittest.cc
+++ b/content/browser/service_worker/service_worker_request_handler_unittest.cc
@@ -42,20 +42,20 @@
helper_.reset(new EmbeddedWorkerTestHelper(base::FilePath()));
// A new unstored registration/version.
- registration_ = new ServiceWorkerRegistration(
- GURL("http://host/scope/"), 1L, context()->AsWeakPtr());
+ registration_ = new ServiceWorkerRegistration(GURL("https://host/scope/"),
+ 1L, context()->AsWeakPtr());
version_ = new ServiceWorkerVersion(registration_.get(),
- GURL("http://host/script.js"),
- 1L,
+ GURL("https://host/script.js"), 1L,
context()->AsWeakPtr());
// An empty host.
std::unique_ptr<ServiceWorkerProviderHost> host(
- new ServiceWorkerProviderHost(helper_->mock_render_process_id(),
- MSG_ROUTING_NONE, kMockProviderId,
- SERVICE_WORKER_PROVIDER_FOR_WINDOW,
- context()->AsWeakPtr(), nullptr));
- host->SetDocumentUrl(GURL("http://host/scope/"));
+ new ServiceWorkerProviderHost(
+ helper_->mock_render_process_id(), MSG_ROUTING_NONE,
+ kMockProviderId, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+ context()->AsWeakPtr(), nullptr));
+ host->SetDocumentUrl(GURL("https://host/scope/"));
provider_host_ = host->AsWeakPtr();
context()->AddProviderHost(std::move(host));
diff --git a/content/browser/service_worker/service_worker_storage_unittest.cc b/content/browser/service_worker/service_worker_storage_unittest.cc
index ac3281e..478ace7 100644
--- a/content/browser/service_worker/service_worker_storage_unittest.cc
+++ b/content/browser/service_worker/service_worker_storage_unittest.cc
@@ -1276,6 +1276,7 @@
std::unique_ptr<ServiceWorkerProviderHost> host(new ServiceWorkerProviderHost(
33 /* dummy render process id */, MSG_ROUTING_NONE,
1 /* dummy provider_id */, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
context()->AsWeakPtr(), NULL));
registration_->active_version()->AddControllee(host.get());
@@ -1327,6 +1328,7 @@
std::unique_ptr<ServiceWorkerProviderHost> host(new ServiceWorkerProviderHost(
33 /* dummy render process id */, MSG_ROUTING_NONE,
1 /* dummy provider_id */, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
context()->AsWeakPtr(), NULL));
registration_->active_version()->AddControllee(host.get());
@@ -1486,6 +1488,7 @@
std::unique_ptr<ServiceWorkerProviderHost> host(new ServiceWorkerProviderHost(
33 /* dummy render process id */, MSG_ROUTING_NONE,
1 /* dummy provider_id */, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
context()->AsWeakPtr(), NULL));
registration_->active_version()->AddControllee(host.get());
diff --git a/content/browser/service_worker/service_worker_url_request_job_unittest.cc b/content/browser/service_worker/service_worker_url_request_job_unittest.cc
index 8da106c4..53b3fe81 100644
--- a/content/browser/service_worker/service_worker_url_request_job_unittest.cc
+++ b/content/browser/service_worker/service_worker_url_request_job_unittest.cc
@@ -143,13 +143,9 @@
helper_.reset(helper);
registration_ = new ServiceWorkerRegistration(
- GURL("http://example.com/"),
- 1L,
- helper_->context()->AsWeakPtr());
+ GURL("https://example.com/"), 1L, helper_->context()->AsWeakPtr());
version_ = new ServiceWorkerVersion(
- registration_.get(),
- GURL("http://example.com/service_worker.js"),
- 1L,
+ registration_.get(), GURL("https://example.com/service_worker.js"), 1L,
helper_->context()->AsWeakPtr());
std::vector<ServiceWorkerDatabase::ResourceRecord> records;
records.push_back(
@@ -181,10 +177,11 @@
std::unique_ptr<ServiceWorkerProviderHost> provider_host(
new ServiceWorkerProviderHost(
helper_->mock_render_process_id(), MSG_ROUTING_NONE, kProviderID,
- SERVICE_WORKER_PROVIDER_FOR_WINDOW, helper_->context()->AsWeakPtr(),
- nullptr));
+ SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+ helper_->context()->AsWeakPtr(), nullptr));
provider_host_ = provider_host->AsWeakPtr();
- provider_host->SetDocumentUrl(GURL("http://example.com/"));
+ provider_host->SetDocumentUrl(GURL("https://example.com/"));
registration_->SetActiveVersion(version_);
provider_host->AssociateRegistration(registration_.get(),
false /* notify_controllerchange */);
@@ -198,7 +195,7 @@
url_request_job_factory_.reset(new net::URLRequestJobFactoryImpl);
url_request_job_factory_->SetProtocolHandler(
- "http",
+ "https",
base::WrapUnique(new MockHttpProtocolHandler(
provider_host->AsWeakPtr(), browser_context_->GetResourceContext(),
blob_storage_context->AsWeakPtr(), this)));
@@ -240,7 +237,7 @@
const std::string& expected_response,
bool expect_valid_ssl) {
request_ = url_request_context_.CreateRequest(
- GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+ GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
&url_request_delegate_);
request_->set_method("GET");
@@ -377,7 +374,7 @@
TEST_F(ServiceWorkerURLRequestJobTest, DeletedProviderHostBeforeFetchEvent) {
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
request_ = url_request_context_.CreateRequest(
- GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+ GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
&url_request_delegate_);
request_->set_method("GET");
@@ -521,7 +518,7 @@
SetUpWithHelper(new StreamResponder(stream_url));
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
request_ = url_request_context_.CreateRequest(
- GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+ GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
&url_request_delegate_);
request_->set_method("GET");
request_->Start();
@@ -569,7 +566,7 @@
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
request_ = url_request_context_.CreateRequest(
- GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+ GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
&url_request_delegate_);
request_->set_method("GET");
request_->Start();
@@ -629,7 +626,7 @@
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
request_ = url_request_context_.CreateRequest(
- GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+ GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
&url_request_delegate_);
request_->set_method("GET");
request_->Start();
@@ -671,7 +668,7 @@
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
request_ = url_request_context_.CreateRequest(
- GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+ GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
&url_request_delegate_);
request_->set_method("GET");
request_->Start();
@@ -719,7 +716,7 @@
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
request_ = url_request_context_.CreateRequest(
- GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+ GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
&url_request_delegate_);
request_->set_method("GET");
request_->Start();
@@ -769,7 +766,7 @@
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
request_ = url_request_context_.CreateRequest(
- GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+ GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
&url_request_delegate_);
request_->set_method("GET");
request_->Start();
@@ -816,7 +813,7 @@
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
request_ = url_request_context_.CreateRequest(
- GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+ GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
&url_request_delegate_);
request_->set_method("GET");
request_->Start();
@@ -846,7 +843,7 @@
SetUpWithHelper(new EmbeddedWorkerTestHelper(base::FilePath()), false);
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
request_ = url_request_context_.CreateRequest(
- GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+ GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
&url_request_delegate_);
request_->set_method("GET");
request_->Start();
diff --git a/content/browser/service_worker/service_worker_version_unittest.cc b/content/browser/service_worker/service_worker_version_unittest.cc
index 3240d541..2b1987f 100644
--- a/content/browser/service_worker/service_worker_version_unittest.cc
+++ b/content/browser/service_worker/service_worker_version_unittest.cc
@@ -621,6 +621,7 @@
std::unique_ptr<ServiceWorkerProviderHost> host(new ServiceWorkerProviderHost(
33 /* dummy render process id */, MSG_ROUTING_NONE /* render_frame_id */,
1 /* dummy provider_id */, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
helper_->context()->AsWeakPtr(), NULL));
version_->AddControllee(host.get());
EXPECT_TRUE(version_->timeout_timer_.IsRunning());
diff --git a/content/browser/service_worker/service_worker_write_to_cache_job_unittest.cc b/content/browser/service_worker/service_worker_write_to_cache_job_unittest.cc
index cc8de0f..b63291f 100644
--- a/content/browser/service_worker/service_worker_write_to_cache_job_unittest.cc
+++ b/content/browser/service_worker/service_worker_write_to_cache_job_unittest.cc
@@ -280,9 +280,11 @@
int provider_id,
const scoped_refptr<ServiceWorkerVersion>& version) {
std::unique_ptr<ServiceWorkerProviderHost> host(
- new ServiceWorkerProviderHost(process_id, MSG_ROUTING_NONE, provider_id,
- SERVICE_WORKER_PROVIDER_FOR_WORKER,
- context()->AsWeakPtr(), nullptr));
+ new ServiceWorkerProviderHost(
+ process_id, MSG_ROUTING_NONE, provider_id,
+ SERVICE_WORKER_PROVIDER_FOR_WORKER,
+ ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+ context()->AsWeakPtr(), nullptr));
base::WeakPtr<ServiceWorkerProviderHost> provider_host = host->AsWeakPtr();
context()->AddProviderHost(std::move(host));
provider_host->running_hosted_version_ = version;
diff --git a/content/child/service_worker/service_worker_network_provider.cc b/content/child/service_worker/service_worker_network_provider.cc
index 0632e81..ab7597cd 100644
--- a/content/child/service_worker/service_worker_network_provider.cc
+++ b/content/child/service_worker/service_worker_network_provider.cc
@@ -11,6 +11,8 @@
#include "content/common/service_worker/service_worker_messages.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/common/browser_side_navigation_policy.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebSandboxFlags.h"
namespace content {
@@ -43,7 +45,7 @@
ServiceWorkerNetworkProvider::CreateForNavigation(
int route_id,
const RequestNavigationParams& request_params,
- blink::WebSandboxFlags sandbox_flags,
+ blink::WebLocalFrame* frame,
bool content_initiated) {
bool browser_side_navigation = IsBrowserSideNavigationEnabled();
bool should_create_provider_for_window = false;
@@ -65,24 +67,33 @@
service_worker_provider_id == kInvalidServiceWorkerProviderId);
} else {
should_create_provider_for_window =
- (sandbox_flags & blink::WebSandboxFlags::Origin) !=
- blink::WebSandboxFlags::Origin;
+ ((frame->effectiveSandboxFlags() & blink::WebSandboxFlags::Origin) !=
+ blink::WebSandboxFlags::Origin);
}
// Now create the ServiceWorkerNetworkProvider (with invalid id if needed).
if (should_create_provider_for_window) {
+ // Ideally Document::isSecureContext would be called here, but the document
+ // is not created yet, and due to redirects the URL may change. So pass
+ // is_parent_frame_secure to the browser process, so it can determine the
+ // context security when deciding whether to allow a service worker to
+ // control the document.
+ bool is_parent_frame_secure =
+ !frame->parent() || frame->parent()->canHaveSecureChild();
+
if (service_worker_provider_id == kInvalidServiceWorkerProviderId) {
network_provider = std::unique_ptr<ServiceWorkerNetworkProvider>(
new ServiceWorkerNetworkProvider(route_id,
- SERVICE_WORKER_PROVIDER_FOR_WINDOW));
+ SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ is_parent_frame_secure));
} else {
CHECK(browser_side_navigation);
DCHECK(ServiceWorkerUtils::IsBrowserAssignedProviderId(
service_worker_provider_id));
network_provider = std::unique_ptr<ServiceWorkerNetworkProvider>(
- new ServiceWorkerNetworkProvider(route_id,
- SERVICE_WORKER_PROVIDER_FOR_WINDOW,
- service_worker_provider_id));
+ new ServiceWorkerNetworkProvider(
+ route_id, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+ service_worker_provider_id, is_parent_frame_secure));
}
} else {
network_provider = std::unique_ptr<ServiceWorkerNetworkProvider>(
@@ -94,7 +105,8 @@
ServiceWorkerNetworkProvider::ServiceWorkerNetworkProvider(
int route_id,
ServiceWorkerProviderType provider_type,
- int browser_provider_id)
+ int browser_provider_id,
+ bool is_parent_frame_secure)
: provider_id_(browser_provider_id) {
if (provider_id_ == kInvalidServiceWorkerProviderId)
return;
@@ -104,15 +116,17 @@
provider_id_, provider_type,
ChildThreadImpl::current()->thread_safe_sender());
ChildThreadImpl::current()->Send(new ServiceWorkerHostMsg_ProviderCreated(
- provider_id_, route_id, provider_type));
+ provider_id_, route_id, provider_type, is_parent_frame_secure));
}
ServiceWorkerNetworkProvider::ServiceWorkerNetworkProvider(
int route_id,
- ServiceWorkerProviderType provider_type)
+ ServiceWorkerProviderType provider_type,
+ bool is_parent_frame_secure)
: ServiceWorkerNetworkProvider(route_id,
provider_type,
- GetNextProviderId()) {}
+ GetNextProviderId(),
+ is_parent_frame_secure) {}
ServiceWorkerNetworkProvider::ServiceWorkerNetworkProvider()
: provider_id_(kInvalidServiceWorkerProviderId) {}
diff --git a/content/child/service_worker/service_worker_network_provider.h b/content/child/service_worker/service_worker_network_provider.h
index 178adf5a..1d09950 100644
--- a/content/child/service_worker/service_worker_network_provider.h
+++ b/content/child/service_worker/service_worker_network_provider.h
@@ -15,7 +15,11 @@
#include "base/supports_user_data.h"
#include "content/common/content_export.h"
#include "content/common/service_worker/service_worker_types.h"
-#include "third_party/WebKit/public/web/WebSandboxFlags.h"
+
+namespace blink {
+class WebDataSource;
+class WebLocalFrame;
+} // namespace blink
namespace content {
@@ -46,15 +50,18 @@
static std::unique_ptr<ServiceWorkerNetworkProvider> CreateForNavigation(
int route_id,
const RequestNavigationParams& request_params,
- blink::WebSandboxFlags sandbox_flags,
+ blink::WebLocalFrame* frame,
bool content_initiated);
// PlzNavigate
// The |browser_provider_id| is initialized by the browser for navigations.
ServiceWorkerNetworkProvider(int route_id,
ServiceWorkerProviderType type,
- int browser_provider_id);
- ServiceWorkerNetworkProvider(int route_id, ServiceWorkerProviderType type);
+ int browser_provider_id,
+ bool is_parent_frame_secure);
+ ServiceWorkerNetworkProvider(int route_id,
+ ServiceWorkerProviderType type,
+ bool is_parent_frame_secure);
ServiceWorkerNetworkProvider();
~ServiceWorkerNetworkProvider() override;
diff --git a/content/common/service_worker/service_worker_messages.h b/content/common/service_worker/service_worker_messages.h
index 05f22dfb..412c0fc0 100644
--- a/content/common/service_worker/service_worker_messages.h
+++ b/content/common/service_worker/service_worker_messages.h
@@ -203,10 +203,21 @@
// MSG_ROUTING_NONE. |provider_type| identifies whether this provider is for
// Service Worker controllees (documents and Shared Workers) or for controllers
// (Service Workers).
-IPC_MESSAGE_CONTROL3(ServiceWorkerHostMsg_ProviderCreated,
+//
+// |is_parent_frame_secure| is false if the provider is created for a
+// document whose parent frame is not secure from the point of view of the
+// document; that is, blink::WebFrame::canHaveSecureChild() returns false.
+// This doesn't mean the document is necessarily an insecure context,
+// because the document may have a URL whose scheme is granted an exception
+// that allows bypassing the ancestor secure context check. See the
+// comment in blink::Document::isSecureContextImpl for more details.
+// If the provider is not created for a document, or the document does not have
+// a parent frame, |is_parent_frame_secure| is true.
+IPC_MESSAGE_CONTROL4(ServiceWorkerHostMsg_ProviderCreated,
int /* provider_id */,
int /* route_id */,
- content::ServiceWorkerProviderType /* provider_type */)
+ content::ServiceWorkerProviderType /* provider_type */,
+ bool /* is_parent_frame_secure */)
// Informs the browser of a ServiceWorkerProvider being destroyed.
IPC_MESSAGE_CONTROL1(ServiceWorkerHostMsg_ProviderDestroyed,
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 8f38d85f..b43bb620 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -596,6 +596,12 @@
virtual void GetAdditionalAllowedSchemesForFileSystem(
std::vector<std::string>* additional_schemes) {}
+ // |schemes| is a return value parameter that gets a whitelist of schemes that
+ // should bypass the Is Privileged Context check.
+ // See http://www.w3.org/TR/powerful-features/#settings-privileged
+ virtual void GetSchemesBypassingSecureContextCheckWhitelist(
+ std::set<std::string>* schemes) {}
+
// Returns auto mount handlers for URL requests for FileSystem APIs.
virtual void GetURLRequestAutoMountHandlers(
std::vector<storage::URLRequestAutoMountHandler>* handlers) {}
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index e8ccd7e5..60145c2 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -3140,8 +3140,8 @@
ServiceWorkerNetworkProvider::AttachToDocumentState(
DocumentState::FromDataSource(datasource),
ServiceWorkerNetworkProvider::CreateForNavigation(
- routing_id_, navigation_state->request_params(),
- frame->effectiveSandboxFlags(), content_initiated));
+ routing_id_, navigation_state->request_params(), frame,
+ content_initiated));
}
void RenderFrameImpl::didStartProvisionalLoad(blink::WebLocalFrame* frame,
diff --git a/content/renderer/service_worker/service_worker_context_client.cc b/content/renderer/service_worker/service_worker_context_client.cc
index ee29751..d802fbae 100644
--- a/content/renderer/service_worker/service_worker_context_client.cc
+++ b/content/renderer/service_worker/service_worker_context_client.cc
@@ -579,7 +579,8 @@
// we can observe its requests.
std::unique_ptr<ServiceWorkerNetworkProvider> provider(
new ServiceWorkerNetworkProvider(MSG_ROUTING_NONE,
- SERVICE_WORKER_PROVIDER_FOR_CONTROLLER));
+ SERVICE_WORKER_PROVIDER_FOR_CONTROLLER,
+ true /* is_parent_frame_secure */));
provider_context_ = provider->context();
// Tell the network provider about which version to load.
diff --git a/content/renderer/shared_worker/embedded_shared_worker_stub.cc b/content/renderer/shared_worker/embedded_shared_worker_stub.cc
index ba2707c..4b750975 100644
--- a/content/renderer/shared_worker/embedded_shared_worker_stub.cc
+++ b/content/renderer/shared_worker/embedded_shared_worker_stub.cc
@@ -242,7 +242,8 @@
// we can observe its requests.
std::unique_ptr<ServiceWorkerNetworkProvider> provider(
new ServiceWorkerNetworkProvider(
- route_id_, SERVICE_WORKER_PROVIDER_FOR_SHARED_WORKER));
+ route_id_, SERVICE_WORKER_PROVIDER_FOR_SHARED_WORKER,
+ true /* is_parent_frame_secure */));
// The provider is kept around for the lifetime of the DataSource
// and ownership is transferred to the DataSource.
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/insecure-parent-frame.html b/third_party/WebKit/LayoutTests/http/tests/serviceworker/insecure-parent-frame.html
new file mode 100644
index 0000000..98cdf66
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/serviceworker/insecure-parent-frame.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="../resources/get-host-info.js?pipe=sub"></script>
+<script src="resources/test-helpers.js"></script>
+<title>Insecure parent frame test</title>
+<body></body>
+<script>
+// Asks |worker| to call clients.claim. Returns a promise that resolves when
+// the worker acks that claim finished.
+function wait_for_claim(worker) {
+ var saw_message = new Promise(resolve => {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = (e => resolve(e.data));
+ worker.postMessage({port: channel.port2}, [channel.port2]);
+ });
+
+ return saw_message.then(data => {
+ assert_equals(data, 'PASS', 'claim should finish');
+ });
+}
+
+// Asks |frame| whether it has a controller. Returns a promise that resolves
+// if controller was null.
+function assert_no_controller(frame, description) {
+ var saw_message = new Promise(resolve => {
+ window.onmessage = (e => resolve(e.data));
+ frame.contentWindow.postMessage('', '*');
+ });
+
+ return saw_message.then(data => assert_equals(data, 'PASS', description));
+}
+
+// This test creates https iframes inside insecure http iframes. It registers a
+// service worker that should not control the in-scope iframes. The iframes
+// communicate whether they have a controller to the top-level frame.
+promise_test(t => {
+ var script = 'resources/claim-worker.js';
+ var scope = 'resources/insecure-inscope';
+ var registration;
+ var insecure_url = get_host_info().UNAUTHENTICATED_ORIGIN +
+ '/serviceworker/resources/insecure-parent.html';
+ var pre_registration_frame;
+ var post_registration_frame;
+
+ return navigator.serviceWorker.getRegistration(scope)
+ // Unregister.
+ .then(reg => {
+ if (reg)
+ return reg.unregister();
+ })
+
+ // Create an iframe prior to registration.
+ .then(() => with_iframe(insecure_url))
+
+ // Register.
+ .then(frame => {
+ pre_registration_frame = frame;
+ add_result_callback(() => pre_registration_frame.remove());
+ return navigator.serviceWorker.register(script, {scope:scope});
+ })
+ .then(reg => {
+ registration = reg;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+
+ // Create an iframe after registration.
+ .then(() => with_iframe(insecure_url))
+ .then(frame => post_registration_frame = frame)
+
+ // Check that no frame is controlled.
+ .then(() => assert_no_controller(pre_registration_frame,
+ 'pre_registration_frame should not be controlled'))
+ .then(() => assert_no_controller(post_registration_frame,
+ 'post_registration_frame should not be controlled'))
+
+ // Attempt to claim. The iframes should still have no controllers.
+ .then(() => wait_for_claim(registration.active))
+ .then(() => assert_no_controller(pre_registration_frame,
+ 'pre_registration_frame should not be claimed'))
+ .then(() => assert_no_controller(post_registration_frame,
+ 'post_registration_frame should not be claimed'));
+ }, 'Service worker does not control a subframe of an insecure frame');
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/insecure-inscope.html b/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/insecure-inscope.html
new file mode 100644
index 0000000..7fd97f2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/insecure-inscope.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Insecure inscope page</title>
+<script>
+// The top window messages us when it wants to check for a controller.
+window.onmessage = (e => {
+ if (navigator.serviceWorker.controller === null)
+ window.top.postMessage('PASS', '*');
+ else
+ window.top.postMessage('FAIL', '*');
+ });
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/insecure-parent.html b/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/insecure-parent.html
new file mode 100644
index 0000000..52a066a0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/insecure-parent.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="../../resources/get-host-info.js?pipe=sub"></script>
+<title>Page Title</title>
+<body></body>
+<script>
+var iframe = document.createElement('iframe');
+iframe.src = get_host_info().HTTP_ORIGIN +
+ '/serviceworker/resources/insecure-inscope.html';
+document.body.appendChild(iframe);
+
+// The top frame messages us to message the subframe.
+window.addEventListener('message', e => {
+ iframe.contentWindow.postMessage(e.data, '*');
+ });
+</script>
diff --git a/third_party/WebKit/Source/core/dom/Document.cpp b/third_party/WebKit/Source/core/dom/Document.cpp
index bcd02ab..5e6bf70 100644
--- a/third_party/WebKit/Source/core/dom/Document.cpp
+++ b/third_party/WebKit/Source/core/dom/Document.cpp
@@ -359,15 +359,6 @@
return element.document().frame() && element.rootEditableElement();
}
-static bool isOriginPotentiallyTrustworthy(SecurityOrigin* origin, String* errorMessage)
-{
- if (origin->isPotentiallyTrustworthy())
- return true;
- if (errorMessage)
- *errorMessage = origin->isPotentiallyTrustworthyErrorMessage();
- return false;
-}
-
uint64_t Document::s_globalTreeVersion = 0;
static bool s_threadedParsingEnabledForTesting = true;
@@ -3326,7 +3317,7 @@
setMimeType(other.contentType());
}
-bool Document::isSecureContextImpl(String* errorMessage, const SecureContextCheck privilegeContextCheck) const
+bool Document::isSecureContextImpl(const SecureContextCheck privilegeContextCheck) const
{
// There may be exceptions for the secure context check defined for certain
// schemes. The exceptions are applied only to the special scheme and to
@@ -3356,21 +3347,16 @@
//
// In all cases, a frame must be potentially trustworthy in addition to
// having an exception listed in order for the exception to be granted.
- if (!isOriginPotentiallyTrustworthy(getSecurityOrigin(), errorMessage))
+ if (!getSecurityOrigin()->isPotentiallyTrustworthy())
return false;
if (SchemeRegistry::schemeShouldBypassSecureContextCheck(getSecurityOrigin()->protocol()))
return true;
if (privilegeContextCheck == StandardSecureContextCheck) {
- if (!m_frame)
- return true;
- Frame* parent = m_frame->tree().parent();
- while (parent) {
- if (!isOriginPotentiallyTrustworthy(parent->securityContext()->getSecurityOrigin(), errorMessage))
- return false;
- parent = parent->tree().parent();
- }
+ Frame* parent = m_frame ? m_frame->tree().parent() : nullptr;
+ if (parent && !parent->canHaveSecureChild())
+ return false;
}
return true;
}
@@ -5848,12 +5834,15 @@
bool Document::isSecureContext(String& errorMessage, const SecureContextCheck privilegeContextCheck) const
{
- return isSecureContextImpl(&errorMessage, privilegeContextCheck);
+ if (isSecureContextImpl(privilegeContextCheck))
+ return true;
+ errorMessage = SecurityOrigin::isPotentiallyTrustworthyErrorMessage();
+ return false;
}
bool Document::isSecureContext(const SecureContextCheck privilegeContextCheck) const
{
- return isSecureContextImpl(nullptr, privilegeContextCheck);
+ return isSecureContextImpl(privilegeContextCheck);
}
WebTaskRunner* Document::loadingTaskRunner() const
diff --git a/third_party/WebKit/Source/core/dom/Document.h b/third_party/WebKit/Source/core/dom/Document.h
index 616b47d..19878dc0 100644
--- a/third_party/WebKit/Source/core/dom/Document.h
+++ b/third_party/WebKit/Source/core/dom/Document.h
@@ -1146,7 +1146,7 @@
bool childTypeAllowed(NodeType) const final;
Node* cloneNode(bool deep) final;
void cloneDataFromDocument(const Document&);
- bool isSecureContextImpl(String* errorMessage, const SecureContextCheck priviligeContextCheck) const;
+ bool isSecureContextImpl(const SecureContextCheck priviligeContextCheck) const;
ShadowCascadeOrder m_shadowCascadeOrder = ShadowCascadeNone;
diff --git a/third_party/WebKit/Source/core/frame/Frame.cpp b/third_party/WebKit/Source/core/frame/Frame.cpp
index 3e1d8f5..833e9200 100644
--- a/third_party/WebKit/Source/core/frame/Frame.cpp
+++ b/third_party/WebKit/Source/core/frame/Frame.cpp
@@ -305,6 +305,15 @@
childFrames[i]->didChangeVisibilityState();
}
+bool Frame::canHaveSecureChild() const
+{
+ for (const Frame* parent = this; parent; parent = parent->tree().parent()) {
+ if (!parent->securityContext()->getSecurityOrigin()->isPotentiallyTrustworthy())
+ return false;
+ }
+ return true;
+}
+
Frame::Frame(FrameClient* client, FrameHost* host, FrameOwner* owner)
: m_treeNode(this)
, m_host(host)
diff --git a/third_party/WebKit/Source/core/frame/Frame.h b/third_party/WebKit/Source/core/frame/Frame.h
index 423e882..698e71a 100644
--- a/third_party/WebKit/Source/core/frame/Frame.h
+++ b/third_party/WebKit/Source/core/frame/Frame.h
@@ -136,6 +136,15 @@
virtual void didChangeVisibilityState();
+ // Use Document::isSecureContext() instead of this function to
+ // check whether this frame's document is a secure context.
+ //
+ // Returns whether it's possible for a document whose frame is a descendant
+ // of this frame to be a secure context, not considering scheme exceptions
+ // (since any document can be a secure context if it has a scheme
+ // exception). See Document::isSecureContextImpl for more details.
+ bool canHaveSecureChild() const;
+
protected:
Frame(FrameClient*, FrameHost*, FrameOwner*);
diff --git a/third_party/WebKit/Source/web/WebFrame.cpp b/third_party/WebKit/Source/web/WebFrame.cpp
index fe56e61..afd1640 100644
--- a/third_party/WebKit/Source/web/WebFrame.cpp
+++ b/third_party/WebKit/Source/web/WebFrame.cpp
@@ -285,6 +285,14 @@
return fromFrame(toHTMLFrameOwnerElement(element)->contentFrame());
}
+bool WebFrame::canHaveSecureChild() const
+{
+ Frame* frame = toImplBase()->frame();
+ if (!frame)
+ return false;
+ return frame->canHaveSecureChild();
+}
+
bool WebFrame::isLoading() const
{
if (Frame* frame = toImplBase()->frame())
diff --git a/third_party/WebKit/Source/web/tests/WebFrameTest.cpp b/third_party/WebKit/Source/web/tests/WebFrameTest.cpp
index 4b6d254..cc7b83617 100644
--- a/third_party/WebKit/Source/web/tests/WebFrameTest.cpp
+++ b/third_party/WebKit/Source/web/tests/WebFrameTest.cpp
@@ -8644,4 +8644,62 @@
EXPECT_EQ("world", toCoreString(result->ToString(mainFrame->mainWorldScriptContext()).ToLocalChecked()));
}
+static void setSecurityOrigin(WebFrame* frame, PassRefPtr<SecurityOrigin> securityOrigin)
+{
+ Document* document = frame->document();
+ document->setSecurityOrigin(securityOrigin);
+}
+
+TEST_F(WebFrameTest, CanHaveSecureChild)
+{
+ FrameTestHelpers::WebViewHelper helper;
+ FrameTestHelpers::TestWebFrameClient client;
+ helper.initialize(true, &client, nullptr, nullptr);
+ WebFrame* mainFrame = helper.webView()->mainFrame();
+ RefPtr<SecurityOrigin> secureOrigin = SecurityOrigin::createFromString("https://example.com");
+ RefPtr<SecurityOrigin> insecureOrigin = SecurityOrigin::createFromString("http://example.com");
+
+ // Secure frame.
+ setSecurityOrigin(mainFrame, secureOrigin);
+ ASSERT_TRUE(mainFrame->canHaveSecureChild());
+
+ // Insecure frame.
+ setSecurityOrigin(mainFrame, insecureOrigin);
+ ASSERT_FALSE(mainFrame->canHaveSecureChild());
+
+ // Create a chain of frames.
+ FrameTestHelpers::loadFrame(mainFrame, "data:text/html,<iframe></iframe>");
+ WebFrame* childFrame = mainFrame->firstChild();
+ FrameTestHelpers::loadFrame(childFrame, "data:text/html,<iframe></iframe>");
+ WebFrame* grandchildFrame = childFrame->firstChild();
+
+ // Secure -> insecure -> secure frame.
+ setSecurityOrigin(mainFrame, secureOrigin);
+ setSecurityOrigin(childFrame, insecureOrigin);
+ setSecurityOrigin(grandchildFrame, secureOrigin);
+ ASSERT_TRUE(mainFrame->canHaveSecureChild());
+ ASSERT_FALSE(childFrame->canHaveSecureChild());
+ ASSERT_FALSE(grandchildFrame->canHaveSecureChild());
+
+ // A document in an insecure context can be considered secure if it has a
+ // scheme that bypasses the secure context check. But the exception doesn't
+ // apply to children of that document's frame.
+ SchemeRegistry::registerURLSchemeBypassingSecureContextCheck("very-special-scheme");
+ SchemeRegistry::registerURLSchemeAsSecure("very-special-scheme");
+ RefPtr<SecurityOrigin> specialOrigin = SecurityOrigin::createFromString("very-special-scheme://example.com");
+
+ setSecurityOrigin(mainFrame, insecureOrigin);
+ setSecurityOrigin(childFrame, specialOrigin);
+ setSecurityOrigin(grandchildFrame, secureOrigin);
+ ASSERT_FALSE(mainFrame->canHaveSecureChild());
+ ASSERT_FALSE(childFrame->canHaveSecureChild());
+ ASSERT_FALSE(grandchildFrame->canHaveSecureChild());
+ Document* mainDocument = mainFrame->document();
+ Document* childDocument = childFrame->document();
+ Document* grandchildDocument = grandchildFrame->document();
+ ASSERT_FALSE(mainDocument->isSecureContext());
+ ASSERT_TRUE(childDocument->isSecureContext());
+ ASSERT_FALSE(grandchildDocument->isSecureContext());
+}
+
} // namespace blink
diff --git a/third_party/WebKit/public/web/WebFrame.h b/third_party/WebKit/public/web/WebFrame.h
index fef5c51..2eb2c08 100644
--- a/third_party/WebKit/public/web/WebFrame.h
+++ b/third_party/WebKit/public/web/WebFrame.h
@@ -557,6 +557,15 @@
// the given element is not a frame, iframe or if the frame is empty.
BLINK_EXPORT static WebFrame* fromFrameOwnerElement(const WebElement&);
+ // Use WebDocument::isSecureContext() instead of this function to
+ // check whether this frame's document is a secure context.
+ //
+ // Returns whether it's possible for a document whose frame is a descendant
+ // of this frame to be a secure context, not considering scheme exceptions
+ // (since any document can be a secure context if it has a scheme
+ // exception). See Document::isSecureContextImpl for more details.
+ BLINK_EXPORT bool canHaveSecureChild() const;
+
#if BLINK_IMPLEMENTATION
static WebFrame* fromFrame(Frame*);