blob: 86bc8698a7015fc13b9ccafbd638ccd9aceefc76 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/renderer/resource_request_policy.h"
#include <string_view>
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/types/optional_util.h"
#include "content/public/common/url_constants.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
#include "extensions/common/manifest_handlers/webview_info.h"
#include "extensions/renderer/dispatcher.h"
#include "pdf/buildflags.h"
#include "third_party/blink/public/platform/url_conversion.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/web/web_console_message.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace extensions {
ResourceRequestPolicy::ResourceRequestPolicy(Dispatcher* dispatcher,
std::unique_ptr<Delegate> delegate)
: dispatcher_(dispatcher), delegate_(std::move(delegate)) {}
ResourceRequestPolicy::~ResourceRequestPolicy() = default;
void ResourceRequestPolicy::OnExtensionLoaded(const Extension& extension) {
if (WebAccessibleResourcesInfo::HasWebAccessibleResources(&extension) ||
WebviewInfo::HasWebviewAccessibleResources(
extension,
dispatcher_->webview_partition_id().value_or(std::string())) ||
// Hosted app icons are accessible.
// TODO(devlin): Should we incorporate this into
// WebAccessibleResourcesInfo?
(extension.is_hosted_app() && !IconsInfo::GetIcons(&extension).empty())) {
web_accessible_resources_map_[extension.id()] = extension.guid();
}
}
bool ResourceRequestPolicy::IsWebAccessibleHost(const std::string& host) {
if (web_accessible_resources_map_.find(host) !=
web_accessible_resources_map_.end()) {
return true;
}
for (const auto& [id, guid] : web_accessible_resources_map_) {
if (host == guid) {
return true;
}
}
return false;
}
void ResourceRequestPolicy::OnExtensionUnloaded(
const ExtensionId& extension_id) {
web_accessible_resources_map_.erase(extension_id);
}
// This method does a security check whether chrome-extension:// URLs can be
// requested by the renderer. Since this is in an untrusted process, the browser
// has a similar check to enforce the policy, in case this process is exploited.
// If you are changing this function, ensure equivalent checks are added to
// extension_protocols.cc's AllowExtensionResourceLoad.
bool ResourceRequestPolicy::CanRequestResource(
const GURL& upstream_url,
const GURL& target_url,
blink::WebLocalFrame* frame,
ui::PageTransition transition_type,
const url::Origin* initiator_origin) {
// `target_url` is expected to have a chrome-extension scheme.
// `upstream_url` could be empty, have a chrome-extension scheme, or other.
CHECK(target_url.SchemeIs(kExtensionScheme));
GURL frame_url = frame->GetDocument().Url();
url::Origin frame_origin = frame->GetDocument().GetSecurityOrigin();
// Navigations from chrome://, devtools://, or embedder-specified pages need
// to be allowed, even if the target |url| is not web-accessible. See also:
// - https://crbug.com/662602
// - similar scheme checks in ExtensionNavigationThrottle
if (frame_origin.scheme() == content::kChromeUIScheme ||
frame_origin.scheme() == content::kChromeDevToolsScheme) {
return true;
}
if (delegate_ &&
delegate_->ShouldAlwaysAllowRequestForFrameOrigin(frame_origin)) {
return true;
}
// The page_origin may be GURL("null") for unique origins like data URLs,
// but this is ok for the checks below. We only care if it matches the
// current extension or has a devtools scheme.
GURL page_origin = url::Origin(frame->Top()->GetSecurityOrigin()).GetURL();
GURL extension_origin = target_url.DeprecatedGetOriginAsURL();
// We always allow loads in the following cases, regardless of web accessible
// resources:
// Empty urls (needed for some edge cases when we have empty urls).
if (frame_url.is_empty()) {
return true;
}
// Extensions requesting their own resources (frame_url check is for images,
// page_url check is for iframes).
// TODO(devlin): We should be checking the ancestor chain, not just the
// top-level frame. Additionally, we should be checking the security origin
// of the frame, to account for about:blank subframes being scripted by an
// extension parent (though we'll still need the frame origin check for
// sandboxed frames).
if (frame_url.DeprecatedGetOriginAsURL() == extension_origin ||
page_origin == extension_origin) {
return true;
}
if (!ui::PageTransitionIsWebTriggerable(transition_type)) {
return true;
}
// Unreachable web page error page (to allow showing the icon of the
// unreachable app on this page).
if (frame_url == content::kUnreachableWebDataURL) {
return true;
}
#if BUILDFLAG(ENABLE_PDF)
// Handle specific cases for the PDF viewer.
if (extension_origin.GetScheme() == kExtensionScheme &&
extension_origin.GetHost() == extension_misc::kPdfExtensionId) {
// For the PDF viewer, `page_origin` doesn't match the `extension_origin`,
// but the PDF extension frame should still be able to request resources
// from itself. The PDF content frame should also be able to request
// resources from the PDF extension. For both cases, the parent origin of
// the current frame matches the extension origin.
blink::WebFrame* parent = frame->Parent();
if (parent) {
GURL parent_origin = url::Origin(parent->GetSecurityOrigin()).GetURL();
if (parent_origin == extension_origin) {
return true;
}
}
}
#endif // BUILDFLAG(ENABLE_PDF)
if (delegate_ &&
delegate_->AllowLoadForDevToolsPage(page_origin, target_url)) {
return true;
}
// Note: we check |web_accessible_ids_| (rather than first looking up the
// extension in the registry and checking that) to be more resistant against
// timing attacks. This way, determining access for an extension that isn't
// installed takes the same amount of time as determining access for an
// extension with no web accessible resources. We aren't worried about any
// extensions with web accessible resources, since those are inherently
// identifiable.
if (!IsWebAccessibleHost(extension_origin.GetHost())) {
return false;
}
const Extension* extension =
RendererExtensionRegistry::Get()->GetExtensionOrAppByURL(
target_url, true /*include_guid*/);
DCHECK(extension);
// Disallow loading of packaged resources for hosted apps. We don't allow
// hybrid hosted/packaged apps. The one exception is access to icons, since
// some extensions want to be able to do things like create their own
// launchers.
std::string_view resource_root_relative_path =
target_url.path().empty() ? std::string_view()
: target_url.path().substr(1);
if (extension->is_hosted_app() &&
!IconsInfo::GetIcons(extension).ContainsPath(
resource_root_relative_path)) {
LOG(ERROR) << "Denying load of " << target_url.spec() << " from "
<< "hosted app.";
return false;
}
// Disallow loading of extension resources which are not explicitly listed
// as web or WebView accessible if the manifest version is 2 or greater.
// `upstream_url` might be set to GURL() in some cases, if it's not available.
auto opt_initiator_origin = base::OptionalFromPtr(initiator_origin);
if (!WebAccessibleResourcesInfo::IsResourceWebAccessibleRedirect(
extension, target_url, opt_initiator_origin, upstream_url) &&
!WebviewInfo::IsResourceWebviewAccessible(
extension,
dispatcher_->webview_partition_id().value_or(std::string()),
target_url.GetPath())) {
std::string message = base::StringPrintf(
"Denying load of %s. Resources must be listed in the "
"web_accessible_resources manifest key in order to be loaded by "
"pages outside the extension.",
target_url.spec().c_str());
frame->AddMessageToConsole(
blink::WebConsoleMessage(blink::mojom::ConsoleMessageLevel::kError,
blink::WebString::FromUTF8(message)));
return false;
}
return true;
}
} // namespace extensions