| 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 |