1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/command_line.h"
6 #include "base/strings/pattern.h"
7 #include "base/strings/string_util.h"
8 #include "base/strings/stringprintf.h"
9 #include "base/test/histogram_tester.h"
10 #include "content/public/common/content_switches.h"
11 #include "content/public/common/resource_type.h"
12 #include "content/public/test/browser_test_utils.h"
13 #include "content/public/test/content_browser_test.h"
14 #include "content/public/test/content_browser_test_utils.h"
15 #include "content/shell/browser/shell.h"
16 #include "net/test/spawned_test_server/spawned_test_server.h"
17 #include "testing/gmock/include/gmock/gmock.h"
21 // These tests simulate exploited renderer processes, which can fetch arbitrary
22 // resources from other websites, not constrained by the Same Origin Policy. We
23 // are trying to verify that the renderer cannot fetch any cross-site document
24 // responses even when the Same Origin Policy is turned off inside the renderer.
25 class SiteIsolationStatsGathererBrowserTest
: public ContentBrowserTest
{
27 SiteIsolationStatsGathererBrowserTest() {}
28 ~SiteIsolationStatsGathererBrowserTest() override
{}
30 void SetUpCommandLine(base::CommandLine
* command_line
) override
{
31 ASSERT_TRUE(test_server()->Start());
32 // Add a host resolver rule to map all outgoing requests to the test server.
33 // This allows us to use "real" hostnames in URLs, which we can use to
34 // create arbitrary SiteInstances.
35 command_line
->AppendSwitchASCII(
36 switches::kHostResolverRules
,
37 "MAP * " + test_server()->host_port_pair().ToString() +
38 ",EXCLUDE localhost");
40 // Since we assume exploited renderer process, it can bypass the same origin
41 // policy at will. Simulate that by passing the disable-web-security flag.
42 command_line
->AppendSwitch(switches::kDisableWebSecurity
);
45 void InspectHistograms(const base::HistogramTester
& histograms
,
46 bool should_be_blocked
,
47 const std::string
& resource_name
) {
49 int mime_type
= 0; // Hardcoded because histogram enums mustn't change.
50 if (base::MatchPattern(resource_name
, "*.html")) {
53 } else if (base::MatchPattern(resource_name
, "*.xml")) {
56 } else if (base::MatchPattern(resource_name
, "*.json")) {
59 } else if (base::MatchPattern(resource_name
, "*.txt")) {
62 if (base::MatchPattern(resource_name
, "json*")) {
64 } else if (base::MatchPattern(resource_name
, "html*")) {
66 } else if (base::MatchPattern(resource_name
, "xml*")) {
72 FetchHistogramsFromChildProcesses();
74 // A few histograms are incremented unconditionally.
75 histograms
.ExpectUniqueSample("SiteIsolation.AllResponses", 1, 1);
76 base::HistogramTester::CountsMap expected_metrics
;
77 expected_metrics
["SiteIsolation.XSD.DataLength"] = 1;
78 expected_metrics
["SiteIsolation.XSD.MimeType"] = 1;
80 // Determine the appropriate conditionally-incremented histograms.
81 std::string base
= "SiteIsolation.XSD." + bucket
;
82 if (should_be_blocked
) {
83 expected_metrics
[base
+ ".Blocked"] = 1;
84 expected_metrics
[base
+ ".Blocked.RenderableStatusCode"] = 1;
86 expected_metrics
[base
+ ".NotBlocked"] = 1;
87 if (base::MatchPattern(resource_name
, "*js.*")) {
88 expected_metrics
[base
+ ".NotBlocked.MaybeJS"] = 1;
92 // Make sure that the expected metrics, and only those metrics, were
94 EXPECT_THAT(histograms
.GetTotalCountsForPrefix("SiteIsolation.XSD."),
95 testing::ContainerEq(expected_metrics
))
96 << "For resource_name=" << resource_name
97 << ", should_be_blocked=" << should_be_blocked
;
99 EXPECT_THAT(histograms
.GetAllSamples("SiteIsolation.XSD.MimeType"),
100 testing::ElementsAre(base::Bucket(mime_type
, 1)))
101 << "The wrong mime type bucket was incremented.";
102 if (should_be_blocked
) {
103 static_assert(13 == RESOURCE_TYPE_XHR
, "Histogram enums mustn't change.");
105 histograms
.GetAllSamples(base
+ ".Blocked.RenderableStatusCode"),
106 testing::ElementsAre(base::Bucket(RESOURCE_TYPE_XHR
, 1)))
107 << "The wrong RenderableStatusCode bucket was incremented.";
112 DISALLOW_COPY_AND_ASSIGN(SiteIsolationStatsGathererBrowserTest
);
115 // TODO(dsjang): we cannot run these tests on Android since SetUpCommandLine()
116 // is executed before the I/O thread is created on Android. After this bug
117 // (crbug.com/278425) is resolved, we can enable this test case on Android.
118 #if defined(OS_ANDROID)
119 #define MAYBE_CrossSiteDocumentBlockingForMimeType \
120 DISABLED_CrossSiteDocumentBlockingForMimeType
122 #define MAYBE_CrossSiteDocumentBlockingForMimeType \
123 CrossSiteDocumentBlockingForMimeType
126 IN_PROC_BROWSER_TEST_F(SiteIsolationStatsGathererBrowserTest
,
127 MAYBE_CrossSiteDocumentBlockingForMimeType
) {
128 // Load a page that issues illegal cross-site document requests to bar.com.
129 // The page uses XHR to request HTML/XML/JSON documents from bar.com, and
130 // inspects if any of them were successfully received. Currently, on illegal
131 // access, the XHR requests should succeed, but the UMA histograms should
132 // record that they would have been blocked. This test is only possible since
133 // we run the browser without the same origin policy.
134 GURL
foo("http://foo.com/files/cross_site_document_request.html");
136 NavigateToURL(shell(), foo
);
138 // Flush out existing histogram activity.
139 FetchHistogramsFromChildProcesses();
141 // The following are files under content/test/data/site_isolation. All
142 // should be disallowed for cross site XHR under the document blocking policy.
143 const char* blocked_resources
[] = {
144 "comment_valid.html",
157 for (const char* resource
: blocked_resources
) {
158 SCOPED_TRACE(base::StringPrintf("... while testing page: %s", resource
));
159 base::HistogramTester histograms
;
162 ASSERT_TRUE(ExecuteScriptAndExtractBool(
163 shell()->web_contents(),
164 base::StringPrintf("sendRequest(\"%s\");", resource
), &was_blocked
));
165 ASSERT_FALSE(was_blocked
);
167 InspectHistograms(histograms
, true, resource
);
170 // These files should be allowed for XHR under the document blocking policy.
171 const char* allowed_resources
[] = {"js.html",
181 for (const char* resource
: allowed_resources
) {
182 SCOPED_TRACE(base::StringPrintf("... while testing page: %s", resource
));
183 base::HistogramTester histograms
;
186 ASSERT_TRUE(ExecuteScriptAndExtractBool(
187 shell()->web_contents(),
188 base::StringPrintf("sendRequest(\"%s\");", resource
), &was_blocked
));
189 ASSERT_FALSE(was_blocked
);
191 InspectHistograms(histograms
, false, resource
);
195 // TODO(dsjang): we cannot run these tests on Android since SetUpCommandLine()
196 // is executed before the I/O thread is created on Android. After this bug
197 // (crbug.com/278425) is resolved, we can enable this test case on Android.
198 #if defined(OS_ANDROID)
199 #define MAYBE_CrossSiteDocumentBlockingForDifferentTargets \
200 DISABLED_CrossSiteDocumentBlockingForDifferentTargets
202 #define MAYBE_CrossSiteDocumentBlockingForDifferentTargets \
203 CrossSiteDocumentBlockingForDifferentTargets
206 IN_PROC_BROWSER_TEST_F(SiteIsolationStatsGathererBrowserTest
,
207 MAYBE_CrossSiteDocumentBlockingForDifferentTargets
) {
208 // This webpage loads a cross-site HTML page in different targets such as
209 // <img>,<link>,<embed>, etc. Since the requested document is blocked, and one
210 // character string (' ') is returned instead, this tests that the renderer
211 // does not crash even when it receives a response body which is " ", whose
212 // length is different from what's described in "content-length" for such
213 // different targets.
215 // TODO(nick): Split up these cases, and add positive assertions here about
216 // what actually happens in these various resource-block cases.
217 GURL
foo("http://foo.com/files/cross_site_document_request_target.html");
218 NavigateToURL(shell(), foo
);
221 } // namespace content