| // 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 <tuple> |
| |
| #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/content_script_tracker.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/guest_view/web_view/web_view_renderer_state.h" |
| #include "extensions/browser/process_map_factory.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/features/feature.h" |
| |
| namespace extensions { |
| |
| namespace { |
| |
| // Returns true if `process_id` is associated with a WebUI process. |
| bool ProcessHasWebUIBindings(int process_id) { |
| // TODO(crbug.com/1055656): 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) { |
| 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; |
| } |
| |
| } // namespace |
| |
| // Item |
| struct ProcessMap::Item { |
| Item(const std::string& extension_id, int process_id) |
| : extension_id(extension_id), process_id(process_id) {} |
| |
| Item(const Item&) = delete; |
| Item& operator=(const Item&) = delete; |
| |
| ~Item() = default; |
| |
| Item(ProcessMap::Item&&) = default; |
| Item& operator=(ProcessMap::Item&&) = default; |
| |
| bool operator<(const ProcessMap::Item& other) const { |
| return std::tie(extension_id, process_id) < |
| std::tie(other.extension_id, other.process_id); |
| } |
| |
| std::string extension_id; |
| int process_id = 0; |
| }; |
| |
| |
| // ProcessMap |
| ProcessMap::ProcessMap() = default; |
| |
| ProcessMap::~ProcessMap() = default; |
| |
| // static |
| ProcessMap* ProcessMap::Get(content::BrowserContext* browser_context) { |
| return ProcessMapFactory::GetForBrowserContext(browser_context); |
| } |
| |
| bool ProcessMap::Insert(const std::string& extension_id, int process_id) { |
| return items_.insert(Item(extension_id, process_id)).second; |
| } |
| |
| int ProcessMap::RemoveAllFromProcess(int process_id) { |
| int result = 0; |
| for (auto iter = items_.begin(); iter != items_.end();) { |
| if (iter->process_id == process_id) { |
| items_.erase(iter++); |
| ++result; |
| } else { |
| ++iter; |
| } |
| } |
| return result; |
| } |
| |
| bool ProcessMap::Contains(const std::string& extension_id, |
| int process_id) const { |
| for (auto iter = items_.cbegin(); iter != items_.cend(); ++iter) { |
| if (iter->process_id == process_id && iter->extension_id == extension_id) |
| return true; |
| } |
| return false; |
| } |
| |
| bool ProcessMap::Contains(int process_id) const { |
| for (auto iter = items_.cbegin(); iter != items_.cend(); ++iter) { |
| if (iter->process_id == process_id) |
| return true; |
| } |
| return false; |
| } |
| |
| std::set<std::string> ProcessMap::GetExtensionsInProcess(int process_id) const { |
| std::set<std::string> result; |
| for (auto iter = items_.cbegin(); iter != items_.cend(); ++iter) { |
| if (iter->process_id == process_id) |
| result.insert(iter->extension_id); |
| } |
| return result; |
| } |
| |
| 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) && |
| // Lock screen contexts are not the same as privileged extension |
| // processes. |
| !is_lock_screen_context_; |
| } |
| |
| bool ProcessMap::CanProcessHostContextType( |
| const Extension* extension, |
| const content::RenderProcessHost& process, |
| Feature::Context context_type) { |
| const int process_id = process.GetID(); |
| switch (context_type) { |
| case Feature::UNSPECIFIED_CONTEXT: |
| // 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 Feature::OFFSCREEN_EXTENSION_CONTEXT: |
| case Feature::BLESSED_EXTENSION_CONTEXT: |
| // Offscreen documents run in the main extension process, so both of these |
| // require a privileged extension process. |
| return extension && IsPrivilegedExtensionProcess(*extension, process_id); |
| case Feature::UNBLESSED_EXTENSION_CONTEXT: |
| return extension && |
| IsWebViewProcessForExtension(process_id, extension->id()); |
| case Feature::CONTENT_SCRIPT_CONTEXT: |
| // Currently, we assume any process can host a content script. |
| // TODO(crbug.com/1186557): This could be better by looking at |
| // ContentScriptTracker, as we do for user scripts below. |
| return !!extension; |
| case Feature::USER_SCRIPT_CONTEXT: |
| return extension && |
| ContentScriptTracker::DidProcessRunUserScriptFromExtension( |
| process, extension->id()); |
| case Feature::LOCK_SCREEN_EXTENSION_CONTEXT: |
| // Lock screen contexts are essentially blessed contexts that run on the |
| // lock screen profile. We don't run component hosted apps there, so no |
| // need to allow those. |
| return is_lock_screen_context_ && extension && |
| !extension->is_hosted_app() && |
| Contains(extension->id(), process_id); |
| case Feature::BLESSED_WEB_PAGE_CONTEXT: |
| // A blessed 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 Feature::WEBUI_UNTRUSTED_CONTEXT: |
| // 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 Feature::WEB_PAGE_CONTEXT: |
| // 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 Feature::WEBUI_CONTEXT: |
| // Don't consider extensions in webui (like content scripts) to be |
| // webui. |
| return !extension && ProcessHasWebUIBindings(process_id); |
| } |
| } |
| |
| Feature::Context 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/1055168): 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 Feature::WEBUI_CONTEXT; |
| } |
| |
| 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 Feature::WEBUI_UNTRUSTED_CONTEXT; |
| |
| return Feature::WEB_PAGE_CONTEXT; |
| } |
| |
| 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 BLESSED_EXTENSION_CONTEXTs. |
| if (url && extension->origin().IsSameOriginWith(*url) && |
| IsWebViewProcessForExtension(process_id, extension->id())) { |
| // Yep, it's an extension frame in a webview. |
| return Feature::UNBLESSED_EXTENSION_CONTEXT; |
| } |
| |
| // Otherwise, it's a content script (the context in which an extension can |
| // run in an unassociated, non-webview process). |
| return Feature::CONTENT_SCRIPT_CONTEXT; |
| } |
| |
| if (extension->is_hosted_app() && |
| extension->location() != mojom::ManifestLocation::kComponent) { |
| return Feature::BLESSED_WEB_PAGE_CONTEXT; |
| } |
| |
| // TODO(https://crbug.com/1339382): Currently, offscreen document contexts |
| // are misclassified as BLESSED_EXTENSION_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 BLESSED_EXTENSION_CONTEXTs don't). |
| |
| return is_lock_screen_context_ ? Feature::LOCK_SCREEN_EXTENSION_CONTEXT |
| : Feature::BLESSED_EXTENSION_CONTEXT; |
| } |
| |
| } // namespace extensions |