| // 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 |