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
|
// 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.
#ifndef EXTENSIONS_BROWSER_PROCESS_MAP_H_
#define EXTENSIONS_BROWSER_PROCESS_MAP_H_
#include <stddef.h>
#include <optional>
#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "components/keyed_service/core/keyed_service.h"
#include "content/public/browser/site_instance.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/mojom/context_type.mojom-forward.h"
namespace content {
class BrowserContext;
} // namespace content
namespace extensions {
class Extension;
// Contains information about which extensions are assigned to which processes.
//
// The relationship between extensions and processes is complex:
//
// - Extensions can be either "split" mode or "spanning" mode.
// - In spanning mode, extensions *generally* share a single process between all
// incognito and normal windows. This was the original mode for extensions.
// - In split mode, extensions have separate processes in incognito windows.
// - There are also hosted apps, which are a kind of extensions, and those
// usually have a process model similar to normal web sites: multiple
// processes per-profile.
// - An extension can also opt into Cross Origin Isolation in which case it can
// have multiple processes per profile since cross-origin-isolated and
// non-cross-origin-isolated contexts don't share a process.
//
// Under the current model, a single extension can correspond to multiple
// processes (see explanation below), but a single process cannot be shared by
// multiple extensions.
//
// Note that because of content scripts, frames, and other edge cases in
// Chrome's process isolation, extension code can still end up running outside
// an assigned process.
//
// But we only allow high-privilege operations to be performed by an extension
// when it is running in an assigned process.
//
// ===========================================================================
// WARNINGS - PLEASE UNDERSTAND THESE BEFORE CALLING OR MODIFYING THIS CLASS
// ===========================================================================
//
// 1. This class contains the processes for hosted apps as well as extensions
// and packaged apps. Just because a process is present here *does not* mean
// it is an "extension process" (e.g., for UI purposes). It may contain only
// hosted apps. See crbug.com/102533.
//
// 2. An extension can show up in multiple processes. That is why there is no
// GetExtensionProcess() method here. There are multiple such cases:
// a) The extension is actually a hosted app.
// b) There is an incognito window open and the extension is "split mode".
// c) The extension is cross origin isolated but has
// non-cross-origin-isolated contexts.
// It is *not safe* to assume that there is one process per extension.
//
// 3. The process ids contained in this class are *not limited* to the Profile
// you got this map from. They can also be associated with that profile's
// incognito/normal twin. If you care about this, use
// RenderProcessHost::FromID() and check the profile of the resulting object.
//
// TODO(aa): The above warnings suggest this class could use improvement :).
//
// TODO(kalman): This class is not threadsafe, but is used on both the UI and IO
// threads. Somebody should fix that, either make it threadsafe or
// enforce single thread. Investigation required.
class ProcessMap : public KeyedService {
public:
explicit ProcessMap(content::BrowserContext* browser_context);
ProcessMap(const ProcessMap&) = delete;
ProcessMap& operator=(const ProcessMap&) = delete;
~ProcessMap() override;
void Shutdown() override;
// Returns the instance for `browser_context`. An instance is shared between
// an incognito and a regular context.
static ProcessMap* Get(content::BrowserContext* browser_context);
size_t size() const { return items_.size(); }
bool Insert(const ExtensionId& extension_id, int process_id);
int Remove(int process_id);
bool Contains(const ExtensionId& extension_id, int process_id) const;
bool Contains(int process_id) const;
// Returns true if an extension with the given `extension_id` has any
// associated tracked process.
bool ExtensionHasProcess(const ExtensionId& extension_id) const;
// Returns a pointer to an enabled extension running in `process_id` or
// nullptr.
const Extension* GetEnabledExtensionByProcessID(int process_id) const;
std::optional<ExtensionId> GetExtensionIdForProcess(int process_id) const;
// Returns true if the given `process_id` is considered a privileged context
// for the given `extension`. That is, if it would *probably* correspond to a
// mojom::ContextType::kPrivilegedExtension.
// NOTE: There are circumstances in which a context from a privileged
// extension *process* may not correspond to a privileged extension *context*
// (mojom::ContextType::kPrivilegedExtension).
// These include, for instance, sandboxed extension frames or offscreen
// documents, which run in the same process, but are not considered
// privileged contexts.
// However, these are not necessarily security bugs. There is no security
// boundary between an extension's offscreen document and other frames, and
// extension sandboxed frames behave slightly differently than sandboxed pages
// on the web.
bool IsPrivilegedExtensionProcess(const Extension& extension, int process_id);
// Returns true if the given `context_type` - associated with the given
// `extension`, if provided - is valid for the given `process`.
//
// Use this method to validate whether a context type claimed by the renderer
// is possible.
//
// Important notes:
// - This will return false for any invalid combinations. For instance, it is
// never possible to have a web page context associated with an extension.
// - This relies on certain architectural guarantees. For instance, web pages
// should never, ever share a process with an extension or with webui.
// - Multiple context types (with some difference in privilege levels) may be
// valid for a given process and extension pairing. For instance, a
// privileged extension process could host any of privileged extension
// contexts, offscreen document contexts, and content script contexts. Thus,
// a compromised renderer could, in theory, claim a more privileged context
// (such as claiming to be a privileged extension context from an offscreen
// document context). This *is not* a security bug; if the renderer is
// compromised and could host privileged extension contexts, it could simply
// create (or hijack) one.
// - This only looks at process-level guarantees. Thus, for contexts like
// untrusted webui (chrome-untrusted:// pages), the caller is responsible
// for doing additional verification (such as checking the origin).
//
// This method is preferable to GetMostLikelyContextType() as it allows the
// renderer to supply a context type to differentiate between possible
// contexts in the non-compromised-renderer case, whereas
// GetMostLikelyContextType() cannot (and has to just "pick" a possible
// context type).
bool CanProcessHostContextType(const Extension* extension,
const content::RenderProcessHost& process,
mojom::ContextType context_type);
// Gets the most likely context type for the process with ID `process_id`
// which hosts Extension `extension`, if any (may be nullptr). Context types
// are renderer (JavaScript) concepts but the browser can do a decent job in
// guessing what the process hosts.
//
// For Context types with no `extension` e.g. untrusted WebUIs, we use `url`
// which should correspond to the URL where the API is running.`url` could be
// the frame's URL, the Content Script's URL, or the URL where a Content
// Script is running. So `url` should only be used when there is no
// `extension`. `url` may be also be nullptr when running in Service Workers.
// Currently, the `url` provided by event_router.cc is passed from the
// renderer process and therefore can't be fully trusted.
// TODO(ortuno): Change call sites to only pass in a URL when `extension` is
// nullptr and only use a URL retrieved from the browser process.
//
// `extension` is the funky part - unfortunately we need to trust the
// caller of this method to be correct that indeed the context does feature
// an extension. This matters for iframes, where an extension could be
// hosted in another extension's process (privilege level needs to be
// downgraded) or in a web page's process (privilege level needs to be
// upgraded).
//
// The latter of these is slightly problematic from a security perspective;
// if a web page renderer gets owned it could try to pretend it's an
// extension and get access to some unprivileged APIs. Luckly, when OOP
// iframes lauch, it won't be an issue.
//
// Anyhow, the expected behaviour is:
// - For hosted app processes, this will be `kPrivilegedWebPage`.
// - For other extension processes, this will be `kPrivilegedExtension`.
// - For WebUI processes, this will be `kWebUi`.
// - For chrome-untrusted:// URLs, this will be a `kUntrustedWebUi`.
// - For any other extension we have the choice of `kUnprivilegedExtension`
// or `kContentScript`. Since content scripts are more common, guess that.
// We *could* in theory track which web processes have extension frames
// in them, and those would be `kUnprivilegedExtension`, but we don't at
// the moment, and once OOP iframes exist then there won't even be such a
// thing as a `kUnprivilegedExtension` context.
// - For anything else, `kWebPage`.
virtual mojom::ContextType GetMostLikelyContextType(
const Extension* extension,
int process_id,
const GURL* url) const;
private:
using ProcessId = int;
base::flat_map<ProcessId, ExtensionId> items_;
raw_ptr<content::BrowserContext> browser_context_;
};
} // namespace extensions
#endif // EXTENSIONS_BROWSER_PROCESS_MAP_H_
|