blob: 6c5e1989429495c1b11baa284c4a18e6e6650c24 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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 <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/threading/thread.h"
#include "build/build_config.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/renderer_host/render_process_host_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/public/browser/resource_context.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/bindings/self_owned_receiver.h"
#include "net/base/io_buffer.h"
#include "third_party/blink/public/common/cache_storage/cache_storage_utils.h"
#include "url/gurl.h"
#include "url/origin.h"
using blink::mojom::CacheStorageError;
namespace content {
namespace {
// 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. 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 base::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.
// Case 4. base::nullopt otherwise.
base::Optional<GURL> GetSecondaryKeyForCodeCache(const GURL& resource_url,
int render_process_id) {
if (!resource_url.is_valid() || !resource_url.SchemeIsHTTPOrHTTPS())
return base::nullopt;
ProcessLock process_lock =
ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
render_process_id);
// Case 1: 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.is_locked_to_site())
return GURL::EmptyGURL();
// Case 2: 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 base::nullopt.
if (process_lock.HasOpaqueOrigin())
return base::nullopt;
// Case 3: process_lock_url is used to enfore 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 processes.
if (process_lock.matches_scheme(url::kHttpScheme) ||
process_lock.matches_scheme(url::kHttpsScheme) ||
process_lock.matches_scheme(content::kChromeUIScheme)) {
return process_lock.lock_url();
}
return base::nullopt;
}
} // namespace
CodeCacheHostImpl::CodeCacheHostImpl(
int render_process_id,
RenderProcessHostImpl* render_process_host_impl,
scoped_refptr<GeneratedCodeCacheContext> generated_code_cache_context,
mojo::PendingReceiver<blink::mojom::CodeCacheHost> receiver)
: render_process_id_(render_process_id),
render_process_host_impl_(render_process_host_impl),
generated_code_cache_context_(std::move(generated_code_cache_context)),
receiver_(this, std::move(receiver)) {
// render_process_host_impl may be null in tests.
}
CodeCacheHostImpl::~CodeCacheHostImpl() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
void CodeCacheHostImpl::SetCacheStorageControlForTesting(
storage::mojom::CacheStorageControl* cache_storage_control) {
cache_storage_control_for_testing_ = cache_storage_control;
}
void CodeCacheHostImpl::DidGenerateCacheableMetadata(
blink::mojom::CodeCacheType cache_type,
const GURL& url,
base::Time expected_response_time,
mojo_base::BigBuffer data) {
if (!url.SchemeIsHTTPOrHTTPS()) {
mojo::ReportBadMessage("Invalid URL scheme for code cache.");
return;
}
DCHECK_CURRENTLY_ON(BrowserThread::UI);
GeneratedCodeCache* code_cache = GetCodeCache(cache_type);
if (!code_cache)
return;
base::Optional<GURL> origin_lock =
GetSecondaryKeyForCodeCache(url, render_process_id_);
if (!origin_lock)
return;
code_cache->WriteEntry(url, *origin_lock, expected_response_time,
std::move(data));
}
void CodeCacheHostImpl::FetchCachedCode(blink::mojom::CodeCacheType cache_type,
const GURL& url,
FetchCachedCodeCallback callback) {
GeneratedCodeCache* code_cache = GetCodeCache(cache_type);
if (!code_cache) {
std::move(callback).Run(base::Time(), std::vector<uint8_t>());
return;
}
base::Optional<GURL> origin_lock =
GetSecondaryKeyForCodeCache(url, render_process_id_);
if (!origin_lock) {
std::move(callback).Run(base::Time(), std::vector<uint8_t>());
return;
}
auto read_callback =
base::BindOnce(&CodeCacheHostImpl::OnReceiveCachedCode,
weak_ptr_factory_.GetWeakPtr(), std::move(callback));
code_cache->FetchEntry(url, *origin_lock, std::move(read_callback));
}
void CodeCacheHostImpl::ClearCodeCacheEntry(
blink::mojom::CodeCacheType cache_type,
const GURL& url) {
GeneratedCodeCache* code_cache = GetCodeCache(cache_type);
if (!code_cache)
return;
base::Optional<GURL> origin_lock =
GetSecondaryKeyForCodeCache(url, render_process_id_);
if (!origin_lock)
return;
code_cache->DeleteEntry(url, *origin_lock);
}
void CodeCacheHostImpl::DidGenerateCacheableMetadataInCacheStorage(
const GURL& url,
base::Time expected_response_time,
mojo_base::BigBuffer data,
const url::Origin& cache_storage_origin,
const std::string& cache_storage_cache_name) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// We cannot trust the renderer to give us the correct origin here. Validate
// it against the ChildProcessSecurityPolicy.
bool origin_allowed =
ChildProcessSecurityPolicyImpl::GetInstance()->CanAccessDataForOrigin(
render_process_id_, cache_storage_origin);
if (!origin_allowed) {
receiver_.ReportBadMessage("Bad cache_storage origin.");
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;
storage::mojom::CacheStorageControl* cache_storage_control =
cache_storage_control_for_testing_
? cache_storage_control_for_testing_
: render_process_host_impl_->GetStoragePartition()
->GetCacheStorageControl();
cache_storage_control->AddReceiver(
cross_origin_embedder_policy, mojo::NullRemote(), cache_storage_origin,
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::OpenResultPtr result) {
if (result->is_status()) {
// Silently ignore errors.
return;
}
mojo::AssociatedRemote<blink::mojom::CacheStorageCache> remote;
remote.Bind(std::move(result->get_cache()));
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)));
}
GeneratedCodeCache* CodeCacheHostImpl::GetCodeCache(
blink::mojom::CodeCacheType cache_type) {
if (!generated_code_cache_context_)
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();
}
void CodeCacheHostImpl::OnReceiveCachedCode(FetchCachedCodeCallback callback,
const base::Time& response_time,
mojo_base::BigBuffer data) {
std::move(callback).Run(response_time, std::move(data));
}
} // namespace content