Skip to content

Commit

Permalink
test sandbox flags inheritance in popups
Browse files Browse the repository at this point in the history
This adds test for the popup's initial empty document sandbox flags
inheritance:
the document should inherit the CSP sandbox flags from the opener
document even if the allow-popups-to-escape-sandbox flag is set.

Bug: 114805
Change-Id: I79db2571278be9d7b50008f49212d0d1f210c5d8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3571921
Auto-Submit: Pâris Meuleman <pmeuleman@chromium.org>
Commit-Queue: Pâris Meuleman <pmeuleman@chromium.org>
Reviewed-by: Arthur Sonzogni <arthursonzogni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1002595}
  • Loading branch information
ParisMeuleman authored and Chromium LUCI CQ committed May 12, 2022
1 parent 7251bf9 commit a8a4102
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 45 deletions.
69 changes: 30 additions & 39 deletions content/browser/renderer_host/render_frame_host_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1497,19 +1497,6 @@ RenderFrameHostImpl::RenderFrameHostImpl(

InitializePolicyContainerHost(renderer_initiated_creation_of_main_frame);

if (policy_container_host_) {
// The initial empty documents sandbox flags is the union from:
// - The parent or opener document's sandbox flags inherited by policy
// container.
// - The frame's sandbox flags, contained in browsing_context_state. This
// are either:
// 1. For iframe: the parent + iframe.sandbox attribute.
// 2. For popups: the opener if "allow-popups-to-escape-sandbox" isn't
// set.
policy_container_host_->set_sandbox_flags(
browsing_context_state_->effective_frame_policy().sandbox_flags);
}

InitializePrivateNetworkRequestPolicy();

unload_event_monitor_timeout_ =
Expand Down Expand Up @@ -2819,40 +2806,44 @@ void RenderFrameHostImpl::InitializePolicyContainerHost(

if (parent_) {
SetPolicyContainerHost(parent_->policy_container_host()->Clone());
return;
}

if (frame_tree_node_->opener()) {
} else if (frame_tree_node_->opener()) {
SetPolicyContainerHost(frame_tree_node_->opener()
->current_frame_host()
->policy_container_host()
->Clone());
return;
}
} else {
PolicyContainerPolicies policies;

PolicyContainerPolicies policies;
// 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 (is_main_frame() && !renderer_initiated_creation_of_main_frame &&
lifecycle_state_ != LifecycleStateImpl::kPrerendering) {
policies.ip_address_space = network::mojom::IPAddressSpace::kLocal;
}

// 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 (is_main_frame() && !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)));
}

SetPolicyContainerHost(
base::MakeRefCounted<PolicyContainerHost>(std::move(policies)));
// The initial empty documents sandbox flags is the frame's sandbox flags,
// contained in browsing_context_state. This are either:
// 1. For iframe: the parent + iframe.sandbox attribute.
// 2. For popups: the opener if "allow-popups-to-escape-sandbox" isn't
// set.
policy_container_host_->set_sandbox_flags(
browsing_context_state_->effective_frame_policy().sandbox_flags);
}

