1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
|
// Copyright 2018 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/browser/url_loader_factory_manager.h"
#include <algorithm>
#include <utility>
#include <vector>
#include "base/feature_list.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/process_map.h"
#include "extensions/browser/script_injection_tracker.h"
#include "extensions/common/constants.h"
#include "extensions/common/cors_util.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/manifest_handlers/permissions_parser.h"
#include "extensions/common/mojom/host_id.mojom.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/url_pattern.h"
#include "extensions/common/url_pattern_set.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/scheme_host_port.h"
#include "url/url_constants.h"
namespace extensions {
namespace {
enum class FactoryUser {
kContentScript,
kExtensionProcess,
};
bool DoContentScriptsDependOnRelaxedOrbOrCors(const Extension& extension) {
// Content scripts injected by Chrome Apps (e.g. into <webview> tag) need to
// run with relaxed ORB.
//
// TODO(crbug.com/40158699): Remove this exception once Chrome Platform
// Apps are gone.
if (extension.is_platform_app()) {
return true;
}
// Content scripts are not granted an ability to relax ORB and/or CORS.
return false;
}
bool DoExtensionPermissionsCoverHttpOrHttpsOrigins(
const PermissionSet& permissions) {
// Looking at explicit (rather than effective) hosts results in stricter
// checks that better match ORB/CORS behavior.
return std::ranges::any_of(
permissions.explicit_hosts(), [](const URLPattern& permission) {
return permission.MatchesScheme(url::kHttpScheme) ||
permission.MatchesScheme(url::kHttpsScheme);
});
}
bool DoExtensionPermissionsCoverHttpOrHttpsOrigins(const Extension& extension) {
// Extension with an ActiveTab permission can later gain permission to access
// any http origin (once the ActiveTab permission is activated).
const PermissionsData* permissions = extension.permissions_data();
if (permissions->HasAPIPermission(mojom::APIPermissionID::kActiveTab)) {
return true;
}
// Optional extension permissions to http origins may be granted later.
//
// TODO(lukasza): Consider only handing out ORB/CORS-disabled
// URLLoaderFactory after the optional permission is *actually* granted. Care
// might need to be take to make sure that updating the URLLoaderFactory is
// robust in presence of races (the new factory should reach the all [?]
// extension frames/contexts *before* the ack/response about the newly granted
// permission).
if (DoExtensionPermissionsCoverHttpOrHttpsOrigins(
PermissionsParser::GetOptionalPermissions(&extension))) {
return true;
}
// Check required extension permissions. Note that this is broader than
// `permissions->GetEffectiveHostPermissions()` to account for policy that may
// change at runtime.
if (DoExtensionPermissionsCoverHttpOrHttpsOrigins(
PermissionsParser::GetRequiredPermissions(&extension))) {
return true;
}
// Otherwise, report that the `extension` will never get HTTP permissions.
return false;
}
// Returns whether to allow bypassing CORS (by disabling ORB, and paying
// attention to the `isolated_world_origin` from content scripts, and using
// SecFetchSiteValue::kNoOrigin from extensions).
bool ShouldRelaxCors(const Extension& extension, FactoryUser factory_user) {
if (!DoExtensionPermissionsCoverHttpOrHttpsOrigins(extension)) {
return false;
}
switch (factory_user) {
case FactoryUser::kContentScript:
return DoContentScriptsDependOnRelaxedOrbOrCors(extension);
case FactoryUser::kExtensionProcess:
return true;
}
}
bool ShouldCreateSeparateFactoryForContentScripts(const Extension& extension) {
return ShouldRelaxCors(extension, FactoryUser::kContentScript);
}
void OverrideFactoryParams(const Extension& extension,
bool is_for_service_worker,
FactoryUser factory_user,
network::mojom::URLLoaderFactoryParams* params) {
if (is_for_service_worker &&
base::FeatureList::IsEnabled(
extensions_features::kSkipResetServiceWorkerURLLoaderFactories)) {
CHECK_EQ(factory_user, FactoryUser::kExtensionProcess);
params->ignore_factory_reset = true;
}
if (ShouldRelaxCors(extension, factory_user)) {
params->is_orb_enabled = false;
switch (factory_user) {
case FactoryUser::kContentScript:
// Requests from content scripts set
// network::ResourceRequest::isolated_world_origin to the origin of the
// extension. This field of ResourceRequest is normally ignored, but by
// setting `ignore_isolated_world_origin` to false below, we ensure that
// OOR-CORS will use the extension origin when checking if content
// script requests should bypass CORS.
params->ignore_isolated_world_origin = false;
break;
case FactoryUser::kExtensionProcess:
params->unsafe_non_webby_initiator = true;
break;
}
}
}
void MarkIsolatedWorldsAsRequiringSeparateURLLoaderFactory(
content::RenderFrameHost* frame,
const std::vector<url::Origin>& request_initiators,
bool push_to_renderer_now) {
DCHECK(!request_initiators.empty());
frame->MarkIsolatedWorldsAsRequiringSeparateURLLoaderFactory(
request_initiators, push_to_renderer_now);
}
} // namespace
// static
void URLLoaderFactoryManager::WillInjectContentScriptsWhenNavigationCommits(
base::PassKey<ScriptInjectionTracker> pass_key,
content::NavigationHandle* navigation,
const std::vector<const Extension*>& extensions) {
// Same-document navigations do not send URLLoaderFactories to the renderer
// process.
if (navigation->IsSameDocument()) {
return;
}
std::vector<url::Origin> initiators_requiring_separate_factory;
for (const Extension* extension : extensions) {
if (!ShouldCreateSeparateFactoryForContentScripts(*extension)) {
continue;
}
initiators_requiring_separate_factory.push_back(extension->origin());
}
if (!initiators_requiring_separate_factory.empty()) {
// At ReadyToCommitNavigation time there is no need to trigger an explicit
// push of URLLoaderFactoryBundle to the renderer - it is sufficient if the
// factories are pushed slightly later - during the commit.
constexpr bool kPushToRendererNow = false;
MarkIsolatedWorldsAsRequiringSeparateURLLoaderFactory(
navigation->GetRenderFrameHost(), initiators_requiring_separate_factory,
kPushToRendererNow);
}
}
// static
void URLLoaderFactoryManager::WillProgrammaticallyInjectContentScript(
base::PassKey<ScriptInjectionTracker> pass_key,
content::RenderFrameHost* frame,
const Extension& extension) {
if (!ShouldCreateSeparateFactoryForContentScripts(extension)) {
return;
}
// When WillExecuteCode runs, the frame already received the initial
// URLLoaderFactoryBundle - therefore we need to request a separate push
// below. This doesn't race with the ExecuteCode mojo message,
// because the URLLoaderFactoryBundle is sent to the renderer over
// content.mojom.Frame interface which is associated with the
// extensions.mojom.LocalFrame (raciness will be introduced if that ever
// changes).
constexpr bool kPushToRendererNow = true;
MarkIsolatedWorldsAsRequiringSeparateURLLoaderFactory(
frame, {extension.origin()}, kPushToRendererNow);
}
// static
void URLLoaderFactoryManager::OverrideURLLoaderFactoryParams(
content::BrowserContext* browser_context,
const url::Origin& origin,
bool is_for_isolated_world,
bool is_for_service_worker,
network::mojom::URLLoaderFactoryParams* factory_params) {
const ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context);
DCHECK(registry); // CreateFactory shouldn't happen during shutdown.
// Opaque origins normally don't inherit security properties of their
// precursor origins, but here opaque origins (e.g. think data: URIs) created
// by an extension should inherit CORS/ORB treatment of the extension.
url::SchemeHostPort precursor_origin =
origin.GetTupleOrPrecursorTupleIfOpaque();
// Don't change factory params for something that is not an extension.
if (precursor_origin.scheme() != kExtensionScheme) {
return;
}
// Find the |extension| associated with |initiator_origin|.
const Extension* extension =
registry->enabled_extensions().GetByID(precursor_origin.host());
if (!extension) {
// This may happen if an extension gets disabled between the time
// RenderFrameHost::MarkIsolatedWorldAsRequiringSeparateURLLoaderFactory is
// called and the time
// ContentBrowserClient::OverrideURLLoaderFactoryParams is called.
return;
}
// Identify and set |factory_params| that need to be overridden.
FactoryUser factory_user = is_for_isolated_world
? FactoryUser::kContentScript
: FactoryUser::kExtensionProcess;
OverrideFactoryParams(*extension, is_for_service_worker, factory_user,
factory_params);
}
} // namespace extensions
|