| // 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 "content/browser/renderer_host/code_cache_host_impl.h" |
| |
| #include <optional> |
| #include <string_view> |
| #include <utility> |
| |
| #include "base/check_is_test.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/thread_annotations.h" |
| #include "base/threading/thread.h" |
| #include "base/types/expected_macros.h" |
| #include "build/build_config.h" |
| #include "components/persistent_cache/pending_backend.h" |
| #include "components/services/storage/public/cpp/buckets/bucket_locator.h" |
| #include "components/services/storage/public/mojom/cache_storage_control.mojom.h" |
| #include "content/browser/child_process_security_policy_impl.h" |
| #include "content/browser/code_cache/generated_code_cache.h" |
| #include "content/browser/code_cache/generated_code_cache_context.h" |
| #include "content/browser/process_lock.h" |
| #include "content/browser/renderer_host/render_process_host_impl.h" |
| #include "content/public/browser/site_isolation_policy.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/url_constants.h" |
| #include "mojo/public/cpp/base/big_buffer.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "net/base/features.h" |
| #include "net/base/io_buffer.h" |
| #include "third_party/blink/public/common/cache_storage/cache_storage_utils.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/scheme_registry.h" |
| #include "third_party/blink/public/mojom/loader/code_cache.mojom-data-view.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| using blink::mojom::CacheStorageError; |
| |
| namespace content { |
| |
| namespace { |
| |
| bool use_empty_secondary_key_for_testing_ = false; |
| |
| enum class Operation { |
| kRead, |
| kWrite, |
| }; |
| |
| bool CheckSecurityForAccessingCodeCacheData(const GURL& resource_url, |
| int render_process_id, |
| Operation operation) { |
| if (!resource_url.is_valid()) { |
| return false; |
| } |
| |
| ProcessLock process_lock = |
| ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock( |
| render_process_id); |
| |
| // Code caching is only allowed for http(s) and chrome/chrome-untrusted |
| // scripts. Furthermore, there is no way for http(s) pages to load chrome or |
| // chrome-untrusted scripts, so any http(s) page attempting to store data |
| // about a chrome or chrome-untrusted script would be an indication of |
| // suspicious activity. |
| if (resource_url.SchemeIs(content::kChromeUIScheme) || |
| resource_url.SchemeIs(content::kChromeUIUntrustedScheme)) { |
| if (!process_lock.IsLockedToSite()) { |
| // We can't tell for certain whether this renderer is doing something |
| // malicious, but we don't trust it enough to store data. |
| return false; |
| } |
| if (process_lock.MatchesScheme(url::kHttpScheme) || |
| process_lock.MatchesScheme(url::kHttpsScheme)) { |
| if (operation == Operation::kWrite) { |
| mojo::ReportBadMessage("HTTP(S) pages cannot cache WebUI code"); |
| } |
| return false; |
| } |
| // Other schemes which might successfully load chrome or chrome-untrusted |
| // scripts, such as the PDF viewer, are unsupported but not considered |
| // dangerous. |
| return process_lock.MatchesScheme(content::kChromeUIScheme) || |
| process_lock.MatchesScheme(content::kChromeUIUntrustedScheme); |
| } |
| if (resource_url.SchemeIsHTTPOrHTTPS() || |
| blink::CommonSchemeRegistry::IsExtensionScheme( |
| resource_url.GetScheme())) { |
| if (process_lock.MatchesScheme(content::kChromeUIScheme) || |
| process_lock.MatchesScheme(content::kChromeUIUntrustedScheme)) { |
| // It is possible for WebUI pages to include open-web content, but such |
| // usage is rare and we've decided that reasoning about security is easier |
| // if the WebUI code cache includes only WebUI scripts. |
| return false; |
| } |
| return true; |
| } |
| |
| if (operation == Operation::kWrite) { |
| mojo::ReportBadMessage("Invalid URL scheme for code cache."); |
| } |
| return false; |
| } |
| |
| // Code caches use two keys: the URL of requested resource |resource_url| |
| // as the primary key and the origin lock of the renderer that requested this |
| // resource as secondary key. This function returns the origin lock of the |
| // renderer that will be used as the secondary key for the code cache. |
| // The secondary key is: |
| // Case 1. std::nullopt if the render process is locked to a WebUI page and the |
| // WebUICodeCache feature is not enabled. |
| // Case 2. an empty GURL if the render process is not locked to an origin. In |
| // this case: |
| // - local code cache: |resource_url| is used as the key. |
| // - code cache with PersistentCache: caching is disabled if full site isolation |
| // (site-per-process) is enabled; otherwise, one of two fixed keys is used so |
| // that there is effectively one cache shared by all renderers for the open |
| // web and one for WebUI. |
| // Case 3. a std::nullopt, if the origin lock is opaque (for ex: browser |
| // initiated navigation to a data: URL). In these cases, the code should not be |
| // cached since the serialized value of opaque origins should not be used as a |
| // key. |
| // Case 4. origin_lock if the scheme of origin_lock is |
| // Http/Https/chrome/chrome-untrusted. |
| // Case 5. std::nullopt otherwise. |
| std::optional<GURL> GetOriginLock(int render_process_id) { |
| ProcessLock process_lock = |
| ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock( |
| render_process_id); |
| |
| if (process_lock.MatchesScheme(content::kChromeUIScheme) || |
| process_lock.MatchesScheme(content::kChromeUIUntrustedScheme)) { |
| if (!base::FeatureList::IsEnabled(features::kWebUICodeCache)) { |
| // Case 1: No caching for WebUI when WebUICodeCache is disabled. |
| return std::nullopt; |
| } |
| // No caching for WebUI when PersistentCache is used. |
| // TODO(374930286): Remove this condition when all other blockers for |
| // caching WebUI resources with PersistentCache are resolved. |
| if (blink::features::IsPersistentCacheForCodeCacheEnabled()) { |
| return std::nullopt; |
| } |
| } |
| |
| // Case 2: If process is not locked to a site, it is safe to just use the |
| // |resource_url| of the requested resource as the key. Return an empty GURL |
| // as the second key. |
| if (!process_lock.IsLockedToSite()) { |
| return GURL(); |
| } |
| |
| // Case 3: Don't cache the code corresponding to opaque origins. The same |
| // origin checks should always fail for opaque origins but the serialized |
| // value of opaque origins does not ensure this. |
| // NOTE: HasOpaqueOrigin() will return true if the ProcessLock lock url is |
| // invalid, leading to a return value of std::nullopt. |
| if (process_lock.HasOpaqueOrigin()) { |
| return std::nullopt; |
| } |
| |
| // Case 4: process_lock_url is used to enforce site-isolation in code caches. |
| // Http/https/chrome schemes are safe to be used as a secondary key. Other |
| // schemes could be enabled if they are known to be safe and if it is |
| // required to cache code from those origins. |
| // |
| // file:// URLs will have a "file:" process lock and would thus share a |
| // cache across all file:// URLs. That would likely be ok for security, but |
| // since this case is not performance sensitive we will keep things simple and |
| // limit the cache to http/https/chrome/chrome-untrusted processes. |
| if (process_lock.MatchesScheme(url::kHttpScheme) || |
| process_lock.MatchesScheme(url::kHttpsScheme) || |
| process_lock.MatchesScheme(content::kChromeUIScheme) || |
| process_lock.MatchesScheme(content::kChromeUIUntrustedScheme) || |
| blink::CommonSchemeRegistry::IsExtensionScheme( |
| process_lock.GetProcessLockURL().GetScheme())) { |
| return process_lock.GetProcessLockURL(); |
| } |
| |
| return std::nullopt; |
| } |
| |
| void DidGenerateCacheableMetadataInCacheStorageOnUI( |
| const GURL& url, |
| base::Time expected_response_time, |
| mojo_base::BigBuffer data, |
| const std::string& cache_storage_cache_name, |
| int render_process_id, |
| const blink::StorageKey& code_cache_storage_key, |
| storage::mojom::CacheStorageControl* cache_storage_control_for_testing, |
| mojo::ReportBadMessageCallback bad_message_callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| auto* render_process_host = RenderProcessHost::FromID(render_process_id); |
| if (!render_process_host) |
| return; |
| |
| int64_t trace_id = blink::cache_storage::CreateTraceId(); |
| TRACE_EVENT_WITH_FLOW1( |
| "CacheStorage", |
| "CodeCacheHostImpl::DidGenerateCacheableMetadataInCacheStorage", |
| TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT, "url", url.spec()); |
| |
| mojo::Remote<blink::mojom::CacheStorage> remote; |
| network::CrossOriginEmbedderPolicy cross_origin_embedder_policy; |
| network::DocumentIsolationPolicy document_isolation_policy; |
| |
| storage::mojom::CacheStorageControl* cache_storage_control = |
| cache_storage_control_for_testing |
| ? cache_storage_control_for_testing |
| : render_process_host->GetStoragePartition() |
| ->GetCacheStorageControl(); |
| |
| cache_storage_control->AddReceiver( |
| cross_origin_embedder_policy, mojo::NullRemote(), |
| document_isolation_policy, mojo::NullRemote(), |
| storage::BucketLocator::ForDefaultBucket(code_cache_storage_key), |
| storage::mojom::CacheStorageOwner::kCacheAPI, |
| remote.BindNewPipeAndPassReceiver()); |
| |
| // Call the remote pointer directly so we can pass the remote to the callback |
| // itself to preserve its lifetime. |
| auto* raw_remote = remote.get(); |
| raw_remote->Open( |
| base::UTF8ToUTF16(cache_storage_cache_name), trace_id, |
| base::BindOnce( |
| [](const GURL& url, base::Time expected_response_time, |
| mojo_base::BigBuffer data, int64_t trace_id, |
| mojo::Remote<blink::mojom::CacheStorage> preserve_remote_lifetime, |
| blink::mojom::CacheStorage::OpenResult result) { |
| if (!result.has_value()) { |
| // Silently ignore errors. |
| return; |
| } |
| |
| mojo::AssociatedRemote<blink::mojom::CacheStorageCache> remote; |
| remote.Bind(std::move(result.value())); |
| remote->WriteSideData( |
| url, expected_response_time, std::move(data), trace_id, |
| base::BindOnce( |
| [](mojo::Remote<blink::mojom::CacheStorage> |
| preserve_remote_lifetime, |
| CacheStorageError error) { |
| // Silently ignore errors. |
| }, |
| std::move(preserve_remote_lifetime))); |
| }, |
| url, expected_response_time, std::move(data), trace_id, |
| std::move(remote))); |
| } |
| |
| void AddCodeCacheReceiver( |
| mojo::UniqueReceiverSet<blink::mojom::CodeCacheHost>* receiver_set, |
| scoped_refptr<GeneratedCodeCacheContext> context, |
| int render_process_id, |
| const net::NetworkIsolationKey& nik, |
| const blink::StorageKey& storage_key, |
| mojo::PendingReceiver<blink::mojom::CodeCacheHost> receiver, |
| CodeCacheHostImpl::ReceiverSet::CodeCacheHostReceiverHandler handler) { |
| auto host = |
| CodeCacheHostImpl::Create(render_process_id, context, nik, storage_key); |
| auto* raw_host = host.get(); |
| auto id = receiver_set->Add(std::move(host), std::move(receiver)); |
| if (handler) |
| std::move(handler).Run(raw_host, id, *receiver_set); |
| } |
| |
| // NoopCodeCacheHost ----------------------------------------------------------- |
| |
| // An implementation of CodeCacheHostImpl that does nothing. This is used for |
| // cases where there is no GeneratedCodeCacheContext available, so it is not |
| // possible to serve any sort of cache. |
| class NoopCodeCacheHost : public CodeCacheHostImpl { |
| public: |
| NoopCodeCacheHost( |
| int render_process_id, |
| scoped_refptr<GeneratedCodeCacheContext> generated_code_cache_context, |
| const net::NetworkIsolationKey& nik, |
| const blink::StorageKey& storage_key) |
| : CodeCacheHostImpl(render_process_id, |
| std::move(generated_code_cache_context), |
| nik, |
| storage_key) {} |
| |
| // CodeCacheHostImpl: |
| void GetPendingBackend(blink::mojom::CodeCacheType cache_type, |
| GetPendingBackendCallback callback) override { |
| std::move(callback).Run({}); |
| } |
| |
| void DidGenerateCacheableMetadata(blink::mojom::CodeCacheType cache_type, |
| const GURL& url, |
| base::Time expected_response_time, |
| mojo_base::BigBuffer data) override {} |
| |
| void FetchCachedCode(blink::mojom::CodeCacheType cache_type, |
| const GURL& url, |
| FetchCachedCodeCallback callback) override { |
| std::move(callback).Run({}, {}); |
| } |
| |
| void ClearCodeCacheEntry(blink::mojom::CodeCacheType cache_type, |
| const GURL& url) override {} |
| }; |
| |
| // LocalCodeCacheHost ---------------------------------------------------------- |
| |
| // An implementation of CodeCacheHostImpl that uses GeneratedCodeCache locally |
| // for all operations. |
| class LocalCodeCacheHost : public CodeCacheHostImpl { |
| public: |
| LocalCodeCacheHost( |
| int render_process_id, |
| scoped_refptr<GeneratedCodeCacheContext> generated_code_cache_context, |
| const net::NetworkIsolationKey& nik, |
| const blink::StorageKey& storage_key) |
| : CodeCacheHostImpl(render_process_id, |
| std::move(generated_code_cache_context), |
| nik, |
| storage_key) { |
| CHECK(this->generated_code_cache_context()); |
| } |
| |
| // CodeCacheHostImpl: |
| void GetPendingBackend(blink::mojom::CodeCacheType cache_type, |
| GetPendingBackendCallback callback) override { |
| mojo::ReportBadMessage("Not using PersistentCache"); |
| } |
| |
| void DidGenerateCacheableMetadata(blink::mojom::CodeCacheType cache_type, |
| const GURL& url, |
| base::Time expected_response_time, |
| mojo_base::BigBuffer data) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| ASSIGN_OR_RETURN(GURL secondary_key, |
| GetSecondaryKeyForCodeCache(url, render_process_id(), |
| Operation::kWrite), |
| [] {}); |
| |
| if (GeneratedCodeCache* code_cache = GetCodeCache(cache_type); code_cache) { |
| code_cache->WriteEntry(url, secondary_key, network_isolation_key(), |
| expected_response_time, std::move(data)); |
| } |
| } |
| |
| void FetchCachedCode(blink::mojom::CodeCacheType cache_type, |
| const GURL& url, |
| FetchCachedCodeCallback callback) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| ASSIGN_OR_RETURN( |
| GURL secondary_key, |
| GetSecondaryKeyForCodeCache(url, render_process_id(), Operation::kRead), |
| [&callback] { std::move(callback).Run({}, {}); }); |
| |
| if (GeneratedCodeCache* code_cache = GetCodeCache(cache_type); code_cache) { |
| auto read_callback = |
| base::BindOnce(&LocalCodeCacheHost::OnReceiveCachedCode, |
| weak_ptr_factory_.GetWeakPtr(), cache_type, |
| base::TimeTicks::Now(), std::move(callback)); |
| code_cache->FetchEntry(url, secondary_key, network_isolation_key(), |
| std::move(read_callback)); |
| } else { |
| std::move(callback).Run(base::Time(), {}); |
| } |
| } |
| |
| void ClearCodeCacheEntry(blink::mojom::CodeCacheType cache_type, |
| const GURL& url) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| ASSIGN_OR_RETURN( |
| GURL secondary_key, |
| GetSecondaryKeyForCodeCache(url, render_process_id(), Operation::kRead), |
| [] {}); |
| |
| if (GeneratedCodeCache* code_cache = GetCodeCache(cache_type); code_cache) { |
| code_cache->DeleteEntry(url, secondary_key, network_isolation_key()); |
| } |
| } |
| |
| private: |
| GeneratedCodeCache* GetCodeCache(blink::mojom::CodeCacheType cache_type) |
| VALID_CONTEXT_REQUIRED(sequence_checker_) { |
| ProcessLock process_lock = |
| ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock( |
| render_process_id()); |
| |
| // To minimize the chance of any cache bug resulting in privilege escalation |
| // from an ordinary web page to trusted WebUI, we use a completely separate |
| // GeneratedCodeCache instance for WebUI pages. |
| if (process_lock.MatchesScheme(content::kChromeUIScheme) || |
| process_lock.MatchesScheme(content::kChromeUIUntrustedScheme)) { |
| if (cache_type == blink::mojom::CodeCacheType::kJavascript) { |
| return generated_code_cache_context()->generated_webui_js_code_cache(); |
| } |
| |
| // WebAssembly in WebUI pages is not supported due to no current usage. |
| return nullptr; |
| } |
| |
| if (cache_type == blink::mojom::CodeCacheType::kJavascript) { |
| return generated_code_cache_context()->generated_js_code_cache(); |
| } |
| |
| DCHECK_EQ(blink::mojom::CodeCacheType::kWebAssembly, cache_type); |
| return generated_code_cache_context()->generated_wasm_code_cache(); |
| } |
| |
| // Code caches use two keys: the URL of requested resource |resource_url| |
| // as the primary key and the origin lock of the renderer that requested this |
| // resource as secondary key. This function returns the origin lock of the |
| // renderer that will be used as the secondary key for the code cache. |
| // The secondary key is: |
| // Case 0. std::nullopt if the resource URL or origin lock have unsupported |
| // schemes, or if they represent potentially dangerous combinations such as |
| // WebUI code in an open-web page. |
| // Case 1. an empty GURL if the render process is not locked to an origin. In |
| // this case, code cache uses |resource_url| as the key. |
| // Case 2. a std::nullopt, if the origin lock is opaque (for ex: browser |
| // initiated navigation to a data: URL). In these cases, the code should not |
| // be cached since the serialized value of opaque origins should not be used |
| // as a key. |
| // Case 3: origin_lock if the scheme of origin_lock is |
| // Http/Https/chrome/chrome-untrusted. |
| // Case 4. std::nullopt otherwise. |
| static std::optional<GURL> GetSecondaryKeyForCodeCache( |
| const GURL& resource_url, |
| int render_process_id, |
| Operation operation) { |
| if (use_empty_secondary_key_for_testing_) { |
| return GURL(); |
| } |
| // Case 0: check for invalid schemes. |
| if (!CheckSecurityForAccessingCodeCacheData(resource_url, render_process_id, |
| operation)) { |
| return std::nullopt; |
| } |
| |
| return GetOriginLock(render_process_id); |
| } |
| |
| void OnReceiveCachedCode(blink::mojom::CodeCacheType cache_type, |
| base::TimeTicks start_time, |
| FetchCachedCodeCallback callback, |
| const base::Time& response_time, |
| mojo_base::BigBuffer data) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (cache_type == blink::mojom::CodeCacheType::kJavascript && |
| data.size() > 0) { |
| base::UmaHistogramTimes("SiteIsolatedCodeCache.JS.FetchCodeCache", |
| base::TimeTicks::Now() - start_time); |
| } |
| |
| if (data.size() > 0) { |
| base::UmaHistogramCustomCounts("SiteIsolatedCodeCache.DataSize", |
| data.size(), 1, 10000000, 100); |
| } |
| |
| std::move(callback).Run(response_time, std::move(data)); |
| } |
| |
| base::WeakPtrFactory<LocalCodeCacheHost> weak_ptr_factory_{this}; |
| }; |
| |
| #if !BUILDFLAG(IS_FUCHSIA) |
| // CodeCacheWithPersistentCacheHost -------------------------------------------- |
| |
| // An implementation of CodeCacheHostImpl that uses PersistentCache. Inserts |
| // take place here in the browser process, while lookups take place in the |
| // renderer by way of read-only view of the cache. |
| class CodeCacheWithPersistentCacheHost : public CodeCacheHostImpl { |
| public: |
| CodeCacheWithPersistentCacheHost( |
| int render_process_id, |
| scoped_refptr<GeneratedCodeCacheContext> generated_code_cache_context, |
| const net::NetworkIsolationKey& nik, |
| const blink::StorageKey& storage_key) |
| : CodeCacheHostImpl(render_process_id, |
| std::move(generated_code_cache_context), |
| nik, |
| storage_key) { |
| CHECK(this->generated_code_cache_context()); |
| } |
| |
| // CodeCacheHostImpl: |
| void GetPendingBackend(blink::mojom::CodeCacheType cache_type, |
| GetPendingBackendCallback callback) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| ASSIGN_OR_RETURN(std::string cache_id, GetCacheId(cache_type), |
| [&callback] { std::move(callback).Run(std::nullopt); }); |
| |
| std::move(callback).Run( |
| generated_code_cache_context()->ShareReadOnlyConnection(cache_id)); |
| } |
| |
| void DidGenerateCacheableMetadata(blink::mojom::CodeCacheType cache_type, |
| const GURL& url, |
| base::Time expected_response_time, |
| mojo_base::BigBuffer data) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Ignore insert attempts for invalid URLs. |
| if (!CheckSecurityForAccessingCodeCacheData(url, render_process_id(), |
| Operation::kWrite)) { |
| return; |
| } |
| |
| ASSIGN_OR_RETURN(std::string cache_id, GetCacheId(cache_type), [] {}); |
| |
| std::string resource_key = GeneratedCodeCache::GetResourceKey( |
| url, MojoCacheTypeToCodeCacheType(cache_type)); |
| |
| generated_code_cache_context()->InsertIntoPersistentCacheCollection( |
| cache_id, resource_key, std::move(data), |
| persistent_cache::EntryMetadata{ |
| .input_signature = expected_response_time.ToDeltaSinceWindowsEpoch() |
| .InMicroseconds()}); |
| } |
| |
| // Note: In an operational browser, `FetchCachedCode` is implemented in |
| // renderers in `CodeCacheWithPersistentCacheHostImpl`. Ideally, this |
| // implementation would be nothing more than `NOTREACHED()`. In light of the |
| // fact that many tests expect to be able to use this to validate inserts into |
| // the cache, it is implemented here. `CHECK_IS_TEST()` is used to prevent |
| // accidental use in the product. |
| void FetchCachedCode(blink::mojom::CodeCacheType cache_type, |
| const GURL& url, |
| FetchCachedCodeCallback callback) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| CHECK_IS_TEST(); // Fetch is handled directly in the client in blink. |
| |
| // For simplicity's sake, the implementation here is used for tests; see |
| // comment above. |
| ASSIGN_OR_RETURN(std::string cache_id, GetCacheId(cache_type), |
| [&callback] { std::move(callback).Run({}, {}); }); |
| |
| std::string resource_key = GeneratedCodeCache::GetResourceKey( |
| url, MojoCacheTypeToCodeCacheType(cache_type)); |
| |
| if (auto metadata_and_content = |
| generated_code_cache_context()->FindInPersistentCacheCollection( |
| cache_id, resource_key); |
| metadata_and_content.has_value() && |
| metadata_and_content->content.size() > 0) { |
| // Cache hit with content. |
| std::move(callback).Run( |
| base::Time::FromDeltaSinceWindowsEpoch(base::Microseconds( |
| metadata_and_content->metadata.input_signature)), |
| std::move(metadata_and_content->content)); |
| } else { |
| // Cache miss or error. |
| std::move(callback).Run(base::Time(), mojo_base::BigBuffer()); |
| } |
| } |
| |
| void ClearCodeCacheEntry(blink::mojom::CodeCacheType cache_type, |
| const GURL& url) override { |
| // `PersistentCache` does not expose the ability to delete specific entries. |
| // This will lead to entries that are known to be unusable by renderers |
| // remaining in the cache. This does not lead to keys being unusable forever |
| // since the entries can get overwritten by valid entries. Additionally this |
| // does not lead to invalid values being used by renderers since the fact |
| // that they are unusable was detected by the clients themselves. |
| // User-driven requests to clear browsing data will clear caches wholesale |
| // rather than delete individual entries. |
| } |
| |
| private: |
| // Returns the identifier by which this host's storage for data of type |
| // `cache_type` is known by the PersistentCacheCollection; or no value in case |
| // no data should be cached. |
| std::optional<std::string> GetCacheId(blink::mojom::CodeCacheType cache_type) |
| VALID_CONTEXT_REQUIRED(sequence_checker_) { |
| ASSIGN_OR_RETURN( |
| GURL origin_lock, GetOriginLock(render_process_id()), |
| []() -> std::optional<std::string> { return std::nullopt; }); |
| |
| if (!origin_lock.is_empty()) { |
| return GeneratedCodeCache::GetContextKey( |
| origin_lock, network_isolation_key(), |
| MojoCacheTypeToCodeCacheType(cache_type)); |
| } |
| |
| // `origin_lock` will be empty if the renderer is not locked to an origin. |
| |
| // Do not cache if site isolation is enabled and is at least as strict as |
| // site-per-process. |
| if (content::SiteIsolationPolicy::IsSitePerProcessOrStricter()) { |
| return std::nullopt; |
| } |
| |
| // Alternatively, Android uses partial Site Isolation (i.e., some sites |
| // require dedicated processes and others do not). |
| // |
| // An empty string is not a valid context key for PersistentCacheCollection |
| // so a shared context key is used instead. This lets all unlocked processes |
| // share a context (and thus a cache) like is achieved when using |
| // GeneratedCodeCache through the implementation of `GetCacheKey()` which |
| // will construct the full cache key using only the resource URL for |
| // requests from unlocked processes. |
| // |
| // The context key returned by this function needs to enfcorce the "jail" |
| // and "citadel" concepts (see: |
| // https://chromium.googlesource.com/chromium/src/+/main/docs/process_model_and_site_isolation.md) |
| // |
| // 1) Locked processes are "jailed" since they cannot access shared context |
| // with their non-empty context key which will never equal |
| // `kSharedContextKeyForRelaxedIsolation'. |
| // 2) The "citadel" concept is upheld because unlocked processes do not have |
| // access to data from locked processes because locked processed store their |
| // data using their specific keys and not the shared context key. |
| // |
| // Use distinct cache IDs for WebUI vs. the open web to minimize the chance |
| // of any cache bug resulting in privilege escalation from an ordinary web |
| // page to trusted WebUI. |
| ProcessLock process_lock = |
| ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock( |
| render_process_id()); |
| static constexpr char kSharedContextKeyForRelaxedIsolation[] = |
| "_shared_context_for_relaxed_isolation"; |
| static constexpr char kSharedContextKeyForRelaxedIsolationWebUi[] = |
| "_shared_context_for_relaxed_isolation_webui"; |
| |
| return process_lock.MatchesScheme(content::kChromeUIScheme) || |
| process_lock.MatchesScheme(content::kChromeUIUntrustedScheme) |
| ? kSharedContextKeyForRelaxedIsolationWebUi |
| : kSharedContextKeyForRelaxedIsolation; |
| } |
| |
| static GeneratedCodeCache::CodeCacheType MojoCacheTypeToCodeCacheType( |
| blink::mojom::CodeCacheType type) { |
| switch (type) { |
| case blink::mojom::CodeCacheType::kJavascript: |
| return GeneratedCodeCache::CodeCacheType::kJavaScript; |
| case blink::mojom::CodeCacheType::kWebAssembly: |
| return GeneratedCodeCache::CodeCacheType::kWebAssembly; |
| } |
| } |
| }; |
| #endif // !BUILDFLAG(IS_FUCHSIA) |
| |
| } // namespace |
| |
| CodeCacheHostImpl::ReceiverSet::ReceiverSet( |
| scoped_refptr<GeneratedCodeCacheContext> generated_code_cache_context) |
| : generated_code_cache_context_(generated_code_cache_context), |
| receiver_set_( |
| new mojo::UniqueReceiverSet<blink::mojom::CodeCacheHost>(), |
| base::OnTaskRunnerDeleter(GeneratedCodeCacheContext::GetTaskRunner( |
| generated_code_cache_context))) {} |
| |
| CodeCacheHostImpl::ReceiverSet::~ReceiverSet() = default; |
| |
| void CodeCacheHostImpl::ReceiverSet::Add( |
| int render_process_id, |
| const net::NetworkIsolationKey& nik, |
| const blink::StorageKey& storage_key, |
| mojo::PendingReceiver<blink::mojom::CodeCacheHost> receiver, |
| CodeCacheHostReceiverHandler handler) { |
| if (!receiver_set_) { |
| receiver_set_ = { |
| new mojo::UniqueReceiverSet<blink::mojom::CodeCacheHost>(), |
| base::OnTaskRunnerDeleter(GeneratedCodeCacheContext::GetTaskRunner( |
| generated_code_cache_context_))}; |
| } |
| // |receiver_set_| will be deleted on the code cache thread, so it is safe to |
| // post a task to the code cache thread with the raw pointer. |
| GeneratedCodeCacheContext::RunOrPostTask( |
| generated_code_cache_context_, FROM_HERE, |
| base::BindOnce(&AddCodeCacheReceiver, receiver_set_.get(), |
| generated_code_cache_context_, render_process_id, nik, |
| storage_key, std::move(receiver), std::move(handler))); |
| } |
| |
| void CodeCacheHostImpl::ReceiverSet::Add( |
| int render_process_id, |
| const net::NetworkIsolationKey& nik, |
| const blink::StorageKey& storage_key, |
| mojo::PendingReceiver<blink::mojom::CodeCacheHost> receiver) { |
| Add(render_process_id, nik, storage_key, std::move(receiver), |
| CodeCacheHostReceiverHandler()); |
| } |
| |
| void CodeCacheHostImpl::ReceiverSet::Clear() { |
| receiver_set_.reset(); |
| } |
| |
| // CodeCacheHostImpl ----------------------------------------------------------- |
| |
| std::unique_ptr<CodeCacheHostImpl> CodeCacheHostImpl::Create( |
| int render_process_id, |
| scoped_refptr<GeneratedCodeCacheContext> generated_code_cache_context, |
| const net::NetworkIsolationKey& nik, |
| const blink::StorageKey& storage_key) { |
| if (!generated_code_cache_context) { |
| // Without context, there's nothing to be done. |
| return std::make_unique<NoopCodeCacheHost>( |
| render_process_id, std::move(generated_code_cache_context), nik, |
| storage_key); |
| } |
| if (blink::features::IsPersistentCacheForCodeCacheEnabled()) { |
| #if !BUILDFLAG(IS_FUCHSIA) |
| return std::make_unique<CodeCacheWithPersistentCacheHost>( |
| render_process_id, std::move(generated_code_cache_context), nik, |
| storage_key); |
| #else |
| NOTREACHED(); |
| #endif // !BUILDFLAG(IS_FUCHSIA) |
| } |
| return std::make_unique<LocalCodeCacheHost>( |
| render_process_id, std::move(generated_code_cache_context), nik, |
| storage_key); |
| } |
| |
| CodeCacheHostImpl::~CodeCacheHostImpl() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| void CodeCacheHostImpl::SetCacheStorageControlForTesting( |
| storage::mojom::CacheStorageControl* cache_storage_control) { |
| cache_storage_control_for_testing_ = cache_storage_control; |
| } |
| |
| // static |
| void CodeCacheHostImpl::SetUseEmptySecondaryKeyForTesting() { |
| use_empty_secondary_key_for_testing_ = true; |
| } |
| |
| CodeCacheHostImpl::CodeCacheHostImpl( |
| int render_process_id, |
| scoped_refptr<GeneratedCodeCacheContext> generated_code_cache_context, |
| const net::NetworkIsolationKey& nik, |
| const blink::StorageKey& storage_key) |
| : render_process_id_(render_process_id), |
| generated_code_cache_context_(std::move(generated_code_cache_context)), |
| network_isolation_key_(nik), |
| storage_key_(storage_key) {} |
| |
| void CodeCacheHostImpl::DidGenerateCacheableMetadataInCacheStorage( |
| const GURL& url, |
| base::Time expected_response_time, |
| mojo_base::BigBuffer data, |
| const std::string& cache_storage_cache_name) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&DidGenerateCacheableMetadataInCacheStorageOnUI, url, |
| expected_response_time, std::move(data), |
| cache_storage_cache_name, render_process_id_, storage_key_, |
| cache_storage_control_for_testing_, |
| mojo::GetBadMessageCallback())); |
| } |
| |
| } // namespace content |