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 2013 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/process_map.h"
#include <algorithm>
#include <string>
#include <tuple>
#include "base/containers/contains.h"
#include "base/containers/map_util.h"
#include "base/types/optional_util.h"
#include "components/guest_view/buildflags/buildflags.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/url_constants.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/process_map_factory.h"
#include "extensions/browser/script_injection_tracker.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/mojom/context_type.mojom.h"
#include "pdf/buildflags.h"
#if BUILDFLAG(ENABLE_GUEST_VIEW)
#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
#endif
#if BUILDFLAG(ENABLE_PDF)
#include "extensions/common/constants.h"
#include "pdf/pdf_features.h"
#endif
namespace extensions {
namespace {
// Returns true if `process_id` is associated with a WebUI process.
bool ProcessHasWebUIBindings(int process_id) {
// TODO(crbug.com/40676401): HasWebUIBindings does not always return true for
// WebUIs. This should be changed to use something else.
return content::ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings(
process_id);
}
// Returns true if `process_id` is associated with a webview owned by the
// extension with the specified `extension_id`.
bool IsWebViewProcessForExtension(int process_id,
const ExtensionId& extension_id) {
#if BUILDFLAG(ENABLE_GUEST_VIEW)
WebViewRendererState* web_view_state = WebViewRendererState::GetInstance();
if (!web_view_state->IsGuest(process_id)) {
return false;
}
std::string webview_owner;
int owner_process_id = -1;
bool found_info = web_view_state->GetOwnerInfo(process_id, &owner_process_id,
&webview_owner);
return found_info && webview_owner == extension_id;
#else
return false;
#endif
}
} // namespace
// ProcessMap
ProcessMap::ProcessMap(content::BrowserContext* browser_context)
: browser_context_(browser_context) {}
ProcessMap::~ProcessMap() = default;
void ProcessMap::Shutdown() {
browser_context_ = nullptr;
}
// static
ProcessMap* ProcessMap::Get(content::BrowserContext* browser_context) {
return ProcessMapFactory::GetForBrowserContext(browser_context);
}
bool ProcessMap::Insert(const ExtensionId& extension_id, int process_id) {
return items_.emplace(process_id, extension_id).second;
}
int ProcessMap::Remove(int process_id) {
return items_.erase(process_id);
}
bool ProcessMap::Contains(const ExtensionId& extension_id_in,
int process_id) const {
auto* extension_id = base::FindOrNull(items_, process_id);
return extension_id && *extension_id == extension_id_in;
}
bool ProcessMap::Contains(int process_id) const {
return base::Contains(items_, process_id);
}
bool ProcessMap::ExtensionHasProcess(const ExtensionId& extension_id) const {
return std::ranges::find_if(items_, [extension_id](const auto& entry) {
return entry.second == extension_id;
}) != items_.end();
}
const Extension* ProcessMap::GetEnabledExtensionByProcessID(
int process_id) const {
auto* extension_id = base::FindOrNull(items_, process_id);
return extension_id ? ExtensionRegistry::Get(browser_context_)
->enabled_extensions()
.GetByID(*extension_id)
: nullptr;
}
std::optional<ExtensionId> ProcessMap::GetExtensionIdForProcess(
int process_id) const {
return base::OptionalFromPtr(base::FindOrNull(items_, process_id));
}
bool ProcessMap::IsPrivilegedExtensionProcess(const Extension& extension,
int process_id) {
return Contains(extension.id(), process_id) &&
// Hosted apps aren't considered privileged extension processes...
(!extension.is_hosted_app() ||
// ... Unless they're component hosted apps, like the webstore.
// TODO(https://crbug/1429667): We can clean this up when we remove
// special handling of component hosted apps.
extension.location() == mojom::ManifestLocation::kComponent);
}
bool ProcessMap::CanProcessHostContextType(
const Extension* extension,
const content::RenderProcessHost& process,
mojom::ContextType context_type) {
const int process_id = process.GetDeprecatedID();
switch (context_type) {
case mojom::ContextType::kUnspecified:
// We never consider unspecified contexts valid. Even though they would be
// permissionless, they should never be able to make a request to the
// browser.
return false;
case mojom::ContextType::kOffscreenExtension:
case mojom::ContextType::kPrivilegedExtension:
// Offscreen documents run in the main extension process, so both of these
// require a privileged extension process.
return extension && IsPrivilegedExtensionProcess(*extension, process_id);
case mojom::ContextType::kUnprivilegedExtension:
return extension &&
IsWebViewProcessForExtension(process_id, extension->id());
case mojom::ContextType::kContentScript:
// Currently, we assume any process can host a content script.
// TODO(crbug.com/40055126): This could be better by looking at
// ScriptInjectionTracker, as we do for user scripts below.
return !!extension;
case mojom::ContextType::kUserScript:
return extension &&
ScriptInjectionTracker::DidProcessRunUserScriptFromExtension(
process, extension->id());
case mojom::ContextType::kPrivilegedWebPage:
// A privileged web page is a (non-component) hosted app process.
return extension && extension->is_hosted_app() &&
extension->location() != mojom::ManifestLocation::kComponent &&
Contains(extension->id(), process_id);
case mojom::ContextType::kUntrustedWebUi:
// Unfortunately, we have no way of checking if a *process* can host
// untrusted webui contexts. Callers should look at (ideally, the
// browser-verified) origin.
[[fallthrough]];
case mojom::ContextType::kWebPage:
// Any context not associated with an extension, not running in an
// extension process, and without webui bindings can be considered a
// web page process.
return !extension && !Contains(process_id) &&
!ProcessHasWebUIBindings(process_id);
case mojom::ContextType::kWebUi:
// Don't consider extensions in webui (like content scripts) to be
// webui.
return !extension && ProcessHasWebUIBindings(process_id);
}
}
mojom::ContextType ProcessMap::GetMostLikelyContextType(
const Extension* extension,
int process_id,
const GURL* url) const {
// WARNING: This logic must match ScriptContextSet::ClassifyJavaScriptContext,
// as much as possible.
// TODO(crbug.com/40676105): Move this into the !extension if statement below
// or document why we want to return WEBUI_CONTEXT for content scripts in
// WebUIs.
if (ProcessHasWebUIBindings(process_id)) {
return mojom::ContextType::kWebUi;
}
if (!extension) {
// Note that blob/filesystem schemes associated with an inner URL of
// chrome-untrusted will be considered regular pages.
if (url && url->SchemeIs(content::kChromeUIUntrustedScheme)) {
return mojom::ContextType::kUntrustedWebUi;
}
return mojom::ContextType::kWebPage;
}
const ExtensionId& extension_id = extension->id();
if (!Contains(extension_id, process_id)) {
// If the process map doesn't contain the process, it might be an extension
// frame in a webview.
// We (deliberately) don't add webview-hosted frames to the process map and
// don't classify them as kPrivilegedExtension contexts.
if (url && extension->origin().IsSameOriginWith(*url) &&
IsWebViewProcessForExtension(process_id, extension->id())) {
// Yep, it's an extension frame in a webview.
#if BUILDFLAG(ENABLE_PDF)
// The PDF Viewer extension is an exception, since webviews need to be
// able to load the PDF Viewer. The PDF extension needs a
// kPrivilegedExtension context to load, so the PDF extension frame is
// added to the process map and shouldn't reach here.
if (chrome_pdf::features::IsOopifPdfEnabled()) {
CHECK_NE(extension_id, extension_misc::kPdfExtensionId);
}
#endif // BUILDFLAG(ENABLE_PDF)
return mojom::ContextType::kUnprivilegedExtension;
}
// Otherwise, it's a content script (the context in which an extension can
// run in an unassociated, non-webview process).
return mojom::ContextType::kContentScript;
}
if (extension->is_hosted_app() &&
extension->location() != mojom::ManifestLocation::kComponent) {
return mojom::ContextType::kPrivilegedWebPage;
}
// TODO(crbug.com/40849649): Currently, offscreen document contexts
// are misclassified as kPrivilegedExtension contexts. This is not ideal
// because there is a mismatch between the browser and the renderer), but it's
// not a security issue because, while offscreen documents have fewer
// capabilities, this is an API distinction, and not a security enforcement.
// Offscreen documents run in the same process as the rest of the extension
// and can message the extension, so could easily - though indirectly -
// access all the same features.
// Even so, we should fix this to properly classify offscreen documents (and
// this would be a problem if offscreen documents ever have access to APIs
// that kPrivilegedExtension contexts don't).
return mojom::ContextType::kPrivilegedExtension;
}
} // namespace extensions
|