blob: ed4ce8bb74658b584abf67636091611242330fd2 [file] [log] [blame]
// 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.
#include "content/browser/site_per_process_browsertest.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/test/render_document_feature.h"
namespace content {
// Out-of-process-sandboxed-iframe (OOPSIF) tests.
//
// Test classes for isolating sandboxed iframes and documents in a different
// process from the rest of their site.
// See https://crbug.com/510122.
class SitePerProcessIsolatedSandboxedIframeTest
: public SitePerProcessBrowserTest {
public:
SitePerProcessIsolatedSandboxedIframeTest() {
feature_list_.InitAndEnableFeature(features::kIsolateSandboxedIframes);
}
private:
base::test::ScopedFeatureList feature_list_;
};
class SitePerProcessNotIsolatedSandboxedIframeTest
: public SitePerProcessBrowserTest {
public:
SitePerProcessNotIsolatedSandboxedIframeTest() {
feature_list_.InitAndDisableFeature(features::kIsolateSandboxedIframes);
}
private:
base::test::ScopedFeatureList feature_list_;
};
// A test class to allow testing isolated sandboxed iframes using the per-origin
// process model.
class SitePerProcessPerOriginIsolatedSandboxedIframeTest
: public SitePerProcessBrowserTest {
public:
SitePerProcessPerOriginIsolatedSandboxedIframeTest() {
feature_list_.InitWithFeaturesAndParameters(
{{features::kIsolateSandboxedIframes, {{"grouping", "per-origin"}}}},
{/* disabled_features */});
}
private:
base::test::ScopedFeatureList feature_list_;
};
// The following test should not crash. In this test the
// kIsolateSandboxedIframes flag is forced off, so we don't need to verify
// the process isolation details, as is done in
// SitePerProcessIsolatedSandboxedIframeTest.SrcdocCspSandboxIsIsolated below.
// https://crbug.com/1319430
IN_PROC_BROWSER_TEST_P(SitePerProcessNotIsolatedSandboxedIframeTest,
SrcdocSandboxFlagsCheck) {
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Create sandboxed srcdoc child frame, with csp sandbox.
EXPECT_TRUE(ExecJs(shell(),
"var frame = document.createElement('iframe'); "
"frame.csp = 'sandbox'; "
"frame.srcdoc = 'foo'; "
"document.body.appendChild(frame);"));
ASSERT_TRUE(WaitForLoadStop(web_contents()));
}
// Test that a srcdoc iframe that receives its sandbox flags from the CSP
// attribute also gets process isolation. This test starts the same as
// SitePerProcessNotIsolatedSandboxedIframeTest.SrcdocSandboxFlagsCheck, but in
// this test the kIsolateSandboxedIframes flag is on, so we also verify that
// the process isolation has indeed occurred.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
SrcdocCspSandboxIsIsolated) {
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Create sandboxed srcdoc child frame, with csp sandbox.
EXPECT_TRUE(ExecJs(shell(),
"var frame = document.createElement('iframe'); "
"frame.csp = 'sandbox'; "
"frame.srcdoc = 'foo'; "
"document.body.appendChild(frame);"));
ASSERT_TRUE(WaitForLoadStop(web_contents()));
// Check frame-tree.
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child = root->child_at(0);
EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
child->current_frame_host()->active_sandbox_flags());
EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
EXPECT_TRUE(child->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
EXPECT_FALSE(root->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
}
// A test to verify that an iframe that is sandboxed using the 'csp' attribute
// instead of the 'sandbox' attribute gets process isolation when the
// kIsolatedSandboxedIframes flag is enabled.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
CspIsolatedSandbox) {
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
// The child needs to have the same origin as the parent.
GURL child_url(main_url);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Create csp-sandboxed child frame, same-origin.
{
std::string js_str = base::StringPrintf(
"var frame = document.createElement('iframe'); "
"frame.csp = 'sandbox'; "
"frame.src = '%s'; "
"document.body.appendChild(frame);",
child_url.spec().c_str());
EXPECT_TRUE(ExecJs(shell(), js_str));
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
}
// Check frame-tree.
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child = root->child_at(0);
EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
child->current_frame_host()->active_sandbox_flags());
EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
EXPECT_TRUE(child->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
EXPECT_FALSE(root->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
}
// A test to verify that an iframe with a fully-restrictive sandbox is rendered
// in a separate process from its parent frame even if they have the same
// origin.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
IsolatedSandbox) {
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
// The child needs to have the same origin as the parent.
GURL child_url(main_url);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Create sandboxed child frame, same-origin.
{
std::string js_str = base::StringPrintf(
"var frame = document.createElement('iframe'); "
"frame.sandbox = ''; "
"frame.src = '%s'; "
"document.body.appendChild(frame);",
child_url.spec().c_str());
EXPECT_TRUE(ExecJs(shell(), js_str));
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
}
// Check frame-tree.
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child = root->child_at(0);
EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
child->effective_frame_policy().sandbox_flags);
EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
EXPECT_TRUE(child->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
EXPECT_FALSE(root->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
}
// Test that sandboxed iframes that are same-site with their parent but
// cross-origin from each other are put in different processes from each other,
// when the 'per-origin' isolation grouping is active for
// kIsolateSandboxedIframes. (In 'per-site' isolation mode they would be in the
// same process.)
IN_PROC_BROWSER_TEST_P(SitePerProcessPerOriginIsolatedSandboxedIframeTest,
CrossOriginIsolatedSandboxedIframes) {
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
// The children need to have the same origin as the parent, but be cross
// origin from each other.
GURL same_origin_child_url(main_url);
GURL cross_origin_child_url(
embedded_test_server()->GetURL("sub.a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Create sandboxed child frames, both same-origin and cross-origin.
{
std::string js_str = base::StringPrintf(
"var frame1 = document.createElement('iframe'); "
"frame1.sandbox = ''; "
"frame1.src = '%s'; "
"document.body.appendChild(frame1); "
"var frame2 = document.createElement('iframe'); "
"frame2.sandbox = ''; "
"frame2.src = '%s'; "
"document.body.appendChild(frame2);",
same_origin_child_url.spec().c_str(),
cross_origin_child_url.spec().c_str());
EXPECT_TRUE(ExecJs(shell(), js_str));
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
}
// Check frame-tree.
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
ASSERT_EQ(2U, root->child_count());
FrameTreeNode* child1 = root->child_at(0); // a.com
EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
child1->effective_frame_policy().sandbox_flags);
EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
child1->current_frame_host()->GetSiteInstance());
EXPECT_TRUE(child1->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
EXPECT_FALSE(root->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
FrameTreeNode* child2 = root->child_at(1); // sub.a.com
EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
child2->effective_frame_policy().sandbox_flags);
EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
child2->current_frame_host()->GetSiteInstance());
EXPECT_TRUE(child2->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
// This is the key result for this test: the sandboxed iframes for 'a.com' and
// 'sub.a.com' should be in different SiteInstances.
auto* child1_site_instance1 = child1->current_frame_host()->GetSiteInstance();
auto* child2_site_instance1 = child2->current_frame_host()->GetSiteInstance();
EXPECT_NE(child1_site_instance1, child2_site_instance1);
EXPECT_NE(child1_site_instance1->GetProcess(),
child2_site_instance1->GetProcess());
}
// Test that, while using 'per-origin' isolation grouping, navigating a
// sandboxed iframe from 'a.foo.com' to 'b.foo.com' results in the sandbox using
// two different SiteInstances.
IN_PROC_BROWSER_TEST_P(SitePerProcessPerOriginIsolatedSandboxedIframeTest,
CrossOriginNavigationSwitchesSiteInstances) {
GURL main_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
GURL cross_origin_child_url(
embedded_test_server()->GetURL("a.foo.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Create sandboxed cross-origin child frame.
{
std::string js_str = base::StringPrintf(
"var frame = document.createElement('iframe'); "
"frame.id = 'test_frame'; "
"frame.sandbox = ''; "
"frame.src = '%s'; "
"document.body.appendChild(frame);",
cross_origin_child_url.spec().c_str());
EXPECT_TRUE(ExecJs(shell(), js_str));
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
}
// Check frame-tree.
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child = root->child_at(0); // a.foo.com
EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
child->effective_frame_policy().sandbox_flags);
scoped_refptr<SiteInstanceImpl> site_instance_root =
root->current_frame_host()->GetSiteInstance();
scoped_refptr<SiteInstanceImpl> site_instance1 =
child->current_frame_host()->GetSiteInstance();
EXPECT_NE(site_instance_root, site_instance1);
EXPECT_TRUE(site_instance1->GetSiteInfo().is_sandboxed());
EXPECT_FALSE(site_instance_root->GetSiteInfo().is_sandboxed());
// Navigate sandboxed frame cross-origin to b.foo.com.
EXPECT_TRUE(NavigateIframeToURL(
shell()->web_contents(), "test_frame",
GURL(embedded_test_server()->GetURL("b.foo.com", "/title1.html"))));
scoped_refptr<SiteInstanceImpl> site_instance2 =
child->current_frame_host()->GetSiteInstance();
EXPECT_NE(site_instance_root, site_instance2);
EXPECT_NE(site_instance1, site_instance2);
EXPECT_NE(site_instance1->GetProcess(), site_instance2->GetProcess());
}
// Test that navigating cross-origin from a non-sandboxed iframe to a CSP
// sandboxed iframe results in switching to a new SiteInstance in a different
// process.
IN_PROC_BROWSER_TEST_P(SitePerProcessPerOriginIsolatedSandboxedIframeTest,
CrossOriginNavigationToCSPSwitchesSiteInstances) {
GURL main_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
GURL cross_origin_child_url(
embedded_test_server()->GetURL("a.foo.com", "/title1.html"));
GURL cross_origin_csp_child_url(
embedded_test_server()->GetURL("b.foo.com",
"/set-header?"
"Content-Security-Policy: sandbox "));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Create non-sandboxed cross-origin child frame.
{
std::string js_str = base::StringPrintf(
"var frame = document.createElement('iframe'); "
"frame.id = 'test_frame'; "
"frame.src = '%s'; "
"document.body.appendChild(frame);",
cross_origin_child_url.spec().c_str());
EXPECT_TRUE(ExecJs(shell(), js_str));
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
}
// Check frame-tree.
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child = root->child_at(0); // a.foo.com
scoped_refptr<SiteInstanceImpl> site_instance_root =
root->current_frame_host()->GetSiteInstance();
scoped_refptr<SiteInstanceImpl> site_instance1 =
child->current_frame_host()->GetSiteInstance();
EXPECT_EQ(site_instance_root, site_instance1);
EXPECT_FALSE(site_instance1->GetSiteInfo().is_sandboxed());
// Navigate child frame cross-origin to CSP-isolated b.foo.com.
EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test_frame",
cross_origin_csp_child_url));
// The child frame should now have a different SiteInstance and process than
// it did before the navigation.
scoped_refptr<SiteInstanceImpl> site_instance2 =
child->current_frame_host()->GetSiteInstance();
EXPECT_NE(site_instance1, site_instance2);
EXPECT_NE(site_instance1->GetProcess(), site_instance2->GetProcess());
EXPECT_TRUE(site_instance2->GetSiteInfo().is_sandboxed());
}
// Check that two same-site sandboxed iframes in unrelated windows share the
// same process due to subframe process reuse.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
SandboxProcessReuse) {
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
// The child needs to have the same origin as the parent.
GURL child_url(main_url);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Create sandboxed child frame, same-origin.
std::string js_str = base::StringPrintf(
"var frame = document.createElement('iframe'); "
"frame.sandbox = ''; "
"frame.src = '%s'; "
"document.body.appendChild(frame);",
child_url.spec().c_str());
EXPECT_TRUE(ExecJs(shell(), js_str));
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child = root->child_at(0);
EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
child->effective_frame_policy().sandbox_flags);
EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
EXPECT_TRUE(child->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
EXPECT_FALSE(root->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
// Set up an unrelated window with the same frame hierarchy.
Shell* new_shell = CreateBrowser();
EXPECT_TRUE(NavigateToURL(new_shell, main_url));
FrameTreeNode* new_root =
static_cast<WebContentsImpl*>(new_shell->web_contents())
->GetPrimaryFrameTree()
.root();
EXPECT_TRUE(ExecJs(new_shell, js_str));
ASSERT_TRUE(WaitForLoadStop(new_shell->web_contents()));
FrameTreeNode* new_child = new_root->child_at(0);
EXPECT_TRUE(new_child->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
EXPECT_FALSE(new_root->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
// Check that the two sandboxed subframes end up in separate
// BrowsingInstances but in the same process.
EXPECT_FALSE(
new_child->current_frame_host()->GetSiteInstance()->IsRelatedSiteInstance(
child->current_frame_host()->GetSiteInstance()));
EXPECT_EQ(new_child->current_frame_host()->GetProcess(),
child->current_frame_host()->GetProcess());
}
// A test to verify that when an iframe has two sibling subframes, each with a
// fully-restrictive sandbox, that each of the three gets its own process
// even though they are all same-origin.
// Note: using "sandbox = ''" in this and the following tests creates fully
// restricted sandboxes, which will include the kOrigin case we are interested
// in.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
IsolatedSandboxSiblingSubframes) {
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
// The child needs to have the same origin as the parent.
GURL child_url(main_url);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Create sandboxed child frame, same-origin.
{
std::string js_str = base::StringPrintf(
"var frame1 = document.createElement('iframe'); "
"frame1.sandbox = ''; "
"frame1.src = '%s'; "
"document.body.appendChild(frame1); "
"var frame2 = document.createElement('iframe'); "
"frame2.sandbox = ''; "
"frame2.src = '%s'; "
"document.body.appendChild(frame2);",
child_url.spec().c_str(), child_url.spec().c_str());
EXPECT_TRUE(ExecJs(shell(), js_str));
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
}
// Check frame-tree.
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
ASSERT_EQ(2U, root->child_count());
FrameTreeNode* child1 = root->child_at(0);
FrameTreeNode* child2 = root->child_at(1);
EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
child1->effective_frame_policy().sandbox_flags);
EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
child2->effective_frame_policy().sandbox_flags);
EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
child1->current_frame_host()->GetSiteInstance());
EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
child2->current_frame_host()->GetSiteInstance());
// Because the siblings are same-site to each other (in fact, same origin) we
// expect them to share a process when sandboxed.
EXPECT_EQ(child1->current_frame_host()->GetSiteInstance(),
child2->current_frame_host()->GetSiteInstance());
EXPECT_TRUE(child1->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
EXPECT_TRUE(child2->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
EXPECT_FALSE(root->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
IsolatedSandboxSrcdocSubframe) {
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Create sandboxed child frame, with srcdoc content.
std::string child_inner_text("srcdoc sandboxed subframe");
{
std::string js_str = base::StringPrintf(
"var frame = document.createElement('iframe'); "
"frame.sandbox = 'allow-scripts'; "
"frame.srcdoc = '%s'; "
"document.body.appendChild(frame);",
child_inner_text.c_str());
EXPECT_TRUE(ExecJs(shell(), js_str));
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
}
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child = root->child_at(0);
// Verify that the child has only the 'allow-scripts' permission set.
EXPECT_EQ(child->effective_frame_policy().sandbox_flags,
network::mojom::WebSandboxFlags::kAll &
~network::mojom::WebSandboxFlags::kScripts &
~network::mojom::WebSandboxFlags::kAutomaticFeatures);
EXPECT_EQ(std::string(url::kAboutSrcdocURL),
child->current_frame_host()->GetLastCommittedURL());
EXPECT_TRUE(child->current_frame_host()->GetLastCommittedOrigin().opaque());
// Verify that the child's precursor origin matches 'a.com'. Note: we create
// the expected value using `main_url` so that the test server port will be
// correctly matched.
EXPECT_EQ(url::SchemeHostPort(main_url),
child->current_origin().GetTupleOrPrecursorTupleIfOpaque());
EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
EXPECT_TRUE(child->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
EXPECT_FALSE(root->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
{
std::string js_str("document.body.innerText;");
EXPECT_EQ(child_inner_text, EvalJs(child->current_frame_host(), js_str));
}
}
// A test to make sure that about:blank in a sandboxed iframe doesn't get
// process isolation. If it did, it would be impossible for the parent to inject
// any content, and it would be stuck as empty content.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
NotIsolatedSandboxAboutBlankSubframe) {
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Create sandboxed child frame, with about:blank content.
{
std::string js_str(
"var frame = document.createElement('iframe'); "
"frame.id = 'child_frame'; "
"frame.sandbox = ''; "
"frame.src = 'about:blank'; "
"document.body.appendChild(frame);");
EXPECT_TRUE(ExecJs(shell(), js_str));
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
}
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child = root->child_at(0);
// Verify that the child has no permissions set.
EXPECT_EQ(child->effective_frame_policy().sandbox_flags,
network::mojom::WebSandboxFlags::kAll);
EXPECT_EQ(GURL(url::kAboutBlankURL),
child->current_frame_host()->GetLastCommittedURL());
EXPECT_TRUE(child->current_frame_host()->GetLastCommittedOrigin().opaque());
// Verify that the child's precursor origin matches 'a.com'. Note: we create
// the expected value using `main_url` so that the test server port will be
// correctly matched.
EXPECT_EQ(url::SchemeHostPort(main_url),
child->current_origin().GetTupleOrPrecursorTupleIfOpaque());
// The child needs to be in the parent's SiteInstance.
EXPECT_EQ(root->current_frame_host()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
EXPECT_FALSE(root->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
// Navigate to a page that should get process isolation.
GURL isolated_child_url(
embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateFrameToURL(child, isolated_child_url));
EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
EXPECT_TRUE(child->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
// Navigate back to about:blank, and verify it's put back into the parent's
// SiteInstance.
scoped_refptr<SiteInstanceImpl> child_previous_site_instance =
child->current_frame_host()->GetSiteInstance();
EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "child_frame",
GURL("about:blank")));
EXPECT_EQ(root->current_frame_host()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
EXPECT_NE(child_previous_site_instance,
child->current_frame_host()->GetSiteInstance());
EXPECT_FALSE(child->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
}
// Test to make sure that javascript: urls don't execute in a sandboxed iframe.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
SandboxedIframeWithJSUrl) {
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Create sandboxed child frame with a javascript: URL.
std::string js_url_str("javascript:\"foo\"");
{
std::string js_str = base::StringPrintf(
"var frame = document.createElement('iframe'); "
"frame.id = 'test_frame'; "
"frame.sandbox = 'allow-scripts'; "
"frame.src = '%s'; "
"document.body.appendChild(frame);",
js_url_str.c_str());
EXPECT_TRUE(ExecJs(shell(), js_str));
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
}
// Verify parent and child frames share a SiteInstance. A sandboxed iframe
// with a javascript: url shouldn't get its own process.
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child = root->child_at(0);
EXPECT_EQ(root->current_frame_host()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
// Verify that the javascript: url did not execute. This is expected
// regardless of IsolatedSandboxedIframes since sandboxed iframes get opaque
// origins, and javascript: urls don't execute in opaque origins.
EXPECT_TRUE(
EvalJs(child->current_frame_host(), "document.body.innerHTML == ''")
.ExtractBool());
}
// Test to make sure that an iframe with a data:url is process isolated.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
SandboxedIframeWithDataURLIsIsolated) {
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Create sandboxed child frame with a data URL.
std::string data_url_str("data:text/html,dataurl");
{
std::string js_str = base::StringPrintf(
"var frame = document.createElement('iframe'); "
"frame.id = 'test_frame'; "
"frame.sandbox = ''; "
"frame.src = '%s'; "
"document.body.appendChild(frame);",
data_url_str.c_str());
EXPECT_TRUE(ExecJs(shell(), js_str));
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
}
// Verify parent and child frames don't share a SiteInstance
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child = root->child_at(0);
EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
EXPECT_TRUE(child->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
EXPECT_FALSE(root->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
}
// Test to make sure that an iframe with a data:url is appropriately sandboxed.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
SandboxedIframeWithDataURL) {
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Create non-sandboxed child frame with a data URL.
std::string data_url_str("data:text/html,dataurl");
{
std::string js_str = base::StringPrintf(
"var frame = document.createElement('iframe'); "
"frame.id = 'test_frame'; "
"frame.src = '%s'; "
"document.body.appendChild(frame);",
data_url_str.c_str());
EXPECT_TRUE(ExecJs(shell(), js_str));
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
}
// Verify parent and child frames share a SiteInstance
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child = root->child_at(0);
EXPECT_EQ(root->current_frame_host()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
// Now make subframe sandboxed.
{
std::string js_str(
"var frame = document.getElementById('test_frame'); "
"frame.sandbox = ''; ");
EXPECT_TRUE(ExecJs(shell(), js_str));
}
NavigateFrameToURL(child,
embedded_test_server()->GetURL("b.com", "/title1.html"));
// Child should now be in a different SiteInstance.
EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
// Go back and ensure the data: URL committed in the same SiteInstance as the
// original navigation.
EXPECT_TRUE(web_contents()->GetController().CanGoBack());
{
TestFrameNavigationObserver frame_observer(child);
web_contents()->GetController().GoBack();
frame_observer.WaitForCommit();
}
EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
EXPECT_EQ(GURL(data_url_str),
child->current_frame_host()->GetLastCommittedURL());
}
// Test to make sure that a sandboxed child iframe with a data url and a
// sandboxed parent end up in the same SiteInstance.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
SandboxedParentWithSandboxedChildWithDataURL) {
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
std::string parent_url_str = main_url.spec();
std::string data_url_str("data:text/html,dataurl");
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Allow "parent" to have the allow-scripts permissions so it can create
// a child.
{
std::string js_str = base::StringPrintf(
"var frame = document.createElement('iframe'); "
"frame.sandbox = 'allow-scripts'; "
"frame.src = '%s'; "
"document.body.appendChild(frame);",
parent_url_str.c_str());
EXPECT_TRUE(ExecJs(shell(), js_str));
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
}
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child = root->child_at(0);
EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
// Give the grandchild the allow-scripts permissions so it matches the child.
{
std::string js_str = base::StringPrintf(
"var frame = document.createElement('iframe'); "
"frame.sandbox = 'allow-scripts'; "
"frame.src = '%s'; "
"document.body.appendChild(frame);",
data_url_str.c_str());
EXPECT_TRUE(ExecJs(child->current_frame_host(), js_str));
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
}
ASSERT_EQ(1U, child->child_count());
FrameTreeNode* grandchild = child->child_at(0);
EXPECT_EQ(child->current_frame_host()->GetSiteInstance(),
grandchild->current_frame_host()->GetSiteInstance());
EXPECT_EQ(GURL(data_url_str),
grandchild->current_frame_host()->GetLastCommittedURL());
}
// Test to make sure that a sandboxed iframe with a (not-explicitly) sandboxed
// subframe ends up in the same SiteInstance/process as its subframe.
IN_PROC_BROWSER_TEST_P(SitePerProcessIsolatedSandboxedIframeTest,
IsolatedSandboxWithNonSandboxedSubframe) {
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
// The child needs to have the same origin as the parent.
GURL child_url(
embedded_test_server()->GetURL("a.com", "/page_with_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Create sandboxed child frame, same-origin.
{
std::string js_str = base::StringPrintf(
"var frame = document.createElement('iframe'); "
"frame.sandbox = ''; "
"frame.src = '%s'; "
"document.body.appendChild(frame);",
child_url.spec().c_str());
EXPECT_TRUE(ExecJs(shell(), js_str));
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
}
// Check child vs. parent.
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child = root->child_at(0);
EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
child->effective_frame_policy().sandbox_flags);
EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
EXPECT_TRUE(child->current_frame_host()
->GetSiteInstance()
->GetSiteInfo()
.is_sandboxed());
// Check grandchild vs. child.
ASSERT_EQ(1U, child->child_count());
FrameTreeNode* grand_child = child->child_at(0);
EXPECT_EQ(network::mojom::WebSandboxFlags::kAll,
grand_child->effective_frame_policy().sandbox_flags);
EXPECT_EQ(child->current_frame_host()->GetSiteInstance(),
grand_child->current_frame_host()->GetSiteInstance());
}
INSTANTIATE_TEST_SUITE_P(All,
SitePerProcessIsolatedSandboxedIframeTest,
testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(All,
SitePerProcessNotIsolatedSandboxedIframeTest,
testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(All,
SitePerProcessPerOriginIsolatedSandboxedIframeTest,
testing::ValuesIn(RenderDocumentFeatureLevelValues()));
} // namespace content