OLD | NEW |
| (Empty) |
1 // Copyright 2013 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. | |
4 | |
5 #include "base/command_line.h" | |
6 #include "base/strings/string_util.h" | |
7 #include "base/strings/stringprintf.h" | |
8 #include "base/test/histogram_tester.h" | |
9 #include "content/public/common/content_switches.h" | |
10 #include "content/public/common/resource_type.h" | |
11 #include "content/public/test/browser_test_utils.h" | |
12 #include "content/public/test/content_browser_test.h" | |
13 #include "content/public/test/content_browser_test_utils.h" | |
14 #include "content/shell/browser/shell.h" | |
15 #include "net/test/spawned_test_server/spawned_test_server.h" | |
16 | |
17 namespace content { | |
18 | |
19 // These tests simulate exploited renderer processes, which can fetch arbitrary | |
20 // resources from other websites, not constrained by the Same Origin Policy. We | |
21 // are trying to verify that the renderer cannot fetch any cross-site document | |
22 // responses even when the Same Origin Policy is turned off inside the renderer. | |
23 class SiteIsolationStatsGathererBrowserTest : public ContentBrowserTest { | |
24 public: | |
25 SiteIsolationStatsGathererBrowserTest() {} | |
26 ~SiteIsolationStatsGathererBrowserTest() override {} | |
27 | |
28 void SetUpCommandLine(base::CommandLine* command_line) override { | |
29 ASSERT_TRUE(test_server()->Start()); | |
30 // Add a host resolver rule to map all outgoing requests to the test server. | |
31 // This allows us to use "real" hostnames in URLs, which we can use to | |
32 // create arbitrary SiteInstances. | |
33 command_line->AppendSwitchASCII( | |
34 switches::kHostResolverRules, | |
35 "MAP * " + test_server()->host_port_pair().ToString() + | |
36 ",EXCLUDE localhost"); | |
37 | |
38 // Since we assume exploited renderer process, it can bypass the same origin | |
39 // policy at will. Simulate that by passing the disable-web-security flag. | |
40 command_line->AppendSwitch(switches::kDisableWebSecurity); | |
41 } | |
42 | |
43 void InspectHistograms(const base::HistogramTester& histograms, | |
44 bool should_be_blocked, | |
45 const std::string& resource_name) { | |
46 std::string bucket; | |
47 int mime_type = 0; // Hardcoded because histogram enums mustn't change. | |
48 if (MatchPattern(resource_name, "*.html")) { | |
49 bucket = "HTML"; | |
50 mime_type = 0; | |
51 } else if (MatchPattern(resource_name, "*.xml")) { | |
52 bucket = "XML"; | |
53 mime_type = 1; | |
54 } else if (MatchPattern(resource_name, "*.json")) { | |
55 bucket = "JSON"; | |
56 mime_type = 2; | |
57 } else if (MatchPattern(resource_name, "*.txt")) { | |
58 bucket = "Plain"; | |
59 mime_type = 3; | |
60 if (MatchPattern(resource_name, "json.*")) { | |
61 bucket += ".JSON"; | |
62 } else if (MatchPattern(resource_name, "html.*")) { | |
63 bucket += ".HTML"; | |
64 } else if (MatchPattern(resource_name, "xml.*")) { | |
65 bucket += ".XML"; | |
66 } | |
67 } else { | |
68 FAIL(); | |
69 } | |
70 FetchHistogramsFromChildProcesses(); | |
71 | |
72 // A few histograms are incremented unconditionally. | |
73 histograms.ExpectUniqueSample("SiteIsolation.AllResponses", 1, 1); | |
74 histograms.ExpectTotalCount("SiteIsolation.XSD.DataLength", 1); | |
75 histograms.ExpectUniqueSample("SiteIsolation.XSD.MimeType", mime_type, 1); | |
76 | |
77 // Inspect the appropriate conditionally-incremented histogram[s]. | |
78 std::set<std::string> expected_metrics; | |
79 std::string base_metric = "SiteIsolation.XSD." + bucket; | |
80 base_metric += should_be_blocked ? ".Blocked" : ".NotBlocked"; | |
81 expected_metrics.insert(base_metric); | |
82 if (should_be_blocked) { | |
83 expected_metrics.insert(base_metric + ".RenderableStatusCode"); | |
84 } else if (MatchPattern(resource_name, "*js.*")) { | |
85 expected_metrics.insert(base_metric + ".MaybeJS"); | |
86 } | |
87 | |
88 for (std::string metric : expected_metrics) { | |
89 if (MatchPattern(metric, "*.RenderableStatusCode")) { | |
90 histograms.ExpectUniqueSample(metric, RESOURCE_TYPE_XHR, 1); | |
91 } else { | |
92 histograms.ExpectUniqueSample(metric, 1, 1); | |
93 } | |
94 } | |
95 | |
96 // Make sure no other conditionally-incremented histograms were touched. | |
97 const char* all_metrics[] = { | |
98 "SiteIsolation.XSD.HTML.Blocked", | |
99 "SiteIsolation.XSD.HTML.Blocked.NonRenderableStatusCode", | |
100 "SiteIsolation.XSD.HTML.Blocked.RenderableStatusCode", | |
101 "SiteIsolation.XSD.HTML.NoSniffBlocked", | |
102 "SiteIsolation.XSD.HTML.NoSniffBlocked.NonRenderableStatusCode", | |
103 "SiteIsolation.XSD.HTML.NoSniffBlocked.RenderableStatusCode", | |
104 "SiteIsolation.XSD.HTML.NotBlocked", | |
105 "SiteIsolation.XSD.HTML.NotBlocked.MaybeJS", | |
106 "SiteIsolation.XSD.JSON.Blocked", | |
107 "SiteIsolation.XSD.JSON.Blocked.NonRenderableStatusCode", | |
108 "SiteIsolation.XSD.JSON.Blocked.RenderableStatusCode", | |
109 "SiteIsolation.XSD.JSON.NoSniffBlocked", | |
110 "SiteIsolation.XSD.JSON.NoSniffBlocked.NonRenderableStatusCode", | |
111 "SiteIsolation.XSD.JSON.NoSniffBlocked.RenderableStatusCode", | |
112 "SiteIsolation.XSD.JSON.NotBlocked", | |
113 "SiteIsolation.XSD.JSON.NotBlocked.MaybeJS", | |
114 "SiteIsolation.XSD.Plain.HTML.Blocked", | |
115 "SiteIsolation.XSD.Plain.HTML.Blocked.NonRenderableStatusCode", | |
116 "SiteIsolation.XSD.Plain.HTML.Blocked.RenderableStatusCode", | |
117 "SiteIsolation.XSD.Plain.JSON.Blocked", | |
118 "SiteIsolation.XSD.Plain.JSON.Blocked.NonRenderableStatusCode", | |
119 "SiteIsolation.XSD.Plain.JSON.Blocked.RenderableStatusCode", | |
120 "SiteIsolation.XSD.Plain.NoSniffBlocked", | |
121 "SiteIsolation.XSD.Plain.NoSniffBlocked.NonRenderableStatusCode", | |
122 "SiteIsolation.XSD.Plain.NoSniffBlocked.RenderableStatusCode", | |
123 "SiteIsolation.XSD.Plain.NotBlocked", | |
124 "SiteIsolation.XSD.Plain.NotBlocked.MaybeJS", | |
125 "SiteIsolation.XSD.Plain.XML.Blocked", | |
126 "SiteIsolation.XSD.Plain.XML.Blocked.NonRenderableStatusCode", | |
127 "SiteIsolation.XSD.Plain.XML.Blocked.RenderableStatusCode", | |
128 "SiteIsolation.XSD.XML.Blocked", | |
129 "SiteIsolation.XSD.XML.Blocked.NonRenderableStatusCode", | |
130 "SiteIsolation.XSD.XML.Blocked.RenderableStatusCode", | |
131 "SiteIsolation.XSD.XML.NoSniffBlocked", | |
132 "SiteIsolation.XSD.XML.NoSniffBlocked.NonRenderableStatusCode", | |
133 "SiteIsolation.XSD.XML.NoSniffBlocked.RenderableStatusCode", | |
134 "SiteIsolation.XSD.XML.NotBlocked", | |
135 "SiteIsolation.XSD.XML.NotBlocked.MaybeJS"}; | |
136 | |
137 for (const char* metric : all_metrics) { | |
138 if (!expected_metrics.count(metric)) { | |
139 histograms.ExpectTotalCount(metric, 0); | |
140 } | |
141 } | |
142 } | |
143 | |
144 private: | |
145 DISALLOW_COPY_AND_ASSIGN(SiteIsolationStatsGathererBrowserTest); | |
146 }; | |
147 | |
148 // TODO(dsjang): we cannot run these tests on Android since SetUpCommandLine() | |
149 // is executed before the I/O thread is created on Android. After this bug | |
150 // (crbug.com/278425) is resolved, we can enable this test case on Android. | |
151 #if defined(OS_ANDROID) | |
152 #define MAYBE_CrossSiteDocumentBlockingForMimeType \ | |
153 DISABLED_CrossSiteDocumentBlockingForMimeType | |
154 #else | |
155 #define MAYBE_CrossSiteDocumentBlockingForMimeType \ | |
156 CrossSiteDocumentBlockingForMimeType | |
157 #endif | |
158 | |
159 IN_PROC_BROWSER_TEST_F(SiteIsolationStatsGathererBrowserTest, | |
160 MAYBE_CrossSiteDocumentBlockingForMimeType) { | |
161 // Load a page that issues illegal cross-site document requests to bar.com. | |
162 // The page uses XHR to request HTML/XML/JSON documents from bar.com, and | |
163 // inspects if any of them were successfully received. Currently, on illegal | |
164 // access, the XHR requests should succeed, but the UMA histograms should | |
165 // record that they would have been blocked. This test is only possible since | |
166 // we run the browser without the same origin policy. | |
167 GURL foo("http://foo.com/files/cross_site_document_request.html"); | |
168 | |
169 NavigateToURL(shell(), foo); | |
170 | |
171 // Flush out existing histogram activity. | |
172 FetchHistogramsFromChildProcesses(); | |
173 | |
174 // The following are files under content/test/data/site_isolation. All | |
175 // should be disallowed for XHR under the document blocking policy. | |
176 // TODO(nick): xml.txt is logged under HTML, not XML. Not sure if this is a | |
177 // bug with the logging or the test expectation. | |
178 const char* blocked_resources[] = {"valid.html", | |
179 "comment_valid.html", | |
180 "valid.xml", | |
181 "valid.json", | |
182 "html.txt", | |
183 /* "xml.txt", */ // Broken, see above. | |
184 "json.txt"}; | |
185 | |
186 for (const char* resource : blocked_resources) { | |
187 SCOPED_TRACE(base::StringPrintf("... while testing page: %s", resource)); | |
188 base::HistogramTester histograms; | |
189 | |
190 bool was_blocked; | |
191 ASSERT_TRUE(ExecuteScriptAndExtractBool( | |
192 shell()->web_contents(), | |
193 base::StringPrintf("sendRequest(\"%s\");", resource), &was_blocked)); | |
194 ASSERT_FALSE(was_blocked); | |
195 | |
196 InspectHistograms(histograms, true, resource); | |
197 } | |
198 | |
199 // These files should be allowed for XHR under the document blocking policy. | |
200 const char* allowed_resources[] = {"js.html", | |
201 "comment_js.html", | |
202 "js.xml", | |
203 "js.json", | |
204 "js.txt", | |
205 "img.html", | |
206 "img.xml", | |
207 "img.json", | |
208 "img.txt", | |
209 "comment_js.html"}; | |
210 for (const char* resource : allowed_resources) { | |
211 SCOPED_TRACE(base::StringPrintf("... while testing page: %s", resource)); | |
212 base::HistogramTester histograms; | |
213 | |
214 bool was_blocked; | |
215 ASSERT_TRUE(ExecuteScriptAndExtractBool( | |
216 shell()->web_contents(), | |
217 base::StringPrintf("sendRequest(\"%s\");", resource), &was_blocked)); | |
218 ASSERT_FALSE(was_blocked); | |
219 | |
220 InspectHistograms(histograms, false, resource); | |
221 } | |
222 } | |
223 | |
224 // TODO(dsjang): we cannot run these tests on Android since SetUpCommandLine() | |
225 // is executed before the I/O thread is created on Android. After this bug | |
226 // (crbug.com/278425) is resolved, we can enable this test case on Android. | |
227 #if defined(OS_ANDROID) | |
228 #define MAYBE_CrossSiteDocumentBlockingForDifferentTargets \ | |
229 DISABLED_CrossSiteDocumentBlockingForDifferentTargets | |
230 #else | |
231 #define MAYBE_CrossSiteDocumentBlockingForDifferentTargets \ | |
232 CrossSiteDocumentBlockingForDifferentTargets | |
233 #endif | |
234 | |
235 IN_PROC_BROWSER_TEST_F(SiteIsolationStatsGathererBrowserTest, | |
236 MAYBE_CrossSiteDocumentBlockingForDifferentTargets) { | |
237 // This webpage loads a cross-site HTML page in different targets such as | |
238 // <img>,<link>,<embed>, etc. Since the requested document is blocked, and one | |
239 // character string (' ') is returned instead, this tests that the renderer | |
240 // does not crash even when it receives a response body which is " ", whose | |
241 // length is different from what's described in "content-length" for such | |
242 // different targets. | |
243 | |
244 // TODO(nick): Split up these cases, and add positive assertions here about | |
245 // what actually happens in these various resource-block cases. | |
246 GURL foo("http://foo.com/files/cross_site_document_request_target.html"); | |
247 NavigateToURL(shell(), foo); | |
248 } | |
249 | |
250 } | |
OLD | NEW |