void RenderFrameHostImpl::SetPolicyContainerHost(
Expand Down
63 changes: 57 additions & 6 deletions content/browser/site_per_process_browsertest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5423,7 +5423,7 @@ IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,

// Verify that popup frames opened from sandboxed documents with the
// "allow-popups-to-escape-sandbox" directive do *not* inherit sandbox flags AND
// that local scheme documents do inherit flags from the opener/initiator.
// that local scheme documents do *not* inherit flags from the opener/initiator.
IN_PROC_BROWSER_TEST_P(
SitePerProcessBrowserTest,
OpenSandboxedDocumentInUnsandboxedPopupFromSandboxedFrame) {
Expand Down Expand Up @@ -5463,9 +5463,61 @@ IN_PROC_BROWSER_TEST_P(
EXPECT_EQ(expected_flags,
root->child_at(0)->effective_frame_policy().sandbox_flags);

// Open a popup named "foo" from the child frame.
GURL b_url("about:blank");
Shell* foo_shell = OpenPopup(root->child_at(0), b_url, "foo");
// Open a popup named "foo" from the child frame on about:blank.
GURL foo_url("about:blank");
Shell* foo_shell = OpenPopup(root->child_at(0), foo_url, "foo");
EXPECT_TRUE(foo_shell);

FrameTreeNode* foo_root =
static_cast<WebContentsImpl*>(foo_shell->web_contents())
->GetPrimaryFrameTree()
.root();

// Check that the sandbox flags for new popup frame are correct in the browser
// process. They should not have been inherited.
EXPECT_EQ(network::mojom::WebSandboxFlags::kNone,
foo_root->effective_frame_policy().sandbox_flags);
// Check that the sandbox flags for the popup document are correct in the
// browser process. They should not have been inherited (for about:blank).
EXPECT_EQ(network::mojom::WebSandboxFlags::kNone,
foo_root->current_frame_host()->active_sandbox_flags());
}

// Verify that popup frames opened from sandboxed documents with the
// "allow-popups-to-escape-sandbox" directive do *not* inherit sandbox flags AND
// that local scheme documents do inherit CSP sandbox flags from the
// opener/initiator.
IN_PROC_BROWSER_TEST_P(
SitePerProcessBrowserTest,
OpenSandboxedDocumentInUnsandboxedPopupFromCSPSandboxedDocument) {
GURL main_url = embedded_test_server()->GetURL(
"a.test",
"/set-header?"
"Content-Security-Policy: sandbox "
"allow-scripts allow-popups allow-popups-to-escape-sandbox");

EXPECT_TRUE(NavigateToURL(shell(), main_url));

// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();

// Set expected flags for the child frame. Note that "allow-scripts" resets
// both network::mojom::WebSandboxFlags::Scripts and
// network::mojom::WebSandboxFlags::AutomaticFeatures bits per
// blink::parseSandboxPolicy().
network::mojom::WebSandboxFlags expected_flags =
network::mojom::WebSandboxFlags::kAll &
~network::mojom::WebSandboxFlags::kScripts &
~network::mojom::WebSandboxFlags::kAutomaticFeatures &
~network::mojom::WebSandboxFlags::kPopups &
~network::mojom::WebSandboxFlags::kTopNavigationToCustomProtocols &
~network::mojom::WebSandboxFlags::kPropagatesToAuxiliaryBrowsingContexts;

EXPECT_EQ(expected_flags, root->current_frame_host()->active_sandbox_flags());

// Open a popup named "foo" from the child frame on about:blank.
GURL foo_url("about:blank");
Shell* foo_shell = OpenPopup(root, foo_url, "foo");
EXPECT_TRUE(foo_shell);

FrameTreeNode* foo_root =
Expand All @@ -5478,8 +5530,7 @@ IN_PROC_BROWSER_TEST_P(
EXPECT_EQ(network::mojom::WebSandboxFlags::kNone,
foo_root->effective_frame_policy().sandbox_flags);
// Check that the sandbox flags for the popup document are correct in the
// browser process: They should have been inherited.
// TODO(https://crbug.com/1148405) This should be expected_flags.
// browser process. They should have been inherited.
EXPECT_EQ(network::mojom::WebSandboxFlags::kNone,
foo_root->current_frame_host()->active_sandbox_flags());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>
// Sandbox flags are inherited from a document toward every frame it creates,
// which then is inherited to every new document created in this frame.
//
// Using the flag 'allow-popups-to-escape-sandbox' inhibits this inheritance
// mechanism when the new frame is a popup.
//
// Sandbox flags are not inherited from the initiator/creator when loading a
// local scheme document unlike CSP (tested in
// ./sandbox-inherit-to-blank-document-unsandboxed.html)
//
// This tests in particular the initial empty document and the first
// about:blank navigation and verifies that no sandbox is applied on the
// popups.
promise_test(async test => {
const msg = await new Promise(r => window.addEventListener("message", r));
assert_false(msg.data.access_initial_navigation_to_about_blank_throws,
"Failed to access initial about:blank popup, it is probably sandboxed"
);
assert_false(msg.data.access_first_navigation_to_about_blank_throws,
"Failed to access navigation to about:blank, it is probably sandboxed"
);
}, "Popup do not inherit sandbox, because of " +
"'allow-popups-to-escape-sandbox'. The document isn't sandboxed.")

</script>
<iframe
sandbox="allow-scripts allow-popups allow-popups-to-escape-sandbox"
srcdoc="
<script>
let access_initial_navigation_to_about_blank_throws = false;
let access_first_navigation_to_about_blank_throws = false;
const initial_about_blank_window =
window.open('common/blank.html?pipe=status(204)');
try {
initial_about_blank_window.origin;
} catch(e) {
access_initial_navigation_to_about_blank_throws = true;
}
const renavigated_about_blank_window = window.open('about:blank');
try {
renavigated_about_blank_window.origin;
} catch(e) {
access_first_navigation_to_about_blank_throws = true;
}
top.postMessage({
'access_initial_navigation_to_about_blank_throws':
access_initial_navigation_to_about_blank_throws,
'access_first_navigation_to_about_blank_throws':
access_first_navigation_to_about_blank_throws
}, '*');
</script>"
>
</iframe>
</body>
</html>

0 comments on commit a8a4102

Please sign in to comment.