blob: 537a48d09a6301c2b24b7e5f875d7450f5dc8214 [file] [log] [blame]
// Copyright 2021 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/shared_storage/shared_storage_document_service_impl.h"
#include <stdint.h>
#include <string>
#include <utility>
#include "base/strings/strcat.h"
#include "components/services/storage/shared_storage/shared_storage_database.h"
#include "components/services/storage/shared_storage/shared_storage_manager.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/shared_storage/shared_storage_worklet_host.h"
#include "content/browser/shared_storage/shared_storage_worklet_host_manager.h"
#include "content/browser/storage_partition_impl.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_client.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/shared_storage/shared_storage_utils.h"
#include "url/url_constants.h"
namespace content {
namespace {
// Note that this function would also return false if the context origin is
// opaque. This is stricter than the web platform's notion of "secure context".
// TODO(yaoxia): This should be function in FrameTreeNode.
bool IsSecureFrame(RenderFrameHost* frame) {
while (frame) {
if (!network::IsOriginPotentiallyTrustworthy(
frame->GetLastCommittedOrigin())) {
return false;
}
frame = frame->GetParent();
}
return true;
}
using AccessType =
SharedStorageWorkletHostManager::SharedStorageObserverInterface::AccessType;
using OperationResult = storage::SharedStorageManager::OperationResult;
using GetResult = storage::SharedStorageManager::GetResult;
} // namespace
const char kSharedStorageDisabledMessage[] = "sharedStorage is disabled";
const char kSharedStorageSelectURLDisabledMessage[] =
"sharedStorage.selectURL is disabled";
const char kSharedStorageAddModuleDisabledMessage[] =
"sharedStorage.worklet.addModule is disabled because either sharedStorage "
"is disabled or both sharedStorage.selectURL and privateAggregation are "
"disabled";
const char kSharedStorageSelectURLLimitReachedMessage[] =
"sharedStorage.selectURL limit has been reached";
// NOTE: To preserve user privacy, the default value of the
// `blink::features::kSharedStorageExposeDebugMessageForSettingsStatus`
// feature param MUST remain set to false (although the value can be overridden
// via the command line or in tests).
std::string GetSharedStorageErrorMessage(const std::string& debug_message,
const std::string& input_message) {
return blink::features::kSharedStorageExposeDebugMessageForSettingsStatus
.Get()
? base::StrCat({input_message, "\nDebug: ", debug_message})
: input_message;
}
SharedStorageDocumentServiceImpl::~SharedStorageDocumentServiceImpl() {
GetSharedStorageWorkletHostManager()->OnDocumentServiceDestroyed(this);
}
void SharedStorageDocumentServiceImpl::Bind(
mojo::PendingAssociatedReceiver<blink::mojom::SharedStorageDocumentService>
receiver) {
CHECK(!receiver_)
<< "Multiple attempts to bind the SharedStorageDocumentService receiver";
if (render_frame_host().GetLastCommittedOrigin().opaque()) {
mojo::ReportBadMessage(
"Attempted to request SharedStorageDocumentService from an opaque "
"origin context");
return;
}
bool is_secure_frame = IsSecureFrame(&render_frame_host());
base::UmaHistogramBoolean(
"Storage.SharedStorage.DocumentServiceBind.IsSecureFrame",
is_secure_frame);
if (!is_secure_frame) {
// TODO(crbug.com/40068897): Invoke mojo::ReportBadMessage here when
// we can be sure honest renderers won't hit this path.
return;
}
receiver_.Bind(std::move(receiver));
}
void SharedStorageDocumentServiceImpl::CreateWorklet(
const GURL& script_source_url,
network::mojom::CredentialsMode credentials_mode,
const std::vector<blink::mojom::OriginTrialFeature>& origin_trial_features,
mojo::PendingAssociatedReceiver<blink::mojom::SharedStorageWorkletHost>
worklet_host,
CreateWorkletCallback callback) {
// A document can only create multiple worklets with `kSharedStorageAPIM125`
// enabled.
if (!base::FeatureList::IsEnabled(blink::features::kSharedStorageAPIM125)) {
if (create_worklet_called_) {
// This could indicate a compromised renderer, so let's terminate it.
receiver_.ReportBadMessage("Attempted to create multiple worklets.");
LogSharedStorageWorkletError(
blink::SharedStorageWorkletErrorType::
kAddModuleNonWebVisibleMulipleWorkletsDisabled);
return;
}
}
create_worklet_called_ = true;
bool is_same_origin =
render_frame_host().GetLastCommittedOrigin().IsSameOriginWith(
script_source_url);
// A document can only create cross-origin worklets with
// `kSharedStorageAPIM125` enabled.
if (!base::FeatureList::IsEnabled(blink::features::kSharedStorageAPIM125) &&
!is_same_origin) {
// This could indicate a compromised renderer, so let's terminate it.
receiver_.ReportBadMessage(
"Attempted to load a cross-origin module script.");
LogSharedStorageWorkletError(
blink::SharedStorageWorkletErrorType::
kAddModuleNonWebVisibleCrossOriginWorkletsDisabled);
return;
}
std::string debug_message;
if (!IsSharedStorageAddModuleAllowed(&debug_message)) {
OnCreateWorkletResponseIntercepted(
is_same_origin, std::move(callback),
/*prefs_success=*/false,
/*success=*/false,
/*error_message=*/
GetSharedStorageErrorMessage(debug_message,
kSharedStorageAddModuleDisabledMessage));
return;
}
GetSharedStorageWorkletHostManager()->CreateWorkletHost(
this, render_frame_host().GetLastCommittedOrigin(), script_source_url,
credentials_mode, origin_trial_features, std::move(worklet_host),
base::BindOnce(
&SharedStorageDocumentServiceImpl::OnCreateWorkletResponseIntercepted,
weak_ptr_factory_.GetWeakPtr(), is_same_origin, std::move(callback),
/*prefs_success=*/true));
}
void SharedStorageDocumentServiceImpl::SharedStorageGet(
const std::u16string& key,
SharedStorageGetCallback callback) {
if (!render_frame_host().IsNestedWithinFencedFrame()) {
mojo::ReportBadMessage(
"Attempted to call get() outside of a fenced frame.");
return;
}
std::string debug_message;
if (!IsSharedStorageAllowed(&debug_message)) {
std::move(callback).Run(blink::mojom::SharedStorageGetStatus::kError,
/*error_message=*/
GetSharedStorageErrorMessage(
debug_message, kSharedStorageDisabledMessage),
/*value=*/{});
return;
}
if (!(static_cast<RenderFrameHostImpl&>(render_frame_host())
.CanReadFromSharedStorage())) {
std::move(callback).Run(
blink::mojom::SharedStorageGetStatus::kError,
/*error_message=*/
"sharedStorage.get() is not allowed in a fenced frame until network "
"access for it and all descendent frames has been revoked with "
"window.fence.disableUntrustedNetwork()",
/*value=*/{});
return;
}
GetSharedStorageWorkletHostManager()->NotifySharedStorageAccessed(
AccessType::kDocumentGet, main_frame_id(), SerializeLastCommittedOrigin(),
SharedStorageEventParams::CreateForGetOrDelete(base::UTF16ToUTF8(key)));
auto operation_completed_callback = base::BindOnce(
[](SharedStorageGetCallback callback, GetResult result) {
// If the key is not found but there is no other error, the worklet will
// resolve the promise to undefined.
if (result.result == OperationResult::kNotFound ||
result.result == OperationResult::kExpired) {
std::move(callback).Run(
blink::mojom::SharedStorageGetStatus::kNotFound,
/*error_message=*/"sharedStorage.get() could not find key",
/*value=*/{});
return;
}
if (result.result != OperationResult::kSuccess) {
std::move(callback).Run(
blink::mojom::SharedStorageGetStatus::kError,
/*error_message=*/"sharedStorage.get() failed", /*value=*/{});
return;
}
std::move(callback).Run(blink::mojom::SharedStorageGetStatus::kSuccess,
/*error_message=*/{}, /*value=*/result.data);
},
std::move(callback));
GetSharedStorageManager()->Get(render_frame_host().GetLastCommittedOrigin(),
key, std::move(operation_completed_callback));
}
void SharedStorageDocumentServiceImpl::SharedStorageSet(
const std::u16string& key,
const std::u16string& value,
bool ignore_if_present,
SharedStorageSetCallback callback) {
std::string debug_message;
if (!IsSharedStorageAllowed(&debug_message)) {
std::move(callback).Run(
/*success=*/false,
/*error_message=*/GetSharedStorageErrorMessage(
debug_message, kSharedStorageDisabledMessage));
return;
}
storage::SharedStorageDatabase::SetBehavior set_behavior =
ignore_if_present
? storage::SharedStorageDatabase::SetBehavior::kIgnoreIfPresent
: storage::SharedStorageDatabase::SetBehavior::kDefault;
GetSharedStorageWorkletHostManager()->NotifySharedStorageAccessed(
AccessType::kDocumentSet, main_frame_id(), SerializeLastCommittedOrigin(),
SharedStorageEventParams::CreateForSet(
base::UTF16ToUTF8(key), base::UTF16ToUTF8(value), ignore_if_present));
GetSharedStorageManager()->Set(render_frame_host().GetLastCommittedOrigin(),
key, value, base::DoNothing(), set_behavior);
std::move(callback).Run(/*success=*/true, /*error_message=*/{});
}
void SharedStorageDocumentServiceImpl::SharedStorageAppend(
const std::u16string& key,
const std::u16string& value,
SharedStorageAppendCallback callback) {
std::string debug_message;
if (!IsSharedStorageAllowed(&debug_message)) {
std::move(callback).Run(
/*success=*/false,
/*error_message=*/GetSharedStorageErrorMessage(
debug_message, kSharedStorageDisabledMessage));
return;
}
GetSharedStorageWorkletHostManager()->NotifySharedStorageAccessed(
AccessType::kDocumentAppend, main_frame_id(),
SerializeLastCommittedOrigin(),
SharedStorageEventParams::CreateForAppend(base::UTF16ToUTF8(key),
base::UTF16ToUTF8(value)));
GetSharedStorageManager()->Append(
render_frame_host().GetLastCommittedOrigin(), key, value,
base::DoNothing());
std::move(callback).Run(/*success=*/true, /*error_message=*/{});
}
void SharedStorageDocumentServiceImpl::SharedStorageDelete(
const std::u16string& key,
SharedStorageDeleteCallback callback) {
std::string debug_message;
if (!IsSharedStorageAllowed(&debug_message)) {
std::move(callback).Run(
/*success=*/false,
/*error_message=*/GetSharedStorageErrorMessage(
debug_message, kSharedStorageDisabledMessage));
return;
}
GetSharedStorageWorkletHostManager()->NotifySharedStorageAccessed(
AccessType::kDocumentDelete, main_frame_id(),
SerializeLastCommittedOrigin(),
SharedStorageEventParams::CreateForGetOrDelete(base::UTF16ToUTF8(key)));
GetSharedStorageManager()->Delete(
render_frame_host().GetLastCommittedOrigin(), key, base::DoNothing());
std::move(callback).Run(/*success=*/true, /*error_message=*/{});
}
void SharedStorageDocumentServiceImpl::SharedStorageClear(
SharedStorageClearCallback callback) {
std::string debug_message;
if (!IsSharedStorageAllowed(&debug_message)) {
std::move(callback).Run(
/*success=*/false,
/*error_message=*/GetSharedStorageErrorMessage(
debug_message, kSharedStorageDisabledMessage));
return;
}
GetSharedStorageWorkletHostManager()->NotifySharedStorageAccessed(
AccessType::kDocumentClear, main_frame_id(),
SerializeLastCommittedOrigin(),
SharedStorageEventParams::CreateDefault());
GetSharedStorageManager()->Clear(render_frame_host().GetLastCommittedOrigin(),
base::DoNothing());
std::move(callback).Run(/*success=*/true, /*error_message=*/{});
}
base::WeakPtr<SharedStorageDocumentServiceImpl>
SharedStorageDocumentServiceImpl::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
SharedStorageDocumentServiceImpl::SharedStorageDocumentServiceImpl(
RenderFrameHost* rfh)
: DocumentUserData<SharedStorageDocumentServiceImpl>(rfh),
main_frame_origin_(
rfh->GetOutermostMainFrame()->GetLastCommittedOrigin()),
main_frame_id_(
static_cast<RenderFrameHostImpl*>(rfh->GetOutermostMainFrame())
->GetFrameTreeNodeId()) {}
void SharedStorageDocumentServiceImpl::OnCreateWorkletResponseIntercepted(
bool is_same_origin,
CreateWorkletCallback original_callback,
bool prefs_success,
bool success,
const std::string& error_message) {
// When the worklet and the worklet creator are not same-origin, the user
// preferences for the worklet origin should not be revealed.
if (!is_same_origin) {
if (!prefs_success) {
LogSharedStorageWorkletError(
blink::SharedStorageWorkletErrorType::
kAddModuleNonWebVisibleCrossOriginSharedStorageDisabled);
} else if (!success) {
LogSharedStorageWorkletError(
blink::SharedStorageWorkletErrorType::kAddModuleNonWebVisibleOther);
} else {
LogSharedStorageWorkletError(
blink::SharedStorageWorkletErrorType::kSuccess);
}
std::move(original_callback).Run(/*success=*/true, /*error_message=*/{});
return;
}
if (success) {
LogSharedStorageWorkletError(
blink::SharedStorageWorkletErrorType::kSuccess);
}
std::move(original_callback).Run(success, error_message);
}
storage::SharedStorageManager*
SharedStorageDocumentServiceImpl::GetSharedStorageManager() {
storage::SharedStorageManager* shared_storage_manager =
static_cast<StoragePartitionImpl*>(
render_frame_host().GetProcess()->GetStoragePartition())
->GetSharedStorageManager();
// This `SharedStorageDocumentServiceImpl` is created only if
// `kSharedStorageAPI` is enabled, in which case the `shared_storage_manager`
// must be valid.
DCHECK(shared_storage_manager);
return shared_storage_manager;
}
SharedStorageWorkletHostManager*
SharedStorageDocumentServiceImpl::GetSharedStorageWorkletHostManager() {
return static_cast<StoragePartitionImpl*>(
render_frame_host().GetProcess()->GetStoragePartition())
->GetSharedStorageWorkletHostManager();
}
bool SharedStorageDocumentServiceImpl::IsSharedStorageAllowed(
std::string* out_debug_message) {
// Will trigger a call to
// `content_settings::PageSpecificContentSettings::BrowsingDataAccessed()` for
// reporting purposes.
return GetContentClient()->browser()->IsSharedStorageAllowed(
render_frame_host().GetBrowserContext(), &render_frame_host(),
main_frame_origin_, render_frame_host().GetLastCommittedOrigin(),
out_debug_message);
}
bool SharedStorageDocumentServiceImpl::IsSharedStorageAddModuleAllowed(
std::string* out_debug_message) {
// Will trigger a call to
// `content_settings::PageSpecificContentSettings::BrowsingDataAccessed()` for
// reporting purposes.
if (!IsSharedStorageAllowed(out_debug_message)) {
return false;
}
return GetContentClient()->browser()->IsSharedStorageSelectURLAllowed(
render_frame_host().GetBrowserContext(), main_frame_origin_,
render_frame_host().GetLastCommittedOrigin(), out_debug_message) ||
GetContentClient()->browser()->IsPrivateAggregationAllowed(
render_frame_host().GetBrowserContext(), main_frame_origin_,
render_frame_host().GetLastCommittedOrigin());
}
std::string SharedStorageDocumentServiceImpl::SerializeLastCommittedOrigin()
const {
return render_frame_host().GetLastCommittedOrigin().Serialize();
}
DOCUMENT_USER_DATA_KEY_IMPL(SharedStorageDocumentServiceImpl);
} // namespace content