diff options
Diffstat (limited to 'chromium/content/browser/webui')
11 files changed, 650 insertions, 238 deletions
diff --git a/chromium/content/browser/webui/content_web_ui_controller_factory.cc b/chromium/content/browser/webui/content_web_ui_controller_factory.cc index d0a353e6193..6012229fe08 100644 --- a/chromium/content/browser/webui/content_web_ui_controller_factory.cc +++ b/chromium/content/browser/webui/content_web_ui_controller_factory.cc @@ -15,6 +15,7 @@ #include "content/browser/process_internals/process_internals_ui.h" #include "content/browser/service_worker/service_worker_internals_ui.h" #include "content/browser/tracing/tracing_ui.h" +#include "content/browser/ukm_internals_ui.h" #include "content/browser/webrtc/webrtc_internals_ui.h" #include "content/public/browser/storage_partition.h" #include "content/public/browser/web_contents.h" @@ -42,7 +43,8 @@ WebUI::TypeID ContentWebUIControllerFactory::GetWebUIType( url.host_piece() == kChromeUIAppCacheInternalsHost || url.host_piece() == kChromeUINetworkErrorsListingHost || url.host_piece() == kChromeUIProcessInternalsHost || - url.host_piece() == kChromeUIConversionInternalsHost) { + url.host_piece() == kChromeUIConversionInternalsHost || + url.host_piece() == kChromeUIUkmHost) { return const_cast<ContentWebUIControllerFactory*>(this); } return WebUI::kNoWebUI; @@ -90,6 +92,8 @@ ContentWebUIControllerFactory::CreateWebUIControllerForURL(WebUI* web_ui, return std::make_unique<ProcessInternalsUI>(web_ui); if (url.host_piece() == kChromeUIConversionInternalsHost) return std::make_unique<ConversionInternalsUI>(web_ui); + if (url.host_piece() == kChromeUIUkmHost) + return std::make_unique<UkmInternalsUI>(web_ui); return nullptr; } diff --git a/chromium/content/browser/webui/shared_resources_data_source.cc b/chromium/content/browser/webui/shared_resources_data_source.cc index 91ce33a80d0..402caeee8eb 100644 --- a/chromium/content/browser/webui/shared_resources_data_source.cc +++ b/chromium/content/browser/webui/shared_resources_data_source.cc @@ -12,6 +12,7 @@ #include "base/files/file_path.h" #include "base/memory/ref_counted_memory.h" #include "base/notreached.h" +#include "base/strings/strcat.h" #include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "base/task/post_task.h" @@ -28,6 +29,9 @@ #include "content/public/common/url_constants.h" #include "mojo/public/js/grit/mojo_bindings_resources.h" #include "mojo/public/js/grit/mojo_bindings_resources_map.h" +#include "services/network/public/mojom/content_security_policy.mojom.h" +#include "skia/grit/skia_resources.h" +#include "skia/grit/skia_resources_map.h" #include "ui/base/layout.h" #include "ui/base/webui/web_ui_util.h" #include "ui/resources/grit/webui_resources.h" @@ -141,6 +145,17 @@ const std::map<int, std::string> CreateMojoResourceIdToAliasMap() { }; } +const std::map<int, std::string> CreateSkiaResourceIdToAliasMap() { + return std::map<int, std::string>{ + {IDR_SKIA_BITMAP_MOJOM_LITE_JS, + "mojo/skia/public/mojom/bitmap.mojom-lite.js"}, + {IDR_SKIA_IMAGE_INFO_MOJOM_LITE_JS, + "mojo/skia/public/mojom/image_info.mojom-lite.js"}, + {IDR_SKIA_SKCOLOR_MOJOM_LITE_JS, + "mojo/skia/public/mojom/skcolor.mojom-lite.js"}, + }; +} + #if defined(OS_CHROMEOS) const std::map<int, std::string> CreateChromeosMojoResourceIdToAliasMap() { return std::map<int, std::string>{ @@ -186,6 +201,12 @@ const std::map<int, std::string> CreateChromeosMojoResourceIdToAliasMap() { {IDR_IP_ADDRESS_MOJOM_LITE_JS, "mojo/services/network/public/mojom/" "ip_address.mojom-lite.js"}, + {IDR_NETWORK_HEALTH_MOJOM_HTML, + "mojo/chromeos/services/network_health/public/mojom/" + "network_health.mojom.html"}, + {IDR_NETWORK_HEALTH_MOJOM_LITE_JS, + "mojo/chromeos/services/network_health/public/mojom/" + "network_health.mojom-lite.js"}, }; } #endif // !defined(OS_CHROMEOS) @@ -270,6 +291,8 @@ const ResourcesMap* CreateResourcesMap() { AddAliasedResourcesToMap(CreateMojoResourceIdToAliasMap(), kMojoBindingsResources, kMojoBindingsResourcesSize, result); + AddAliasedResourcesToMap(CreateSkiaResourceIdToAliasMap(), kSkiaResources, + kSkiaResourcesSize, result); #if defined(OS_CHROMEOS) AddAliasedResourcesToMap(CreateChromeosMojoResourceIdToAliasMap(), kChromeosResources, kChromeosResourcesSize, result); @@ -291,14 +314,39 @@ int GetIdrForPath(const std::string& path) { } // namespace -SharedResourcesDataSource::SharedResourcesDataSource() { +// static +std::unique_ptr<SharedResourcesDataSource> +SharedResourcesDataSource::CreateForChromeScheme() { + return std::make_unique<SharedResourcesDataSource>(PassKey(), + kChromeUIScheme); } -SharedResourcesDataSource::~SharedResourcesDataSource() { +// static +std::unique_ptr<SharedResourcesDataSource> +SharedResourcesDataSource::CreateForChromeUntrustedScheme() { + return std::make_unique<SharedResourcesDataSource>(PassKey(), + kChromeUIUntrustedScheme); } +SharedResourcesDataSource::SharedResourcesDataSource(PassKey, + const std::string& scheme) + : scheme_(scheme) {} + +SharedResourcesDataSource::~SharedResourcesDataSource() = default; + std::string SharedResourcesDataSource::GetSource() { - return kChromeUIResourcesHost; + // URLDataManagerBackend assumes that chrome:// data sources return just the + // hostname for GetSource(). + if (scheme_ == kChromeUIScheme) + return kChromeUIResourcesHost; + + // We only expect chrome-untrusted:// scheme at this point. + DCHECK_EQ(kChromeUIUntrustedScheme, scheme_); + + // Other schemes (i.e. chrome-untrusted://) return the scheme and host + // together. + return base::StrCat( + {scheme_, url::kStandardSchemeSeparator, kChromeUIResourcesHost}); } void SharedResourcesDataSource::StartDataRequest( @@ -386,12 +434,12 @@ bool SharedResourcesDataSource::ShouldServeMimeTypeAsContentTypeHeader() { std::string SharedResourcesDataSource::GetAccessControlAllowOriginForOrigin( const std::string& origin) { - // For now we give access only for "chrome://*" origins. + // For now we give access only for origins with the allowed scheme. // According to CORS spec, Access-Control-Allow-Origin header doesn't support // wildcards, so we need to set its value explicitly by passing the |origin| // back. - std::string allowed_origin_prefix = kChromeUIScheme; - allowed_origin_prefix += "://"; + const std::string allowed_origin_prefix = + base::StrCat({scheme_, url::kStandardSchemeSeparator}); if (!base::StartsWith(origin, allowed_origin_prefix, base::CompareCase::SENSITIVE)) { return "null"; @@ -399,8 +447,13 @@ std::string SharedResourcesDataSource::GetAccessControlAllowOriginForOrigin( return origin; } -std::string SharedResourcesDataSource::GetContentSecurityPolicyWorkerSrc() { - return "worker-src blob: 'self';"; +std::string SharedResourcesDataSource::GetContentSecurityPolicy( + network::mojom::CSPDirectiveName directive) { + if (directive == network::mojom::CSPDirectiveName::WorkerSrc) { + return "worker-src blob: 'self';"; + } + + return content::URLDataSource::GetContentSecurityPolicy(directive); } #if defined(OS_CHROMEOS) diff --git a/chromium/content/browser/webui/shared_resources_data_source.h b/chromium/content/browser/webui/shared_resources_data_source.h index 4500bb40c17..9da35b581bf 100644 --- a/chromium/content/browser/webui/shared_resources_data_source.h +++ b/chromium/content/browser/webui/shared_resources_data_source.h @@ -8,16 +8,34 @@ #include <string> #include "base/compiler_specific.h" -#include "base/macros.h" -#include "base/single_thread_task_runner.h" +#include "base/util/type_safety/pass_key.h" #include "content/public/browser/url_data_source.h" namespace content { -// A DataSource for chrome://resources/ URLs. +// A DataSource for chrome://resources/ and chrome-untrusted://resources/ URLs. +// TODO(https://crbug.com/866236): chrome-untrusted://resources/ is not +// currently fully functional, as some resources have absolute +// chrome://resources URLs. If you need access to chrome-untrusted://resources/ +// resources that are not currently functional, it is up to you to get them +// working. class SharedResourcesDataSource : public URLDataSource { public: - SharedResourcesDataSource(); + using PassKey = util::PassKey<SharedResourcesDataSource>; + + // Creates a SharedResourcesDataSource instance for chrome://resources. + static std::unique_ptr<SharedResourcesDataSource> CreateForChromeScheme(); + + // Creates a SharedResourcesDataSource instance for + // chrome-untrusted://resources. + static std::unique_ptr<SharedResourcesDataSource> + CreateForChromeUntrustedScheme(); + + SharedResourcesDataSource(PassKey, const std::string& scheme); + SharedResourcesDataSource(const SharedResourcesDataSource&) = delete; + SharedResourcesDataSource& operator=(const SharedResourcesDataSource&) = + delete; + ~SharedResourcesDataSource() override; // URLDataSource implementation. std::string GetSource() override; @@ -29,7 +47,8 @@ class SharedResourcesDataSource : public URLDataSource { bool ShouldServeMimeTypeAsContentTypeHeader() override; std::string GetAccessControlAllowOriginForOrigin( const std::string& origin) override; - std::string GetContentSecurityPolicyWorkerSrc() override; + std::string GetContentSecurityPolicy( + network::mojom::CSPDirectiveName directive) override; #if defined(OS_CHROMEOS) void DisablePolymer2ForHost(const std::string& host) override; #endif // defined (OS_CHROMEOS) @@ -41,9 +60,9 @@ class SharedResourcesDataSource : public URLDataSource { bool IsPolymer2DisabledForPage(const WebContents::Getter& wc_getter); #endif // defined (OS_CHROMEOS) - ~SharedResourcesDataSource() override; - - DISALLOW_COPY_AND_ASSIGN(SharedResourcesDataSource); + // The URL scheme this data source is accessed from, e.g. "chrome" or + // "chrome-untrusted". + const std::string scheme_; }; } // namespace content diff --git a/chromium/content/browser/webui/url_data_manager.cc b/chromium/content/browser/webui/url_data_manager.cc index 401b38a0088..ab81bf4e06e 100644 --- a/chromium/content/browser/webui/url_data_manager.cc +++ b/chromium/content/browser/webui/url_data_manager.cc @@ -16,7 +16,6 @@ #include "base/stl_util.h" #include "base/strings/string_util.h" #include "base/synchronization/lock.h" -#include "base/task/post_task.h" #include "base/thread_annotations.h" #include "content/browser/resource_context_impl.h" #include "content/browser/webui/url_data_manager_backend.h" @@ -105,8 +104,8 @@ void URLDataManager::DeleteDataSource(const URLDataSourceImpl* data_source) { } if (schedule_delete) { // Schedule a task to delete the DataSource back on the UI thread. - base::PostTask(FROM_HERE, {BrowserThread::UI}, - base::BindOnce(&URLDataManager::DeleteDataSources)); + GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&URLDataManager::DeleteDataSources)); } } diff --git a/chromium/content/browser/webui/url_data_manager_backend.cc b/chromium/content/browser/webui/url_data_manager_backend.cc index 2e4c8136128..a9d88c5b4cf 100644 --- a/chromium/content/browser/webui/url_data_manager_backend.cc +++ b/chromium/content/browser/webui/url_data_manager_backend.cc @@ -43,6 +43,7 @@ #include "net/url_request/url_request_error_job.h" #include "net/url_request/url_request_job.h" #include "net/url_request/url_request_job_factory.h" +#include "services/network/public/mojom/content_security_policy.mojom.h" #include "ui/base/template_expressions.h" #include "ui/base/webui/i18n_source_stream.h" #include "url/url_util.h" @@ -71,9 +72,17 @@ bool SchemeIsInSchemes(const std::string& scheme, } // namespace URLDataManagerBackend::URLDataManagerBackend() : next_request_id_(0) { - URLDataSource* shared_source = new SharedResourcesDataSource(); - AddDataSource(new URLDataSourceImpl(shared_source->GetSource(), - base::WrapUnique(shared_source))); + // Add a shared data source for chrome://resources. For chrome:// data sources + // we use the host name as the source name. + AddDataSource(new URLDataSourceImpl( + kChromeUIResourcesHost, + SharedResourcesDataSource::CreateForChromeScheme())); + + // Add a shared data source for chrome-untrusted://resources. For + // chrome-untrusted:// data sources we use the full origin as the source name. + AddDataSource(new URLDataSourceImpl( + kChromeUIUntrustedResourcesURL, + SharedResourcesDataSource::CreateForChromeUntrustedScheme())); } URLDataManagerBackend::~URLDataManagerBackend() = default; @@ -156,19 +165,31 @@ scoped_refptr<net::HttpResponseHeaders> URLDataManagerBackend::GetHeaders( // response headers. if (source->ShouldAddContentSecurityPolicy()) { std::string csp_header; - csp_header.append(source->GetContentSecurityPolicyChildSrc()); - csp_header.append(source->GetContentSecurityPolicyDefaultSrc()); - csp_header.append(source->GetContentSecurityPolicyImgSrc()); - csp_header.append(source->GetContentSecurityPolicyObjectSrc()); - csp_header.append(source->GetContentSecurityPolicyScriptSrc()); - csp_header.append(source->GetContentSecurityPolicyStyleSrc()); - csp_header.append(source->GetContentSecurityPolicyWorkerSrc()); + + const network::mojom::CSPDirectiveName kAllDirectives[] = { + network::mojom::CSPDirectiveName::ChildSrc, + network::mojom::CSPDirectiveName::DefaultSrc, + network::mojom::CSPDirectiveName::ImgSrc, + network::mojom::CSPDirectiveName::MediaSrc, + network::mojom::CSPDirectiveName::ObjectSrc, + network::mojom::CSPDirectiveName::ScriptSrc, + network::mojom::CSPDirectiveName::StyleSrc, + network::mojom::CSPDirectiveName::WorkerSrc, + network::mojom::CSPDirectiveName::ConnectSrc}; + + for (auto& directive : kAllDirectives) { + csp_header.append(source->GetContentSecurityPolicy(directive)); + } + // TODO(crbug.com/1051745): Both CSP frame ancestors and XFO headers may be // added to the response but frame ancestors would take precedence. In the // future, XFO will be removed so when that happens remove the check and // always add frame ancestors. - if (source->ShouldDenyXFrameOptions()) - csp_header.append(source->GetContentSecurityPolicyFrameAncestors()); + if (source->ShouldDenyXFrameOptions()) { + csp_header.append(source->GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::FrameAncestors)); + } + headers->SetHeader(kChromeURLContentSecurityPolicyHeaderName, csp_header); } @@ -218,13 +239,12 @@ bool URLDataManagerBackend::CheckURLIsValid(const GURL& url) { } bool URLDataManagerBackend::IsValidNetworkErrorCode(int error_code) { - std::unique_ptr<base::DictionaryValue> error_codes = net::GetNetConstants(); + base::Value error_codes = net::GetNetConstants(); const base::DictionaryValue* net_error_codes_dict = nullptr; - for (base::DictionaryValue::Iterator itr(*error_codes); !itr.IsAtEnd(); - itr.Advance()) { - if (itr.key() == kNetworkErrorKey) { - itr.value().GetAsDictionary(&net_error_codes_dict); + for (const auto& item : error_codes.DictItems()) { + if (item.first == kNetworkErrorKey) { + item.second.GetAsDictionary(&net_error_codes_dict); break; } } diff --git a/chromium/content/browser/webui/web_ui_data_source_impl.cc b/chromium/content/browser/webui/web_ui_data_source_impl.cc index 6df18f9c6e2..3b43e4950ab 100644 --- a/chromium/content/browser/webui/web_ui_data_source_impl.cc +++ b/chromium/content/browser/webui/web_ui_data_source_impl.cc @@ -21,6 +21,7 @@ #include "content/public/browser/content_browser_client.h" #include "content/public/common/content_client.h" #include "content/public/common/url_constants.h" +#include "services/network/public/mojom/content_security_policy.mojom.h" #include "ui/base/template_expressions.h" #include "ui/base/webui/jstemplate_builder.h" #include "ui/base/webui/web_ui_util.h" @@ -80,42 +81,21 @@ class WebUIDataSourceImpl::InternalDataSource : public URLDataSource { } bool AllowCaching() override { return false; } bool ShouldAddContentSecurityPolicy() override { return parent_->add_csp_; } - std::string GetContentSecurityPolicyChildSrc() override { - return parent_->child_src_.value_or( - URLDataSource::GetContentSecurityPolicyChildSrc()); - } - std::string GetContentSecurityPolicyDefaultSrc() override { - return parent_->default_src_.value_or( - URLDataSource::GetContentSecurityPolicyDefaultSrc()); - } - std::string GetContentSecurityPolicyImgSrc() override { - return parent_->img_src_.value_or( - URLDataSource::GetContentSecurityPolicyImgSrc()); - } - std::string GetContentSecurityPolicyObjectSrc() override { - return parent_->object_src_.value_or( - URLDataSource::GetContentSecurityPolicyObjectSrc()); - } - std::string GetContentSecurityPolicyScriptSrc() override { - return parent_->script_src_.value_or( - URLDataSource::GetContentSecurityPolicyScriptSrc()); - } - std::string GetContentSecurityPolicyStyleSrc() override { - return parent_->style_src_.value_or( - URLDataSource::GetContentSecurityPolicyStyleSrc()); - } - std::string GetContentSecurityPolicyWorkerSrc() override { - return parent_->worker_src_.value_or( - URLDataSource::GetContentSecurityPolicyWorkerSrc()); - } - std::string GetContentSecurityPolicyFrameAncestors() override { - std::string frame_ancestors; - if (parent_->frame_ancestors_.size() == 0) - frame_ancestors += " 'none'"; - for (const GURL& frame_ancestor : parent_->frame_ancestors_) { - frame_ancestors += " " + frame_ancestor.spec(); + std::string GetContentSecurityPolicy( + network::mojom::CSPDirectiveName directive) override { + if (parent_->csp_overrides_.contains(directive)) { + return parent_->csp_overrides_.at(directive); + } else if (directive == network::mojom::CSPDirectiveName::FrameAncestors) { + std::string frame_ancestors; + if (parent_->frame_ancestors_.size() == 0) + frame_ancestors += " 'none'"; + for (const GURL& frame_ancestor : parent_->frame_ancestors_) { + frame_ancestors += " " + frame_ancestor.spec(); + } + return "frame-ancestors" + frame_ancestors + ";"; } - return "frame-ancestors" + frame_ancestors + ";"; + + return URLDataSource::GetContentSecurityPolicy(directive); } bool ShouldDenyXFrameOptions() override { return parent_->deny_xframe_options_; @@ -214,39 +194,10 @@ void WebUIDataSourceImpl::DisableContentSecurityPolicy() { add_csp_ = false; } -void WebUIDataSourceImpl::OverrideContentSecurityPolicyChildSrc( - const std::string& data) { - child_src_ = data; -} - -void WebUIDataSourceImpl::OverrideContentSecurityPolicyDefaultSrc( - const std::string& data) { - default_src_ = data; -} - -void WebUIDataSourceImpl::OverrideContentSecurityPolicyImgSrc( - const std::string& data) { - img_src_ = data; -} - -void WebUIDataSourceImpl::OverrideContentSecurityPolicyObjectSrc( - const std::string& data) { - object_src_ = data; -} - -void WebUIDataSourceImpl::OverrideContentSecurityPolicyScriptSrc( - const std::string& data) { - script_src_ = data; -} - -void WebUIDataSourceImpl::OverrideContentSecurityPolicyStyleSrc( - const std::string& data) { - style_src_ = data; -} - -void WebUIDataSourceImpl::OverrideContentSecurityPolicyWorkerSrc( - const std::string& data) { - worker_src_ = data; +void WebUIDataSourceImpl::OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName directive, + const std::string& value) { + csp_overrides_.insert_or_assign(directive, value); } void WebUIDataSourceImpl::AddFrameAncestor(const GURL& frame_ancestor) { diff --git a/chromium/content/browser/webui/web_ui_data_source_impl.h b/chromium/content/browser/webui/web_ui_data_source_impl.h index 7d8c45420c7..626edd936c2 100644 --- a/chromium/content/browser/webui/web_ui_data_source_impl.h +++ b/chromium/content/browser/webui/web_ui_data_source_impl.h @@ -12,6 +12,7 @@ #include "base/callback.h" #include "base/compiler_specific.h" +#include "base/containers/flat_map.h" #include "base/gtest_prod_util.h" #include "base/macros.h" #include "base/values.h" @@ -45,14 +46,8 @@ class CONTENT_EXPORT WebUIDataSourceImpl : public URLDataSourceImpl, handle_request_callback) override; void DisableReplaceExistingSource() override; void DisableContentSecurityPolicy() override; - void OverrideContentSecurityPolicyChildSrc(const std::string& data) override; - void OverrideContentSecurityPolicyDefaultSrc( - const std::string& data) override; - void OverrideContentSecurityPolicyImgSrc(const std::string& data) override; - void OverrideContentSecurityPolicyObjectSrc(const std::string& data) override; - void OverrideContentSecurityPolicyScriptSrc(const std::string& data) override; - void OverrideContentSecurityPolicyStyleSrc(const std::string& data) override; - void OverrideContentSecurityPolicyWorkerSrc(const std::string& data) override; + void OverrideContentSecurityPolicy(network::mojom::CSPDirectiveName directive, + const std::string& value) override; void DisableDenyXFrameOptions() override; void EnableReplaceI18nInJS() override; std::string GetSource() override; @@ -116,13 +111,7 @@ class CONTENT_EXPORT WebUIDataSourceImpl : public URLDataSourceImpl, bool add_csp_ = true; - base::Optional<std::string> child_src_; - base::Optional<std::string> default_src_; - base::Optional<std::string> img_src_; - base::Optional<std::string> object_src_; - base::Optional<std::string> script_src_; - base::Optional<std::string> style_src_; - base::Optional<std::string> worker_src_; + base::flat_map<network::mojom::CSPDirectiveName, std::string> csp_overrides_; bool deny_xframe_options_ = true; bool add_load_time_data_defaults_ = true; bool replace_existing_source_ = true; diff --git a/chromium/content/browser/webui/web_ui_data_source_unittest.cc b/chromium/content/browser/webui/web_ui_data_source_unittest.cc index 36d70553e12..7ec74093d48 100644 --- a/chromium/content/browser/webui/web_ui_data_source_unittest.cc +++ b/chromium/content/browser/webui/web_ui_data_source_unittest.cc @@ -9,6 +9,7 @@ #include "content/browser/webui/web_ui_data_source_impl.h" #include "content/public/test/browser_task_environment.h" #include "content/test/test_content_client.h" +#include "services/network/public/mojom/content_security_policy.mojom.h" #include "testing/gtest/include/gtest/gtest.h" namespace content { @@ -274,41 +275,76 @@ TEST_F(WebUIDataSourceTest, SetCspValues) { // Default values. EXPECT_EQ("child-src 'none';", - url_data_source->GetContentSecurityPolicyChildSrc()); - EXPECT_EQ("", url_data_source->GetContentSecurityPolicyDefaultSrc()); - EXPECT_EQ("", url_data_source->GetContentSecurityPolicyImgSrc()); + url_data_source->GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::ChildSrc)); + EXPECT_EQ("", url_data_source->GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::DefaultSrc)); + EXPECT_EQ("", url_data_source->GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::ImgSrc)); + EXPECT_EQ("", url_data_source->GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::MediaSrc)); EXPECT_EQ("object-src 'none';", - url_data_source->GetContentSecurityPolicyObjectSrc()); + url_data_source->GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::ObjectSrc)); EXPECT_EQ("script-src chrome://resources 'self';", - url_data_source->GetContentSecurityPolicyScriptSrc()); - EXPECT_EQ("", url_data_source->GetContentSecurityPolicyStyleSrc()); + url_data_source->GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::ScriptSrc)); + EXPECT_EQ("", url_data_source->GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::StyleSrc)); + EXPECT_EQ("", url_data_source->GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::ConnectSrc)); // Override each directive and test it updates the underlying URLDataSource. - source()->OverrideContentSecurityPolicyChildSrc("child-src 'self';"); + source()->OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName::ChildSrc, "child-src 'self';"); EXPECT_EQ("child-src 'self';", - url_data_source->GetContentSecurityPolicyChildSrc()); + url_data_source->GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::ChildSrc)); - source()->OverrideContentSecurityPolicyDefaultSrc("default-src 'self';"); + source()->OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName::DefaultSrc, "default-src 'self';"); EXPECT_EQ("default-src 'self';", - url_data_source->GetContentSecurityPolicyDefaultSrc()); + url_data_source->GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::DefaultSrc)); - source()->OverrideContentSecurityPolicyImgSrc("img-src 'self' blob:;"); + source()->OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName::ImgSrc, "img-src 'self' blob:;"); EXPECT_EQ("img-src 'self' blob:;", - url_data_source->GetContentSecurityPolicyImgSrc()); + url_data_source->GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::ImgSrc)); - source()->OverrideContentSecurityPolicyObjectSrc("object-src 'self' data:;"); + source()->OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName::MediaSrc, "media-src 'self' blob:;"); + EXPECT_EQ("media-src 'self' blob:;", + url_data_source->GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::MediaSrc)); + + source()->OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName::ObjectSrc, "object-src 'self' data:;"); EXPECT_EQ("object-src 'self' data:;", - url_data_source->GetContentSecurityPolicyObjectSrc()); + url_data_source->GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::ObjectSrc)); - source()->OverrideContentSecurityPolicyScriptSrc( + source()->OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName::ScriptSrc, "script-src chrome://resources 'self' 'unsafe-inline';"); EXPECT_EQ("script-src chrome://resources 'self' 'unsafe-inline';", - url_data_source->GetContentSecurityPolicyScriptSrc()); + url_data_source->GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::ScriptSrc)); - source()->OverrideContentSecurityPolicyStyleSrc( + source()->OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName::StyleSrc, "style-src 'self' 'unsafe-inline';"); EXPECT_EQ("style-src 'self' 'unsafe-inline';", - url_data_source->GetContentSecurityPolicyStyleSrc()); + url_data_source->GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::StyleSrc)); + + source()->OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName::ConnectSrc, + "connect-src 'self' 'unsafe-inline';"); + EXPECT_EQ("connect-src 'self' 'unsafe-inline';", + url_data_source->GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::ConnectSrc)); } } // namespace content diff --git a/chromium/content/browser/webui/web_ui_navigation_browsertest.cc b/chromium/content/browser/webui/web_ui_navigation_browsertest.cc index 6d0542244bf..c77fb54c3fd 100644 --- a/chromium/content/browser/webui/web_ui_navigation_browsertest.cc +++ b/chromium/content/browser/webui/web_ui_navigation_browsertest.cc @@ -7,6 +7,7 @@ #include "build/build_config.h" #include "content/browser/child_process_security_policy_impl.h" #include "content/browser/frame_host/frame_tree_node.h" +#include "content/browser/frame_host/render_frame_host_impl.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/browser/web_contents.h" #include "content/public/common/bindings_policy.h" @@ -35,6 +36,15 @@ const char kAddIframeScript[] = "frame.src = $1;\n" "document.body.appendChild(frame);\n"; +content::mojom::OpenURLParamsPtr CreateOpenURLParams(const GURL& url) { + auto params = content::mojom::OpenURLParams::New(); + params->url = url; + params->disposition = WindowOpenDisposition::CURRENT_TAB; + params->should_replace_current_entry = false; + params->user_gesture = true; + return params; +} + } // namespace class WebUINavigationBrowserTest : public ContentBrowserTest { @@ -241,7 +251,7 @@ IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest, WebFrameInChromeUntrustedSchemeAllowedByCSP) { // Add a DataSource with no iframe restrictions. TestUntrustedDataSourceCSP csp; - csp.child_src = ""; + csp.child_src = "child-src * data:;"; AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(), "test-host", csp); GURL main_frame_url(GetChromeUntrustedUIURL("test-host/title1.html")); @@ -378,14 +388,12 @@ IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest, } // Verify that a browser check stops websites from embeding chrome:// iframes. -// This tests the FrameHostMsg_OpenURL path. +// This tests the OpenURL Mojo method. IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest, DisallowEmbeddingChromeSchemeFromWebFrameBrowserCheck) { GURL main_frame_url(embedded_test_server()->GetURL("/title1.html")); EXPECT_TRUE(NavigateToURL(shell(), main_frame_url)); - GURL webui_url(GetWebUIURL("web-ui/title1.html?noxfo=true")); - // Add iframe but don't navigate it to a chrome:// URL yet. EXPECT_TRUE(ExecJs(shell(), "var frame = document.createElement('iframe');\n" @@ -403,8 +411,8 @@ IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest, // This bypasses the renderer-side check that would have stopped the // navigation. TestNavigationObserver observer(shell()->web_contents()); - content::PwnMessageHelper::OpenURL(child->GetProcess(), child->GetRoutingID(), - webui_url); + static_cast<content::RenderFrameHostImpl*>(child)->OpenURL( + CreateOpenURLParams(GetWebUIURL("web-ui/title1.html?noxfo=true"))); observer.Wait(); child = root->child_at(0)->current_frame_host(); @@ -412,7 +420,7 @@ IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest, } // Verify that a browser check stops websites from embeding chrome-untrusted:// -// iframes. This tests the FrameHostMsg_OpenURL path. +// iframes. This tests the OpenURL Mojo method path. IN_PROC_BROWSER_TEST_F( WebUINavigationBrowserTest, DisallowEmbeddingChromeUntrustedSchemeFromWebFrameBrowserCheck) { @@ -424,8 +432,6 @@ IN_PROC_BROWSER_TEST_F( AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(), "test-iframe-host", csp); - GURL untrusted_url(GetChromeUntrustedUIURL("test-iframe-host/title1.html")); - // Add iframe but don't navigate it to a chrome-untrusted:// URL yet. EXPECT_TRUE(ExecJs(shell(), "var frame = document.createElement('iframe');\n" @@ -439,11 +445,12 @@ IN_PROC_BROWSER_TEST_F( RenderFrameHost* child = root->child_at(0)->current_frame_host(); EXPECT_EQ("about:blank", child->GetLastCommittedURL()); - // Simulate an IPC message to navigate the subframe to a - // chrome-untrusted:// URL. + // Simulate a Mojo message to navigate the subframe to a chrome-untrusted:// + // URL. TestNavigationObserver observer(shell()->web_contents()); - content::PwnMessageHelper::OpenURL(child->GetProcess(), child->GetRoutingID(), - untrusted_url); + static_cast<content::RenderFrameHostImpl*>(child)->OpenURL( + CreateOpenURLParams( + GetChromeUntrustedUIURL("test-iframe-host/title1.html"))); observer.Wait(); child = root->child_at(0)->current_frame_host(); @@ -642,16 +649,17 @@ IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest, FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) ->GetFrameTree() ->root(); - RenderFrameHost* webui_rfh = root->current_frame_host(); - scoped_refptr<SiteInstance> webui_site_instance = + RenderFrameHostImpl* webui_rfh = root->current_frame_host(); + scoped_refptr<SiteInstanceImpl> webui_site_instance = webui_rfh->GetSiteInstance(); EXPECT_EQ(main_frame_url, webui_rfh->GetLastCommittedURL()); EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings( webui_rfh->GetProcess()->GetID())); + EXPECT_FALSE(webui_site_instance->lock_url().is_empty()); EXPECT_EQ(ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock( root->current_frame_host()->GetProcess()->GetID()), - webui_site_instance->GetSiteURL()); + webui_site_instance->lock_url()); TestUntrustedDataSourceCSP csp; std::vector<std::string> frame_ancestors({"chrome://web-ui"}); @@ -836,8 +844,8 @@ IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest, WebUIMainFrameToWebAllowed) { FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) ->GetFrameTree() ->root(); - RenderFrameHost* webui_rfh = root->current_frame_host(); - scoped_refptr<SiteInstance> webui_site_instance = + RenderFrameHostImpl* webui_rfh = root->current_frame_host(); + scoped_refptr<SiteInstanceImpl> webui_site_instance = webui_rfh->GetSiteInstance(); EXPECT_EQ(chrome_url, webui_rfh->GetLastCommittedURL()); @@ -845,7 +853,7 @@ IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest, WebUIMainFrameToWebAllowed) { webui_rfh->GetProcess()->GetID())); EXPECT_EQ(ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock( root->current_frame_host()->GetProcess()->GetID()), - webui_site_instance->GetSiteURL()); + webui_site_instance->lock_url()); GURL web_url(embedded_test_server()->GetURL("/title2.html")); std::string script = @@ -864,7 +872,7 @@ IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest, WebUIMainFrameToWebAllowed) { root->current_frame_host()->GetProcess()->GetID())); EXPECT_NE(ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock( root->current_frame_host()->GetProcess()->GetID()), - webui_site_instance->GetSiteURL()); + webui_site_instance->lock_url()); } #if !defined(OS_ANDROID) diff --git a/chromium/content/browser/webui/web_ui_security_browsertest.cc b/chromium/content/browser/webui/web_ui_security_browsertest.cc index 8e53304c2ab..2fe4c5914c2 100644 --- a/chromium/content/browser/webui/web_ui_security_browsertest.cc +++ b/chromium/content/browser/webui/web_ui_security_browsertest.cc @@ -7,20 +7,25 @@ #include "base/hash/hash.h" #include "base/memory/ref_counted_memory.h" #include "base/path_service.h" +#include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" #include "base/test/scoped_feature_list.h" #include "base/threading/thread_restrictions.h" +#include "build/build_config.h" #include "content/browser/child_process_security_policy_impl.h" #include "content/browser/frame_host/frame_tree_node.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/browser/webui/web_ui_controller_factory_registry.h" +#include "content/common/content_navigation_policy.h" #include "content/public/browser/site_isolation_policy.h" +#include "content/public/browser/url_data_source.h" #include "content/public/browser/web_ui.h" #include "content/public/browser/web_ui_controller.h" #include "content/public/browser/web_ui_data_source.h" #include "content/public/common/bindings_policy.h" #include "content/public/common/content_features.h" #include "content/public/common/content_paths.h" +#include "content/public/common/url_constants.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/content_browser_test.h" @@ -36,6 +41,25 @@ namespace content { +namespace { + +// Loads a given module script. The promise resolves to true if the script loads +// successfully, and false otherwise. +const char kAddScriptModuleScript[] = + "new Promise((resolve, reject) => {\n" + " const script = document.createElement('script');\n" + " script.src = $1;\n" + " script.type = 'module';\n" + " script.onload = () => resolve(true);\n" + " script.onerror = () => resolve(false);\n" + " document.body.appendChild(script);\n" + "});\n"; + +// Path to an existing chrome-untrusted://resources script. +const char kSharedResourcesModuleJsPath[] = "resources/js/assert.m.js"; + +} // namespace + class WebUISecurityTest : public ContentBrowserTest { public: WebUISecurityTest() { WebUIControllerFactory::RegisterFactory(&factory_); } @@ -295,7 +319,11 @@ IN_PROC_BROWSER_TEST_F(WebUISecurityTest, WebUIReuseInSubframe) { } EXPECT_EQ(initial_site_instance, child->current_frame_host()->GetSiteInstance()); - EXPECT_EQ(initial_web_ui, child->current_frame_host()->web_ui()); + if (CreateNewHostForSameSiteSubframe()) { + EXPECT_NE(initial_web_ui, child->current_frame_host()->web_ui()); + } else { + EXPECT_EQ(initial_web_ui, child->current_frame_host()->web_ui()); + } // Navigate the child frame cross-site. GURL subframe_cross_site_url(GetWebUIURL("web-ui-subframe/title1.html")); @@ -328,7 +356,11 @@ IN_PROC_BROWSER_TEST_F(WebUISecurityTest, WebUIReuseInSubframe) { } EXPECT_EQ(second_site_instance, child->current_frame_host()->GetSiteInstance()); - EXPECT_EQ(second_web_ui, child->current_frame_host()->web_ui()); + if (CreateNewHostForSameSiteSubframe()) { + EXPECT_NE(second_web_ui, child->current_frame_host()->web_ui()); + } else { + EXPECT_EQ(second_web_ui, child->current_frame_host()->web_ui()); + } // Navigate back to the first document in the subframe, which should bring // it back to the initial SiteInstance, but use a different RenderFrameHost @@ -428,81 +460,6 @@ IN_PROC_BROWSER_TEST_F(WebUISecurityTest, WebUIFailedNavigation) { EXPECT_EQ(0, root->current_frame_host()->GetEnabledBindings()); } -// Verify fetch request to chrome-untrusted:// is blocked. -IN_PROC_BROWSER_TEST_F(WebUISecurityTest, - DisallowFetchRequestToChromeUntrusted) { - ASSERT_TRUE(embedded_test_server()->Start()); - GURL web_url(embedded_test_server()->GetURL("/title2.html")); - AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(), - "test-host"); - - EXPECT_TRUE(NavigateToURL(shell(), web_url)); - EXPECT_EQ(web_url, shell()->web_contents()->GetLastCommittedURL()); - - const char kFetchRequestScript[] = - "(async () => {" - " try {" - " let response = await fetch($1); " - " }" - " catch (e) {" - " return e.message;" - " }" - " throw 'Fetch should fail';" - "})();"; - { - GURL untrusted_url(GetChromeUntrustedUIURL("test-host/script.js")); - WebContentsConsoleObserver console_observer(shell()->web_contents()); - console_observer.SetPattern( - "Fetch API cannot load " + untrusted_url.spec() + - ". URL scheme must be \"http\" or \"https\" for CORS request."); - - EXPECT_EQ("Failed to fetch", - EvalJs(shell(), JsReplace(kFetchRequestScript, untrusted_url), - EXECUTE_SCRIPT_DEFAULT_OPTIONS, 1 /* world_id */)); - console_observer.Wait(); - } -} - -// Verify XHR request to chrome-untrusted:// is blocked. -IN_PROC_BROWSER_TEST_F(WebUISecurityTest, DisallowXHRRequestToChromeUntrusted) { - ASSERT_TRUE(embedded_test_server()->Start()); - GURL web_url(embedded_test_server()->GetURL("/title2.html")); - AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(), - "test-host"); - - EXPECT_TRUE(NavigateToURL(shell(), web_url)); - EXPECT_EQ(web_url, shell()->web_contents()->GetLastCommittedURL()); - - const char kXHRRequest[] = - "new Promise((resolve) => {" - " const xhttp = new XMLHttpRequest();" - " xhttp.open('GET', $1, true);" - " xhttp.onload = () => { " - " resolve('Request should have failed');" - " };" - " xhttp.onerror = () => {" - " resolve('Request failed');" - " };" - " xhttp.send();" - "}); "; - { - GURL untrusted_url(GetChromeUntrustedUIURL("test-host/script.js")); - const std::string host = web_url.GetOrigin().spec(); - - WebContentsConsoleObserver console_observer(shell()->web_contents()); - console_observer.SetPattern( - "Access to XMLHttpRequest at '" + untrusted_url.spec() + - "' from origin '" + host.substr(0, host.length() - 1) + - "' has been blocked by CORS policy: Cross origin requests are only " - "supported for protocol schemes: http, data, chrome, https."); - - EXPECT_EQ("Request failed", - EvalJs(shell(), JsReplace(kXHRRequest, untrusted_url), - EXECUTE_SCRIPT_DEFAULT_OPTIONS, 1 /* world_id */)); - console_observer.Wait(); - } -} - // Verify load script from chrome-untrusted:// is blocked. IN_PROC_BROWSER_TEST_F(WebUISecurityTest, DisallowResourceRequestToChromeUntrusted) { @@ -537,6 +494,46 @@ IN_PROC_BROWSER_TEST_F(WebUISecurityTest, } } +#if defined(OS_ANDROID) +// TODO(https://crbug.com/1085196): This sometimes fails on Android bots. +#define MAYBE_ChromeUntrustedFramesCanUseChromeUntrustedResources \ + DISABLED_ChromeUntrustedFramesCanUseChromeUntrustedResources +#else +#define MAYBE_ChromeUntrustedFramesCanUseChromeUntrustedResources \ + ChromeUntrustedFramesCanUseChromeUntrustedResources +#endif // defined(OS_ANDROID) +IN_PROC_BROWSER_TEST_F( + WebUISecurityTest, + MAYBE_ChromeUntrustedFramesCanUseChromeUntrustedResources) { + // Add a DataSource whose CSP allows chrome-untrusted://resources scripts. + TestUntrustedDataSourceCSP csp; + csp.script_src = "script-src chrome-untrusted://resources;"; + AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(), + "test-host", csp); + GURL main_frame_url(GetChromeUntrustedUIURL("test-host/title1.html")); + EXPECT_TRUE(NavigateToURL(shell(), main_frame_url)); + + // A chrome-untrusted://resources resources should load successfully. + GURL script_url = GetChromeUntrustedUIURL(kSharedResourcesModuleJsPath); + EXPECT_TRUE(EvalJs(shell(), JsReplace(kAddScriptModuleScript, script_url), + EXECUTE_SCRIPT_DEFAULT_OPTIONS, 1 /* world_id */) + .ExtractBool()); +} + +// Verify that websites cannot access chrome-untrusted://resources scripts. +IN_PROC_BROWSER_TEST_F(WebUISecurityTest, + DisallowChromeUntrustedResourcesFromWebFrame) { + ASSERT_TRUE(embedded_test_server()->Start()); + GURL main_frame_url(embedded_test_server()->GetURL("/title1.html")); + EXPECT_TRUE(NavigateToURL(shell(), main_frame_url)); + + // A chrome-untrusted://resources resources should fail to load. + GURL script_url = GetChromeUntrustedUIURL(kSharedResourcesModuleJsPath); + EXPECT_FALSE(EvalJs(shell(), JsReplace(kAddScriptModuleScript, script_url), + EXECUTE_SCRIPT_DEFAULT_OPTIONS, 1 /* world_id */) + .ExtractBool()); +} + class WebUISecurityTestWithWebUIReportOnlyTrustedTypesEnabled : public WebUISecurityTest { public: @@ -578,4 +575,341 @@ IN_PROC_BROWSER_TEST_F(WebUISecurityTestWithWebUIReportOnlyTrustedTypesEnabled, } } +namespace { +class UntrustedSourceWithCorsSupport : public URLDataSource { + public: + static std::unique_ptr<UntrustedSourceWithCorsSupport> CreateForHost( + std::string host) { + std::string source_name = base::StrCat( + {kChromeUIUntrustedScheme, url::kStandardSchemeSeparator, host, "/"}); + return std::make_unique<UntrustedSourceWithCorsSupport>(source_name); + } + explicit UntrustedSourceWithCorsSupport(std::string name) : name_(name) {} + UntrustedSourceWithCorsSupport& operator=( + const UntrustedSourceWithCorsSupport&) = delete; + UntrustedSourceWithCorsSupport(const UntrustedSourceWithCorsSupport&) = + delete; + ~UntrustedSourceWithCorsSupport() override = default; + + // URLDataSource: + std::string GetSource() override { return name_; } + std::string GetAccessControlAllowOriginForOrigin( + const std::string& origin) override { + return origin; + } + std::string GetMimeType(const std::string& path) override { + return "text/html"; + } + void StartDataRequest(const GURL& url, + const WebContents::Getter& wc_getter, + GotDataCallback callback) override { + std::string dummy_html = "<html><body>dummy</body></html>"; + scoped_refptr<base::RefCountedString> response = + base::RefCountedString::TakeString(&dummy_html); + std::move(callback).Run(response.get()); + } + + private: + std::string name_; +}; + +enum FetchMode { SAME_ORIGIN, CORS, NO_CORS }; + +EvalJsResult PerformFetch(Shell* shell, + const GURL& fetch_url, + FetchMode fetch_mode = FetchMode::CORS) { + std::string fetch_mode_string; + switch (fetch_mode) { + case SAME_ORIGIN: + fetch_mode_string = "same-origin"; + break; + case CORS: + fetch_mode_string = "cors"; + break; + case NO_CORS: + fetch_mode_string = "no-cors"; + break; + default: + NOTREACHED(); + } + const char kFetchRequestScript[] = + "fetch($1, {mode: $2}).then(" + " response => 'success'," + " error => error.message" + ");"; + + return EvalJs(shell, + JsReplace(kFetchRequestScript, fetch_url, fetch_mode_string), + EXECUTE_SCRIPT_DEFAULT_OPTIONS, 1 /* world_id */); +} +} // namespace + +// Verify fetch request from web pages to chrome-untrusted:// is blocked, +// because web pages don't have WebUIURLLoaderFactory for chrome-untrusted:// +// scheme. +IN_PROC_BROWSER_TEST_F(WebUISecurityTest, + DisallowWebPageFetchRequestToChromeUntrusted) { + const GURL untrusted_url = GURL("chrome-untrusted://test/title1.html"); + AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(), + untrusted_url.host()); + ASSERT_TRUE(embedded_test_server()->Start()); + + const GURL web_url = embedded_test_server()->GetURL("/title2.html"); + EXPECT_TRUE(NavigateToURL(shell(), web_url)); + + { + DevToolsInspectorLogWatcher log_watcher(shell()->web_contents()); + EXPECT_EQ("Failed to fetch", + PerformFetch(shell(), untrusted_url, FetchMode::CORS)); + log_watcher.FlushAndStopWatching(); + + EXPECT_EQ(log_watcher.last_message(), + "Failed to load resource: net::ERR_UNKNOWN_URL_SCHEME"); + } + + { + DevToolsInspectorLogWatcher log_watcher(shell()->web_contents()); + EXPECT_EQ("Failed to fetch", + PerformFetch(shell(), untrusted_url, FetchMode::NO_CORS)); + log_watcher.FlushAndStopWatching(); + + EXPECT_EQ(log_watcher.last_message(), + "Failed to load resource: net::ERR_UNKNOWN_URL_SCHEME"); + } +} + +// Verify a chrome-untrusted:// document can fetch itself. +IN_PROC_BROWSER_TEST_F(WebUISecurityTest, ChromeUntrustedFetchRequestToSelf) { + const GURL untrusted_url = GURL("chrome-untrusted://test/title1.html"); + AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(), + untrusted_url.host()); + + EXPECT_TRUE(NavigateToURL(shell(), untrusted_url)); + EXPECT_EQ("success", + PerformFetch(shell(), untrusted_url, FetchMode::SAME_ORIGIN)); +} + +// Verify cross-origin fetch request from a chrome-untrusted:// page to another +// chrome-untrusted:// page is blocked by the default "default-src 'self'" +// Content Security Policy on URLDataSource. +IN_PROC_BROWSER_TEST_F( + WebUISecurityTest, + DisallowCrossOriginFetchRequestToChromeUntrustedByDefault) { + const GURL untrusted_url1 = GURL("chrome-untrusted://test1/title1.html"); + AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(), + untrusted_url1.host()); + const GURL untrusted_url2 = GURL("chrome-untrusted://test2/title2.html"); + URLDataSource::Add( + shell()->web_contents()->GetBrowserContext(), + UntrustedSourceWithCorsSupport::CreateForHost(untrusted_url2.host())); + + EXPECT_TRUE(NavigateToURL(shell(), untrusted_url1)); + + { + WebContentsConsoleObserver console_observer(shell()->web_contents()); + EXPECT_EQ("Failed to fetch", + PerformFetch(shell(), untrusted_url2, FetchMode::CORS)); + console_observer.Wait(); + EXPECT_EQ(console_observer.GetMessageAt(0), + base::StringPrintf( + "Refused to connect to '%s' because it violates the " + "following Content Security Policy directive: \"default-src " + "'self'\". Note that 'connect-src' was not explicitly set, " + "so 'default-src' is used as a fallback.\n", + untrusted_url2.spec().c_str())); + } + + { + WebContentsConsoleObserver console_observer(shell()->web_contents()); + EXPECT_EQ("Failed to fetch", + PerformFetch(shell(), untrusted_url2, FetchMode::NO_CORS)); + console_observer.Wait(); + EXPECT_EQ(console_observer.GetMessageAt(0), + base::StringPrintf( + "Refused to connect to '%s' because it violates the " + "following Content Security Policy directive: \"default-src " + "'self'\". Note that 'connect-src' was not explicitly set, " + "so 'default-src' is used as a fallback.\n", + untrusted_url2.spec().c_str())); + } +} + +// Verify cross-origin fetch request from a chrome-untrusted:// page to another +// chrome-untrusted:// page succeeds if Content Security Policy allows it. +IN_PROC_BROWSER_TEST_F(WebUISecurityTest, + CrossOriginFetchRequestToChromeUntrusted) { + TestUntrustedDataSourceCSP csp; + csp.default_src = "default-src chrome-untrusted://test2;"; + const GURL untrusted_url1 = GURL("chrome-untrusted://test1/title1.html"); + AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(), + untrusted_url1.host(), csp); + + const GURL untrusted_url2 = GURL("chrome-untrusted://test2/title2.html"); + URLDataSource::Add( + shell()->web_contents()->GetBrowserContext(), + UntrustedSourceWithCorsSupport::CreateForHost(untrusted_url2.host())); + + EXPECT_TRUE(NavigateToURL(shell(), untrusted_url1)); + EXPECT_EQ("success", PerformFetch(shell(), untrusted_url2, FetchMode::CORS)); + EXPECT_EQ("success", + PerformFetch(shell(), untrusted_url2, FetchMode::NO_CORS)); +} + +// Verify fetch request from a chrome-untrusted:// page to a chrome:// page +// is blocked because chrome-untrusted:// pages don't have WebUIURLLoaderFactory +// for chrome:// scheme, even if CSP allows this. +IN_PROC_BROWSER_TEST_F(WebUISecurityTest, + DisallowChromeUntrustedFetchRequestToChrome) { + TestUntrustedDataSourceCSP csp; + csp.default_src = "default-src chrome://webui;"; + const GURL untrusted_url = GURL("chrome-untrusted://test1/title1.html"); + AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(), + untrusted_url.host(), csp); + + const GURL chrome_url = GURL("chrome://webui/title2.html"); + + EXPECT_TRUE(NavigateToURL(shell(), untrusted_url)); + + { + WebContentsConsoleObserver console_observer(shell()->web_contents()); + EXPECT_EQ("Failed to fetch", + PerformFetch(shell(), chrome_url, FetchMode::CORS)); + console_observer.Wait(); + EXPECT_EQ(console_observer.GetMessageAt(0), + base::StringPrintf("Fetch API cannot load %s. URL scheme must be " + "\"http\" or \"https\" for CORS request.", + chrome_url.spec().c_str())); + } + + { + WebContentsConsoleObserver console_observer(shell()->web_contents()); + EXPECT_EQ("Failed to fetch", + PerformFetch(shell(), chrome_url, FetchMode::NO_CORS)); + console_observer.Wait(); + EXPECT_EQ( + console_observer.GetMessageAt(0), + base::StringPrintf( + "Fetch API cannot load %s. URL scheme \"chrome\" is not supported.", + chrome_url.spec().c_str())); + } +} + +namespace { +EvalJsResult PerformXHRRequest(Shell* shell, const GURL& xhr_url) { + const char kXHRRequestScript[] = + "new Promise((resolve) => {" + " const xhr = new XMLHttpRequest();" + " xhr.open('GET', $1);" + " xhr.onload = () => resolve('success');" + " xhr.onerror = progress_event => resolve(progress_event.type);" + " xhr.send();" + "}); "; + + return EvalJs(shell, JsReplace(kXHRRequestScript, xhr_url), + EXECUTE_SCRIPT_DEFAULT_OPTIONS, 1 /* world_id */); +} +} // namespace + +// Verify XHR request from web pages to chrome-untrusted:// is blocked, because +// web pages don't have WebUIURLLoader required to load chrome-untrusted:// +// resources. +IN_PROC_BROWSER_TEST_F(WebUISecurityTest, + DisallowWebPageXHRRequestToChromeUntrusted) { + const GURL untrusted_url = GURL("chrome-untrusted://test/title1.html"); + AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(), + untrusted_url.host()); + ASSERT_TRUE(embedded_test_server()->Start()); + const GURL web_url = embedded_test_server()->GetURL("/title2.html"); + + EXPECT_TRUE(NavigateToURL(shell(), web_url)); + + DevToolsInspectorLogWatcher log_watcher(shell()->web_contents()); + EXPECT_EQ("error", PerformXHRRequest(shell(), untrusted_url)); + log_watcher.FlushAndStopWatching(); + + EXPECT_EQ(log_watcher.last_message(), + "Failed to load resource: net::ERR_UNKNOWN_URL_SCHEME"); +} + +// Verify a chrome-untrusted:// document can XHR itself. +IN_PROC_BROWSER_TEST_F(WebUISecurityTest, + AllowChromeUntrustedXHRRequestToSelf) { + const GURL untrusted_url = GURL("chrome-untrusted://test/title1.html"); + AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(), + untrusted_url.host()); + + EXPECT_TRUE(NavigateToURL(shell(), untrusted_url)); + EXPECT_EQ("success", PerformXHRRequest(shell(), untrusted_url)); +} + +// Verify cross-origin XHR request from a chrome-untrusted:// page to another +// chrome-untrusted:// page is blocked by "default-src 'self';" Content Security +// Policy. +IN_PROC_BROWSER_TEST_F( + WebUISecurityTest, + DisallowCrossOriginXHRRequestToChromeUntrustedByDefault) { + const GURL untrusted_url1 = GURL("chrome-untrusted://test1/title1.html"); + AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(), + untrusted_url1.host()); + const GURL untrusted_url2 = GURL("chrome-untrusted://test2/"); + URLDataSource::Add( + shell()->web_contents()->GetBrowserContext(), + UntrustedSourceWithCorsSupport::CreateForHost(untrusted_url2.host())); + + EXPECT_TRUE(NavigateToURL(shell(), untrusted_url1)); + + WebContentsConsoleObserver console_observer(shell()->web_contents()); + EXPECT_EQ("error", PerformXHRRequest(shell(), untrusted_url2)); + console_observer.Wait(); + EXPECT_EQ(console_observer.GetMessageAt(0), + base::StringPrintf( + "Refused to connect to '%s' because it violates the " + "following Content Security Policy directive: \"default-src " + "'self'\". Note that 'connect-src' was not explicitly set, " + "so 'default-src' is used as a fallback.\n", + untrusted_url2.spec().c_str())); +} + +// Verify cross-origin XHR request from a chrome-untrusted:// page to another +// chrome-untrusted:// page is successful, if Content Security Policy allows it, +// and the requested resource presents an Access-Control-Allow-Origin header. +IN_PROC_BROWSER_TEST_F( + WebUISecurityTest, + CrossOriginXHRRequestToChromeUntrustedIfContenSecurityPolicyAllowsIt) { + TestUntrustedDataSourceCSP csp; + csp.default_src = "default-src chrome-untrusted://test2;"; + const GURL untrusted_url1 = GURL("chrome-untrusted://test1/title1.html"); + AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(), + untrusted_url1.host(), csp); + const GURL untrusted_url2 = GURL("chrome-untrusted://test2/"); + URLDataSource::Add( + shell()->web_contents()->GetBrowserContext(), + UntrustedSourceWithCorsSupport::CreateForHost(untrusted_url2.host())); + + EXPECT_TRUE(NavigateToURL(shell(), untrusted_url1)); + EXPECT_EQ("success", PerformXHRRequest(shell(), untrusted_url2)); +} + +// Verify XHR request from a chrome-untrusted:// page to a chrome:// page is +// blocked, even if CSP allows this. +IN_PROC_BROWSER_TEST_F(WebUISecurityTest, + DisallowChromeUntrustedXHRRequestToChrome) { + TestUntrustedDataSourceCSP csp; + csp.default_src = "default-src chrome://webui;"; + const GURL untrusted_url = GURL("chrome-untrusted://test1/title1.html"); + AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(), + untrusted_url.host(), csp); + + const GURL chrome_url = GURL("chrome://webui/title2.html"); + + EXPECT_TRUE(NavigateToURL(shell(), untrusted_url)); + + WebContentsConsoleObserver console_observer(shell()->web_contents()); + EXPECT_EQ("error", PerformXHRRequest(shell(), chrome_url)); + console_observer.Wait(); + EXPECT_EQ(console_observer.GetMessageAt(0), + base::StringPrintf("Not allowed to load local resource: %s", + chrome_url.spec().c_str())); +} + } // namespace content diff --git a/chromium/content/browser/webui/web_ui_url_loader_factory.cc b/chromium/content/browser/webui/web_ui_url_loader_factory.cc index 1814672916a..76fead0019e 100644 --- a/chromium/content/browser/webui/web_ui_url_loader_factory.cc +++ b/chromium/content/browser/webui/web_ui_url_loader_factory.cc @@ -14,7 +14,6 @@ #include "base/memory/ref_counted_memory.h" #include "base/single_thread_task_runner.h" #include "base/strings/string_piece.h" -#include "base/task/post_task.h" #include "base/task/thread_pool.h" #include "content/browser/bad_message.h" #include "content/browser/blob_storage/blob_internals_url_loader.h" @@ -272,8 +271,8 @@ class WebUIURLLoaderFactory : public network::mojom::URLLoaderFactory, } if (request.url.host_piece() == kChromeUIBlobInternalsHost) { - base::PostTask( - FROM_HERE, {BrowserThread::IO}, + GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&StartBlobInternalsURLLoader, request, std::move(client), base::Unretained(ChromeBlobStorageContext::GetFor( |