Skip to content

Commit

Permalink
Fenced frame: enable attribution reporting for opaque-ads
Browse files Browse the repository at this point in the history
Opaque-ads fenced frames need to have attribution reporting enabled
to be able to support FLEDGE. Until the opaque URN attributes feature
is in place, this is a temporary solution to allow a fenced frame to
have attribution reporting.

- An opaque-ads fenced frame will not be allowed to load if attribution reporting is explicitly disabled in its embedder.
- If the embedder doesn't specify any attribution reporting policy in
its header, the opaque-ads fenced frame will act as if it was enabled
via allow (*) and allow itself to load.
- If an opaque-ads fenced frame loads, it will turn on attribution reporting in its permissions policy.
- Update fenced frame creation/initialization to do most of the
initialization after the fenced frame constructor. This ensures that the
fenced frame is properly read into the fenced frames vector before
initialization, allowing us to determine the mode for MPArch (which
expects the fenced frame to be in the vector).

See design doc:
https://docs.google.com/document/d/1vnCoglukP61ucDY4ob6zgizM1aoilmlFOq4Gcs8OVX8/edit?usp=sharing&resourcekey=0-9UtvE--vXmeNJHiKZgx7GA

Change-Id: Ib53c4be57e7701bbf052377fcdf7bfd2570b2e00
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3779724
Reviewed-by: Dominic Farolino <dom@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Arthur Sonzogni <arthursonzogni@chromium.org>
Reviewed-by: Shivani Sharma <shivanisha@chromium.org>
Commit-Queue: Liam Brady <lbrady@google.com>
Cr-Commit-Position: refs/heads/main@{#1034236}
  • Loading branch information
Liam Brady authored and Chromium LUCI CQ committed Aug 11, 2022
1 parent 1c7d56b commit b1c43c2
Show file tree
Hide file tree
Showing 28 changed files with 395 additions and 52 deletions.
4 changes: 2 additions & 2 deletions content/browser/client_hints/client_hints.cc
Expand Up @@ -553,8 +553,8 @@ struct ClientHintsExtendedData {
outermost_main_frame_origin = resource_origin;
is_1p_origin = true;
} else if (frame_tree_node->IsInFencedFrameTree()) {
permissions_policy =
blink::PermissionsPolicy::CreateForFencedFrame(resource_origin);
permissions_policy = blink::PermissionsPolicy::CreateForFencedFrame(
resource_origin, frame_tree_node->GetFencedFrameMode().value());
} else {
RenderFrameHostImpl* outermost_main_frame = frame_tree_node->frame_tree()
->GetMainFrame()
Expand Down
64 changes: 33 additions & 31 deletions content/browser/fenced_frame/fenced_frame.cc
Expand Up @@ -61,36 +61,7 @@ FencedFrame::FencedFrame(
/*page_delegate=*/web_contents_,
FrameTree::Type::kFencedFrame,
devtools_frame_token)),
mode_(mode) {
scoped_refptr<SiteInstance> site_instance =
SiteInstanceImpl::CreateForFencedFrame(
owner_render_frame_host->GetSiteInstance());

// Set the mandatory sandbox flags from the beginning.
blink::FramePolicy frame_policy;
frame_policy.sandbox_flags = blink::kFencedFrameForcedSandboxFlags;
// Note that even though this is happening in response to an event in the
// renderer (i.e., the creation of a <fencedframe> element), we are still
// putting `renderer_initiated_creation` as false. This is because that
// parameter is only used when a renderer is creating a new window and has
// already created the main frame for the window, but wants the browser to
// refrain from showing the main frame until the renderer signals the browser
// via the mojom.LocalMainFrameHost.ShowCreatedWindow(). This flow does not
// apply for fenced frames, portals, and prerendered nested FrameTrees, hence
// the decision to mark it as false.
frame_tree_->Init(site_instance.get(), /*renderer_initiated_creation=*/false,
/*main_frame_name=*/"", /*opener_for_origin=*/nullptr,
frame_policy);
// Note that pending frame policy will be passed as `frame_policy` in
// `replication_state` in `mojom::CreateFrameParams`.
// See `RenderFrameHostImpl::CreateRenderFrame`.
frame_tree_->root()->SetPendingFramePolicy(frame_policy);

// TODO(crbug.com/1199679): This should be moved to FrameTree::Init.
web_contents_->NotifySwappedFromRenderManager(
/*old_frame=*/nullptr,
frame_tree_->root()->render_manager()->current_frame_host());
}
mode_(mode) {}

FencedFrame::~FencedFrame() {
DCHECK(frame_tree_);
Expand Down Expand Up @@ -180,11 +151,42 @@ FrameTree* FencedFrame::LoadingTree() {
return web_contents_->LoadingTree();
}

RenderFrameProxyHost* FencedFrame::CreateProxyAndAttachToOuterFrameTree(
RenderFrameProxyHost*
FencedFrame::InitInnerFrameTreeAndReturnProxyToOuterFrameTree(
blink::mojom::RemoteFrameInterfacesFromRendererPtr remote_frame_interfaces,
const blink::RemoteFrameToken& frame_token) {
DCHECK(remote_frame_interfaces);
DCHECK(outer_delegate_frame_tree_node_);

scoped_refptr<SiteInstance> site_instance =
SiteInstanceImpl::CreateForFencedFrame(
owner_render_frame_host_->GetSiteInstance());

// Set the mandatory sandbox flags from the beginning.
blink::FramePolicy frame_policy;
frame_policy.sandbox_flags = blink::kFencedFrameForcedSandboxFlags;
// Note that even though this is happening in response to an event in the
// renderer (i.e., the creation of a <fencedframe> element), we are still
// putting `renderer_initiated_creation` as false. This is because that
// parameter is only used when a renderer is creating a new window and has
// already created the main frame for the window, but wants the browser to
// refrain from showing the main frame until the renderer signals the browser
// via the mojom.LocalMainFrameHost.ShowCreatedWindow(). This flow does not
// apply for fenced frames, portals, and prerendered nested FrameTrees, hence
// the decision to mark it as false.
frame_tree_->Init(site_instance.get(), /*renderer_initiated_creation=*/false,
/*main_frame_name=*/"", /*opener_for_origin=*/nullptr,
frame_policy);
// Note that pending frame policy will be passed as `frame_policy` in
// `replication_state` in `mojom::CreateFrameParams`.
// See `RenderFrameHostImpl::CreateRenderFrame`.
frame_tree_->root()->SetPendingFramePolicy(frame_policy);

// TODO(crbug.com/1199679): This should be moved to FrameTree::Init.
web_contents_->NotifySwappedFromRenderManager(
/*old_frame=*/nullptr,
frame_tree_->root()->render_manager()->current_frame_host());

// Connect the outer delegate RenderFrameHost with the inner main
// FrameTreeNode. This allows us to traverse from the outer delegate RFH
// inward, to the inner fenced frame FrameTree.
Expand Down
4 changes: 2 additions & 2 deletions content/browser/fenced_frame/fenced_frame.h
Expand Up @@ -49,7 +49,7 @@ class CONTENT_EXPORT FencedFrame : public blink::mojom::FencedFrameOwnerHost,
// renderer. This creates a proxy representing the main frame of the inner
// `FrameTree`, for use by the embedding RenderFrameHostImpl.
// `remote_frame_interfaces` must not be null.
RenderFrameProxyHost* CreateProxyAndAttachToOuterFrameTree(
RenderFrameProxyHost* InitInnerFrameTreeAndReturnProxyToOuterFrameTree(
blink::mojom::RemoteFrameInterfacesFromRendererPtr
remote_frame_interfaces,
const blink::RemoteFrameToken& frame_token);
Expand Down Expand Up @@ -106,7 +106,7 @@ class CONTENT_EXPORT FencedFrame : public blink::mojom::FencedFrameOwnerHost,
// frame FrameTree. It is a "dummy" child FrameTreeNode that `this` is
// responsible for adding as a child of `owner_render_frame_host_`; it is
// initially null, and only set in the constructor (indirectly via
// `CreateProxyAndAttachToOuterFrameTree()`).
// `InitInnerFrameTreeAndReturnProxyToOuterFrameTree()`).
// Furthermore, the lifetime of `this` is directly tied to it (see
// `OnFrameTreeNodeDestroyed()`).
raw_ptr<FrameTreeNode> outer_delegate_frame_tree_node_ = nullptr;
Expand Down
45 changes: 45 additions & 0 deletions content/browser/renderer_host/navigation_request.cc
Expand Up @@ -144,6 +144,7 @@
#include "third_party/blink/public/common/client_hints/client_hints.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h"
#include "third_party/blink/public/common/frame/fenced_frame_permissions_policies.h"
#include "third_party/blink/public/common/frame/fenced_frame_sandbox_flags.h"
#include "third_party/blink/public/common/frame/frame_owner_element_type.h"
#include "third_party/blink/public/common/navigation/navigation_params_mojom_traits.h"
Expand All @@ -152,6 +153,7 @@
#include "third_party/blink/public/common/origin_trials/trial_token_result.h"
#include "third_party/blink/public/common/origin_trials/trial_token_validator.h"
#include "third_party/blink/public/common/permissions_policy/document_policy.h"
#include "third_party/blink/public/common/permissions_policy/policy_helper_public.h"
#include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
#include "third_party/blink/public/common/security/address_space_feature.h"
#include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
Expand Down Expand Up @@ -3854,6 +3856,17 @@ void NavigationRequest::OnResponseStarted(
return;
}

if (render_frame_host_ &&
!CheckPermissionsPoliciesForFencedFrames(GetOriginToCommit())) {
OnRequestFailedInternal(
network::URLLoaderCompletionStatus(net::ERR_ABORTED),
false /*skip_throttles*/, absl::nullopt /*error_page_content*/,
false /*collapse_frame*/);
// DO NOT ADD CODE after this. The previous call to
// OnRequestFailedInternal has destroyed the NavigationRequest.
return;
}

// Check if the navigation should be allowed to proceed.
WillProcessResponse();
}
Expand Down Expand Up @@ -7414,6 +7427,38 @@ bool NavigationRequest::CoopCoepSanityCheck() {
return true;
}

bool NavigationRequest::CheckPermissionsPoliciesForFencedFrames(
const url::Origin& origin) {
// These checks only apply to fenced frames.
if (!frame_tree_node_->IsFencedFrameRoot())
return true;

for (const blink::mojom::PermissionsPolicyFeature feature :
blink::kFencedFrameOpaqueAdsDefaultAllowedFeatures) {
// Only check if the feature is enabled for this origin if
// a policy was explicitly specified.
if (GetParentFrameOrOuterDocument()
->permissions_policy()
->GetAllowlistForFeatureIfExists(feature) &&
!GetParentFrameOrOuterDocument()
->permissions_policy()
->IsFeatureEnabledForOrigin(feature, origin)) {
const blink::PermissionsPolicyFeatureToNameMap& feature_to_name_map =
blink::GetPermissionsPolicyFeatureToNameMap();
const std::string feature_string(
(feature_to_name_map.find(feature))->second);
AddDeferredConsoleMessage(
blink::mojom::ConsoleMessageLevel::kError,
base::StringPrintf(
"Refused to frame '%s' as a fenced frame because permissions "
"policy '%s' is not allowed for the frame's origin.",
origin.Serialize().c_str(), feature_string.c_str()));
return false;
}
}
return true;
}

std::unique_ptr<PeakGpuMemoryTracker>
NavigationRequest::TakePeakGpuMemoryTracker() {
return std::move(loading_mem_tracker_);
Expand Down
5 changes: 5 additions & 0 deletions content/browser/renderer_host/navigation_request.h
Expand Up @@ -1451,6 +1451,11 @@ class CONTENT_EXPORT NavigationRequest
// If they aren't, this returns false and emits a crash report.
bool CoopCoepSanityCheck();

// Checks if all of the permissions policies that a fenced frame requires to
// be enabled for its origin are enabled. If not, it logs a console message
// and returns false.
bool CheckPermissionsPoliciesForFencedFrames(const url::Origin&);

// Returns the user-agent override, or an empty string if one isn't set.
std::string GetUserAgentOverride();

Expand Down
12 changes: 7 additions & 5 deletions content/browser/renderer_host/render_frame_host_impl.cc
Expand Up @@ -771,8 +771,9 @@ DetermineWhetherToForbidTrustTokenRedemption(
if (frame->IsNestedWithinFencedFrame()) {
// In Fenced Frames, all permission policy gated features must be disabled
// for privacy reasons.
subframe_policy =
blink::PermissionsPolicy::CreateForFencedFrame(subframe_origin);
subframe_policy = blink::PermissionsPolicy::CreateForFencedFrame(
subframe_origin,
frame->frame_tree_node()->GetFencedFrameMode().value());
} else {
// For main frame loads, the frame's permissions policy is determined
// entirely by response headers, which are provided by the renderer.
Expand Down Expand Up @@ -7697,7 +7698,7 @@ void RenderFrameHostImpl::CreateFencedFrame(
weak_ptr_factory_.GetSafeRef(), mode, devtools_frame_token));
FencedFrame* fenced_frame = fenced_frames_.back().get();
RenderFrameProxyHost* proxy_host =
fenced_frame->CreateProxyAndAttachToOuterFrameTree(
fenced_frame->InitInnerFrameTreeAndReturnProxyToOuterFrameTree(
std::move(remote_frame_interfaces), frame_token);
fenced_frame->Bind(std::move(pending_receiver));

Expand Down Expand Up @@ -10336,8 +10337,9 @@ void RenderFrameHostImpl::ResetPermissionsPolicy() {
if (IsNestedWithinFencedFrame()) {
// In Fenced Frames, all permission policy gated features must be disabled
// for privacy reasons.
permissions_policy_ =
blink::PermissionsPolicy::CreateForFencedFrame(last_committed_origin_);
permissions_policy_ = blink::PermissionsPolicy::CreateForFencedFrame(
last_committed_origin_,
frame_tree_node()->GetFencedFrameMode().value());
return;
}

Expand Down
2 changes: 1 addition & 1 deletion content/test/test_render_frame_host.cc
Expand Up @@ -281,7 +281,7 @@ TestRenderFrameHost* TestRenderFrameHost::AppendFencedFrame(
mojo::AssociatedRemote<blink::mojom::RemoteFrame> frame;
std::ignore = frame.BindNewEndpointAndPassDedicatedReceiver();
remote_frame_interfaces->frame = frame.Unbind();
fenced_frame->CreateProxyAndAttachToOuterFrameTree(
fenced_frame->InitInnerFrameTreeAndReturnProxyToOuterFrameTree(
std::move(remote_frame_interfaces), blink::RemoteFrameToken());
return static_cast<TestRenderFrameHost*>(fenced_frame->GetInnerRoot());
}
Expand Down
19 changes: 16 additions & 3 deletions third_party/blink/common/permissions_policy/permissions_policy.cc
Expand Up @@ -9,6 +9,7 @@
#include "base/no_destructor.h"
#include "services/network/public/mojom/web_sandbox_flags.mojom-shared.h"
#include "third_party/blink/public/common/client_hints/client_hints.h"
#include "third_party/blink/public/common/frame/fenced_frame_permissions_policies.h"
#include "third_party/blink/public/common/permissions_policy/permissions_policy_features.h"
#include "third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom.h"

Expand Down Expand Up @@ -358,18 +359,30 @@ PermissionsPolicy::~PermissionsPolicy() = default;

// static
std::unique_ptr<PermissionsPolicy> PermissionsPolicy::CreateForFencedFrame(
const url::Origin& origin) {
return CreateForFencedFrame(origin, GetPermissionsPolicyFeatureList());
const url::Origin& origin,
blink::mojom::FencedFrameMode mode) {
return CreateForFencedFrame(origin, GetPermissionsPolicyFeatureList(), mode);
}

std::unique_ptr<PermissionsPolicy> PermissionsPolicy::CreateForFencedFrame(
const url::Origin& origin,
const PermissionsPolicyFeatureList& features) {
const PermissionsPolicyFeatureList& features,
blink::mojom::FencedFrameMode mode) {
std::unique_ptr<PermissionsPolicy> new_policy =
base::WrapUnique(new PermissionsPolicy(origin, features));
for (const auto& feature : features) {
new_policy->inherited_policies_[feature.first] = false;
}
// TODO(crbug.com/1347953): this is a medium-term solution to allow
// attribution reporting inside an opaque ad. This will eventually be replaced
// by urn:uuid bound attributes as outlined in this document:
// https://docs.google.com/document/d/11QaI40IAr12CDFrIUQbugxmS9LfircghHUghW-EDzMk/edit?usp=sharing
if (mode == blink::mojom::FencedFrameMode::kOpaqueAds) {
for (const blink::mojom::PermissionsPolicyFeature feature :
blink::kFencedFrameOpaqueAdsDefaultAllowedFeatures) {
new_policy->inherited_policies_[feature] = true;
}
}
return new_policy;
}

Expand Down
Expand Up @@ -39,6 +39,8 @@ class PermissionsPolicyTest : public testing::Test {
{kDefaultSelfFeature,
PermissionsPolicyFeatureDefault::EnableForSelf},
{mojom::PermissionsPolicyFeature::kClientHintDPR,
PermissionsPolicyFeatureDefault::EnableForSelf},
{mojom::PermissionsPolicyFeature::kAttributionReporting,
PermissionsPolicyFeatureDefault::EnableForSelf}}) {}

~PermissionsPolicyTest() override = default;
Expand Down Expand Up @@ -66,8 +68,9 @@ class PermissionsPolicyTest : public testing::Test {
origin, feature_list_);
}
std::unique_ptr<PermissionsPolicy> CreateForFencedFrame(
const url::Origin& origin) {
return PermissionsPolicy::CreateForFencedFrame(origin, feature_list_);
const url::Origin& origin,
const blink::mojom::FencedFrameMode mode) {
return PermissionsPolicy::CreateForFencedFrame(origin, feature_list_, mode);
}

bool PolicyContainsInheritedValue(const PermissionsPolicy* policy,
Expand Down Expand Up @@ -1657,10 +1660,22 @@ TEST_F(PermissionsPolicyTest, ProposedTestNestedPolicyPropagates) {
EXPECT_FALSE(policy3->IsFeatureEnabled(kDefaultSelfFeature));
}

TEST_F(PermissionsPolicyTest, CreateForFencedFrame) {
std::unique_ptr<PermissionsPolicy> policy = CreateForFencedFrame(origin_a_);
TEST_F(PermissionsPolicyTest, CreateForDefaultFencedFrame) {
std::unique_ptr<PermissionsPolicy> policy =
CreateForFencedFrame(origin_a_, blink::mojom::FencedFrameMode::kDefault);
EXPECT_FALSE(policy->IsFeatureEnabled(kDefaultOnFeature));
EXPECT_FALSE(policy->IsFeatureEnabled(kDefaultSelfFeature));
EXPECT_FALSE(policy->IsFeatureEnabled(
mojom::PermissionsPolicyFeature::kAttributionReporting));
}

TEST_F(PermissionsPolicyTest, CreateForOpaqueFencedFrame) {
std::unique_ptr<PermissionsPolicy> policy = CreateForFencedFrame(
origin_a_, blink::mojom::FencedFrameMode::kOpaqueAds);
EXPECT_FALSE(policy->IsFeatureEnabled(kDefaultOnFeature));
EXPECT_FALSE(policy->IsFeatureEnabled(kDefaultSelfFeature));
EXPECT_TRUE(policy->IsFeatureEnabled(
mojom::PermissionsPolicyFeature::kAttributionReporting));
}

TEST_F(PermissionsPolicyTest, CreateFromParsedPolicy) {
Expand Down
1 change: 1 addition & 0 deletions third_party/blink/public/common/BUILD.gn
Expand Up @@ -136,6 +136,7 @@ source_set("headers") {
"fetch/fetch_api_request_headers_mojom_traits.h",
"forcedark/forcedark_switches.h",
"frame/event_page_show_persisted.h",
"frame/fenced_frame_permissions_policies.h",
"frame/fenced_frame_sandbox_flags.h",
"frame/frame_ad_evidence.h",
"frame/frame_owner_element_type.h",
Expand Down
@@ -0,0 +1,23 @@
// Copyright 2022 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.

#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_FRAME_FENCED_FRAME_PERMISSIONS_POLICIES_H_
#define THIRD_PARTY_BLINK_PUBLIC_COMMON_FRAME_FENCED_FRAME_PERMISSIONS_POLICIES_H_

namespace blink {

// In fenced frame trees, these permission policies are expected to be enabled.
// If any feature is disallowed for the fenced frame's origin, then the fenced
// frame will not be allowed to navigate. This is a medium-term solution that
// will be replaced by a system where consumer APIs (like FLEDGE) can select
// which features to require in order to navigate a fenced frame successfully.
// If a fenced frame navigates, each of these features will be allowed as if
// its policy was set to "allow: feature(*)".
constexpr blink::mojom::PermissionsPolicyFeature
kFencedFrameOpaqueAdsDefaultAllowedFeatures[] = {
blink::mojom::PermissionsPolicyFeature::kAttributionReporting};

} // namespace blink

#endif // THIRD_PARTY_BLINK_PUBLIC_COMMON_FRAME_FENCED_FRAME_PERMISSIONS_POLICIES_H_
Expand Up @@ -12,6 +12,7 @@
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/common_export.h"
#include "third_party/blink/public/common/permissions_policy/permissions_policy_features.h"
#include "third_party/blink/public/mojom/fenced_frame/fenced_frame.mojom-shared.h"
#include "third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom-forward.h"
#include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom-forward.h"
#include "url/origin.h"
Expand Down Expand Up @@ -194,9 +195,12 @@ class BLINK_COMMON_EXPORT PermissionsPolicy {
const PermissionsPolicy*);

// Creates a PermissionsPolicy for a fenced frame. All permissions are
// disabled in fenced frames.
// disabled in fenced frames except for attribution reporting (which are only
// enabled for opaque-ads fenced frames). Permissions do not inherit from the
// parent to prevent cross-channel communication.
static std::unique_ptr<PermissionsPolicy> CreateForFencedFrame(
const url::Origin& origin);
const url::Origin& origin,
blink::mojom::FencedFrameMode mode);

static std::unique_ptr<PermissionsPolicy> CreateFromParsedPolicy(
const ParsedPermissionsPolicy& parsed_policy,
Expand Down Expand Up @@ -273,7 +277,8 @@ class BLINK_COMMON_EXPORT PermissionsPolicy {

static std::unique_ptr<PermissionsPolicy> CreateForFencedFrame(
const url::Origin& origin,
const PermissionsPolicyFeatureList& features);
const PermissionsPolicyFeatureList& features,
blink::mojom::FencedFrameMode mode);

bool InheritedValueForFeature(
const PermissionsPolicy* parent_policy,
Expand Down

0 comments on commit b1c43c2

Please sign in to comment.