diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-12-19 12:53:25 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-12-19 12:53:25 +0100 |
commit | a1d7154bb73b41145be17283002eb10ee215574c (patch) | |
tree | db5772d1b814594f5ed24051dd2237ddeef8fff1 | |
parent | 0793ceb388fc29ede5cfe2bf5790e74344e4ce83 (diff) | |
parent | abefd5095b41dac94ca451d784ab6e27372e981a (diff) | |
download | qtwebengine-chromium-a1d7154bb73b41145be17283002eb10ee215574c.tar.gz |
Merge branch 'upstream-master' into 63-based
Change-Id: I9909647008cf4786e87f83733a4d82ce9286ea85
47 files changed, 2341 insertions, 112 deletions
diff --git a/chromium/DEPS b/chromium/DEPS index 0ae0dad7656..6add5aa83f8 100644 --- a/chromium/DEPS +++ b/chromium/DEPS @@ -171,7 +171,7 @@ deps = { (Var("chromium_git")) + '/android_tools.git@ca9dc7245b888c75307f0619e4a39fb46a82de66' }, 'src/third_party/angle': - (Var("chromium_git")) + '/angle/angle.git@9095f2b44801efef46a440c391d89278432169d5', + (Var("chromium_git")) + '/angle/angle.git@2ff870db3a3ba33c6dbf4b88c9ec2e52127b7206', 'src/third_party/apache-portable-runtime/src': { 'condition': 'checkout_android', @@ -529,7 +529,7 @@ deps = { 'src/tools/swarming_client': (Var("chromium_git")) + '/infra/luci/client-py.git@5e8001d9a710121ce7a68efd0804430a34b4f9e4', 'src/v8': - (Var("chromium_git")) + '/v8/v8.git@310263b31fd87280b80e4bdd4ecfba5128b4cda7' + (Var("chromium_git")) + '/v8/v8.git@0ffdb062d3ed8067ede1f8771e24dbf822be1a39' } hooks = [ diff --git a/chromium/build/util/LASTCHANGE b/chromium/build/util/LASTCHANGE index fe21d9bad56..b6f9abde98c 100644 --- a/chromium/build/util/LASTCHANGE +++ b/chromium/build/util/LASTCHANGE @@ -1 +1 @@ -LASTCHANGE=9f7c6464592fa694a05b7db5d521510df19243db- +LASTCHANGE=9d8e15517677de090afcca02259a80ffa552235e- diff --git a/chromium/build/util/LASTCHANGE.blink b/chromium/build/util/LASTCHANGE.blink index fe21d9bad56..b6f9abde98c 100644 --- a/chromium/build/util/LASTCHANGE.blink +++ b/chromium/build/util/LASTCHANGE.blink @@ -1 +1 @@ -LASTCHANGE=9f7c6464592fa694a05b7db5d521510df19243db- +LASTCHANGE=9d8e15517677de090afcca02259a80ffa552235e- diff --git a/chromium/chrome/VERSION b/chromium/chrome/VERSION index 2538ca30ef8..ae5f39b57d7 100644 --- a/chromium/chrome/VERSION +++ b/chromium/chrome/VERSION @@ -1,4 +1,4 @@ MAJOR=63 MINOR=0 BUILD=3239 -PATCH=87 +PATCH=117 diff --git a/chromium/chrome/browser/net/chrome_mojo_proxy_resolver_factory.cc b/chromium/chrome/browser/net/chrome_mojo_proxy_resolver_factory.cc new file mode 100644 index 00000000000..4ff6fcecef9 --- /dev/null +++ b/chromium/chrome/browser/net/chrome_mojo_proxy_resolver_factory.cc @@ -0,0 +1,135 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/net/chrome_mojo_proxy_resolver_factory.h" + +#include <utility> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/memory/singleton.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/common/service_manager_connection.h" + +namespace { + +constexpr base::TimeDelta kUtilityProcessIdleTimeout = + base::TimeDelta::FromSeconds(5); + +void BindConnectorOnUIThread(service_manager::mojom::ConnectorRequest request) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + content::ServiceManagerConnection::GetForProcess() + ->GetConnector() + ->BindConnectorRequest(std::move(request)); +} + +} // namespace + +// static +ChromeMojoProxyResolverFactory* ChromeMojoProxyResolverFactory::GetInstance() { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + return base::Singleton< + ChromeMojoProxyResolverFactory, + base::LeakySingletonTraits<ChromeMojoProxyResolverFactory>>::get(); +} + +ChromeMojoProxyResolverFactory::ChromeMojoProxyResolverFactory() + : factory_idle_timeout_(kUtilityProcessIdleTimeout) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); +} + +ChromeMojoProxyResolverFactory::~ChromeMojoProxyResolverFactory() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +std::unique_ptr<base::ScopedClosureRunner> +ChromeMojoProxyResolverFactory::CreateResolver( + const std::string& pac_script, + mojo::InterfaceRequest<proxy_resolver::mojom::ProxyResolver> req, + proxy_resolver::mojom::ProxyResolverFactoryRequestClientPtr client) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!resolver_factory_) + CreateFactory(); + + if (!resolver_factory_) { + // If factory creation failed, close |req|'s message pipe, which should + // cause a connection error. + req = nullptr; + return nullptr; + } + idle_timer_.Stop(); + num_proxy_resolvers_++; + resolver_factory_->CreateResolver(pac_script, std::move(req), + std::move(client)); + return base::MakeUnique<base::ScopedClosureRunner>( + base::Bind(&ChromeMojoProxyResolverFactory::OnResolverDestroyed, + base::Unretained(this))); +} + +void ChromeMojoProxyResolverFactory::SetFactoryIdleTimeoutForTests( + const base::TimeDelta& timeout) { + factory_idle_timeout_ = timeout; +} + +void ChromeMojoProxyResolverFactory::InitServiceManagerConnector() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (service_manager_connector_) + return; + + // The existing ServiceManagerConnection retrieved with + // ServiceManagerConnection::GetForProcess() lives on the UI thread, so we + // can't access it from here. We create our own connector so it can be used + // right away and will bind it on the UI thread. + service_manager::mojom::ConnectorRequest request; + service_manager_connector_ = service_manager::Connector::Create(&request); + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&BindConnectorOnUIThread, base::Passed(&request))); +} + +void ChromeMojoProxyResolverFactory::CreateFactory() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!resolver_factory_); + + InitServiceManagerConnector(); + + service_manager_connector_->BindInterface( + proxy_resolver::mojom::kProxyResolverServiceName, + mojo::MakeRequest(&resolver_factory_)); + + resolver_factory_.set_connection_error_handler(base::Bind( + &ChromeMojoProxyResolverFactory::DestroyFactory, base::Unretained(this))); +} + +void ChromeMojoProxyResolverFactory::DestroyFactory() { + resolver_factory_.reset(); +} + +void ChromeMojoProxyResolverFactory::OnResolverDestroyed() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK_GT(num_proxy_resolvers_, 0u); + if (--num_proxy_resolvers_ == 0) { + // When all proxy resolvers have been destroyed, the proxy resolver factory + // is no longer needed. However, new proxy resolvers may be created + // shortly after being destroyed (e.g. due to a network change). + // + // On desktop, where a utility process is used, if the utility process is + // shut down immediately, this would cause unnecessary process churn, so + // wait for an idle timeout before shutting down the proxy resolver utility + // process. + idle_timer_.Start(FROM_HERE, factory_idle_timeout_, this, + &ChromeMojoProxyResolverFactory::OnIdleTimeout); + } +} + +void ChromeMojoProxyResolverFactory::OnIdleTimeout() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK_EQ(num_proxy_resolvers_, 0u); + DestroyFactory(); +} diff --git a/chromium/chrome/browser/net/chrome_mojo_proxy_resolver_factory.h b/chromium/chrome/browser/net/chrome_mojo_proxy_resolver_factory.h new file mode 100644 index 00000000000..dd6d7702a98 --- /dev/null +++ b/chromium/chrome/browser/net/chrome_mojo_proxy_resolver_factory.h @@ -0,0 +1,84 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_NET_CHROME_MOJO_PROXY_RESOLVER_FACTORY_H_ +#define CHROME_BROWSER_NET_CHROME_MOJO_PROXY_RESOLVER_FACTORY_H_ + +#include <stddef.h> + +#include <memory> +#include <string> + +#include "base/macros.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "content/public/network/mojo_proxy_resolver_factory.h" +#include "services/service_manager/public/cpp/connector.h" +#include "services/service_manager/public/interfaces/connector.mojom.h" + +namespace base { +template <typename Type> +struct DefaultSingletonTraits; +} // namespace base + +// A factory used to create connections to Mojo proxy resolver services. On +// Android, the proxy resolvers will run in the browser process, and on other +// platforms, they'll all be run in the same utility process. +class ChromeMojoProxyResolverFactory + : public content::MojoProxyResolverFactory { + public: + static ChromeMojoProxyResolverFactory* GetInstance(); + + // Overridden from proxy_content::MojoProxyResolverFactory: + std::unique_ptr<base::ScopedClosureRunner> CreateResolver( + const std::string& pac_script, + mojo::InterfaceRequest<proxy_resolver::mojom::ProxyResolver> req, + proxy_resolver::mojom::ProxyResolverFactoryRequestClientPtr client) + override; + + // Used by tests to override the default timeout used when no resolver is + // connected before disconnecting the factory (and causing the service to + // stop). + void SetFactoryIdleTimeoutForTests(const base::TimeDelta& timeout); + + private: + friend struct base::DefaultSingletonTraits<ChromeMojoProxyResolverFactory>; + ChromeMojoProxyResolverFactory(); + ~ChromeMojoProxyResolverFactory() override; + + // Initializes the ServiceManager's connector if it hasn't been already. + void InitServiceManagerConnector(); + + // Creates the proxy resolver factory. On desktop, creates a new utility + // process before creating it out of process. On Android, creates it on the + // current thread. + void CreateFactory(); + + // Destroys |resolver_factory_|. + void DestroyFactory(); + + // Invoked each time a proxy resolver is destroyed. + void OnResolverDestroyed(); + + // Invoked once an idle timeout has elapsed after all proxy resolvers are + // destroyed. + void OnIdleTimeout(); + + proxy_resolver::mojom::ProxyResolverFactoryPtr resolver_factory_; + + std::unique_ptr<service_manager::Connector> service_manager_connector_; + + size_t num_proxy_resolvers_ = 0; + + base::OneShotTimer idle_timer_; + + base::ThreadChecker thread_checker_; + + base::TimeDelta factory_idle_timeout_; + + DISALLOW_COPY_AND_ASSIGN(ChromeMojoProxyResolverFactory); +}; + +#endif // CHROME_BROWSER_NET_CHROME_MOJO_PROXY_RESOLVER_FACTORY_H_ diff --git a/chromium/chrome/common/extensions/api/_permission_features.json b/chromium/chrome/common/extensions/api/_permission_features.json index 5dfafe651f4..2c43b4faf42 100644 --- a/chromium/chrome/common/extensions/api/_permission_features.json +++ b/chromium/chrome/common/extensions/api/_permission_features.json @@ -139,6 +139,7 @@ "B6C2EFAB3EC3BF6EF03701408B6B09A67B2D0069", // http://crbug.com/642141 "96FF2FFA5C9173C76D47184B3E86D267B37781DE", // http://crbug.com/642141 "0136FCB13DB29FD5CD442F56E59E53B61F1DF96F", // http://crbug.com/642141 + "9834387FDA1F66A1B5CA06CB442137B556F12F2A", // http://crbug.com/772346 "930F7D9989A5FBCDCCD7D85BB5C3B7006C24D91D" // http://crbug.com/782139 ] }, diff --git a/chromium/components/google/core/browser/google_util.cc b/chromium/components/google/core/browser/google_util.cc index 845e22a633c..2820e033dbf 100644 --- a/chromium/components/google/core/browser/google_util.cc +++ b/chromium/components/google/core/browser/google_util.cc @@ -142,11 +142,9 @@ bool IsGoogleSearchSubdomainUrl(const GURL& url) { bool HasGoogleSearchQueryParam(base::StringPiece str) { url::Component query(0, static_cast<int>(str.length())), key, value; while (url::ExtractQueryKeyValue(str.data(), &query, &key, &value)) { - if (value.is_nonempty()) { - base::StringPiece key_str = str.substr(key.begin, key.len); - if (key_str == "q" || key_str == "as_q") - return true; - } + base::StringPiece key_str = str.substr(key.begin, key.len); + if (key_str == "q" || key_str == "as_q") + return true; } return false; } diff --git a/chromium/components/google/core/browser/google_util_unittest.cc b/chromium/components/google/core/browser/google_util_unittest.cc index 46072bf5703..477d3ae9ded 100644 --- a/chromium/components/google/core/browser/google_util_unittest.cc +++ b/chromium/components/google/core/browser/google_util_unittest.cc @@ -147,6 +147,8 @@ TEST(GoogleUtilTest, GoodSearches) { "%s://www.google.co.uk/search?%s=something", // It's actually valid for both to have the query parameter. "%s://www.google.com/search?%s=something#q=other", + // Also valid to have an empty query parameter + "%s://www.google.com/search?%s=", // Queries with path "/webhp", "/" or "" need to have the query parameter // in the hash fragment. @@ -216,12 +218,6 @@ TEST(GoogleUtilTest, BadSearches) { } const std::string patterns_q[] = { - // Can't have an empty query parameter. - "%s://www.google.com/search?%s=", - "%s://www.google.com/search?name=bob&%s=", - "%s://www.google.com/webhp#%s=", - "%s://www.google.com/webhp#name=bob&%s=", - // Home page searches without a hash fragment query parameter are invalid. "%s://www.google.com/webhp?%s=something", "%s://www.google.com/webhp?%s=something#no=good", diff --git a/chromium/content/browser/BUILD.gn b/chromium/content/browser/BUILD.gn index ce5d712da7f..8b5b0277c6f 100644 --- a/chromium/content/browser/BUILD.gn +++ b/chromium/content/browser/BUILD.gn @@ -926,6 +926,8 @@ source_set("browser") { "leveldb_wrapper_impl.h", "loader/async_resource_handler.cc", "loader/async_resource_handler.h", + "loader/cross_site_document_resource_handler.cc", + "loader/cross_site_document_resource_handler.h", "loader/detachable_resource_handler.cc", "loader/detachable_resource_handler.h", "loader/downloaded_temp_file_impl.cc", diff --git a/chromium/content/browser/browser_main_loop.cc b/chromium/content/browser/browser_main_loop.cc index b0c9546fdc8..af8fed43857 100644 --- a/chromium/content/browser/browser_main_loop.cc +++ b/chromium/content/browser/browser_main_loop.cc @@ -877,6 +877,9 @@ int BrowserMainLoop::PreCreateThreads() { EVP_set_buggy_rsa_parser( base::FeatureList::IsEnabled(features::kBuggyRSAParser)); + // Record metrics about which site isolation flags have been turned on. + SiteIsolationPolicy::RecordSiteIsolationFlagUsage(); + return result_code_; } diff --git a/chromium/content/browser/frame_host/navigation_request.cc b/chromium/content/browser/frame_host/navigation_request.cc index 66cbc43ab67..a9d1909d17f 100644 --- a/chromium/content/browser/frame_host/navigation_request.cc +++ b/chromium/content/browser/frame_host/navigation_request.cc @@ -1006,14 +1006,25 @@ void NavigationRequest::OnStartChecksComplete( // Mark the fetch_start (Navigation Timing API). request_params_.navigation_timing.fetch_start = base::TimeTicks::Now(); + GURL base_url; +#if defined(OS_ANDROID) + // On Android, a base URL can be set for the frame. If this the case, it is + // the URL to use for cookies. + NavigationEntry* last_committed_entry = + frame_tree_node_->navigator()->GetController()->GetLastCommittedEntry(); + if (last_committed_entry) + base_url = last_committed_entry->GetBaseURLForDataURL(); +#endif + const GURL& top_document_url = + !base_url.is_empty() + ? base_url + : frame_tree_node_->frame_tree()->root()->current_url(); // TODO(mkwst): This is incorrect. It ought to use the definition from // 'Document::firstPartyForCookies()' in Blink, which walks the ancestor tree // and verifies that all origins are PSL-matches (and special-cases extension // URLs). const GURL& site_for_cookies = - frame_tree_node_->IsMainFrame() - ? common_params_.url - : frame_tree_node_->frame_tree()->root()->current_url(); + frame_tree_node_->IsMainFrame() ? common_params_.url : top_document_url; bool parent_is_main_frame = !frame_tree_node_->parent() ? false : frame_tree_node_->parent()->IsMainFrame(); diff --git a/chromium/content/browser/frame_host/navigator_impl.cc b/chromium/content/browser/frame_host/navigator_impl.cc index 6c4c6b7b0e8..ef506cebcde 100644 --- a/chromium/content/browser/frame_host/navigator_impl.cc +++ b/chromium/content/browser/frame_host/navigator_impl.cc @@ -1177,9 +1177,12 @@ void NavigatorImpl::RequestNavigation( // a Javascript URL should not interrupt a previous navigation. // Note: The scoped_request will be destroyed at the end of this function. if (dest_url.SchemeIs(url::kJavaScriptScheme)) { + // Don't call frame_tree_node->render_manager()->GetFrameHostForNavigation + // as that might clear the speculative RFH of an ongoing navigation. RenderFrameHostImpl* render_frame_host = - frame_tree_node->render_manager()->GetFrameHostForNavigation( - *scoped_request.get()); + frame_tree_node->current_frame_host(); + frame_tree_node->render_manager()->InitializeRenderFrameIfNecessary( + render_frame_host); render_frame_host->CommitNavigation( nullptr, // response nullptr, // body diff --git a/chromium/content/browser/frame_host/render_frame_host_manager.cc b/chromium/content/browser/frame_host/render_frame_host_manager.cc index 5541f127d36..f7b53cd5b94 100644 --- a/chromium/content/browser/frame_host/render_frame_host_manager.cc +++ b/chromium/content/browser/frame_host/render_frame_host_manager.cc @@ -1232,6 +1232,36 @@ RenderFrameHostManager::GetSiteInstanceForNavigation( return new_instance; } +void RenderFrameHostManager::InitializeRenderFrameIfNecessary( + RenderFrameHostImpl* render_frame_host) { + // TODO: this copies some logic inside GetFrameHostForNavigation, which also + // duplicates logic in Navigate. They should all use this method, but that + // involves slight reordering. + if (render_frame_host->IsRenderFrameLive()) + return; + + if (!ReinitializeRenderFrame(render_frame_host)) + return; + + if (render_frame_host != render_frame_host_.get()) + return; + + EnsureRenderFrameHostVisibilityConsistent(); + + // TODO: uncomment this when the method is shared. Not adding the call now + // to make merge to 63 easier. + // EnsureRenderFrameHostPageFocusConsistent(); + + // TODO(nasko): This is a very ugly hack. The Chrome extensions process + // manager still uses NotificationService and expects to see a + // RenderViewHost changed notification after WebContents and + // RenderFrameHostManager are completely initialized. This should be + // removed once the process manager moves away from NotificationService. + // See https://crbug.com/462682. + delegate_->NotifyMainFrameSwappedFromRenderManager( + nullptr, render_frame_host_->render_view_host()); +} + RenderFrameHostManager::SiteInstanceDescriptor RenderFrameHostManager::DetermineSiteInstanceForURL( const GURL& dest_url, diff --git a/chromium/content/browser/frame_host/render_frame_host_manager.h b/chromium/content/browser/frame_host/render_frame_host_manager.h index 67a3e97149a..9f33a6eb40c 100644 --- a/chromium/content/browser/frame_host/render_frame_host_manager.h +++ b/chromium/content/browser/frame_host/render_frame_host_manager.h @@ -515,6 +515,9 @@ class CONTENT_EXPORT RenderFrameHostManager scoped_refptr<SiteInstance> GetSiteInstanceForNavigationRequest( const NavigationRequest& navigation_request); + // Helper to initialize the RenderFrame if it's not initialized. + void InitializeRenderFrameIfNecessary(RenderFrameHostImpl* render_frame_host); + private: friend class NavigatorTestWithBrowserSideNavigation; friend class RenderFrameHostManagerTest; diff --git a/chromium/content/browser/loader/DEPS b/chromium/content/browser/loader/DEPS index 03317bf0cc9..194a58803b2 100644 --- a/chromium/content/browser/loader/DEPS +++ b/chromium/content/browser/loader/DEPS @@ -111,6 +111,7 @@ specific_include_rules = { "resource_dispatcher_host_impl\.(cc|h)": [ "-content", "+content/browser/loader/async_resource_handler.h", + "+content/browser/loader/cross_site_document_resource_handler.h", "+content/browser/loader/global_routing_id.h", "+content/browser/loader/loader_delegate.h", "+content/browser/loader/mojo_async_resource_handler.h", diff --git a/chromium/content/browser/loader/cross_site_document_blocking_browsertest.cc b/chromium/content/browser/loader/cross_site_document_blocking_browsertest.cc new file mode 100644 index 00000000000..101bc198bee --- /dev/null +++ b/chromium/content/browser/loader/cross_site_document_blocking_browsertest.cc @@ -0,0 +1,380 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/command_line.h" +#include "base/macros.h" +#include "base/strings/pattern.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/test/histogram_tester.h" +#include "base/test/scoped_feature_list.h" +#include "content/public/common/content_features.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/resource_type.h" +#include "content/public/test/browser_test_utils.h" +#include "content/public/test/content_browser_test.h" +#include "content/public/test/content_browser_test_utils.h" +#include "content/public/test/test_utils.h" +#include "content/shell/browser/shell.h" +#include "net/test/embedded_test_server/embedded_test_server.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace content { + +// These tests verify that the browser process blocks cross-site HTML, XML, +// JSON, and some plain text responses when they are not otherwise permitted +// (e.g., by CORS). This ensures that such responses never end up in the +// renderer process where they might be accessible via a bug. Careful attention +// is paid to allow other cross-site resources necessary for rendering, +// including cases that may be mislabeled as blocked MIME type. +// +// Many of these tests work by turning off the Same Origin Policy in the +// renderer process via --disable-web-security, and then trying to access the +// resource via a cross-origin XHR. If the response is blocked, the XHR should +// see an empty response body. +// +// Note that this BaseTest class does not specify an isolation mode via +// command-line flags. Most of the tests are in the --site-per-process subclass +// below. +class CrossSiteDocumentBlockingBaseTest : public ContentBrowserTest { + public: + CrossSiteDocumentBlockingBaseTest() {} + ~CrossSiteDocumentBlockingBaseTest() override {} + + void SetUpCommandLine(base::CommandLine* command_line) override { + // EmbeddedTestServer::InitializeAndListen() initializes its |base_url_| + // which is required below. This cannot invoke Start() however as that kicks + // off the "EmbeddedTestServer IO Thread" which then races with + // initialization in ContentBrowserTest::SetUp(), http://crbug.com/674545. + ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); + + // Add a host resolver rule to map all outgoing requests to the test server. + // This allows us to use "real" hostnames and standard ports in URLs (i.e., + // without having to inject the port number into all URLs), which we can use + // to create arbitrary SiteInstances. + command_line->AppendSwitchASCII( + switches::kHostResolverRules, + "MAP * " + embedded_test_server()->host_port_pair().ToString() + + ",EXCLUDE localhost"); + + // To test that the renderer process does not receive blocked documents, we + // disable the same origin policy to let it see cross-origin fetches if they + // are received. + command_line->AppendSwitch(switches::kDisableWebSecurity); + } + + void SetUpOnMainThread() override { + // Complete the manual Start() after ContentBrowserTest's own + // initialization, ref. comment on InitializeAndListen() above. + embedded_test_server()->StartAcceptingConnections(); + } + + // Ensure the correct histograms are incremented for blocking events. + // Assumes the resource type is XHR. + void InspectHistograms(const base::HistogramTester& histograms, + bool should_be_blocked, + bool should_be_sniffed, + const std::string& resource_name, + ResourceType resource_type) { + std::string bucket; + if (base::MatchPattern(resource_name, "*.html")) { + bucket = "HTML"; + } else if (base::MatchPattern(resource_name, "*.xml")) { + bucket = "XML"; + } else if (base::MatchPattern(resource_name, "*.json")) { + bucket = "JSON"; + } else if (base::MatchPattern(resource_name, "*.txt")) { + bucket = "Plain"; + } else { + EXPECT_FALSE(should_be_blocked); + bucket = "Other"; + } + + // Determine the appropriate histograms, including a start and end action + // (which are verified in unit tests), a read size if it was sniffed, and + // additional blocked metrics if it was blocked. + base::HistogramTester::CountsMap expected_counts; + std::string base = "SiteIsolation.XSD.Browser"; + expected_counts[base + ".Action"] = 2; + if (should_be_sniffed) + expected_counts[base + ".BytesReadForSniffing"] = 1; + if (should_be_blocked) { + expected_counts[base + ".Blocked"] = 1; + expected_counts[base + ".Blocked." + bucket] = 1; + } + + // Make sure that the expected metrics, and only those metrics, were + // incremented. + EXPECT_THAT(histograms.GetTotalCountsForPrefix("SiteIsolation.XSD.Browser"), + testing::ContainerEq(expected_counts)) + << "For resource_name=" << resource_name + << ", should_be_blocked=" << should_be_blocked; + + // Determine if the bucket for the resource type (XHR) was incremented. + if (should_be_blocked) { + EXPECT_THAT(histograms.GetAllSamples(base + ".Blocked"), + testing::ElementsAre(base::Bucket(resource_type, 1))) + << "The wrong Blocked bucket was incremented."; + EXPECT_THAT(histograms.GetAllSamples(base + ".Blocked." + bucket), + testing::ElementsAre(base::Bucket(resource_type, 1))) + << "The wrong Blocked bucket was incremented."; + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(CrossSiteDocumentBlockingBaseTest); +}; + +// Most tests here use --site-per-process, which enables document blocking +// everywhere. +class CrossSiteDocumentBlockingTest : public CrossSiteDocumentBlockingBaseTest { + public: + CrossSiteDocumentBlockingTest() {} + ~CrossSiteDocumentBlockingTest() override {} + + void SetUpCommandLine(base::CommandLine* command_line) override { + IsolateAllSitesForTesting(command_line); + CrossSiteDocumentBlockingBaseTest::SetUpCommandLine(command_line); + } + + private: + DISALLOW_COPY_AND_ASSIGN(CrossSiteDocumentBlockingTest); +}; + +IN_PROC_BROWSER_TEST_F(CrossSiteDocumentBlockingTest, BlockDocuments) { + // Load a page that issues illegal cross-site document requests to bar.com. + // The page uses XHR to request HTML/XML/JSON documents from bar.com, and + // inspects if any of them were successfully received. This test is only + // possible since we run the browser without the same origin policy, allowing + // it to see the response body if it makes it to the renderer (even if the + // renderer would normally block access to it). + GURL foo_url("http://foo.com/cross_site_document_request.html"); + EXPECT_TRUE(NavigateToURL(shell(), foo_url)); + + // The following are files under content/test/data/site_isolation. All + // should be disallowed for cross site XHR under the document blocking policy. + // valid.* - Correctly labeled HTML/XML/JSON files. + // *.txt - Plain text that sniffs as HTML, XML, or JSON. + // htmlN_dtd.* - Various HTML templates to test. + const char* blocked_resources[] = { + "valid.html", "valid.xml", "valid.json", "html.txt", + "xml.txt", "json.txt", "comment_valid.html", "html4_dtd.html", + "html4_dtd.txt", "html5_dtd.html", "html5_dtd.txt"}; + for (const char* resource : blocked_resources) { + SCOPED_TRACE(base::StringPrintf("... while testing page: %s", resource)); + base::HistogramTester histograms; + bool was_blocked; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + shell(), base::StringPrintf("sendRequest('%s');", resource), + &was_blocked)); + EXPECT_TRUE(was_blocked); + InspectHistograms(histograms, true /* should_be_blocked */, + true /* should_be_sniffed */, resource, + RESOURCE_TYPE_XHR); + } + + // These files should be disallowed without sniffing. + // nosniff.* - Won't sniff correctly, but blocked because of nosniff. + const char* nosniff_blocked_resources[] = {"nosniff.html", "nosniff.xml", + "nosniff.json", "nosniff.txt"}; + for (const char* resource : nosniff_blocked_resources) { + SCOPED_TRACE(base::StringPrintf("... while testing page: %s", resource)); + base::HistogramTester histograms; + bool was_blocked; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + shell(), base::StringPrintf("sendRequest('%s');", resource), + &was_blocked)); + EXPECT_TRUE(was_blocked); + InspectHistograms(histograms, true /* should_be_blocked */, + false /* should_be_sniffed */, resource, + RESOURCE_TYPE_XHR); + } + + // These files are allowed for XHR under the document blocking policy because + // the sniffing logic determines they are not actually documents. + // *js.* - JavaScript mislabeled as a document. + // jsonp.* - JSONP (i.e., script) mislabeled as a document. + // img.* - Contents that won't match the document label. + const char* sniff_allowed_resources[] = { + "js.html", "comment_js.html", "js.xml", "js.json", "js.txt", + "jsonp.html", "jsonp.xml", "jsonp.json", "jsonp.txt", "img.html", + "img.xml", "img.json", "img.txt"}; + for (const char* resource : sniff_allowed_resources) { + SCOPED_TRACE(base::StringPrintf("... while testing page: %s", resource)); + base::HistogramTester histograms; + bool was_blocked; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + shell(), base::StringPrintf("sendRequest('%s');", resource), + &was_blocked)); + EXPECT_FALSE(was_blocked); + InspectHistograms(histograms, false /* should_be_blocked */, + true /* should_be_sniffed */, resource, + RESOURCE_TYPE_XHR); + } + + // These files should be allowed for XHR under the document blocking policy. + // cors.* - Correctly labeled documents with valid CORS headers. + // valid.* - Correctly labeled responses of non-document types. + const char* allowed_resources[] = {"cors.html", "cors.xml", "cors.json", + "cors.txt", "valid.js"}; + for (const char* resource : allowed_resources) { + SCOPED_TRACE(base::StringPrintf("... while testing page: %s", resource)); + base::HistogramTester histograms; + bool was_blocked; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + shell(), base::StringPrintf("sendRequest('%s');", resource), + &was_blocked)); + EXPECT_FALSE(was_blocked); + InspectHistograms(histograms, false /* should_be_blocked */, + false /* should_be_sniffed */, resource, + RESOURCE_TYPE_XHR); + } +} + +// Verify that range requests disable the sniffing logic, so that attackers +// can't cause sniffing to fail to force a response to be allowed. This won't +// be a problem for script files mislabeled as HTML/XML/JSON/text (i.e., the +// reason for sniffing), since script tags won't send Range headers. +IN_PROC_BROWSER_TEST_F(CrossSiteDocumentBlockingTest, RangeRequest) { + GURL foo_url("http://foo.com/cross_site_document_request.html"); + EXPECT_TRUE(NavigateToURL(shell(), foo_url)); + + { + // Try to skip the first byte using a range request in an attempt to get the + // response to fail sniffing and be allowed through. It should still be + // blocked because sniffing is disabled. + base::HistogramTester histograms; + bool was_blocked; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + shell(), "sendRequest('valid.html', 'bytes=1-24');", &was_blocked)); + EXPECT_TRUE(was_blocked); + InspectHistograms(histograms, true /* should_be_blocked */, + false /* should_be_sniffed */, "valid.html", + RESOURCE_TYPE_XHR); + } + { + // Verify that a response which would have been allowed by MIME type anyway + // is still allowed for range requests. + base::HistogramTester histograms; + bool was_blocked; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + shell(), "sendRequest('valid.js', 'bytes=1-5');", &was_blocked)); + EXPECT_FALSE(was_blocked); + InspectHistograms(histograms, false /* should_be_blocked */, + false /* should_be_sniffed */, "valid.js", + RESOURCE_TYPE_XHR); + } + { + // Verify that a response which would have been allowed by CORS anyway is + // still allowed for range requests. + base::HistogramTester histograms; + bool was_blocked; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + shell(), "sendRequest('cors.json', 'bytes=2-7');", &was_blocked)); + EXPECT_FALSE(was_blocked); + InspectHistograms(histograms, false /* should_be_blocked */, + false /* should_be_sniffed */, "cors.json", + RESOURCE_TYPE_XHR); + } +} + +IN_PROC_BROWSER_TEST_F(CrossSiteDocumentBlockingTest, BlockForVariousTargets) { + // This webpage loads a cross-site HTML page in different targets such as + // <img>,<link>,<embed>, etc. Since the requested document is blocked, and one + // character string (' ') is returned instead, this tests that the renderer + // does not crash even when it receives a response body which is " ", whose + // length is different from what's described in "content-length" for such + // different targets. + + // TODO(nick): Split up these cases, and add positive assertions here about + // what actually happens in these various resource-block cases. + GURL foo("http://foo.com/cross_site_document_request_target.html"); + EXPECT_TRUE(NavigateToURL(shell(), foo)); + WaitForLoadStop(shell()->web_contents()); + + // TODO(creis): Wait for all the subresources to load and ensure renderer + // process is still alive. +} + +class CrossSiteDocumentBlockingKillSwitchTest + : public CrossSiteDocumentBlockingTest { + public: + CrossSiteDocumentBlockingKillSwitchTest() { + // Simulate flipping the kill switch. + scoped_feature_list_.InitAndDisableFeature( + features::kCrossSiteDocumentBlockingIfIsolating); + } + + ~CrossSiteDocumentBlockingKillSwitchTest() override {} + + private: + base::test::ScopedFeatureList scoped_feature_list_; + + DISALLOW_COPY_AND_ASSIGN(CrossSiteDocumentBlockingKillSwitchTest); +}; + +// After the kill switch is flipped, there should be no document blocking. +IN_PROC_BROWSER_TEST_F(CrossSiteDocumentBlockingKillSwitchTest, + NoBlockingWithKillSwitch) { + // Load a page that issues illegal cross-site document requests to bar.com. + GURL foo_url("http://foo.com/cross_site_document_request.html"); + EXPECT_TRUE(NavigateToURL(shell(), foo_url)); + + bool was_blocked; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + shell(), "sendRequest(\"valid.html\");", &was_blocked)); + EXPECT_FALSE(was_blocked); +} + +// Without any Site Isolation (in the base test class), there should be no +// document blocking. +IN_PROC_BROWSER_TEST_F(CrossSiteDocumentBlockingBaseTest, + DontBlockDocumentsByDefault) { + if (AreAllSitesIsolatedForTesting()) + return; + + // Load a page that issues illegal cross-site document requests to bar.com. + GURL foo_url("http://foo.com/cross_site_document_request.html"); + EXPECT_TRUE(NavigateToURL(shell(), foo_url)); + + bool was_blocked; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + shell(), "sendRequest(\"valid.html\");", &was_blocked)); + EXPECT_FALSE(was_blocked); +} + +// Test class to verify that documents are blocked for isolated origins as well. +class CrossSiteDocumentBlockingIsolatedOriginTest + : public CrossSiteDocumentBlockingBaseTest { + public: + CrossSiteDocumentBlockingIsolatedOriginTest() {} + ~CrossSiteDocumentBlockingIsolatedOriginTest() override {} + + void SetUpCommandLine(base::CommandLine* command_line) override { + command_line->AppendSwitchASCII(switches::kIsolateOrigins, + "http://bar.com"); + CrossSiteDocumentBlockingBaseTest::SetUpCommandLine(command_line); + } + + private: + DISALLOW_COPY_AND_ASSIGN(CrossSiteDocumentBlockingIsolatedOriginTest); +}; + +IN_PROC_BROWSER_TEST_F(CrossSiteDocumentBlockingIsolatedOriginTest, + BlockDocumentsFromIsolatedOrigin) { + if (AreAllSitesIsolatedForTesting()) + return; + + // Load a page that issues illegal cross-site document requests to the + // isolated origin. + GURL foo_url("http://foo.com/cross_site_document_request.html"); + EXPECT_TRUE(NavigateToURL(shell(), foo_url)); + + bool was_blocked; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + shell(), "sendRequest(\"valid.html\");", &was_blocked)); + EXPECT_TRUE(was_blocked); +} + +} // namespace content diff --git a/chromium/content/browser/loader/cross_site_document_resource_handler.cc b/chromium/content/browser/loader/cross_site_document_resource_handler.cc new file mode 100644 index 00000000000..475ead7f3af --- /dev/null +++ b/chromium/content/browser/loader/cross_site_document_resource_handler.cc @@ -0,0 +1,420 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/loader/cross_site_document_resource_handler.h" + +#include <string.h> +#include <memory> +#include <string> +#include <utility> + +#include "base/logging.h" +#include "base/metrics/histogram_macros.h" +#include "base/strings/string_piece.h" +#include "base/trace_event/trace_event.h" +#include "content/browser/child_process_security_policy_impl.h" +#include "content/browser/loader/detachable_resource_handler.h" +#include "content/browser/loader/resource_request_info_impl.h" +#include "content/browser/site_instance_impl.h" +#include "content/common/site_isolation_policy.h" +#include "content/public/browser/content_browser_client.h" +#include "content/public/browser/resource_context.h" +#include "content/public/common/content_client.h" +#include "net/base/io_buffer.h" +#include "net/base/mime_sniffer.h" +#include "net/url_request/url_request.h" + +namespace content { + +namespace { + +void LogCrossSiteDocumentAction( + CrossSiteDocumentResourceHandler::Action action) { + UMA_HISTOGRAM_ENUMERATION("SiteIsolation.XSD.Browser.Action", action, + CrossSiteDocumentResourceHandler::Action::kCount); +} + +} // namespace + +// ResourceController used in OnWillRead in cases that sniffing will happen. +// When invoked, it runs the corresponding method on the ResourceHandler. +class CrossSiteDocumentResourceHandler::OnWillReadController + : public ResourceController { + public: + // Keeps track of the addresses of the ResourceLoader's buffer and size, + // which will be populated by the downstream ResourceHandler by the time that + // Resume() is called. + explicit OnWillReadController( + CrossSiteDocumentResourceHandler* document_handler, + scoped_refptr<net::IOBuffer>* buf, + int* buf_size) + : document_handler_(document_handler), buf_(buf), buf_size_(buf_size) {} + + ~OnWillReadController() override {} + + // ResourceController implementation: + void Resume() override { + MarkAsUsed(); + + // Now that |buf_| has a buffer written into it by the downstream handler, + // set up sniffing in the CrossSiteDocumentResourceHandler. + document_handler_->ResumeOnWillRead(buf_, buf_size_); + } + + void Cancel() override { + MarkAsUsed(); + document_handler_->Cancel(); + } + + void CancelWithError(int error_code) override { + MarkAsUsed(); + document_handler_->CancelWithError(error_code); + } + + private: + void MarkAsUsed() { +#if DCHECK_IS_ON() + DCHECK(!used_); + used_ = true; +#endif + } + +#if DCHECK_IS_ON() + bool used_ = false; +#endif + + CrossSiteDocumentResourceHandler* document_handler_; + + // Address of the ResourceLoader's buffer, which will be populated by the + // downstream handler before Resume() is called. + scoped_refptr<net::IOBuffer>* buf_; + + // Address of the size of |buf_|, similarly populated downstream. + int* buf_size_; + + DISALLOW_COPY_AND_ASSIGN(OnWillReadController); +}; + +CrossSiteDocumentResourceHandler::CrossSiteDocumentResourceHandler( + std::unique_ptr<ResourceHandler> next_handler, + net::URLRequest* request, + bool is_nocors_plugin_request) + : LayeredResourceHandler(request, std::move(next_handler)), + is_nocors_plugin_request_(is_nocors_plugin_request) {} + +CrossSiteDocumentResourceHandler::~CrossSiteDocumentResourceHandler() {} + +void CrossSiteDocumentResourceHandler::OnResponseStarted( + ResourceResponse* response, + std::unique_ptr<ResourceController> controller) { + has_response_started_ = true; + LogCrossSiteDocumentAction( + CrossSiteDocumentResourceHandler::Action::kResponseStarted); + + should_block_based_on_headers_ = ShouldBlockBasedOnHeaders(response); + next_handler_->OnResponseStarted(response, std::move(controller)); +} + +void CrossSiteDocumentResourceHandler::OnWillRead( + scoped_refptr<net::IOBuffer>* buf, + int* buf_size, + std::unique_ptr<ResourceController> controller) { + DCHECK(has_response_started_); + + // On the next read attempt after the response was blocked, either cancel the + // rest of the request or allow it to proceed in a detached state. + if (blocked_read_completed_) { + DCHECK(should_block_based_on_headers_); + DCHECK(!allow_based_on_sniffing_); + const ResourceRequestInfoImpl* info = GetRequestInfo(); + if (info && info->detachable_handler()) { + // Ensure that prefetch, etc, continue to cache the response, without + // sending it to the renderer. + info->detachable_handler()->Detach(); + } else { + // If it's not detachable, cancel the rest of the request. + controller->Cancel(); + } + return; + } + + // If we intended to block the response and haven't yet decided to allow it + // due to sniffing, we will read some of the data to a local buffer to sniff + // it. Since the downstream handler may defer during the OnWillRead call + // below, the values of |buf| and |buf_size| may not be available right away. + // Instead, create an OnWillReadController to start the sniffing after the + // downstream handler has called Resume on it. + if (should_block_based_on_headers_ && !allow_based_on_sniffing_) { + HoldController(std::move(controller)); + controller = std::make_unique<OnWillReadController>(this, buf, buf_size); + } + + // Have the downstream handler(s) allocate the real buffer to use. + next_handler_->OnWillRead(buf, buf_size, std::move(controller)); +} + +void CrossSiteDocumentResourceHandler::ResumeOnWillRead( + scoped_refptr<net::IOBuffer>* buf, + int* buf_size) { + // We should only get here in cases that we intend to sniff the data, after + // downstream handler finishes its work from OnWillRead. + DCHECK(should_block_based_on_headers_); + DCHECK(!allow_based_on_sniffing_); + DCHECK(!blocked_read_completed_); + + // For most blocked responses, we need to sniff the data to confirm it looks + // like the claimed MIME type (to avoid blocking mislabeled JavaScript, + // JSONP, etc). Read this data into a separate buffer (not shared with the + // renderer), which we will only copy over if we decide to allow it through. + // This is only done when we suspect the response should be blocked. + // + // Make it as big as the downstream handler's buffer to make it easy to copy + // over in one operation. This will be large, since the MIME sniffing + // handler is downstream. Technically we could use a smaller buffer if + // |needs_sniffing_| is false, but there's no need for the extra complexity. + DCHECK_GE(*buf_size, net::kMaxBytesToSniff); + local_buffer_ = + base::MakeRefCounted<net::IOBuffer>(static_cast<size_t>(*buf_size)); + + // Store the next handler's buffer but don't read into it while sniffing, + // since we probably won't want to send the data to the renderer process. + next_handler_buffer_ = *buf; + next_handler_buffer_size_ = *buf_size; + *buf = local_buffer_; + + Resume(); +} + +void CrossSiteDocumentResourceHandler::OnReadCompleted( + int bytes_read, + std::unique_ptr<ResourceController> controller) { + DCHECK(has_response_started_); + DCHECK(!blocked_read_completed_); + + // If we intended to block the response and haven't sniffed yet, try to + // confirm that we should block it. If sniffing is needed, look at the local + // buffer and either report that zero bytes were read (to indicate the + // response is empty and complete), or copy the sniffed data to the next + // handler's buffer and resume the response without blocking. + if (should_block_based_on_headers_ && !allow_based_on_sniffing_) { + bool confirmed_blockable = false; + if (!needs_sniffing_) { + // TODO(creis): Also consider the MIME type confirmed if |bytes_read| is + // too small to do sniffing, or restructure to allow buffering enough. + // For now, responses with small initial reads may be allowed through. + confirmed_blockable = true; + } else { + // Sniff the data to see if it likely matches the MIME type that caused us + // to decide to block it. If it doesn't match, it may be JavaScript, + // JSONP, or another allowable data type and we should let it through. + // Record how many bytes were read to see how often it's too small. (This + // will typically be under 100,000.) + UMA_HISTOGRAM_COUNTS("SiteIsolation.XSD.Browser.BytesReadForSniffing", + bytes_read); + DCHECK_LE(bytes_read, next_handler_buffer_size_); + base::StringPiece data(local_buffer_->data(), bytes_read); + + // Confirm whether the data is HTML, XML, or JSON. + if (canonical_mime_type_ == CROSS_SITE_DOCUMENT_MIME_TYPE_HTML) { + confirmed_blockable = CrossSiteDocumentClassifier::SniffForHTML(data); + } else if (canonical_mime_type_ == CROSS_SITE_DOCUMENT_MIME_TYPE_XML) { + confirmed_blockable = CrossSiteDocumentClassifier::SniffForXML(data); + } else if (canonical_mime_type_ == CROSS_SITE_DOCUMENT_MIME_TYPE_JSON) { + confirmed_blockable = CrossSiteDocumentClassifier::SniffForJSON(data); + } else if (canonical_mime_type_ == CROSS_SITE_DOCUMENT_MIME_TYPE_PLAIN) { + // For responses labeled as plain text, only block them if the data + // sniffs as one of the formats we would block in the first place. + confirmed_blockable = CrossSiteDocumentClassifier::SniffForHTML(data) || + CrossSiteDocumentClassifier::SniffForXML(data) || + CrossSiteDocumentClassifier::SniffForJSON(data); + } + } + + if (confirmed_blockable) { + // Block the response and throw away the data. Report zero bytes read. + bytes_read = 0; + blocked_read_completed_ = true; + + // Log the blocking event. Inline the Serialize call to avoid it when + // tracing is disabled. + TRACE_EVENT2("navigation", + "CrossSiteDocumentResourceHandler::ShouldBlockResponse", + "initiator", + request()->initiator().has_value() + ? request()->initiator().value().Serialize() + : "null", + "url", request()->url().spec()); + + LogCrossSiteDocumentAction( + needs_sniffing_ + ? CrossSiteDocumentResourceHandler::Action::kBlockedAfterSniffing + : CrossSiteDocumentResourceHandler::Action:: + kBlockedWithoutSniffing); + ResourceType resource_type = GetRequestInfo()->GetResourceType(); + UMA_HISTOGRAM_ENUMERATION("SiteIsolation.XSD.Browser.Blocked", + resource_type, + content::RESOURCE_TYPE_LAST_TYPE); + switch (canonical_mime_type_) { + case CROSS_SITE_DOCUMENT_MIME_TYPE_HTML: + UMA_HISTOGRAM_ENUMERATION("SiteIsolation.XSD.Browser.Blocked.HTML", + resource_type, + content::RESOURCE_TYPE_LAST_TYPE); + break; + case CROSS_SITE_DOCUMENT_MIME_TYPE_XML: + UMA_HISTOGRAM_ENUMERATION("SiteIsolation.XSD.Browser.Blocked.XML", + resource_type, + content::RESOURCE_TYPE_LAST_TYPE); + break; + case CROSS_SITE_DOCUMENT_MIME_TYPE_JSON: + UMA_HISTOGRAM_ENUMERATION("SiteIsolation.XSD.Browser.Blocked.JSON", + resource_type, + content::RESOURCE_TYPE_LAST_TYPE); + break; + case CROSS_SITE_DOCUMENT_MIME_TYPE_PLAIN: + UMA_HISTOGRAM_ENUMERATION("SiteIsolation.XSD.Browser.Blocked.Plain", + resource_type, + content::RESOURCE_TYPE_LAST_TYPE); + break; + default: + NOTREACHED(); + } + } else { + // Allow the response through instead and proceed with reading more. + // Copy sniffed data into the next handler's buffer before proceeding. + // Note that the size of the two buffers is the same (see OnWillRead). + DCHECK_LE(bytes_read, next_handler_buffer_size_); + memcpy(next_handler_buffer_->data(), local_buffer_->data(), bytes_read); + allow_based_on_sniffing_ = true; + } + + // Clean up, whether we'll cancel or proceed from here. + local_buffer_ = nullptr; + next_handler_buffer_ = nullptr; + next_handler_buffer_size_ = 0; + } + + next_handler_->OnReadCompleted(bytes_read, std::move(controller)); +} + +void CrossSiteDocumentResourceHandler::OnResponseCompleted( + const net::URLRequestStatus& status, + std::unique_ptr<ResourceController> controller) { + if (blocked_read_completed_) { + // Report blocked responses as successful, rather than the cancellation + // from OnWillRead. + next_handler_->OnResponseCompleted(net::URLRequestStatus(), + std::move(controller)); + } else { + LogCrossSiteDocumentAction( + needs_sniffing_ + ? CrossSiteDocumentResourceHandler::Action::kAllowedAfterSniffing + : CrossSiteDocumentResourceHandler::Action:: + kAllowedWithoutSniffing); + + next_handler_->OnResponseCompleted(status, std::move(controller)); + } +} + +bool CrossSiteDocumentResourceHandler::ShouldBlockBasedOnHeaders( + ResourceResponse* response) { + // The checks in this method are ordered to rule out blocking in most cases as + // quickly as possible. Checks that are likely to lead to returning false or + // that are inexpensive should be near the top. + const GURL& url = request()->url(); + + // Check if the response's site needs to have its documents protected. By + // default, this will usually return false. + // TODO(creis): This check can go away once the logic here is made fully + // backward compatible and we can enforce it always, regardless of Site + // Isolation policy. + switch (SiteIsolationPolicy::IsCrossSiteDocumentBlockingEnabled()) { + case SiteIsolationPolicy::XSDB_ENABLED_UNCONDITIONALLY: + break; + case SiteIsolationPolicy::XSDB_ENABLED_IF_ISOLATED: + if (!SiteIsolationPolicy::UseDedicatedProcessesForAllSites() && + !ChildProcessSecurityPolicyImpl::GetInstance()->IsIsolatedOrigin( + url::Origin::Create(url))) { + return false; + } + break; + case SiteIsolationPolicy::XSDB_DISABLED: + return false; + } + + // Look up MIME type. If it doesn't claim to be a blockable type (i.e., HTML, + // XML, JSON, or plain text), don't block it. + canonical_mime_type_ = CrossSiteDocumentClassifier::GetCanonicalMimeType( + response->head.mime_type); + if (canonical_mime_type_ == CROSS_SITE_DOCUMENT_MIME_TYPE_OTHERS) + return false; + + // Treat a missing initiator as an empty origin to be safe, though we don't + // expect this to happen. Unfortunately, this requires a copy. + url::Origin initiator; + if (request()->initiator().has_value()) + initiator = request()->initiator().value(); + + // Don't block same-site documents. + if (CrossSiteDocumentClassifier::IsSameSite(initiator, url)) + return false; + + // Only block documents from HTTP(S) schemes. + if (!CrossSiteDocumentClassifier::IsBlockableScheme(url)) + return false; + + // Allow requests from file:// URLs for now. + // TODO(creis): Limit this to when the allow_universal_access_from_file_urls + // preference is set. See https://crbug.com/789781. + if (initiator.scheme() == url::kFileScheme) + return false; + + // Only block if this is a request made from a renderer process. + const ResourceRequestInfoImpl* info = GetRequestInfo(); + if (!info || info->GetChildID() == -1) + return false; + + // Give embedder a chance to skip document blocking for this response. + if (GetContentClient()->browser()->ShouldBypassDocumentBlocking( + initiator, url, info->GetResourceType())) { + return false; + } + + // Allow the response through if it has valid CORS headers. + std::string cors_header; + response->head.headers->GetNormalizedHeader("access-control-allow-origin", + &cors_header); + if (CrossSiteDocumentClassifier::IsValidCorsHeaderSet(initiator, url, + cors_header)) { + return false; + } + + // Don't block plugin requests with universal access (e.g., Flash). Such + // requests are made without CORS, and thus dont have an Origin request + // header. Other plugin requests (e.g., NaCl) are made using CORS and have an + // Origin request header. If they fail the CORS check above, they should be + // blocked. + if (info->GetResourceType() == RESOURCE_TYPE_PLUGIN_RESOURCE && + is_nocors_plugin_request_) { + return false; + } + + // We intend to block the response at this point. However, we will usually + // sniff the contents to confirm the MIME type, to avoid blocking incorrectly + // labeled JavaScript, JSONP, etc files. + // + // Note: only sniff if there isn't a nosniff header, and if it is not a range + // request. Range requests would let an attacker bypass blocking by + // requesting a range that fails to sniff as a protected type. + std::string nosniff_header; + response->head.headers->GetNormalizedHeader("x-content-type-options", + &nosniff_header); + std::string range_header; + response->head.headers->GetNormalizedHeader("content-range", &range_header); + needs_sniffing_ = !base::LowerCaseEqualsASCII(nosniff_header, "nosniff") && + range_header.empty(); + + return true; +} + +} // namespace content diff --git a/chromium/content/browser/loader/cross_site_document_resource_handler.h b/chromium/content/browser/loader/cross_site_document_resource_handler.h new file mode 100644 index 00000000000..4dd1d4a89b5 --- /dev/null +++ b/chromium/content/browser/loader/cross_site_document_resource_handler.h @@ -0,0 +1,156 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_LOADER_CROSS_SITE_DOCUMENT_RESOURCE_HANDLER_H_ +#define CONTENT_BROWSER_LOADER_CROSS_SITE_DOCUMENT_RESOURCE_HANDLER_H_ + +#include <memory> + +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "content/browser/loader/layered_resource_handler.h" +#include "content/common/cross_site_document_classifier.h" +#include "content/public/common/resource_type.h" + +namespace net { +class URLRequest; +} // namespace net + +namespace content { + +// A ResourceHandler that prevents the renderer process from receiving network +// responses that contain cross-site documents (HTML, XML, some plain text) or +// similar data that should be opaque (JSON), with appropriate exceptions to +// preserve compatibility. Other cross-site resources such as scripts, images, +// stylesheets, etc are still allowed. +// +// This handler is not used for navigations, which create a new security context +// based on the origin of the response. It currently only protects documents +// from sites that require dedicated renderer processes, though it could be +// expanded to apply to all sites. +// +// When a response is blocked, the renderer is sent an empty response body +// instead of seeing a failed request. A failed request would change page- +// visible behavior (e.g., for a blocked XHR). An empty response can generally +// be consumed by the renderer without noticing the difference. +// +// For more details, see: +// http://chromium.org/developers/design-documents/blocking-cross-site-documents +class CONTENT_EXPORT CrossSiteDocumentResourceHandler + : public LayeredResourceHandler { + public: + // This enum backs a histogram. Update enums.xml if you make any updates, and + // put new entries before |kCount|. + enum class Action { + // Logged at OnResponseStarted. + kResponseStarted, + + // Logged when a response is blocked without requiring sniffing. + kBlockedWithoutSniffing, + + // Logged when a response is blocked as a result of sniffing the content. + kBlockedAfterSniffing, + + // Logged when a response is allowed without requiring sniffing. + kAllowedWithoutSniffing, + + // Logged when a response is allowed as a result of sniffing the content. + kAllowedAfterSniffing, + + kCount + }; + + CrossSiteDocumentResourceHandler( + std::unique_ptr<ResourceHandler> next_handler, + net::URLRequest* request, + bool is_nocors_plugin_request); + ~CrossSiteDocumentResourceHandler() override; + + // LayeredResourceHandler overrides: + void OnResponseStarted( + ResourceResponse* response, + std::unique_ptr<ResourceController> controller) override; + void OnWillRead(scoped_refptr<net::IOBuffer>* buf, + int* buf_size, + std::unique_ptr<ResourceController> controller) override; + void OnReadCompleted(int bytes_read, + std::unique_ptr<ResourceController> controller) override; + void OnResponseCompleted( + const net::URLRequestStatus& status, + std::unique_ptr<ResourceController> controller) override; + + private: + FRIEND_TEST_ALL_PREFIXES(CrossSiteDocumentResourceHandlerTest, + ResponseBlocking); + FRIEND_TEST_ALL_PREFIXES(CrossSiteDocumentResourceHandlerTest, + OnWillReadDefer); + + // ResourceController that manages the read buffer if a downstream handler + // defers during OnWillRead. + class OnWillReadController; + + // Computes whether this response contains a cross-site document that needs to + // be blocked from the renderer process. This is a first approximation based + // on the headers, and may be revised after some of the data is sniffed. + bool ShouldBlockBasedOnHeaders(ResourceResponse* response); + + // Once the downstream handler has allocated the buffer for OnWillRead + // (possibly after deferring), this sets up sniffing into a local buffer. + // Called by the OnWillReadController. + void ResumeOnWillRead(scoped_refptr<net::IOBuffer>* buf, int* buf_size); + + // A local buffer for sniffing content and using for throwaway reads. + // This is not shared with the renderer process. + scoped_refptr<net::IOBuffer> local_buffer_; + + // The buffer allocated by the next ResourceHandler for reads, which is used + // if sniffing determines that we should proceed with the response. + scoped_refptr<net::IOBuffer> next_handler_buffer_; + + // The size of |next_handler_buffer_|. + int next_handler_buffer_size_ = 0; + + // A canonicalization of the specified MIME type, to determine if blocking the + // response is needed, as well as which type of sniffing to perform. + CrossSiteDocumentMimeType canonical_mime_type_ = + CROSS_SITE_DOCUMENT_MIME_TYPE_OTHERS; + + // Indicates whether this request was made by a plugin and was not using CORS. + // Such requests are exempt from blocking, while other plugin requests must be + // blocked if the CORS check fails. + // TODO(creis, nick): Replace this with a plugin process ID check to see if + // the plugin has universal access. + bool is_nocors_plugin_request_; + + // Tracks whether OnResponseStarted has been called, to ensure that it happens + // before OnWillRead and OnReadCompleted. + bool has_response_started_ = false; + + // Whether this response is a cross-site document that should be blocked, + // pending the outcome of sniffing the content. Set in OnResponseStarted and + // should only be read afterwards. + bool should_block_based_on_headers_ = false; + + // Whether the response data should be sniffed before blocking it, to avoid + // blocking mislabeled responses (e.g., JSONP labeled as HTML). This is + // usually true when |should_block_based_on_headers_| is set, unless there is + // a nosniff header or range request. + bool needs_sniffing_ = false; + + // Whether this response will be allowed through despite being flagged for + // blocking (via |should_block_based_on_headers_), because sniffing determined + // it was incorrectly labeled and might be needed for compatibility (e.g., + // in case it is Javascript). + bool allow_based_on_sniffing_ = false; + + // Whether the next ResourceHandler has already been told that the read has + // completed, and thus it is safe to cancel or detach on the next read. + bool blocked_read_completed_ = false; + + DISALLOW_COPY_AND_ASSIGN(CrossSiteDocumentResourceHandler); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_LOADER_CROSS_SITE_DOCUMENT_RESOURCE_HANDLER_H_ diff --git a/chromium/content/browser/loader/cross_site_document_resource_handler_unittest.cc b/chromium/content/browser/loader/cross_site_document_resource_handler_unittest.cc new file mode 100644 index 00000000000..ff3f1423c00 --- /dev/null +++ b/chromium/content/browser/loader/cross_site_document_resource_handler_unittest.cc @@ -0,0 +1,836 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/loader/cross_site_document_resource_handler.h" + +#include <stdint.h> + +#include <memory> +#include <string> +#include <utility> + +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/weak_ptr.h" +#include "base/single_thread_task_runner.h" +#include "base/test/histogram_tester.h" +#include "base/threading/thread_task_runner_handle.h" +#include "content/browser/loader/mock_resource_loader.h" +#include "content/browser/loader/resource_controller.h" +#include "content/browser/loader/test_resource_handler.h" +#include "content/public/browser/resource_request_info.h" +#include "content/public/common/resource_response.h" +#include "content/public/common/resource_type.h" +#include "content/public/common/webplugininfo.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "content/public/test/test_utils.h" +#include "net/base/net_errors.h" +#include "net/traffic_annotation/network_traffic_annotation_test_helper.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_status.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace content { + +namespace { + +enum class OriginHeader { kOmit, kInclude }; + +enum class AccessControlAllowOriginHeader { + kOmit, + kAllowAny, + kAllowNull, + kAllowInitiatorOrigin, + kAllowExampleDotCom +}; + +enum class Verdict { + kAllowWithoutSniffing, + kBlockWithoutSniffing, + kAllowAfterSniffing, + kBlockAfterSniffing +}; + +// This struct is used to describe each test case in this file. It's passed as +// a test parameter to each TEST_P test. +struct TestScenario { + // Attributes to make test failure messages useful. + const char* description; + int source_line; + + // Attributes of the HTTP Request. + const char* target_url; + ResourceType resource_type; + const char* initiator_origin; + OriginHeader cors_request; + + // Attributes of the HTTP response. + const char* response_mime_type; + CrossSiteDocumentMimeType canonical_mime_type; + bool include_no_sniff_header; + AccessControlAllowOriginHeader cors_response; + const char* first_chunk; + + // Expected result. + Verdict verdict; +}; + +// Stream operator to let GetParam() print a useful result if any tests fail. +::std::ostream& operator<<(::std::ostream& os, const TestScenario& scenario) { + std::string cors_response; + switch (scenario.cors_response) { + case AccessControlAllowOriginHeader::kOmit: + cors_response = "AccessControlAllowOriginHeader::kOmit"; + break; + case AccessControlAllowOriginHeader::kAllowAny: + cors_response = "AccessControlAllowOriginHeader::kAllowAny"; + break; + case AccessControlAllowOriginHeader::kAllowNull: + cors_response = "AccessControlAllowOriginHeader::kAllowNull"; + break; + case AccessControlAllowOriginHeader::kAllowInitiatorOrigin: + cors_response = "AccessControlAllowOriginHeader::kAllowInitiatorOrigin"; + break; + case AccessControlAllowOriginHeader::kAllowExampleDotCom: + cors_response = "AccessControlAllowOriginHeader::kAllowExampleDotCom"; + break; + default: + NOTREACHED(); + } + + std::string verdict; + switch (scenario.verdict) { + case Verdict::kAllowWithoutSniffing: + verdict = "Verdict::kAllowWithoutSniffing"; + break; + case Verdict::kBlockWithoutSniffing: + verdict = "Verdict::kBlockWithoutSniffing"; + break; + case Verdict::kAllowAfterSniffing: + verdict = "Verdict::kAllowAfterSniffing"; + break; + case Verdict::kBlockAfterSniffing: + verdict = "Verdict::kBlockAfterSniffing"; + break; + default: + NOTREACHED(); + } + + return os << "\n description = " << scenario.description + << "\n target_url = " << scenario.target_url + << "\n resource_type = " << scenario.resource_type + << "\n initiator_origin = " << scenario.initiator_origin + << "\n cors_request = " + << (scenario.cors_request == OriginHeader::kOmit + ? "OriginHeader::kOmit" + : "OriginHeader::kInclude") + << "\n response_mime_type = " << scenario.response_mime_type + << "\n canonical_mime_type = " << scenario.canonical_mime_type + << "\n include_no_sniff = " + << (scenario.include_no_sniff_header ? "true" : "false") + << "\n cors_response = " << cors_response + << "\n first_chunk = " << scenario.first_chunk + << "\n verdict = " << verdict; +} + +// A set of test cases that verify CrossSiteDocumentResourceHandler correctly +// classifies network responses as allowed or blocked. These TestScenarios are +// passed to the TEST_P tests below as test parameters. +const TestScenario kScenarios[] = { + // Allowed responses: + { + "Allowed: Same-site XHR to HTML", __LINE__, + "http://www.a.com/resource.html", // target_url + RESOURCE_TYPE_XHR, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kOmit, // cors_request + "text/html", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "<html><head>this should sniff as HTML", // first_chunk + Verdict::kAllowWithoutSniffing, // verdict + }, + { + "Allowed: Cross-site script", __LINE__, + "http://www.b.com/resource.html", // target_url + RESOURCE_TYPE_SCRIPT, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kOmit, // cors_request + "application/javascript", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_OTHERS, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "var x=3;", // first_chunk + Verdict::kAllowWithoutSniffing, // verdict + }, + { + "Allowed: Cross-site XHR to HTML with CORS for origin", __LINE__, + "http://www.b.com/resource.html", // target_url + RESOURCE_TYPE_XHR, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kInclude, // cors_request + "text/html", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kAllowInitiatorOrigin, // cors_response + "<html><head>this should sniff as HTML", // first_chunk + Verdict::kAllowWithoutSniffing, // verdict + }, + { + "Allowed: Cross-site XHR to XML with CORS for any", __LINE__, + "http://www.b.com/resource.html", // target_url + RESOURCE_TYPE_XHR, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kInclude, // cors_request + "application/rss+xml", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_XML, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kAllowAny, // cors_response + "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>", // first_chunk + Verdict::kAllowWithoutSniffing, // verdict + }, + { + "Allowed: Cross-site XHR to JSON with CORS for null", __LINE__, + "http://www.b.com/resource.html", // target_url + RESOURCE_TYPE_XHR, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kInclude, // cors_request + "text/json", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_JSON, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kAllowNull, // cors_response + "{\"x\" : 3}", // first_chunk + Verdict::kAllowWithoutSniffing, // verdict + }, + { + "Allowed: Cross-site XHR to HTML over FTP", __LINE__, + "ftp://www.b.com/resource.html", // target_url + RESOURCE_TYPE_XHR, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kOmit, // cors_request + "text/html", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "<html><head>this should sniff as HTML", // first_chunk + Verdict::kAllowWithoutSniffing, // verdict + }, + { + "Allowed: Cross-site XHR to HTML from file://", __LINE__, + "file:///foo/resource.html", // target_url + RESOURCE_TYPE_XHR, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kOmit, // cors_request + "text/html", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "<html><head>this should sniff as HTML", // first_chunk + Verdict::kAllowWithoutSniffing, // verdict + }, + { + "Allowed: Cross-site fetch HTML from Flash without CORS", __LINE__, + "http://www.b.com/plugin.html", // target_url + RESOURCE_TYPE_PLUGIN_RESOURCE, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kOmit, // cors_request + "text/html", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "<html><head>this should sniff as HTML", // first_chunk + Verdict::kAllowWithoutSniffing, // verdict + }, + { + "Allowed: Cross-site fetch HTML from NaCl with CORS response", __LINE__, + "http://www.b.com/plugin.html", // target_url + RESOURCE_TYPE_PLUGIN_RESOURCE, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kInclude, // cors_request + "text/html", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kAllowInitiatorOrigin, // cors_response + "<html><head>this should sniff as HTML", // first_chunk + Verdict::kAllowWithoutSniffing, // verdict + }, + + // Allowed responses due to sniffing: + { + "Allowed: Cross-site script to JSONP labeled as HTML", __LINE__, + "http://www.b.com/resource.html", // target_url + RESOURCE_TYPE_SCRIPT, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kOmit, // cors_request + "text/html", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "foo({\"x\" : 3})", // first_chunk + Verdict::kAllowAfterSniffing, // verdict + }, + { + "Allowed: Cross-site script to JavaScript labeled as text", __LINE__, + "http://www.b.com/resource.html", // target_url + RESOURCE_TYPE_SCRIPT, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kOmit, // cors_request + "text/plain", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_PLAIN, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "var x = 3;", // first_chunk + Verdict::kAllowAfterSniffing, // verdict + }, + { + "Allowed: Cross-site XHR to nonsense labeled as XML", __LINE__, + "http://www.b.com/resource.html", // target_url + RESOURCE_TYPE_XHR, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kOmit, // cors_request + "application/xml", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_XML, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "Won't sniff as XML", // first_chunk + Verdict::kAllowAfterSniffing, // verdict + }, + { + "Allowed: Cross-site XHR to nonsense labeled as JSON", __LINE__, + "http://www.b.com/resource.html", // target_url + RESOURCE_TYPE_XHR, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kOmit, // cors_request + "text/x-json", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_JSON, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "Won't sniff as JSON", // first_chunk + Verdict::kAllowAfterSniffing, // verdict + }, + // TODO(creis): We should block the following response since there isn't + // enough data to confirm it as HTML by sniffing. + { + "Allowed for now: Cross-site XHR to HTML with small first read", + __LINE__, + "http://www.b.com/resource.html", // target_url + RESOURCE_TYPE_XHR, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kOmit, // cors_request + "text/html", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "<htm", // first_chunk + Verdict::kAllowAfterSniffing, // verdict + }, + + // Blocked responses: + { + "Blocked: Cross-site XHR to HTML without CORS", __LINE__, + "http://www.b.com/resource.html", // target_url + RESOURCE_TYPE_XHR, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kOmit, // cors_request + "text/html", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "<html><head>this should sniff as HTML", // first_chunk + Verdict::kBlockAfterSniffing, // verdict + }, + { + "Blocked: Cross-site XHR to XML without CORS", __LINE__, + "http://www.b.com/resource.html", // target_url + RESOURCE_TYPE_XHR, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kOmit, // cors_request + "application/xml", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_XML, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>", // first_chunk + Verdict::kBlockAfterSniffing, // verdict + }, + { + "Blocked: Cross-site XHR to JSON without CORS", __LINE__, + "http://www.b.com/resource.html", // target_url + RESOURCE_TYPE_XHR, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kOmit, // cors_request + "application/json", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_JSON, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "{\"x\" : 3}", // first_chunk + Verdict::kBlockAfterSniffing, // verdict + }, + { + "Blocked: Cross-site XHR to HTML labeled as text without CORS", + __LINE__, + "http://www.b.com/resource.html", // target_url + RESOURCE_TYPE_XHR, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kOmit, // cors_request + "text/plain", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_PLAIN, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "<html><head>this should sniff as HTML", // first_chunk + Verdict::kBlockAfterSniffing, // verdict + }, + { + "Blocked: Cross-site XHR to nosniff HTML without CORS", __LINE__, + "http://www.b.com/resource.html", // target_url + RESOURCE_TYPE_XHR, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kOmit, // cors_request + "text/html", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type + true, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "<html><head>this should sniff as HTML", // first_chunk + Verdict::kBlockWithoutSniffing, // verdict + }, + { + "Blocked: Cross-site XHR to nosniff response without CORS", __LINE__, + "http://www.b.com/resource.html", // target_url + RESOURCE_TYPE_XHR, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kOmit, // cors_request + "text/html", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type + true, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "Wouldn't sniff as HTML", // first_chunk + Verdict::kBlockWithoutSniffing, // verdict + }, + { + "Blocked: Cross-site <script> inclusion of HTML w/ DTD without CORS", + __LINE__, + "http://www.b.com/resource.html", // target_url + RESOURCE_TYPE_SCRIPT, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kOmit, // cors_request + "text/html", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "<!doctype html><html itemscope=\"\" " + "itemtype=\"http://schema.org/SearchResultsPage\" " + "lang=\"en\"><head>", // first_chunk + Verdict::kBlockAfterSniffing, // verdict + }, + { + "Blocked: Cross-site XHR to HTML with wrong CORS", __LINE__, + "http://www.b.com/resource.html", // target_url + RESOURCE_TYPE_XHR, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kInclude, // cors_request + "text/html", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kAllowExampleDotCom, // cors_response + "<html><head>this should sniff as HTML", // first_chunk + Verdict::kBlockAfterSniffing, // verdict + }, + { + "Blocked: Cross-site fetch HTML from NaCl without CORS response", + __LINE__, + "http://www.b.com/plugin.html", // target_url + RESOURCE_TYPE_PLUGIN_RESOURCE, // resource_type + "http://www.a.com/", // initiator_origin + OriginHeader::kInclude, // cors_request + "text/html", // response_mime_type + CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type + false, // include_no_sniff_header + AccessControlAllowOriginHeader::kOmit, // cors_response + "<html><head>this should sniff as HTML", // first_chunk + Verdict::kBlockAfterSniffing, // verdict + }, +}; + +} // namespace + +// Tests that verify CrossSiteDocumentResourceHandler correctly classifies +// network responses as allowed or blocked, and ensures that empty responses are +// sent for the blocked cases. +// +// The various test cases are passed as a list of TestScenario structs. +class CrossSiteDocumentResourceHandlerTest + : public testing::Test, + public testing::WithParamInterface<TestScenario> { + public: + CrossSiteDocumentResourceHandlerTest() + : stream_sink_status_( + net::URLRequestStatus::FromError(net::ERR_IO_PENDING)) { + IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess()); + } + + // Sets up the request, downstream ResourceHandler, test ResourceHandler, and + // ResourceLoader. + void Initialize(const std::string& target_url, + ResourceType resource_type, + const std::string& initiator_origin, + OriginHeader cors_request) { + stream_sink_status_ = net::URLRequestStatus::FromError(net::ERR_IO_PENDING); + + // Initialize |request_| from the parameters. + request_ = context_.CreateRequest(GURL(target_url), net::DEFAULT_PRIORITY, + &delegate_, TRAFFIC_ANNOTATION_FOR_TESTS); + ResourceRequestInfo::AllocateForTesting(request_.get(), resource_type, + nullptr, // context + 3, // render_process_id + 2, // render_view_id + 1, // render_frame_id + true, // is_main_frame + true, // allow_download + true, // is_async + PREVIEWS_OFF); // previews_state + request_->set_initiator(url::Origin::Create(GURL(initiator_origin))); + + // Create a sink handler to capture results. + auto stream_sink = std::make_unique<TestResourceHandler>( + &stream_sink_status_, &stream_sink_body_); + stream_sink_ = stream_sink->GetWeakPtr(); + + // Create the CrossSiteDocumentResourceHandler. + bool is_nocors_plugin_request = + resource_type == RESOURCE_TYPE_PLUGIN_RESOURCE && + cors_request == OriginHeader::kOmit; + document_blocker_ = std::make_unique<CrossSiteDocumentResourceHandler>( + std::move(stream_sink), request_.get(), is_nocors_plugin_request); + + // Create a mock loader to drive the CrossSiteDocumentResourceHandler. + mock_loader_ = + std::make_unique<MockResourceLoader>(document_blocker_.get()); + } + + // Returns a ResourceResponse that matches the TestScenario's parameters. + scoped_refptr<ResourceResponse> CreateResponse( + const char* response_mime_type, + bool include_no_sniff_header, + AccessControlAllowOriginHeader cors_response, + const char* initiator_origin) { + scoped_refptr<ResourceResponse> response = + base::MakeRefCounted<ResourceResponse>(); + response->head.mime_type = response_mime_type; + scoped_refptr<net::HttpResponseHeaders> response_headers = + base::MakeRefCounted<net::HttpResponseHeaders>(""); + + // No sniff header. + if (include_no_sniff_header) + response_headers->AddHeader("X-Content-Type-Options: nosniff"); + + // CORS header. + if (cors_response == AccessControlAllowOriginHeader::kAllowAny) { + response_headers->AddHeader("Access-Control-Allow-Origin: *"); + } else if (cors_response == + AccessControlAllowOriginHeader::kAllowInitiatorOrigin) { + response_headers->AddHeader(base::StringPrintf( + "Access-Control-Allow-Origin: %s", initiator_origin)); + } else if (cors_response == AccessControlAllowOriginHeader::kAllowNull) { + response_headers->AddHeader("Access-Control-Allow-Origin: null"); + } else if (cors_response == + AccessControlAllowOriginHeader::kAllowExampleDotCom) { + response_headers->AddHeader( + "Access-Control-Allow-Origin: http://example.com"); + } + + response->head.headers = response_headers; + + return response; + } + + protected: + TestBrowserThreadBundle thread_bundle_; + net::TestURLRequestContext context_; + net::TestDelegate delegate_; + std::unique_ptr<net::URLRequest> request_; + + // |stream_sink_| is the handler that's immediately after |document_blocker_| + // in the ResourceHandler chain; it records the values passed to it into + // |stream_sink_status_| and |stream_sink_body_|, which our tests assert + // against. + // + // |stream_sink_| is owned by |document_blocker_|, but we retain a reference + // to it. + base::WeakPtr<TestResourceHandler> stream_sink_; + net::URLRequestStatus stream_sink_status_; + std::string stream_sink_body_; + + // |document_blocker_| is the CrossSiteDocuemntResourceHandler instance under + // test. + std::unique_ptr<CrossSiteDocumentResourceHandler> document_blocker_; + + // |mock_loader_| is the mock loader used to drive |document_blocker_|. + std::unique_ptr<MockResourceLoader> mock_loader_; +}; + +// Runs a particular TestScenario (passed as the test's parameter) through the +// ResourceLoader and CrossSiteDocumentResourceHandler, verifying that the +// response is correctly allowed or blocked based on the scenario. +TEST_P(CrossSiteDocumentResourceHandlerTest, ResponseBlocking) { + const TestScenario scenario = GetParam(); + SCOPED_TRACE(testing::Message() + << "\nScenario at " << __FILE__ << ":" << scenario.source_line); + + Initialize(scenario.target_url, scenario.resource_type, + scenario.initiator_origin, scenario.cors_request); + base::HistogramTester histograms; + + ASSERT_EQ(MockResourceLoader::Status::IDLE, + mock_loader_->OnWillStart(request_->url())); + + // Set up response based on scenario. + scoped_refptr<ResourceResponse> response = CreateResponse( + scenario.response_mime_type, scenario.include_no_sniff_header, + scenario.cors_response, scenario.initiator_origin); + + ASSERT_EQ(MockResourceLoader::Status::IDLE, + mock_loader_->OnResponseStarted(response)); + + // Verify MIME type was classified correctly. + EXPECT_EQ(scenario.canonical_mime_type, + document_blocker_->canonical_mime_type_); + + // Verify that we correctly decide whether to block based on headers. Note + // that this includes cases that will later be allowed after sniffing. + bool expected_to_block_based_on_headers = + scenario.verdict == Verdict::kBlockWithoutSniffing || + scenario.verdict == Verdict::kBlockAfterSniffing || + scenario.verdict == Verdict::kAllowAfterSniffing; + EXPECT_EQ(expected_to_block_based_on_headers, + document_blocker_->should_block_based_on_headers_); + + // Verify that we will sniff content into a different buffer if sniffing is + // needed. Note that the different buffer is used even for blocking cases + // where no sniffing is needed, to avoid complexity in the handler. The + // handler doesn't look at the data in that case, but there's no way to verify + // it in the test. + bool expected_to_sniff = scenario.verdict == Verdict::kAllowAfterSniffing || + scenario.verdict == Verdict::kBlockAfterSniffing; + EXPECT_EQ(expected_to_sniff, document_blocker_->needs_sniffing_); + + // Tell the ResourceHandlers to allocate the buffer for reading. In this + // test, the buffer will be allocated immediately by the downstream handler + // and possibly replaced by a different buffer for sniffing. + ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead()); + EXPECT_EQ(1, stream_sink_->on_will_read_called()); + EXPECT_NE(nullptr, mock_loader_->io_buffer()); + if (expected_to_sniff || scenario.verdict == Verdict::kBlockWithoutSniffing) { + EXPECT_EQ(mock_loader_->io_buffer(), document_blocker_->local_buffer_.get()) + << "Should have used a different IOBuffer for sniffing"; + } else { + EXPECT_EQ(mock_loader_->io_buffer(), stream_sink_->buffer()) + << "Should have used original IOBuffer when sniffing not needed"; + } + + // Deliver the first chunk of the response body; this allows sniffing to + // occur. + ASSERT_EQ(MockResourceLoader::Status::IDLE, + mock_loader_->OnReadCompleted(scenario.first_chunk)); + EXPECT_EQ(nullptr, mock_loader_->io_buffer()); + + // Verify that the response is blocked or allowed as expected. + bool should_be_blocked = scenario.verdict == Verdict::kBlockWithoutSniffing || + scenario.verdict == Verdict::kBlockAfterSniffing; + if (should_be_blocked) { + EXPECT_EQ("", stream_sink_body_) + << "Response should not have been delivered to the renderer."; + EXPECT_TRUE(document_blocker_->blocked_read_completed_); + EXPECT_FALSE(document_blocker_->allow_based_on_sniffing_); + } else { + EXPECT_EQ(scenario.first_chunk, stream_sink_body_) + << "Response should have been delivered to the renderer."; + EXPECT_FALSE(document_blocker_->blocked_read_completed_); + if (scenario.verdict == Verdict::kAllowAfterSniffing) + EXPECT_TRUE(document_blocker_->allow_based_on_sniffing_); + } + + if (should_be_blocked) { + // The next OnWillRead should cancel and complete the response. + ASSERT_EQ(MockResourceLoader::Status::CANCELED, mock_loader_->OnWillRead()); + net::URLRequestStatus status(net::URLRequestStatus::CANCELED, + net::ERR_ABORTED); + ASSERT_EQ(MockResourceLoader::Status::IDLE, + mock_loader_->OnResponseCompleted(status)); + } else { + // Simulate the next read being empty to end the response. + ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead()); + ASSERT_EQ(MockResourceLoader::Status::IDLE, + mock_loader_->OnReadCompleted("")); + ASSERT_EQ(MockResourceLoader::Status::IDLE, + mock_loader_->OnResponseCompleted( + net::URLRequestStatus::FromError(net::OK))); + } + + // Verify that histograms are correctly incremented. + base::HistogramTester::CountsMap expected_counts; + std::string histogram_base = "SiteIsolation.XSD.Browser"; + std::string bucket; + switch (scenario.canonical_mime_type) { + case CROSS_SITE_DOCUMENT_MIME_TYPE_HTML: + bucket = "HTML"; + break; + case CROSS_SITE_DOCUMENT_MIME_TYPE_XML: + bucket = "XML"; + break; + case CROSS_SITE_DOCUMENT_MIME_TYPE_JSON: + bucket = "JSON"; + break; + case CROSS_SITE_DOCUMENT_MIME_TYPE_PLAIN: + bucket = "Plain"; + break; + case CROSS_SITE_DOCUMENT_MIME_TYPE_OTHERS: + EXPECT_FALSE(should_be_blocked); + bucket = "Others"; + break; + default: + NOTREACHED(); + } + int start_action = static_cast<int>( + CrossSiteDocumentResourceHandler::Action::kResponseStarted); + int end_action = -1; + switch (scenario.verdict) { + case Verdict::kBlockWithoutSniffing: + end_action = static_cast<int>( + CrossSiteDocumentResourceHandler::Action::kBlockedWithoutSniffing); + break; + case Verdict::kBlockAfterSniffing: + end_action = static_cast<int>( + CrossSiteDocumentResourceHandler::Action::kBlockedAfterSniffing); + break; + case Verdict::kAllowWithoutSniffing: + end_action = static_cast<int>( + CrossSiteDocumentResourceHandler::Action::kAllowedWithoutSniffing); + break; + case Verdict::kAllowAfterSniffing: + end_action = static_cast<int>( + CrossSiteDocumentResourceHandler::Action::kAllowedAfterSniffing); + break; + default: + NOTREACHED(); + } + // Expecting two actions: ResponseStarted and one of the outcomes. + expected_counts[histogram_base + ".Action"] = 2; + EXPECT_THAT(histograms.GetAllSamples(histogram_base + ".Action"), + testing::ElementsAre(base::Bucket(start_action, 1), + base::Bucket(end_action, 1))) + << "Should have incremented the right actions."; + // Expect to hear the number of bytes in the first read when sniffing is + // required. + if (expected_to_sniff) { + std::string first_chunk = scenario.first_chunk; + expected_counts[histogram_base + ".BytesReadForSniffing"] = 1; + EXPECT_EQ( + 1, histograms.GetBucketCount(histogram_base + ".BytesReadForSniffing", + first_chunk.size())); + } + if (should_be_blocked) { + expected_counts[histogram_base + ".Blocked"] = 1; + expected_counts[histogram_base + ".Blocked." + bucket] = 1; + EXPECT_THAT(histograms.GetAllSamples(histogram_base + ".Blocked"), + testing::ElementsAre(base::Bucket(scenario.resource_type, 1))) + << "Should have incremented aggregate blocking."; + EXPECT_THAT(histograms.GetAllSamples(histogram_base + ".Blocked." + bucket), + testing::ElementsAre(base::Bucket(scenario.resource_type, 1))) + << "Should have incremented blocking for resource type."; + } + // Make sure that the expected metrics, and only those metrics, were + // incremented. + EXPECT_THAT(histograms.GetTotalCountsForPrefix("SiteIsolation.XSD.Browser"), + testing::ContainerEq(expected_counts)); +} + +// Similar to the ResponseBlocking test above, but simulates the case that the +// downstream handler does not immediately resume from OnWillRead, in which case +// the downstream buffer may not be allocated until later. +TEST_P(CrossSiteDocumentResourceHandlerTest, OnWillReadDefer) { + const TestScenario scenario = GetParam(); + SCOPED_TRACE(testing::Message() + << "\nScenario at " << __FILE__ << ":" << scenario.source_line); + + Initialize(scenario.target_url, scenario.resource_type, + scenario.initiator_origin, scenario.cors_request); + + ASSERT_EQ(MockResourceLoader::Status::IDLE, + mock_loader_->OnWillStart(request_->url())); + + // Set up response based on scenario. + scoped_refptr<ResourceResponse> response = CreateResponse( + scenario.response_mime_type, scenario.include_no_sniff_header, + scenario.cors_response, scenario.initiator_origin); + + ASSERT_EQ(MockResourceLoader::Status::IDLE, + mock_loader_->OnResponseStarted(response)); + + // Verify that we will sniff content into a different buffer if sniffing is + // needed. Note that the different buffer is used even for blocking cases + // where no sniffing is needed, to avoid complexity in the handler. The + // handler doesn't look at the data in that case, but there's no way to verify + // it in the test. + bool expected_to_sniff = scenario.verdict == Verdict::kAllowAfterSniffing || + scenario.verdict == Verdict::kBlockAfterSniffing; + EXPECT_EQ(expected_to_sniff, document_blocker_->needs_sniffing_); + + // Cause the TestResourceHandler to defer when OnWillRead is called, to make + // sure the test scenarios still work when the downstream handler's buffer + // isn't allocated in the same call. + stream_sink_->set_defer_on_will_read(true); + ASSERT_EQ(MockResourceLoader::Status::CALLBACK_PENDING, + mock_loader_->OnWillRead()); + EXPECT_EQ(1, stream_sink_->on_will_read_called()); + + // No buffers have been allocated yet. + EXPECT_EQ(nullptr, mock_loader_->io_buffer()); + EXPECT_EQ(nullptr, document_blocker_->local_buffer_.get()); + + // Resume the downstream handler, which should establish a buffer for the + // ResourceLoader (either the downstream one or a local one for sniffing). + stream_sink_->Resume(); + EXPECT_NE(nullptr, mock_loader_->io_buffer()); + if (expected_to_sniff || scenario.verdict == Verdict::kBlockWithoutSniffing) { + EXPECT_EQ(mock_loader_->io_buffer(), document_blocker_->local_buffer_.get()) + << "Should have used a different IOBuffer for sniffing"; + } else { + EXPECT_EQ(mock_loader_->io_buffer(), stream_sink_->buffer()) + << "Should have used original IOBuffer when sniffing not needed"; + } + + // Deliver the first chunk of the response body; this allows sniffing to + // occur. + ASSERT_EQ(MockResourceLoader::Status::IDLE, + mock_loader_->OnReadCompleted(scenario.first_chunk)); + EXPECT_EQ(nullptr, mock_loader_->io_buffer()); + + // Verify that the response is blocked or allowed as expected. + if (scenario.verdict == Verdict::kBlockWithoutSniffing || + scenario.verdict == Verdict::kBlockAfterSniffing) { + EXPECT_EQ("", stream_sink_body_) + << "Response should not have been delivered to the renderer."; + EXPECT_TRUE(document_blocker_->blocked_read_completed_); + EXPECT_FALSE(document_blocker_->allow_based_on_sniffing_); + } else { + EXPECT_EQ(scenario.first_chunk, stream_sink_body_) + << "Response should have been delivered to the renderer."; + EXPECT_FALSE(document_blocker_->blocked_read_completed_); + if (scenario.verdict == Verdict::kAllowAfterSniffing) + EXPECT_TRUE(document_blocker_->allow_based_on_sniffing_); + } +} + +INSTANTIATE_TEST_CASE_P(, + CrossSiteDocumentResourceHandlerTest, + ::testing::ValuesIn(kScenarios)); + +} // namespace content diff --git a/chromium/content/browser/loader/resource_dispatcher_host_impl.cc b/chromium/content/browser/loader/resource_dispatcher_host_impl.cc index 88663ce2ae2..b372f8a6f76 100644 --- a/chromium/content/browser/loader/resource_dispatcher_host_impl.cc +++ b/chromium/content/browser/loader/resource_dispatcher_host_impl.cc @@ -44,6 +44,7 @@ #include "content/browser/child_process_security_policy_impl.h" #include "content/browser/frame_host/navigation_request_info.h" #include "content/browser/loader/async_resource_handler.h" +#include "content/browser/loader/cross_site_document_resource_handler.h" #include "content/browser/loader/detachable_resource_handler.h" #include "content/browser/loader/intercepting_resource_handler.h" #include "content/browser/loader/loader_delegate.h" @@ -1489,7 +1490,7 @@ ResourceDispatcherHostImpl::CreateResourceHandler( } return AddStandardHandlers(request, request_data.resource_type, - resource_context, + resource_context, request_data.fetch_request_mode, request_data.fetch_request_context_type, request_data.fetch_mixed_content_context_type, requester_info->appcache_service(), child_id, @@ -1518,6 +1519,7 @@ ResourceDispatcherHostImpl::AddStandardHandlers( net::URLRequest* request, ResourceType resource_type, ResourceContext* resource_context, + FetchRequestMode fetch_request_mode, RequestContextType fetch_request_context_type, blink::WebMixedContentContextType fetch_mixed_content_context_type, AppCacheService* appcache_service, @@ -1616,6 +1618,17 @@ ResourceDispatcherHostImpl::AddStandardHandlers( handler.reset(new ThrottlingResourceHandler( std::move(handler), request, std::move(pre_mime_sniffing_throttles))); + if (!IsResourceTypeFrame(resource_type)) { + // Add a handler to block cross-site documents from the renderer process. + // This should be pre mime-sniffing, since it affects whether the response + // will be read, and since it looks at the original mime type. + bool is_nocors_plugin_request = + resource_type == RESOURCE_TYPE_PLUGIN_RESOURCE && + fetch_request_mode == FETCH_REQUEST_MODE_NO_CORS; + handler.reset(new CrossSiteDocumentResourceHandler( + std::move(handler), request, is_nocors_plugin_request)); + } + return handler; } @@ -2188,11 +2201,12 @@ void ResourceDispatcherHostImpl::BeginNavigationRequest( ->stream() ->CreateHandle(); + // Safe to consider navigations as NO_CORS. // TODO(davidben): Fix the dependency on child_id/route_id. Those are used // by the ResourceScheduler. currently it's a no-op. handler = AddStandardHandlers( new_request.get(), resource_type, resource_context, - info.begin_params.request_context_type, + FETCH_REQUEST_MODE_NO_CORS, info.begin_params.request_context_type, info.begin_params.mixed_content_context_type, appcache_handle_core ? appcache_handle_core->GetAppCacheService() : nullptr, diff --git a/chromium/content/browser/loader/resource_dispatcher_host_impl.h b/chromium/content/browser/loader/resource_dispatcher_host_impl.h index 3ef287d8739..2fb715de446 100644 --- a/chromium/content/browser/loader/resource_dispatcher_host_impl.h +++ b/chromium/content/browser/loader/resource_dispatcher_host_impl.h @@ -638,6 +638,7 @@ class CONTENT_EXPORT ResourceDispatcherHostImpl net::URLRequest* request, ResourceType resource_type, ResourceContext* resource_context, + FetchRequestMode fetch_request_mode, RequestContextType fetch_request_context_type, blink::WebMixedContentContextType fetch_mixed_content_context_type, AppCacheService* appcache_service, diff --git a/chromium/content/browser/loader/url_loader_factory_impl_unittest.cc b/chromium/content/browser/loader/url_loader_factory_impl_unittest.cc index 7d58b15a607..fcdfcb865df 100644 --- a/chromium/content/browser/loader/url_loader_factory_impl_unittest.cc +++ b/chromium/content/browser/loader/url_loader_factory_impl_unittest.cc @@ -162,8 +162,8 @@ TEST_P(URLLoaderFactoryImplTest, GetResponse) { // enabled, the url scheme of frame type requests from the renderer process // must be blob scheme. request.resource_type = RESOURCE_TYPE_XHR; - // Need to set |request_initiator| for non main frame type request. - request.request_initiator = url::Origin(); + // Need to set same-site |request_initiator| for non main frame type request. + request.request_initiator = url::Origin::Create(request.url); factory_->CreateLoaderAndStart( mojo::MakeRequest(&loader), kRoutingId, kRequestId, mojom::kURLLoadOptionNone, request, client.CreateInterfacePtr(), @@ -239,8 +239,8 @@ TEST_P(URLLoaderFactoryImplTest, GetFailedResponse) { // enabled, the url scheme of frame type requests from the renderer process // must be blob scheme. request.resource_type = RESOURCE_TYPE_XHR; - // Need to set |request_initiator| for non main frame type request. - request.request_initiator = url::Origin(); + // Need to set same-site |request_initiator| for non main frame type request. + request.request_initiator = url::Origin::Create(request.url); factory_->CreateLoaderAndStart( mojo::MakeRequest(&loader), 2, 1, mojom::kURLLoadOptionNone, request, client.CreateInterfacePtr(), @@ -269,8 +269,8 @@ TEST_P(URLLoaderFactoryImplTest, GetFailedResponse2) { // enabled, the url scheme of frame type requests from the renderer process // must be blob scheme. request.resource_type = RESOURCE_TYPE_XHR; - // Need to set |request_initiator| for non main frame type request. - request.request_initiator = url::Origin(); + // Need to set same-site |request_initiator| for non main frame type request. + request.request_initiator = url::Origin::Create(request.url); factory_->CreateLoaderAndStart( mojo::MakeRequest(&loader), 2, 1, mojom::kURLLoadOptionNone, request, client.CreateInterfacePtr(), @@ -296,8 +296,8 @@ TEST_P(URLLoaderFactoryImplTest, InvalidURL) { // enabled, the url scheme of frame type requests from the renderer process // must be blob scheme. request.resource_type = RESOURCE_TYPE_XHR; - // Need to set |request_initiator| for non main frame type request. - request.request_initiator = url::Origin(); + // Need to set same-site |request_initiator| for non main frame type request. + request.request_initiator = url::Origin::Create(request.url); ASSERT_FALSE(request.url.is_valid()); factory_->CreateLoaderAndStart( mojo::MakeRequest(&loader), 2, 1, mojom::kURLLoadOptionNone, request, @@ -324,8 +324,8 @@ TEST_P(URLLoaderFactoryImplTest, ShouldNotRequestURL) { // enabled, the url scheme of frame type requests from the renderer process // must be blob scheme. request.resource_type = RESOURCE_TYPE_XHR; - // Need to set |request_initiator| for non main frame type request. - request.request_initiator = url::Origin(); + // Need to set same-site |request_initiator| for non main frame type request. + request.request_initiator = url::Origin::Create(request.url); factory_->CreateLoaderAndStart( mojo::MakeRequest(&loader), 2, 1, mojom::kURLLoadOptionNone, request, client.CreateInterfacePtr(), @@ -355,7 +355,7 @@ TEST_P(URLLoaderFactoryImplTest, DownloadToFile) { request.method = "GET"; request.resource_type = RESOURCE_TYPE_XHR; request.download_to_file = true; - request.request_initiator = url::Origin(); + request.request_initiator = url::Origin::Create(request.url); factory_->CreateLoaderAndStart( mojo::MakeRequest(&loader), kRoutingId, kRequestId, 0, request, client.CreateInterfacePtr(), @@ -423,7 +423,7 @@ TEST_P(URLLoaderFactoryImplTest, DownloadToFileFailure) { request.method = "GET"; request.resource_type = RESOURCE_TYPE_XHR; request.download_to_file = true; - request.request_initiator = url::Origin(); + request.request_initiator = url::Origin::Create(request.url); factory_->CreateLoaderAndStart( mojo::MakeRequest(&loader), kRoutingId, kRequestId, 0, request, client.CreateInterfacePtr(), @@ -484,8 +484,8 @@ TEST_P(URLLoaderFactoryImplTest, OnTransferSizeUpdated) { // enabled, the url scheme of frame type requests from the renderer process // must be blob scheme. request.resource_type = RESOURCE_TYPE_XHR; - // Need to set |request_initiator| for non main frame type request. - request.request_initiator = url::Origin(); + // Need to set same-site |request_initiator| for non main frame type request. + request.request_initiator = url::Origin::Create(request.url); request.report_raw_headers = true; factory_->CreateLoaderAndStart( mojo::MakeRequest(&loader), kRoutingId, kRequestId, @@ -546,8 +546,8 @@ TEST_P(URLLoaderFactoryImplTest, CancelFromRenderer) { // enabled, the url scheme of frame type requests from the renderer process // must be blob scheme. request.resource_type = RESOURCE_TYPE_XHR; - // Need to set |request_initiator| for non main frame type request. - request.request_initiator = url::Origin(); + // Need to set same-site |request_initiator| for non main frame type request. + request.request_initiator = url::Origin::Create(request.url); factory_->CreateLoaderAndStart( mojo::MakeRequest(&loader), kRoutingId, kRequestId, mojom::kURLLoadOptionNone, request, client.CreateInterfacePtr(), diff --git a/chromium/content/browser/web_contents/web_contents_view_android.cc b/chromium/content/browser/web_contents/web_contents_view_android.cc index 5e0a0aeac2e..95f5ad6734d 100644 --- a/chromium/content/browser/web_contents/web_contents_view_android.cc +++ b/chromium/content/browser/web_contents/web_contents_view_android.cc @@ -196,7 +196,7 @@ void WebContentsViewAndroid::Focus() { RenderWidgetHostViewAndroid* rwhv = GetRenderWidgetHostViewAndroid(); if (web_contents_->ShowingInterstitialPage()) { web_contents_->GetInterstitialPage()->Focus(); - } else { + } else if (rwhv) { rwhv->Focus(); } } diff --git a/chromium/content/child/site_isolation_stats_gatherer_browsertest.cc b/chromium/content/child/site_isolation_stats_gatherer_browsertest.cc index 83be84591d6..5df256242ee 100644 --- a/chromium/content/child/site_isolation_stats_gatherer_browsertest.cc +++ b/chromium/content/child/site_isolation_stats_gatherer_browsertest.cc @@ -13,6 +13,7 @@ #include "content/public/test/browser_test_utils.h" #include "content/public/test/content_browser_test.h" #include "content/public/test/content_browser_test_utils.h" +#include "content/public/test/test_utils.h" #include "content/shell/browser/shell.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "testing/gmock/include/gmock/gmock.h" @@ -107,6 +108,10 @@ class SiteIsolationStatsGathererBrowserTest expected_metrics[base + ".NotBlocked.MaybeJS"] = 1; } } + // This metric from the browser process also records start and stop actions + // for the blocking logic, even though blocking is disabled in the browser + // process when this test runs. + expected_metrics["SiteIsolation.XSD.Browser.Action"] = 2; // Make sure that the expected metrics, and only those metrics, were // incremented. @@ -133,6 +138,12 @@ class SiteIsolationStatsGathererBrowserTest IN_PROC_BROWSER_TEST_P(SiteIsolationStatsGathererBrowserTest, CrossSiteDocumentBlockingForMimeType) { + // This test is disabled in --site-per-process, since the documents are + // blocked before arriving in the renderer process and thus the existing + // histograms do not work. + if (AreAllSitesIsolatedForTesting()) + return; + // Load a page that issues illegal cross-site document requests to bar.com. // The page uses XHR to request HTML/XML/JSON documents from bar.com, and // inspects if any of them were successfully received. Currently, on illegal @@ -141,7 +152,7 @@ IN_PROC_BROWSER_TEST_P(SiteIsolationStatsGathererBrowserTest, // we run the browser without the same origin policy. GURL foo("http://foo.com/cross_site_document_request.html"); - NavigateToURL(shell(), foo); + EXPECT_TRUE(NavigateToURL(shell(), foo)); // Flush out existing histogram activity. FetchHistogramsFromChildProcesses(); @@ -149,18 +160,9 @@ IN_PROC_BROWSER_TEST_P(SiteIsolationStatsGathererBrowserTest, // The following are files under content/test/data/site_isolation. All // should be disallowed for cross site XHR under the document blocking policy. const char* blocked_resources[] = { - "comment_valid.html", - "html.txt", - "html4_dtd.html", - "html4_dtd.txt", - "html5_dtd.html", - "html5_dtd.txt", - "json.txt", - "valid.html", - "valid.json", - "valid.xml", - "xml.txt", - }; + "comment_valid.html", "html.txt", "html4_dtd.html", "html4_dtd.txt", + "html5_dtd.html", "html5_dtd.txt", "json.txt", "valid.html", + "valid.json", "valid.xml", "xml.txt"}; for (const char* resource : blocked_resources) { SCOPED_TRACE(base::StringPrintf("... while testing page: %s", resource)); @@ -168,7 +170,7 @@ IN_PROC_BROWSER_TEST_P(SiteIsolationStatsGathererBrowserTest, bool was_blocked; ASSERT_TRUE(ExecuteScriptAndExtractBool( - shell(), base::StringPrintf("sendRequest(\"%s\");", resource), + shell(), base::StringPrintf("sendRequest('%s');", resource), &was_blocked)); ASSERT_FALSE(was_blocked); @@ -176,23 +178,16 @@ IN_PROC_BROWSER_TEST_P(SiteIsolationStatsGathererBrowserTest, } // These files should be allowed for XHR under the document blocking policy. - const char* allowed_resources[] = {"js.html", - "comment_js.html", - "js.xml", - "js.json", - "js.txt", - "img.html", - "img.xml", - "img.json", - "img.txt", - "comment_js.html"}; + const char* allowed_resources[] = {"js.html", "comment_js.html", "js.xml", + "js.json", "js.txt", "img.html", + "img.xml", "img.json", "img.txt"}; for (const char* resource : allowed_resources) { SCOPED_TRACE(base::StringPrintf("... while testing page: %s", resource)); base::HistogramTester histograms; bool was_blocked; ASSERT_TRUE(ExecuteScriptAndExtractBool( - shell(), base::StringPrintf("sendRequest(\"%s\");", resource), + shell(), base::StringPrintf("sendRequest('%s');", resource), &was_blocked)); ASSERT_FALSE(was_blocked); @@ -212,7 +207,7 @@ IN_PROC_BROWSER_TEST_P(SiteIsolationStatsGathererBrowserTest, // TODO(nick): Split up these cases, and add positive assertions here about // what actually happens in these various resource-block cases. GURL foo("http://foo.com/cross_site_document_request_target.html"); - NavigateToURL(shell(), foo); + EXPECT_TRUE(NavigateToURL(shell(), foo)); } INSTANTIATE_TEST_CASE_P(SiteIsolationStatsGathererBrowserTest, diff --git a/chromium/content/common/cross_site_document_classifier.cc b/chromium/content/common/cross_site_document_classifier.cc index 78417aa2b83..3518d84f2c3 100644 --- a/chromium/content/common/cross_site_document_classifier.cc +++ b/chromium/content/common/cross_site_document_classifier.cc @@ -5,6 +5,7 @@ #include "content/common/cross_site_document_classifier.h" #include <stddef.h> +#include <string> #include "base/command_line.h" #include "base/lazy_instance.h" @@ -111,12 +112,16 @@ bool CrossSiteDocumentClassifier::IsValidCorsHeaderSet( // non-standard practice, and not supported by Chrome. Refer to // CrossOriginAccessControl::passesAccessControlCheck(). + // Note that "null" offers no more protection than "*" because it matches any + // unique origin, such as data URLs. Any origin can thus access it, so don't + // bother trying to block this case. + // TODO(dsjang): * is not allowed for the response from a request // with cookies. This allows for more than what the renderer will // eventually be able to receive, so we won't see illegal cross-site // documents allowed by this. We have to find a way to see if this // response is from a cookie-tagged request or not in the future. - if (access_control_origin == "*") + if (access_control_origin == "*" || access_control_origin == "null") return true; // TODO(dsjang): The CORS spec only treats a fully specified URL, except for diff --git a/chromium/content/common/site_isolation_policy.cc b/chromium/content/common/site_isolation_policy.cc index 0d277b01a89..16035a81660 100644 --- a/chromium/content/common/site_isolation_policy.cc +++ b/chromium/content/common/site_isolation_policy.cc @@ -9,6 +9,7 @@ #include "base/command_line.h" #include "base/feature_list.h" #include "base/metrics/field_trial_params.h" +#include "base/metrics/histogram_macros.h" #include "base/strings/string_split.h" #include "content/public/common/content_features.h" #include "content/public/common/content_switches.h" @@ -24,6 +25,21 @@ bool SiteIsolationPolicy::UseDedicatedProcessesForAllSites() { } // static +SiteIsolationPolicy::CrossSiteDocumentBlockingEnabledState +SiteIsolationPolicy::IsCrossSiteDocumentBlockingEnabled() { + if (base::FeatureList::IsEnabled( + ::features::kCrossSiteDocumentBlockingAlways)) + return XSDB_ENABLED_UNCONDITIONALLY; + + if (base::FeatureList::IsEnabled( + ::features::kCrossSiteDocumentBlockingIfIsolating)) { + return XSDB_ENABLED_IF_ISOLATED; + } + + return XSDB_DISABLED; +} + +// static bool SiteIsolationPolicy::IsTopDocumentIsolationEnabled() { // --site-per-process trumps --top-document-isolation. if (UseDedicatedProcessesForAllSites()) @@ -44,8 +60,13 @@ std::vector<url::Origin> SiteIsolationPolicy::GetIsolatedOrigins() { std::string cmdline_arg = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( switches::kIsolateOrigins); - if (!cmdline_arg.empty()) - return ParseIsolatedOrigins(cmdline_arg); + if (!cmdline_arg.empty()) { + std::vector<url::Origin> cmdline_origins = + ParseIsolatedOrigins(cmdline_arg); + UMA_HISTOGRAM_COUNTS_1000("SiteIsolation.IsolateOrigins.Size", + cmdline_origins.size()); + return cmdline_origins; + } if (base::FeatureList::IsEnabled(features::kIsolateOrigins)) { std::string field_trial_arg = base::GetFieldTrialParamValueByFeature( @@ -73,4 +94,20 @@ std::vector<url::Origin> SiteIsolationPolicy::ParseIsolatedOrigins( return origins; } +// static +void SiteIsolationPolicy::RecordSiteIsolationFlagUsage() { + // For --site-per-process and --isolate-origins, include flags specified on + // command-line, in chrome://flags, and via enterprise policy (i.e., include + // switches::kSitePerProcess and switches::kIsolateOrigins). Exclude these + // modes being set through field trials (i.e., exclude + // features::kSitePerProcess and features::IsolateOrigins). + UMA_HISTOGRAM_BOOLEAN("SiteIsolation.Flags.IsolateOrigins", + base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kIsolateOrigins)); + + UMA_HISTOGRAM_BOOLEAN("SiteIsolation.Flags.SitePerProcess", + base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kSitePerProcess)); +} + } // namespace content diff --git a/chromium/content/common/site_isolation_policy.h b/chromium/content/common/site_isolation_policy.h index 44afe7d5c6e..eb4b799d0d3 100644 --- a/chromium/content/common/site_isolation_policy.h +++ b/chromium/content/common/site_isolation_policy.h @@ -27,6 +27,15 @@ class CONTENT_EXPORT SiteIsolationPolicy { // Returns true if every site should be placed in a dedicated process. static bool UseDedicatedProcessesForAllSites(); + // Returns whether cross-site document responses can be blocked. + enum CrossSiteDocumentBlockingEnabledState { + XSDB_ENABLED_UNCONDITIONALLY, + XSDB_ENABLED_IF_ISOLATED, + XSDB_DISABLED, + }; + static CrossSiteDocumentBlockingEnabledState + IsCrossSiteDocumentBlockingEnabled(); + // Returns true if third-party subframes of a page should be kept in a // different process from the main frame. static bool IsTopDocumentIsolationEnabled(); @@ -40,6 +49,10 @@ class CONTENT_EXPORT SiteIsolationPolicy { // ContentBrowserClient::GetOriginsRequiringDedicatedProcess. static std::vector<url::Origin> GetIsolatedOrigins(); + // Records metrics about which site isolation command-line flags are present. + // This should be called once on browser startup. + static void RecordSiteIsolationFlagUsage(); + private: SiteIsolationPolicy(); // Not instantiable. diff --git a/chromium/content/public/browser/content_browser_client.cc b/chromium/content/public/browser/content_browser_client.cc index 6a264c4692f..70cc5e2e8de 100644 --- a/chromium/content/public/browser/content_browser_client.cc +++ b/chromium/content/public/browser/content_browser_client.cc @@ -78,6 +78,13 @@ bool ContentBrowserClient::ShouldLockToOrigin(BrowserContext* browser_context, return true; } +bool ContentBrowserClient::ShouldBypassDocumentBlocking( + const url::Origin& initiator, + const GURL& url, + ResourceType resource_type) { + return false; +} + void ContentBrowserClient::GetAdditionalViewSourceSchemes( std::vector<std::string>* additional_schemes) { GetAdditionalWebUISchemes(additional_schemes); diff --git a/chromium/content/public/browser/content_browser_client.h b/chromium/content/public/browser/content_browser_client.h index 711fa80d0ea..d9af3e7a6af 100644 --- a/chromium/content/public/browser/content_browser_client.h +++ b/chromium/content/public/browser/content_browser_client.h @@ -243,6 +243,13 @@ class CONTENT_EXPORT ContentBrowserClient { virtual bool ShouldLockToOrigin(BrowserContext* browser_context, const GURL& effective_url); + // Returns true if the |initiator| origin should be allowed to receive a + // document at |url|, bypassing the usual blocking logic. Defaults to false. + // This is called on the IO thread. + virtual bool ShouldBypassDocumentBlocking(const url::Origin& initiator, + const GURL& url, + ResourceType resource_type); + // Returns a list additional WebUI schemes, if any. These additional schemes // act as aliases to the chrome: scheme. The additional schemes may or may // not serve specific WebUI pages depending on the particular URLDataSource diff --git a/chromium/content/public/common/content_features.cc b/chromium/content/public/common/content_features.cc index 2b88e0c7a99..f11d51a320c 100644 --- a/chromium/content/public/common/content_features.cc +++ b/chromium/content/public/common/content_features.cc @@ -90,6 +90,16 @@ const base::Feature kCompositorImageAnimation{ const base::Feature kCompositorTouchAction{"CompositorTouchAction", base::FEATURE_DISABLED_BY_DEFAULT}; +// Enables blocking cross-site document responses (not paying attention to +// whether a site isolation mode is also enabled). +const base::Feature kCrossSiteDocumentBlockingAlways{ + "CrossSiteDocumentBlockingAlways", base::FEATURE_DISABLED_BY_DEFAULT}; + +// Enables blocking cross-site document responses if one of site isolation modes +// is (e.g. site-per-process or isolate-origins) is enabled. +const base::Feature kCrossSiteDocumentBlockingIfIsolating{ + "CrossSiteDocumentBlockingIfIsolating", base::FEATURE_ENABLED_BY_DEFAULT}; + // Throttle tasks in Blink background timer queues based on CPU budgets // for the background tab. Bug: https://crbug.com/639852. const base::Feature kExpensiveBackgroundTimerThrottling{ diff --git a/chromium/content/public/common/content_features.h b/chromium/content/public/common/content_features.h index 470b6607c3a..a72f19f4a06 100644 --- a/chromium/content/public/common/content_features.h +++ b/chromium/content/public/common/content_features.h @@ -32,6 +32,8 @@ CONTENT_EXPORT extern const base::Feature kCompositeOpaqueFixedPosition; CONTENT_EXPORT extern const base::Feature kCompositeOpaqueScrollers; CONTENT_EXPORT extern const base::Feature kCompositorImageAnimation; CONTENT_EXPORT extern const base::Feature kCompositorTouchAction; +CONTENT_EXPORT extern const base::Feature kCrossSiteDocumentBlockingAlways; +CONTENT_EXPORT extern const base::Feature kCrossSiteDocumentBlockingIfIsolating; CONTENT_EXPORT extern const base::Feature kExpensiveBackgroundTimerThrottling; CONTENT_EXPORT extern const base::Feature kFeaturePolicy; CONTENT_EXPORT extern const base::Feature kFetchKeepaliveTimeoutSetting; diff --git a/chromium/content/renderer/fetchers/resource_fetcher_browsertest.cc b/chromium/content/renderer/fetchers/resource_fetcher_browsertest.cc index 689e5d3aa6a..2ff25adb0a8 100644 --- a/chromium/content/renderer/fetchers/resource_fetcher_browsertest.cc +++ b/chromium/content/renderer/fetchers/resource_fetcher_browsertest.cc @@ -318,10 +318,10 @@ class ResourceFetcherTests : public ContentBrowserTest { // Test a fetch from the test server. // If this flakes, use http://crbug.com/51622. IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherDownload) { - // Need to spin up the renderer. - NavigateToURL(shell(), GURL(url::kAboutBlankURL)); - + // Need to spin up the renderer to same-site URL. ASSERT_TRUE(embedded_test_server()->Start()); + NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); + GURL url(embedded_test_server()->GetURL("/simple_page.html")); PostTaskToInProcessRendererAndWait( @@ -331,10 +331,10 @@ IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherDownload) { // Test if ResourceFetcher can handle server redirects correctly. IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherRedirect) { - // Need to spin up the renderer. - NavigateToURL(shell(), GURL(url::kAboutBlankURL)); - + // Need to spin up the renderer to same-site URL. ASSERT_TRUE(embedded_test_server()->Start()); + NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); + GURL final_url(embedded_test_server()->GetURL("/simple_page.html")); GURL url( embedded_test_server()->GetURL("/server-redirect?" + final_url.spec())); @@ -345,11 +345,11 @@ IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherRedirect) { } IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcher404) { - // Need to spin up the renderer. - NavigateToURL(shell(), GURL(url::kAboutBlankURL)); + // Need to spin up the renderer to same-site URL. + ASSERT_TRUE(embedded_test_server()->Start()); + NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); // Test 404 response. - ASSERT_TRUE(embedded_test_server()->Start()); GURL url = embedded_test_server()->GetURL("/thisfiledoesntexist.html"); PostTaskToInProcessRendererAndWait( @@ -368,12 +368,12 @@ IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherDidFail) { } IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherTimeout) { - // Need to spin up the renderer. - NavigateToURL(shell(), GURL(url::kAboutBlankURL)); + // Need to spin up the renderer to same-site URL. + ASSERT_TRUE(embedded_test_server()->Start()); + NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); // Grab a page that takes at least 1 sec to respond, but set the fetcher to // timeout in 0 sec. - ASSERT_TRUE(embedded_test_server()->Start()); GURL url(embedded_test_server()->GetURL("/slow?1")); PostTaskToInProcessRendererAndWait( @@ -382,12 +382,12 @@ IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherTimeout) { } IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherDeletedInCallback) { - // Need to spin up the renderer. - NavigateToURL(shell(), GURL(url::kAboutBlankURL)); + // Need to spin up the renderer to same-site URL. + ASSERT_TRUE(embedded_test_server()->Start()); + NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); // Grab a page that takes at least 1 sec to respond, but set the fetcher to // timeout in 0 sec. - ASSERT_TRUE(embedded_test_server()->Start()); GURL url(embedded_test_server()->GetURL("/slow?1")); PostTaskToInProcessRendererAndWait(base::Bind( @@ -397,11 +397,11 @@ IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherDeletedInCallback) { // Test that ResourceFetchers can handle POSTs. IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherPost) { - // Need to spin up the renderer. - NavigateToURL(shell(), GURL(url::kAboutBlankURL)); + // Need to spin up the renderer to same-site URL. + ASSERT_TRUE(embedded_test_server()->Start()); + NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); // Grab a page that echos the POST body. - ASSERT_TRUE(embedded_test_server()->Start()); GURL url(embedded_test_server()->GetURL("/echo")); PostTaskToInProcessRendererAndWait(base::Bind( @@ -410,11 +410,11 @@ IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherPost) { // Test that ResourceFetchers can set headers. IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherSetHeader) { - // Need to spin up the renderer. - NavigateToURL(shell(), GURL(url::kAboutBlankURL)); + // Need to spin up the renderer to same-site URL. + ASSERT_TRUE(embedded_test_server()->Start()); + NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); // Grab a page that echos the POST body. - ASSERT_TRUE(embedded_test_server()->Start()); GURL url(embedded_test_server()->GetURL("/echoheader?header")); PostTaskToInProcessRendererAndWait( diff --git a/chromium/content/test/BUILD.gn b/chromium/content/test/BUILD.gn index 2b12a2416aa..9966b7a9c6c 100644 --- a/chromium/content/test/BUILD.gn +++ b/chromium/content/test/BUILD.gn @@ -705,6 +705,7 @@ test("content_browsertests") { "../browser/indexed_db/mock_browsertest_indexed_db_class_factory.h", "../browser/isolated_origin_browsertest.cc", "../browser/loader/async_resource_handler_browsertest.cc", + "../browser/loader/cross_site_document_blocking_browsertest.cc", "../browser/loader/cross_site_resource_handler_browsertest.cc", "../browser/loader/reload_cache_control_browsertest.cc", "../browser/loader/resource_dispatcher_host_browsertest.cc", @@ -1259,6 +1260,7 @@ test("content_unittests") { "../browser/indexed_db/mock_mojo_indexed_db_database_callbacks.h", "../browser/leveldb_wrapper_impl_unittest.cc", "../browser/loader/async_resource_handler_unittest.cc", + "../browser/loader/cross_site_document_resource_handler_unittest.cc", "../browser/loader/detachable_resource_handler_unittest.cc", "../browser/loader/intercepting_resource_handler_unittest.cc", "../browser/loader/mime_sniffing_resource_handler_unittest.cc", diff --git a/chromium/extensions/common/api/_permission_features.json b/chromium/extensions/common/api/_permission_features.json index ccdddbeac47..79d3f54c578 100644 --- a/chromium/extensions/common/api/_permission_features.json +++ b/chromium/extensions/common/api/_permission_features.json @@ -445,7 +445,8 @@ "E0E94FB0C01FFB9CDC7A5F098C99B5A8D2F95902", // http://crbug.com/610452 "52E0557059A7A28F74ED1D92DDD997E0CCD37806", // http://crbug.com/610452 "61FF4757F9420B62B19BA5C96084649339DB31F5", // http://crbug.com/731941 - "6FB7E1B6C0247B687AC14772E87A117F5F5E4497" // http://crbug.com/731941 + "6FB7E1B6C0247B687AC14772E87A117F5F5E4497", // http://crbug.com/731941 + "9834387FDA1F66A1B5CA06CB442137B556F12F2A" // http://crbug.com/772346 ] }], "networkingPrivate": { diff --git a/chromium/net/dns/dns_transaction.cc b/chromium/net/dns/dns_transaction.cc index 9c0e0d8723c..b41deb4f883 100644 --- a/chromium/net/dns/dns_transaction.cc +++ b/chromium/net/dns/dns_transaction.cc @@ -767,8 +767,15 @@ class DnsTransactionImpl : public DnsTransaction, previous_attempt->GetQuery()->CloneWithNewId(id); RecordLostPacketsIfAny(); - // Cancel all other attempts, no point waiting on them. - attempts_.clear(); + + // Cancel all other attempts that have not received a response, no point + // waiting on them. + for (auto it = attempts_.begin(); it != attempts_.end();) { + if (!(*it)->is_completed()) + it = attempts_.erase(it); + else + ++it; + } unsigned attempt_number = attempts_.size(); diff --git a/chromium/net/dns/dns_transaction_unittest.cc b/chromium/net/dns/dns_transaction_unittest.cc index 53328d7cddc..4a13d08aee9 100644 --- a/chromium/net/dns/dns_transaction_unittest.cc +++ b/chromium/net/dns/dns_transaction_unittest.cc @@ -1055,6 +1055,54 @@ TEST_F(DnsTransactionTest, TCPConnectionClosedSynchronous) { EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } +TEST_F(DnsTransactionTest, MismatchedThenNxdomainThenTCP) { + config_.attempts = 2; + config_.timeout = TestTimeouts::tiny_timeout(); + ConfigureFactory(); + std::unique_ptr<DnsSocketData> data( + new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, false)); + // First attempt gets a mismatched response. + data->AddResponseData(kT1ResponseDatagram, arraysize(kT1ResponseDatagram), + SYNCHRONOUS); + // Second read from first attempt gets TCP required. + data->AddRcode(dns_protocol::kFlagTC, ASYNC); + AddSocketData(std::move(data)); + // Second attempt gets NXDOMAIN, which happens before the TCP required. + AddSyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNXDOMAIN); + std::unique_ptr<DnsSocketData> tcp_data( + new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, true)); + tcp_data->AddReadError(ERR_CONNECTION_CLOSED, SYNCHRONOUS); + AddSocketData(std::move(tcp_data)); + + TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_NAME_NOT_RESOLVED); + EXPECT_TRUE(helper0.Run(transaction_factory_.get())); +} + +TEST_F(DnsTransactionTest, MismatchedThenOkThenTCP) { + config_.attempts = 2; + config_.timeout = TestTimeouts::tiny_timeout(); + ConfigureFactory(); + std::unique_ptr<DnsSocketData> data( + new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, false)); + // First attempt gets a mismatched response. + data->AddResponseData(kT1ResponseDatagram, arraysize(kT1ResponseDatagram), + SYNCHRONOUS); + // Second read from first attempt gets TCP required. + data->AddRcode(dns_protocol::kFlagTC, ASYNC); + AddSocketData(std::move(data)); + // Second attempt gets a valid response, which happens before the TCP + // required. + AddSyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, + kT0ResponseDatagram, arraysize(kT0ResponseDatagram)); + std::unique_ptr<DnsSocketData> tcp_data( + new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, true)); + tcp_data->AddReadError(ERR_CONNECTION_CLOSED, SYNCHRONOUS); + AddSocketData(std::move(tcp_data)); + + TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); + EXPECT_TRUE(helper0.Run(transaction_factory_.get())); +} + TEST_F(DnsTransactionTest, InvalidQuery) { config_.timeout = TestTimeouts::tiny_timeout(); ConfigureFactory(); diff --git a/chromium/skia/ext/skia_commit_hash.h b/chromium/skia/ext/skia_commit_hash.h index 064a3695dba..f5a7a58f634 100644 --- a/chromium/skia/ext/skia_commit_hash.h +++ b/chromium/skia/ext/skia_commit_hash.h @@ -3,6 +3,6 @@ #ifndef SKIA_EXT_SKIA_COMMIT_HASH_H_ #define SKIA_EXT_SKIA_COMMIT_HASH_H_ -#define SKIA_COMMIT_HASH "e376a767ac05c5e37627f950c86ff5c3f6e1cd4c-" +#define SKIA_COMMIT_HASH "1a190d213cb8f711a15d3dc489fd068cd43291bd-" #endif // SKIA_EXT_SKIA_COMMIT_HASH_H_ diff --git a/chromium/third_party/WebKit/Source/bindings/modules/v8/wasm/WasmResponseExtensions.cpp b/chromium/third_party/WebKit/Source/bindings/modules/v8/wasm/WasmResponseExtensions.cpp index 2e34d39e1cb..69305ed3e77 100644 --- a/chromium/third_party/WebKit/Source/bindings/modules/v8/wasm/WasmResponseExtensions.cpp +++ b/chromium/third_party/WebKit/Source/bindings/modules/v8/wasm/WasmResponseExtensions.cpp @@ -129,7 +129,7 @@ void CompileFromResponseCallback( "WebAssembly", "compile"); ExceptionToRejectPromiseScope reject_promise_scope(args, exception_state); - ScriptState* script_state = ScriptState::ForRelevantRealm(args); + ScriptState* script_state = ScriptState::ForCurrentRealm(args); if (!ExecutionContext::From(script_state)) { V8SetReturnValue(args, ScriptPromise().V8Value()); return; @@ -142,7 +142,7 @@ void CompileFromResponseCallback( ScriptPromise::Reject( script_state, V8ThrowException::CreateTypeError( script_state->GetIsolate(), - "An argument must be provided, which must be a" + "An argument must be provided, which must be a " "Response or Promise<Response> object")) .V8Value()); return; @@ -189,7 +189,7 @@ void CompileFromResponseCallback( // See https://crbug.com/708238 for tracking avoiding the hand-generated code. void WasmCompileStreamingImpl(const v8::FunctionCallbackInfo<v8::Value>& args) { v8::Isolate* isolate = args.GetIsolate(); - ScriptState* script_state = ScriptState::ForRelevantRealm(args); + ScriptState* script_state = ScriptState::ForCurrentRealm(args); v8::Local<v8::Function> compile_callback = v8::Function::New(isolate, CompileFromResponseCallback); diff --git a/chromium/third_party/WebKit/Source/core/html/HTMLImageFallbackHelper.cpp b/chromium/third_party/WebKit/Source/core/html/HTMLImageFallbackHelper.cpp index a071a1b217a..47384ecd580 100644 --- a/chromium/third_party/WebKit/Source/core/html/HTMLImageFallbackHelper.cpp +++ b/chromium/third_party/WebKit/Source/core/html/HTMLImageFallbackHelper.cpp @@ -23,8 +23,9 @@ namespace blink { using namespace HTMLNames; static bool NoImageSourceSpecified(const Element& element) { - bool no_src_specified = - !element.hasAttribute(srcAttr) || element.getAttribute(srcAttr).IsNull(); + bool no_src_specified = !element.hasAttribute(srcAttr) || + element.getAttribute(srcAttr).IsNull() || + element.getAttribute(srcAttr).IsEmpty(); bool no_srcset_specified = !element.hasAttribute(srcsetAttr) || element.getAttribute(srcsetAttr).IsNull(); return no_src_specified && no_srcset_specified; diff --git a/chromium/third_party/WebKit/Source/modules/accessibility/AXARIAGrid.cpp b/chromium/third_party/WebKit/Source/modules/accessibility/AXARIAGrid.cpp index 2bef0f90415..8e8ca06a495 100644 --- a/chromium/third_party/WebKit/Source/modules/accessibility/AXARIAGrid.cpp +++ b/chromium/third_party/WebKit/Source/modules/accessibility/AXARIAGrid.cpp @@ -46,10 +46,13 @@ AXARIAGrid* AXARIAGrid::Create(LayoutObject* layout_object, } bool AXARIAGrid::AddRow(AXObject* possible_row) { - if (!possible_row || possible_row->RoleValue() != kRowRole) + // This does not yet handle the case where the row is not an AXARIAGridRow or + // AXTable row because it is in a canvas or is a virtual node, as those + // do not have a layout object, cannot be an AXARIAGridRow, and cannot + // currently implement the rest of our table logic. + if (!possible_row || !possible_row->IsTableRow()) return false; - DCHECK(possible_row->IsTableRow()); AXTableRow* row = ToAXTableRow(possible_row); row->SetRowIndex(static_cast<int>(rows_.size())); rows_.push_back(possible_row); diff --git a/chromium/third_party/WebKit/Source/platform/loader/fetch/ResourceFetcher.cpp b/chromium/third_party/WebKit/Source/platform/loader/fetch/ResourceFetcher.cpp index 46e3cf837d8..74bd9b0436e 100644 --- a/chromium/third_party/WebKit/Source/platform/loader/fetch/ResourceFetcher.cpp +++ b/chromium/third_party/WebKit/Source/platform/loader/fetch/ResourceFetcher.cpp @@ -605,6 +605,20 @@ ResourceFetcher::PrepareRequestResult ResourceFetcher::PrepareRequest( if (blocked_reason != ResourceRequestBlockedReason::kNone) return kBlock; + const RefPtr<SecurityOrigin>& origin = options.security_origin; + if (origin && !origin->IsUnique() && + !origin->IsSameSchemeHostPort(Context().GetSecurityOrigin())) { + // |options.security_origin| may differ from the document's origin if + // this is a fetch initiated by an isolated world execution context, with a + // different SecurityOrigin. In this case, plumb it through as the + // RequestorOrigin so that the browser process can make policy decisions for + // this request, based on any special permissions the isolated world may + // have been granted. + // TODO(nick, dcheng): Find a way to formalize the isolated world origin + // check in https://crbug.com/792154. + resource_request.SetRequestorOrigin(origin); + } + // For initial requests, call prepareRequest() here before revalidation // policy is determined. Context().PrepareRequest(resource_request, @@ -613,7 +627,6 @@ ResourceFetcher::PrepareRequestResult ResourceFetcher::PrepareRequest( if (!params.Url().IsValid()) return kAbort; - RefPtr<SecurityOrigin> origin = options.security_origin; params.MutableOptions().cors_flag = !origin || !origin->CanRequestNoSuborigin(params.Url()); diff --git a/chromium/third_party/angle/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp b/chromium/third_party/angle/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp index 4c75172943f..bf10646d854 100644 --- a/chromium/third_party/angle/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp +++ b/chromium/third_party/angle/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp @@ -1917,6 +1917,9 @@ gl::Error StateManager11::updateState(const gl::Context *context, GLenum drawMod auto dirtyBitsCopy = mInternalDirtyBits; mInternalDirtyBits.reset(); + // TODO(crbug.com/792966): Workaround for bug in this dirty bit + dirtyBitsCopy.set(DIRTY_BIT_PROGRAM_UNIFORM_BUFFERS); + for (auto dirtyBit : dirtyBitsCopy) { switch (dirtyBit) diff --git a/chromium/ui/compositor/compositor.cc b/chromium/ui/compositor/compositor.cc index bb806e183fe..b0fef6f1589 100644 --- a/chromium/ui/compositor/compositor.cc +++ b/chromium/ui/compositor/compositor.cc @@ -355,7 +355,11 @@ void Compositor::SetScaleAndSize(float scale, void Compositor::SetDisplayColorSpace(const gfx::ColorSpace& color_space) { output_color_space_ = color_space; blending_color_space_ = output_color_space_.GetBlendingColorSpace(); - host_->SetRasterColorSpace(output_color_space_.GetRasterColorSpace()); + // Do all ui::Compositor rasterization to sRGB because UI resources will not + // have their color conversion results cached, and will suffer repeated + // image color conversions. + // https://crbug.com/769677 + host_->SetRasterColorSpace(gfx::ColorSpace::CreateSRGB()); // Color space is reset when the output surface is lost, so this must also be // updated then. // TODO(fsamuel): Get rid of this. diff --git a/chromium/v8/include/v8-version.h b/chromium/v8/include/v8-version.h index 46bb92f6506..86c50fd4f52 100644 --- a/chromium/v8/include/v8-version.h +++ b/chromium/v8/include/v8-version.h @@ -11,7 +11,7 @@ #define V8_MAJOR_VERSION 6 #define V8_MINOR_VERSION 3 #define V8_BUILD_NUMBER 292 -#define V8_PATCH_LEVEL 46 +#define V8_PATCH_LEVEL 48 // Use 1 for candidates and 0 otherwise. // (Boolean macro values are not supported by all preprocessors.) diff --git a/chromium/v8/src/builtins/builtins-typedarray-gen.cc b/chromium/v8/src/builtins/builtins-typedarray-gen.cc index 07f122b9098..86ec0e7bd9a 100644 --- a/chromium/v8/src/builtins/builtins-typedarray-gen.cc +++ b/chromium/v8/src/builtins/builtins-typedarray-gen.cc @@ -799,7 +799,7 @@ void TypedArrayBuiltinsAssembler::SetTypedArraySource( // means we're safe from overflows in the following multiplication. TNode<IntPtrT> source_byte_length = IntPtrMul(source_length, source_el_size); CSA_ASSERT(this, - IntPtrGreaterThanOrEqual(source_byte_length, IntPtrConstant(0))); + UintPtrGreaterThanOrEqual(source_byte_length, IntPtrConstant(0))); Label call_memmove(this), fast_c_call(this), out(this); Branch(Word32Equal(source_el_kind, target_el_kind), &call_memmove, @@ -821,8 +821,8 @@ void TypedArrayBuiltinsAssembler::SetTypedArraySource( TNode<IntPtrT> target_byte_length = IntPtrMul(target_length, target_el_size); - CSA_ASSERT(this, - IntPtrGreaterThanOrEqual(target_byte_length, IntPtrConstant(0))); + CSA_ASSERT( + this, UintPtrGreaterThanOrEqual(target_byte_length, IntPtrConstant(0))); TNode<IntPtrT> target_data_end_ptr = IntPtrAdd(target_data_ptr, target_byte_length); @@ -830,8 +830,8 @@ void TypedArrayBuiltinsAssembler::SetTypedArraySource( IntPtrAdd(source_data_ptr, source_byte_length); GotoIfNot( - Word32Or(IntPtrLessThanOrEqual(target_data_end_ptr, source_data_ptr), - IntPtrLessThanOrEqual(source_data_end_ptr, target_data_ptr)), + Word32Or(UintPtrLessThanOrEqual(target_data_end_ptr, source_data_ptr), + UintPtrLessThanOrEqual(source_data_end_ptr, target_data_ptr)), call_runtime); TNode<IntPtrT> source_length = diff --git a/chromium/v8/src/debug/debug-coverage.cc b/chromium/v8/src/debug/debug-coverage.cc index 8fe2edc08a7..8b87286d29a 100644 --- a/chromium/v8/src/debug/debug-coverage.cc +++ b/chromium/v8/src/debug/debug-coverage.cc @@ -544,9 +544,6 @@ void Coverage::SelectMode(Isolate* isolate, debug::Coverage::Mode mode) { if (!shared->IsSubjectToDebugging()) continue; vector->clear_invocation_count(); vectors.emplace_back(vector, isolate); - } else if (current_obj->IsJSFunction()) { - JSFunction* function = JSFunction::cast(current_obj); - function->set_code(function->shared()->code()); } } } |