0

[Private Network Access] Browser-created empty main frames are local.

This CL changes the handling of browser-created main frames to default
to the `local` address space instead of `unknown`, which relaxes their
security constraints. This relaxation allows code injected by e.g.
WebView APIs to fetch resources from the local network from within such
contexts. Because of its potential to allow bypassing Private Network
Access restrictions, this relaxation is circumscribed to only those main
frames in which we can definitively assert that no JS can be injected by
malicious websites.

Fixed: chromium:1191161
Change-Id: I10edc4743b96895c312546ad1302a9a8d1727c29
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2787427
Auto-Submit: Titouan Rigoudy <titouan@chromium.org>
Reviewed-by: Arthur Sonzogni <arthursonzogni@chromium.org>
Reviewed-by: Łukasz Anforowicz <lukasza@chromium.org>
Commit-Queue: Arthur Sonzogni <arthursonzogni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#868596}
This commit is contained in:
Titouan Rigoudy
2021-04-01 18:26:40 +00:00
committed by Chromium LUCI CQ
parent d0599130a8
commit facb13e7a2
3 changed files with 257 additions and 93 deletions

@ -8,6 +8,7 @@
#include <vector>
#include "base/bind.h"
#include "base/strings/strcat.h"
#include "base/strings/string_piece.h"
#include "base/test/scoped_feature_list.h"
#include "content/browser/renderer_host/frame_tree_node.h"
@ -529,28 +530,58 @@ IN_PROC_BROWSER_TEST_F(
EXPECT_TRUE(child_frame->GetLastCommittedOrigin().opaque());
}
// TODO(https://crbug.com/1134601): `about:` URLs are all treated as `kUnknown`
// today. This is ~incorrect, but safe, as their web-facing behavior will be
// equivalent to "public".
// This test verifies the contents of the ClientSecurityState for the initial
// empty document in a new main frame created by the browser.
//
// Note: the renderer-created main frame case is exercised by the
// OpeneeInherits* tests below.
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
CommitsClientSecurityStateForAboutURL) {
ClientSecurityStateForInitialEmptyDoc) {
// Start a navigation. This forces the RenderFrameHost to initialize its
// RenderFrame. The navigation is then cancelled by a HTTP 204 code.
// We're left with a RenderFrameHost containing the default
// ClientSecurityState values.
EXPECT_TRUE(NavigateToURLAndExpectNoCommit(
shell(), embedded_test_server()->GetURL("/nocontent")));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::CrossOriginEmbedderPolicyValue::kNone,
security_state->cross_origin_embedder_policy.value);
EXPECT_EQ(network::mojom::PrivateNetworkRequestPolicy::
kBlockFromInsecureToMorePrivate,
security_state->private_network_request_policy);
// Browser-created empty main frames are trusted to access the local network,
// if they execute code injected via DevTools, WebView APIs or extensions.
EXPECT_EQ(network::mojom::IPAddressSpace::kLocal,
security_state->ip_address_space);
EXPECT_EQ("local", EvalJs(root_frame_host(), "document.addressSpace"));
}
// This test verifies the contents of the ClientSecurityState for `about:blank`
// in a new main frame created by the browser.
//
// Note: the renderer-created main frame case is exercised by the Openee
// inheritance tests below.
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
ClientSecurityStateForAboutBlank) {
EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kUnknown,
EXPECT_EQ(network::mojom::IPAddressSpace::kLocal,
security_state->ip_address_space);
EXPECT_EQ("public", EvalJs(root_frame_host(), "document.addressSpace"));
EXPECT_EQ("local", EvalJs(root_frame_host(), "document.addressSpace"));
}
// TODO(https://crbug.com/1134601): `data:` URLs are all treated as `kUnknown`
// today. This is ~incorrect, but safe, as their web-facing behavior will be
// equivalent to "public".
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
CommitsClientSecurityStateForDataURL) {
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest, ClientSecurityStateForDataURL) {
EXPECT_TRUE(NavigateToURL(shell(), GURL("data:text/html,foo")));
const network::mojom::ClientSecurityStatePtr security_state =
@ -563,8 +594,7 @@ IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
EXPECT_EQ("public", EvalJs(root_frame_host(), "document.addressSpace"));
}
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
CommitsClientSecurityStateForFileURL) {
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest, ClientSecurityStateForFileURL) {
EXPECT_TRUE(NavigateToURL(shell(), GetTestUrl("", "empty.html")));
const network::mojom::ClientSecurityStatePtr security_state =
@ -578,7 +608,7 @@ IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
}
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
CommitsClientSecurityStateForInsecureLocalAddress) {
ClientSecurityStateForInsecureLocalAddress) {
EXPECT_TRUE(
NavigateToURL(shell(), InsecureDefaultURL(*embedded_test_server())));
@ -593,7 +623,7 @@ IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
}
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
CommitsClientSecurityStateForSecureLocalAddress) {
ClientSecurityStateForSecureLocalAddress) {
EXPECT_TRUE(
NavigateToURL(shell(), SecureDefaultURL(*embedded_test_server())));
@ -608,7 +638,7 @@ IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
}
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
CommitsClientSecurityStateForTreatAsPublicAddress) {
ClientSecurityStateForTreatAsPublicAddress) {
EXPECT_TRUE(NavigateToURL(
shell(), SecureTreatAsPublicAddressURL(*embedded_test_server())));
@ -623,7 +653,7 @@ IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
}
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
CommitsClientSecurityStateForPrivateAddress) {
ClientSecurityStateForPrivateAddress) {
// Intercept the page load and pretend it came from a public IP.
const GURL url = InsecureDefaultURL(*embedded_test_server());
@ -645,7 +675,7 @@ IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
}
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
CommitsClientSecurityStateForPublicAddress) {
ClientSecurityStateForPublicAddress) {
// Intercept the page load and pretend it came from a public IP.
const GURL url = InsecureDefaultURL(*embedded_test_server());
@ -669,7 +699,7 @@ IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
// This test verifies that the chrome:// scheme is considered local for the
// purpose of Private Network Access.
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
CommitsClientSecurityStateForSpecialSchemeChromeURL) {
ClientSecurityStateForSpecialSchemeChromeURL) {
// Not all chrome:// hosts are available in content/ but ukm is one of them.
EXPECT_TRUE(NavigateToURL(shell(), GURL("chrome://ukm")));
EXPECT_TRUE(
@ -686,9 +716,8 @@ IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
// The view-source:// scheme should only ever appear in the display URL. It
// shouldn't affect the IPAddressSpace computation. This test verifies that we
// end up with the response IPAddressSpace.
IN_PROC_BROWSER_TEST_F(
CorsRfc1918BrowserTest,
CommitsClientSecurityStateForSpecialSchemeViewSourcePublic) {
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
ClientSecurityStateForSpecialSchemeViewSourcePublic) {
// Intercept the page load and pretend it came from a public IP.
const GURL url = SecureDefaultURL(*embedded_test_server());
@ -709,9 +738,8 @@ IN_PROC_BROWSER_TEST_F(
}
// Variation of above test with a private address.
IN_PROC_BROWSER_TEST_F(
CorsRfc1918BrowserTest,
CommitsClientSecurityStateForSpecialSchemeViewSourcePrivate) {
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
ClientSecurityStateForSpecialSchemeViewSourcePrivate) {
// Intercept the page load and pretend it came from a private IP.
const GURL url = SecureDefaultURL(*embedded_test_server());
@ -734,9 +762,8 @@ IN_PROC_BROWSER_TEST_F(
// The chrome-error:// scheme should only ever appear in origins. It shouldn't
// affect the IPAddressSpace computation. This test verifies that we end up with
// the response IPAddressSpace.
IN_PROC_BROWSER_TEST_F(
CorsRfc1918BrowserTest,
CommitsClientSecurityStateForSpecialSchemeChromeErrorPublic) {
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
ClientSecurityStateForSpecialSchemeChromeErrorPublic) {
// Intercept the page load and pretend it came from a public IP.
const GURL url = SecureDefaultURL(*embedded_test_server());
@ -757,9 +784,8 @@ IN_PROC_BROWSER_TEST_F(
}
// Variation of above test with a private address.
IN_PROC_BROWSER_TEST_F(
CorsRfc1918BrowserTest,
CommitsClientSecurityStateForSpecialSchemeChromeErrorPrivate) {
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
ClientSecurityStateForSpecialSchemeChromeErrorPrivate) {
// Intercept the page load and pretend it came from a public IP.
const GURL url = SecureDefaultURL(*embedded_test_server());
@ -987,38 +1013,74 @@ RenderFrameHostImpl* GetMainFrameHostImpl(Shell* shell) {
// |parent| must not be nullptr.
RenderFrameHostImpl* OpenWindowFromURL(RenderFrameHostImpl* parent,
const GURL& url) {
return GetMainFrameHostImpl(OpenPopup(parent, url, "child"));
return GetMainFrameHostImpl(OpenPopup(parent, url, "_blank"));
}
RenderFrameHostImpl* OpenWindowFromAboutBlank(RenderFrameHostImpl* parent) {
return OpenWindowFromURL(parent, GURL("about:blank"));
}
RenderFrameHostImpl* OpenWindowInitialEmptyDoc(RenderFrameHostImpl* parent) {
// Note: We do not use OpenWindowFromURL() because we do not want to wait for
// a navigation - none will commit.
// Same as above, but with the "noopener" window feature.
RenderFrameHostImpl* OpenWindowFromAboutBlankNoOpener(
RenderFrameHostImpl* parent) {
// Setting the "noopener" window feature makes `window.open()` return `null`.
constexpr bool kNoExpectReturnFromWindowOpen = false;
return GetMainFrameHostImpl(OpenPopup(parent, GURL("about:blank"), "_blank",
"noopener",
kNoExpectReturnFromWindowOpen));
}
RenderFrameHostImpl* OpenWindowFromURLExpectNoCommit(
RenderFrameHostImpl* parent,
const GURL& url,
base::StringPiece features = "") {
ShellAddedObserver observer;
EXPECT_TRUE(ExecJs(parent, R"(
window.open("/nocontent");
)"));
base::StringPiece script_template = R"(
window.open($1, "_blank", $2);
)";
EXPECT_TRUE(ExecJs(parent, JsReplace(script_template, url, features)));
return GetMainFrameHostImpl(observer.GetShell());
}
RenderFrameHostImpl* OpenWindowFromJavascriptURL(RenderFrameHostImpl* parent) {
RenderFrameHostImpl* OpenWindowInitialEmptyDoc(RenderFrameHostImpl* parent) {
// Note: We do not use OpenWindowFromURL() because we do not want to wait for
// a navigation - none will commit.
return OpenWindowFromURLExpectNoCommit(parent, GURL("/nocontent"));
}
// Same as above, but with the "noopener" window feature.
RenderFrameHostImpl* OpenWindowInitialEmptyDocNoOpener(
RenderFrameHostImpl* parent) {
// Note: We do not use OpenWindowFromURL() because we do not want to wait for
// a navigation - none will commit.
return OpenWindowFromURLExpectNoCommit(parent, GURL("/nocontent"),
"noopener");
}
GURL JavascriptURL(base::StringPiece script) {
return GURL(base::StrCat({"javascript:", script}));
}
RenderFrameHostImpl* OpenWindowFromJavascriptURL(
RenderFrameHostImpl* parent,
base::StringPiece script = "'foo'") {
// Note: We do not use OpenWindowFromURL() because we do not want to wait for
// a navigation, since the `javascript:` URL will not commit (`about:blank`
// will).
return OpenWindowFromURLExpectNoCommit(parent, JavascriptURL(script));
}
ShellAddedObserver observer;
EXPECT_TRUE(ExecJs(parent, R"(
window.open("javascript:'foo'");
)"));
return GetMainFrameHostImpl(observer.GetShell());
// Same as above, but with the "noopener" window feature.
RenderFrameHostImpl* OpenWindowFromJavascriptURLNoOpener(
RenderFrameHostImpl* parent,
base::StringPiece script) {
// Note: We do not use OpenWindowFromURL() because we do not want to wait for
// a navigation - none will commit.
return OpenWindowFromURLExpectNoCommit(parent, JavascriptURL(script),
"noopener");
}
RenderFrameHostImpl* OpenWindowFromBlob(RenderFrameHostImpl* parent) {
@ -1096,6 +1158,9 @@ IN_PROC_BROWSER_TEST_F(
security_state->ip_address_space);
}
// This test verifies that a newly-opened window targeting `about:blank`
// inherits its address space from the opener. In this case, the opener's
// address space is `public`.
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
OpeneeInheritsAddressSpaceForAboutBlankFromPublic) {
EXPECT_TRUE(NavigateToURL(
@ -1112,6 +1177,9 @@ IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
security_state->ip_address_space);
}
// This test verifies that a newly-opened window targeting `about:blank`
// inherits its address space from the opener. In this case, the opener's
// address space is `local`.
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
OpeneeInheritsAddressSpaceForAboutBlankFromLocal) {
EXPECT_TRUE(
@ -1128,6 +1196,28 @@ IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
security_state->ip_address_space);
}
// This test verifies that a newly-opened window targeting `about:blank`,
// opened with the "noopener" feature, has its address space set to `local`
// regardless of the address space of the opener.
//
// Compare and contrast against the above tests without "noopener".
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
OpeneeNoOpenerAddressSpaceForAboutBlankIsLocal) {
EXPECT_TRUE(NavigateToURL(
shell(), SecureTreatAsPublicAddressURL(*embedded_test_server())));
RenderFrameHostImpl* window =
OpenWindowFromAboutBlankNoOpener(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLocal,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
IframeInheritsAddressSpaceForInitialEmptyDocFromPublic) {
EXPECT_TRUE(NavigateToURL(
@ -1196,6 +1286,9 @@ IN_PROC_BROWSER_TEST_F(
security_state->ip_address_space);
}
// This test verifies that a newly-opened window containing the initial empty
// document inherits its address space from the opener. In this case, the
// opener's address space is `public`.
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
OpeneeInheritsAddressSpaceForInitialEmptyDocFromPublic) {
EXPECT_TRUE(NavigateToURL(
@ -1212,6 +1305,9 @@ IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
security_state->ip_address_space);
}
// This test verifies that a newly-opened window containing the initial empty
// document inherits its address space from the opener. In this case, the
// opener's address space is `local`.
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
OpeneeInheritsAddressSpaceForInitialEmptyDocFromLocal) {
EXPECT_TRUE(
@ -1228,6 +1324,28 @@ IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
security_state->ip_address_space);
}
// This test verifies that a newly-opened window containing the initial empty
// document, opened with the "noopener" feature, has its address space set to
// `local` regardless of the address space of the opener.
//
// Compare and contrast against the above tests without "noopener".
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
OpeneeNoOpenerAddressSpaceForInitialEmptyDocIsLocal) {
EXPECT_TRUE(NavigateToURL(
shell(), SecureTreatAsPublicAddressURL(*embedded_test_server())));
RenderFrameHostImpl* window =
OpenWindowInitialEmptyDocNoOpener(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLocal,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
IframeInheritsAddressSpaceForAboutSrcdocFromPublic) {
EXPECT_TRUE(NavigateToURL(
@ -1418,9 +1536,35 @@ IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
EXPECT_TRUE(
NavigateToURL(shell(), SecureDefaultURL(*embedded_test_server())));
RenderFrameHostImpl* window = OpenWindowFromJavascriptURL(root_frame_host());
RenderFrameHostImpl* window = OpenWindowFromJavascriptURL(
root_frame_host(), "var injectedCodeWasExecuted = true");
ASSERT_NE(nullptr, window);
// The Javascript in the URL got executed in the new window.
EXPECT_EQ(true, EvalJs(window, "injectedCodeWasExecuted"));
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLocal,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
OpeneeNoOpenerAddressSpaceForJavascriptURLIsLocal) {
EXPECT_TRUE(NavigateToURL(
shell(), SecureTreatAsPublicAddressURL(*embedded_test_server())));
RenderFrameHostImpl* window = OpenWindowFromJavascriptURLNoOpener(
root_frame_host(), "var injectedCodeWasExecuted = true");
ASSERT_NE(nullptr, window);
// The Javascript in the URL was not executed in the new window. This ensures
// it is safe to classify the new window as `local` without allowing the
// opener to execute arbitrary JS in the `local` address space.
EXPECT_EQ("undefined", EvalJs(window, "typeof injectedCodeWasExecuted"));
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
@ -2491,26 +2635,4 @@ IN_PROC_BROWSER_TEST_F(
EXPECT_EQ(false, EvalJs(child_frame, FetchSubresourceScript("image.jpg")));
}
// This test verifies the initial values of a never committed
// RenderFrameHostImpl's ClientSecurityState.
IN_PROC_BROWSER_TEST_F(CorsRfc1918BrowserTest,
InitialNonCommittedRenderFrameHostClientSecurityState) {
// Start a navigation. This forces the RenderFrameHost to initialize its
// RenderFrame. The navigation is then cancelled by a HTTP 204 code.
// We're left with a RenderFrameHost containing the default
// ClientSecurityState values.
EXPECT_TRUE(NavigateToURLAndExpectNoCommit(
shell(), embedded_test_server()->GetURL("/nocontent")));
auto client_security_state = root_frame_host()->BuildClientSecurityState();
EXPECT_FALSE(client_security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::CrossOriginEmbedderPolicyValue::kNone,
client_security_state->cross_origin_embedder_policy.value);
EXPECT_EQ(network::mojom::IPAddressSpace::kUnknown,
client_security_state->ip_address_space);
EXPECT_EQ(network::mojom::PrivateNetworkRequestPolicy::
kBlockFromInsecureToMorePrivate,
client_security_state->private_network_request_policy);
}
} // namespace content

@ -213,6 +213,7 @@
#include "services/network/public/cpp/web_sandbox_flags.h"
#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/cookie_access_observer.mojom.h"
#include "services/network/public/mojom/ip_address_space.mojom.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "services/network/public/mojom/url_loader.mojom-shared.h"
#include "services/network/public/mojom/web_sandbox_flags.mojom-shared.h"
@ -1137,35 +1138,11 @@ RenderFrameHostImpl::RenderFrameHostImpl(
}
}
// The initial empty document inherits its policy container from its creator.
// The creator is either its parent for iframes or its opener for new windows.
//
// Note 1: For normal document created from a navigation, the policy container
// is computed from the NavigationRequest and assigned in
// DidCommitNewDocument().
//
// Note 2: After creating a new frame, blink emits a second IPC
// (DidCommitProvisionalLoad) for committing the initial empty
// document. However, since the RenderFrameHost has already been created, we
// cannot use DidCommitProvisionalLoad to set the policy container, since we
// could run into a race condition. Hence we need to set the policy container
// immediately when creating the RenderFrameHost here.
if (lifecycle_state_ != LifecycleStateImpl::kSpeculative) {
// Creating a RFH in kActive state implies that it is the RFH for a
// newly-created FTN, which should not have committed a real load yet.
DCHECK(!frame_tree_node_->has_committed_real_load());
if (parent_) {
SetPolicyContainerHost(parent_->policy_container_host()->Clone());
} else if (frame_tree_node_->opener()) {
SetPolicyContainerHost(frame_tree_node_->opener()
->current_frame_host()
->policy_container_host()
->Clone());
} else {
SetPolicyContainerHost(base::MakeRefCounted<PolicyContainerHost>());
}
// The initial empty document gets its sandbox flags from either:
// 1. The parent + iframe.sandbox for <iframe>.
// 2. The opener for popup, when "allow-popups-to-escape-sandbox" isn't set.
@ -1175,6 +1152,8 @@ RenderFrameHostImpl::RenderFrameHostImpl(
active_sandbox_flags_ = frame_tree_node_->active_sandbox_flags();
}
InitializePolicyContainerHost(renderer_initiated_creation_of_main_frame);
if (!base::FeatureList::IsEnabled(
features::kBlockInsecurePrivateNetworkRequests)) {
private_network_request_policy_ = network::mojom::
@ -2205,6 +2184,59 @@ RenderFrameHostImpl::AccessibilityGetWebContentsAccessibility() {
return view->GetWebContentsAccessibility();
}
void RenderFrameHostImpl::InitializePolicyContainerHost(
bool renderer_initiated_creation_of_main_frame) {
// No policy container for speculative frames.
if (lifecycle_state_ == LifecycleStateImpl::kSpeculative) {
return;
}
// The initial empty document inherits its policy container from its creator.
// The creator is either its parent for iframes or its opener for new windows.
//
// Note 1: For normal document created from a navigation, the policy container
// is computed from the NavigationRequest and assigned in
// DidCommitNewDocument().
if (parent_) {
SetPolicyContainerHost(parent_->policy_container_host()->Clone());
return;
}
if (frame_tree_node_->opener()) {
SetPolicyContainerHost(frame_tree_node_->opener()
->current_frame_host()
->policy_container_host()
->Clone());
return;
}
auto policies = std::make_unique<PolicyContainerPolicies>();
// Main frames created by the browser are treated as belonging the `local`
// address space, so that they can make requests to any address space
// unimpeded. The only way to execute code in such a context is to inject it
// via DevTools, WebView APIs, or extensions; it is impossible to do so with
// Web Platform means only.
//
// See also https://crbug.com/1191161.
//
// We also exclude prerendering from this case manually, since prendering
// render frame hosts are unconditionally created with the
// `renderer_initiated_creation_of_main_frame` set to false, even though the
// frames arguably are renderer-created.
//
// TODO(https://crbug.com/1194421): Address the prerendering case.
if (frame_tree_node_->IsMainFrame() &&
!renderer_initiated_creation_of_main_frame &&
lifecycle_state_ != LifecycleStateImpl::kPrerendering) {
policies->ip_address_space = network::mojom::IPAddressSpace::kLocal;
}
SetPolicyContainerHost(
base::MakeRefCounted<PolicyContainerHost>(std::move(policies)));
}
void RenderFrameHostImpl::SetPolicyContainerHost(
scoped_refptr<PolicyContainerHost> policy_container_host) {
policy_container_host_ = std::move(policy_container_host);

@ -2778,6 +2778,16 @@ class CONTENT_EXPORT RenderFrameHostImpl
return render_frame_state_ == RenderFrameState::kCreated;
}
// Initializes |policy_container_host_|. Constructor helper.
//
// |renderer_initiated_creation_of_main_frame| specifies whether this render
// frame host's creation was initiated by the renderer process, and this is
// a main frame. See the constructor for more details.
void InitializePolicyContainerHost(
bool renderer_initiated_creation_of_main_frame);
// Sets |policy_container_host_| and associates it with the current frame.
// |policy_container_host| must not be nullptr.
void SetPolicyContainerHost(
scoped_refptr<PolicyContainerHost> policy_container_host);