| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "content/child/site_isolation_policy.h" | 5 #include "content/child/site_isolation_policy.h" |
| 6 | 6 |
| 7 #include "base/basictypes.h" | 7 #include "base/basictypes.h" |
| 8 #include "base/command_line.h" | 8 #include "base/command_line.h" |
| 9 #include "base/lazy_instance.h" | 9 #include "base/lazy_instance.h" |
| 10 #include "base/logging.h" | 10 #include "base/logging.h" |
| 11 #include "base/metrics/histogram.h" | 11 #include "base/metrics/histogram.h" |
| 12 #include "base/strings/string_util.h" | 12 #include "base/strings/string_util.h" |
| 13 #include "content/public/common/content_switches.h" | 13 #include "content/public/common/content_switches.h" |
| 14 #include "content/public/common/resource_response_info.h" | 14 #include "content/public/common/resource_response_info.h" |
| 15 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" | 15 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| 16 #include "net/http/http_response_headers.h" | 16 #include "net/http/http_response_headers.h" |
| 17 | 17 |
| 18 using base::StringPiece; | 18 using base::StringPiece; |
| 19 | 19 |
| 20 namespace content { | 20 namespace content { |
| 21 | 21 |
| 22 namespace { | 22 namespace { |
| 23 | 23 |
| 24 // The cross-site document blocking/UMA data collection is deactivated by | 24 // The gathering of UMA stats for site isolation is deactivated by default, and |
| 25 // default, and only activated in renderer processes. | 25 // only activated in renderer processes. |
| 26 static bool g_policy_enabled = false; | 26 static bool g_stats_gathering_enabled = false; |
| 27 | 27 |
| 28 // MIME types | 28 // MIME types |
| 29 const char kTextHtml[] = "text/html"; | 29 const char kTextHtml[] = "text/html"; |
| 30 const char kTextXml[] = "text/xml"; | 30 const char kTextXml[] = "text/xml"; |
| 31 const char xAppRssXml[] = "application/rss+xml"; | 31 const char xAppRssXml[] = "application/rss+xml"; |
| 32 const char kAppXml[] = "application/xml"; | 32 const char kAppXml[] = "application/xml"; |
| 33 const char kAppJson[] = "application/json"; | 33 const char kAppJson[] = "application/json"; |
| 34 const char kTextJson[] = "text/json"; | 34 const char kTextJson[] = "text/json"; |
| 35 const char kTextXjson[] = "text/x-json"; | 35 const char kTextXjson[] = "text/x-json"; |
| 36 const char kTextPlain[] = "text/plain"; | 36 const char kTextPlain[] = "text/plain"; |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 125 IncrementHistogramCount(bucket_prefix + ".NotBlocked"); | 125 IncrementHistogramCount(bucket_prefix + ".NotBlocked"); |
| 126 if (sniffed_as_js) | 126 if (sniffed_as_js) |
| 127 IncrementHistogramCount(bucket_prefix + ".NotBlocked.MaybeJS"); | 127 IncrementHistogramCount(bucket_prefix + ".NotBlocked.MaybeJS"); |
| 128 } | 128 } |
| 129 | 129 |
| 130 } // namespace | 130 } // namespace |
| 131 | 131 |
| 132 SiteIsolationResponseMetaData::SiteIsolationResponseMetaData() { | 132 SiteIsolationResponseMetaData::SiteIsolationResponseMetaData() { |
| 133 } | 133 } |
| 134 | 134 |
| 135 void SiteIsolationPolicy::SetPolicyEnabled(bool enabled) { | 135 void SiteIsolationStatsGatherer::SetEnabled(bool enabled) { |
| 136 g_policy_enabled = enabled; | 136 g_stats_gathering_enabled = enabled; |
| 137 } | 137 } |
| 138 | 138 |
| 139 linked_ptr<SiteIsolationResponseMetaData> | 139 linked_ptr<SiteIsolationResponseMetaData> |
| 140 SiteIsolationPolicy::OnReceivedResponse(const GURL& frame_origin, | 140 SiteIsolationStatsGatherer::OnReceivedResponse( |
| 141 const GURL& response_url, | 141 const GURL& frame_origin, |
| 142 ResourceType resource_type, | 142 const GURL& response_url, |
| 143 int origin_pid, | 143 ResourceType resource_type, |
| 144 const ResourceResponseInfo& info) { | 144 int origin_pid, |
| 145 if (!g_policy_enabled) | 145 const ResourceResponseInfo& info) { |
| 146 if (!g_stats_gathering_enabled) |
| 146 return linked_ptr<SiteIsolationResponseMetaData>(); | 147 return linked_ptr<SiteIsolationResponseMetaData>(); |
| 147 | 148 |
| 148 // if |origin_pid| is non-zero, it means that this response is for a plugin | 149 // if |origin_pid| is non-zero, it means that this response is for a plugin |
| 149 // spawned from this renderer process. We exclude responses for plugins for | 150 // spawned from this renderer process. We exclude responses for plugins for |
| 150 // now, but eventually, we're going to make plugin processes directly talk to | 151 // now, but eventually, we're going to make plugin processes directly talk to |
| 151 // the browser process so that we don't apply cross-site document blocking to | 152 // the browser process so that we don't apply cross-site document blocking to |
| 152 // them. | 153 // them. |
| 153 if (origin_pid) | 154 if (origin_pid) |
| 154 return linked_ptr<SiteIsolationResponseMetaData>(); | 155 return linked_ptr<SiteIsolationResponseMetaData>(); |
| 155 | 156 |
| 156 UMA_HISTOGRAM_COUNTS("SiteIsolation.AllResponses", 1); | 157 UMA_HISTOGRAM_COUNTS("SiteIsolation.AllResponses", 1); |
| 157 | 158 |
| 158 // See if this is for navigation. If it is, don't block it, under the | 159 // See if this is for navigation. If it is, don't block it, under the |
| 159 // assumption that we will put it in an appropriate process. | 160 // assumption that we will put it in an appropriate process. |
| 160 if (IsResourceTypeFrame(resource_type)) | 161 if (IsResourceTypeFrame(resource_type)) |
| 161 return linked_ptr<SiteIsolationResponseMetaData>(); | 162 return linked_ptr<SiteIsolationResponseMetaData>(); |
| 162 | 163 |
| 163 if (!IsBlockableScheme(response_url)) | 164 if (!CrossSiteDocumentClassifier::IsBlockableScheme(response_url)) |
| 164 return linked_ptr<SiteIsolationResponseMetaData>(); | 165 return linked_ptr<SiteIsolationResponseMetaData>(); |
| 165 | 166 |
| 166 if (IsSameSite(frame_origin, response_url)) | 167 if (CrossSiteDocumentClassifier::IsSameSite(frame_origin, response_url)) |
| 167 return linked_ptr<SiteIsolationResponseMetaData>(); | 168 return linked_ptr<SiteIsolationResponseMetaData>(); |
| 168 | 169 |
| 169 SiteIsolationResponseMetaData::CanonicalMimeType canonical_mime_type = | 170 SiteIsolationResponseMetaData::CanonicalMimeType canonical_mime_type = |
| 170 GetCanonicalMimeType(info.mime_type); | 171 CrossSiteDocumentClassifier::GetCanonicalMimeType(info.mime_type); |
| 171 | 172 |
| 172 if (canonical_mime_type == SiteIsolationResponseMetaData::Others) | 173 if (canonical_mime_type == SiteIsolationResponseMetaData::Others) |
| 173 return linked_ptr<SiteIsolationResponseMetaData>(); | 174 return linked_ptr<SiteIsolationResponseMetaData>(); |
| 174 | 175 |
| 175 // Every CORS request should have the Access-Control-Allow-Origin header even | 176 // Every CORS request should have the Access-Control-Allow-Origin header even |
| 176 // if it is preceded by a pre-flight request. Therefore, if this is a CORS | 177 // if it is preceded by a pre-flight request. Therefore, if this is a CORS |
| 177 // request, it has this header. response.httpHeaderField() internally uses | 178 // request, it has this header. response.httpHeaderField() internally uses |
| 178 // case-insensitive matching for the header name. | 179 // case-insensitive matching for the header name. |
| 179 std::string access_control_origin; | 180 std::string access_control_origin; |
| 180 | 181 |
| 181 // We can use a case-insensitive header name for EnumerateHeader(). | 182 // We can use a case-insensitive header name for EnumerateHeader(). |
| 182 info.headers->EnumerateHeader(NULL, "access-control-allow-origin", | 183 info.headers->EnumerateHeader(NULL, "access-control-allow-origin", |
| 183 &access_control_origin); | 184 &access_control_origin); |
| 184 if (IsValidCorsHeaderSet(frame_origin, response_url, access_control_origin)) | 185 if (CrossSiteDocumentClassifier::IsValidCorsHeaderSet( |
| 186 frame_origin, response_url, access_control_origin)) |
| 185 return linked_ptr<SiteIsolationResponseMetaData>(); | 187 return linked_ptr<SiteIsolationResponseMetaData>(); |
| 186 | 188 |
| 187 // Real XSD data collection starts from here. | 189 // Real XSD data collection starts from here. |
| 188 std::string no_sniff; | 190 std::string no_sniff; |
| 189 info.headers->EnumerateHeader(NULL, "x-content-type-options", &no_sniff); | 191 info.headers->EnumerateHeader(NULL, "x-content-type-options", &no_sniff); |
| 190 | 192 |
| 191 linked_ptr<SiteIsolationResponseMetaData> resp_data( | 193 linked_ptr<SiteIsolationResponseMetaData> resp_data( |
| 192 new SiteIsolationResponseMetaData); | 194 new SiteIsolationResponseMetaData); |
| 193 resp_data->frame_origin = frame_origin.spec(); | 195 resp_data->frame_origin = frame_origin.spec(); |
| 194 resp_data->response_url = response_url; | 196 resp_data->response_url = response_url; |
| 195 resp_data->resource_type = resource_type; | 197 resp_data->resource_type = resource_type; |
| 196 resp_data->canonical_mime_type = canonical_mime_type; | 198 resp_data->canonical_mime_type = canonical_mime_type; |
| 197 resp_data->http_status_code = info.headers->response_code(); | 199 resp_data->http_status_code = info.headers->response_code(); |
| 198 resp_data->no_sniff = base::LowerCaseEqualsASCII(no_sniff, "nosniff"); | 200 resp_data->no_sniff = base::LowerCaseEqualsASCII(no_sniff, "nosniff"); |
| 199 | 201 |
| 200 return resp_data; | 202 return resp_data; |
| 201 } | 203 } |
| 202 | 204 |
| 203 bool SiteIsolationPolicy::OnReceivedFirstChunk( | 205 bool SiteIsolationStatsGatherer::OnReceivedFirstChunk( |
| 204 const linked_ptr<SiteIsolationResponseMetaData>& resp_data, | 206 const linked_ptr<SiteIsolationResponseMetaData>& resp_data, |
| 205 const char* raw_data, | 207 const char* raw_data, |
| 206 int raw_length) { | 208 int raw_length) { |
| 207 if (!g_policy_enabled) | 209 if (!g_stats_gathering_enabled) |
| 208 return false; | 210 return false; |
| 209 | 211 |
| 210 DCHECK(resp_data.get()); | 212 DCHECK(resp_data.get()); |
| 211 | 213 |
| 212 StringPiece data(raw_data, raw_length); | 214 StringPiece data(raw_data, raw_length); |
| 213 | 215 |
| 214 // Record the length of the first received chunk of data to see if it's enough | 216 // Record the length of the first received chunk of data to see if it's enough |
| 215 // for sniffing. | 217 // for sniffing. |
| 216 UMA_HISTOGRAM_COUNTS("SiteIsolation.XSD.DataLength", raw_length); | 218 UMA_HISTOGRAM_COUNTS("SiteIsolation.XSD.DataLength", raw_length); |
| 217 | 219 |
| 218 // Record the number of cross-site document responses with a specific mime | 220 // Record the number of cross-site document responses with a specific mime |
| 219 // type (text/html, text/xml, etc). | 221 // type (text/html, text/xml, etc). |
| 220 UMA_HISTOGRAM_ENUMERATION( | 222 UMA_HISTOGRAM_ENUMERATION( |
| 221 "SiteIsolation.XSD.MimeType", resp_data->canonical_mime_type, | 223 "SiteIsolation.XSD.MimeType", resp_data->canonical_mime_type, |
| 222 SiteIsolationResponseMetaData::MaxCanonicalMimeType); | 224 SiteIsolationResponseMetaData::MaxCanonicalMimeType); |
| 223 | 225 |
| 224 // Store the result of cross-site document blocking analysis. | 226 // Store the result of cross-site document blocking analysis. |
| 225 bool would_block = false; | 227 bool would_block = false; |
| 226 bool sniffed_as_js = SniffForJS(data); | 228 bool sniffed_as_js = SniffForJS(data); |
| 227 | 229 |
| 228 // Record the number of responses whose content is sniffed for what its mime | 230 // Record the number of responses whose content is sniffed for what its mime |
| 229 // type claims it to be. For example, we apply a HTML sniffer for a document | 231 // type claims it to be. For example, we apply a HTML sniffer for a document |
| 230 // tagged with text/html here. Whenever this check becomes true, we'll block | 232 // tagged with text/html here. Whenever this check becomes true, we'll block |
| 231 // the response. | 233 // the response. |
| 232 if (resp_data->canonical_mime_type != SiteIsolationResponseMetaData::Plain) { | 234 if (resp_data->canonical_mime_type != SiteIsolationResponseMetaData::Plain) { |
| 233 std::string bucket_prefix; | 235 std::string bucket_prefix; |
| 234 bool sniffed_as_target_document = false; | 236 bool sniffed_as_target_document = false; |
| 235 if (resp_data->canonical_mime_type == SiteIsolationResponseMetaData::HTML) { | 237 if (resp_data->canonical_mime_type == SiteIsolationResponseMetaData::HTML) { |
| 236 bucket_prefix = "SiteIsolation.XSD.HTML"; | 238 bucket_prefix = "SiteIsolation.XSD.HTML"; |
| 237 sniffed_as_target_document = SniffForHTML(data); | 239 sniffed_as_target_document = |
| 240 CrossSiteDocumentClassifier::SniffForHTML(data); |
| 238 } else if (resp_data->canonical_mime_type == | 241 } else if (resp_data->canonical_mime_type == |
| 239 SiteIsolationResponseMetaData::XML) { | 242 SiteIsolationResponseMetaData::XML) { |
| 240 bucket_prefix = "SiteIsolation.XSD.XML"; | 243 bucket_prefix = "SiteIsolation.XSD.XML"; |
| 241 sniffed_as_target_document = SniffForXML(data); | 244 sniffed_as_target_document = |
| 245 CrossSiteDocumentClassifier::SniffForXML(data); |
| 242 } else if (resp_data->canonical_mime_type == | 246 } else if (resp_data->canonical_mime_type == |
| 243 SiteIsolationResponseMetaData::JSON) { | 247 SiteIsolationResponseMetaData::JSON) { |
| 244 bucket_prefix = "SiteIsolation.XSD.JSON"; | 248 bucket_prefix = "SiteIsolation.XSD.JSON"; |
| 245 sniffed_as_target_document = SniffForJSON(data); | 249 sniffed_as_target_document = |
| 250 CrossSiteDocumentClassifier::SniffForJSON(data); |
| 246 } else { | 251 } else { |
| 247 NOTREACHED() << "Not a blockable mime type: " | 252 NOTREACHED() << "Not a blockable mime type: " |
| 248 << resp_data->canonical_mime_type; | 253 << resp_data->canonical_mime_type; |
| 249 } | 254 } |
| 250 | 255 |
| 251 if (sniffed_as_target_document) { | 256 if (sniffed_as_target_document) { |
| 252 would_block = true; | 257 would_block = true; |
| 253 HistogramCountBlockedResponse(bucket_prefix, resp_data, false); | 258 HistogramCountBlockedResponse(bucket_prefix, resp_data, false); |
| 254 } else { | 259 } else { |
| 255 if (resp_data->no_sniff) { | 260 if (resp_data->no_sniff) { |
| 256 would_block = true; | 261 would_block = true; |
| 257 HistogramCountBlockedResponse(bucket_prefix, resp_data, true); | 262 HistogramCountBlockedResponse(bucket_prefix, resp_data, true); |
| 258 } else { | 263 } else { |
| 259 HistogramCountNotBlockedResponse(bucket_prefix, sniffed_as_js); | 264 HistogramCountNotBlockedResponse(bucket_prefix, sniffed_as_js); |
| 260 } | 265 } |
| 261 } | 266 } |
| 262 } else { | 267 } else { |
| 263 // This block is for plain text documents. We apply our HTML, XML, | 268 // This block is for plain text documents. We apply our HTML, XML, |
| 264 // and JSON sniffer to a text document in the order, and block it | 269 // and JSON sniffer to a text document in the order, and block it |
| 265 // if any of them succeeds in sniffing. | 270 // if any of them succeeds in sniffing. |
| 266 std::string bucket_prefix; | 271 std::string bucket_prefix; |
| 267 if (SniffForHTML(data)) | 272 if (CrossSiteDocumentClassifier::SniffForHTML(data)) |
| 268 bucket_prefix = "SiteIsolation.XSD.Plain.HTML"; | 273 bucket_prefix = "SiteIsolation.XSD.Plain.HTML"; |
| 269 else if (SniffForXML(data)) | 274 else if (CrossSiteDocumentClassifier::SniffForXML(data)) |
| 270 bucket_prefix = "SiteIsolation.XSD.Plain.XML"; | 275 bucket_prefix = "SiteIsolation.XSD.Plain.XML"; |
| 271 else if (SniffForJSON(data)) | 276 else if (CrossSiteDocumentClassifier::SniffForJSON(data)) |
| 272 bucket_prefix = "SiteIsolation.XSD.Plain.JSON"; | 277 bucket_prefix = "SiteIsolation.XSD.Plain.JSON"; |
| 273 | 278 |
| 274 if (bucket_prefix.size() > 0) { | 279 if (bucket_prefix.size() > 0) { |
| 275 would_block = true; | 280 would_block = true; |
| 276 HistogramCountBlockedResponse(bucket_prefix, resp_data, false); | 281 HistogramCountBlockedResponse(bucket_prefix, resp_data, false); |
| 277 } else if (resp_data->no_sniff) { | 282 } else if (resp_data->no_sniff) { |
| 278 would_block = true; | 283 would_block = true; |
| 279 HistogramCountBlockedResponse("SiteIsolation.XSD.Plain", resp_data, true); | 284 HistogramCountBlockedResponse("SiteIsolation.XSD.Plain", resp_data, true); |
| 280 } else { | 285 } else { |
| 281 HistogramCountNotBlockedResponse("SiteIsolation.XSD.Plain", | 286 HistogramCountNotBlockedResponse("SiteIsolation.XSD.Plain", |
| 282 sniffed_as_js); | 287 sniffed_as_js); |
| 283 } | 288 } |
| 284 } | 289 } |
| 285 | 290 |
| 286 return would_block; | 291 return would_block; |
| 287 } | 292 } |
| 288 | 293 |
| 289 SiteIsolationResponseMetaData::CanonicalMimeType | 294 SiteIsolationResponseMetaData::CanonicalMimeType |
| 290 SiteIsolationPolicy::GetCanonicalMimeType(const std::string& mime_type) { | 295 CrossSiteDocumentClassifier::GetCanonicalMimeType( |
| 296 const std::string& mime_type) { |
| 291 if (base::LowerCaseEqualsASCII(mime_type, kTextHtml)) { | 297 if (base::LowerCaseEqualsASCII(mime_type, kTextHtml)) { |
| 292 return SiteIsolationResponseMetaData::HTML; | 298 return SiteIsolationResponseMetaData::HTML; |
| 293 } | 299 } |
| 294 | 300 |
| 295 if (base::LowerCaseEqualsASCII(mime_type, kTextPlain)) { | 301 if (base::LowerCaseEqualsASCII(mime_type, kTextPlain)) { |
| 296 return SiteIsolationResponseMetaData::Plain; | 302 return SiteIsolationResponseMetaData::Plain; |
| 297 } | 303 } |
| 298 | 304 |
| 299 if (base::LowerCaseEqualsASCII(mime_type, kAppJson) || | 305 if (base::LowerCaseEqualsASCII(mime_type, kAppJson) || |
| 300 base::LowerCaseEqualsASCII(mime_type, kTextJson) || | 306 base::LowerCaseEqualsASCII(mime_type, kTextJson) || |
| 301 base::LowerCaseEqualsASCII(mime_type, kTextXjson)) { | 307 base::LowerCaseEqualsASCII(mime_type, kTextXjson)) { |
| 302 return SiteIsolationResponseMetaData::JSON; | 308 return SiteIsolationResponseMetaData::JSON; |
| 303 } | 309 } |
| 304 | 310 |
| 305 if (base::LowerCaseEqualsASCII(mime_type, kTextXml) || | 311 if (base::LowerCaseEqualsASCII(mime_type, kTextXml) || |
| 306 base::LowerCaseEqualsASCII(mime_type, xAppRssXml) || | 312 base::LowerCaseEqualsASCII(mime_type, xAppRssXml) || |
| 307 base::LowerCaseEqualsASCII(mime_type, kAppXml)) { | 313 base::LowerCaseEqualsASCII(mime_type, kAppXml)) { |
| 308 return SiteIsolationResponseMetaData::XML; | 314 return SiteIsolationResponseMetaData::XML; |
| 309 } | 315 } |
| 310 | 316 |
| 311 return SiteIsolationResponseMetaData::Others; | 317 return SiteIsolationResponseMetaData::Others; |
| 312 } | 318 } |
| 313 | 319 |
| 314 bool SiteIsolationPolicy::IsBlockableScheme(const GURL& url) { | 320 bool CrossSiteDocumentClassifier::IsBlockableScheme(const GURL& url) { |
| 315 // We exclude ftp:// from here. FTP doesn't provide a Content-Type | 321 // We exclude ftp:// from here. FTP doesn't provide a Content-Type |
| 316 // header which our policy depends on, so we cannot protect any | 322 // header which our policy depends on, so we cannot protect any |
| 317 // document from FTP servers. | 323 // document from FTP servers. |
| 318 return url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme); | 324 return url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme); |
| 319 } | 325 } |
| 320 | 326 |
| 321 bool SiteIsolationPolicy::IsSameSite(const GURL& frame_origin, | 327 bool CrossSiteDocumentClassifier::IsSameSite(const GURL& frame_origin, |
| 322 const GURL& response_url) { | 328 const GURL& response_url) { |
| 323 if (!frame_origin.is_valid() || !response_url.is_valid()) | 329 if (!frame_origin.is_valid() || !response_url.is_valid()) |
| 324 return false; | 330 return false; |
| 325 | 331 |
| 326 if (frame_origin.scheme() != response_url.scheme()) | 332 if (frame_origin.scheme() != response_url.scheme()) |
| 327 return false; | 333 return false; |
| 328 | 334 |
| 329 // SameDomainOrHost() extracts the effective domains (public suffix plus one) | 335 // SameDomainOrHost() extracts the effective domains (public suffix plus one) |
| 330 // from the two URLs and compare them. | 336 // from the two URLs and compare them. |
| 331 return net::registry_controlled_domains::SameDomainOrHost( | 337 return net::registry_controlled_domains::SameDomainOrHost( |
| 332 frame_origin, response_url, | 338 frame_origin, response_url, |
| 333 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); | 339 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); |
| 334 } | 340 } |
| 335 | 341 |
| 336 // We don't use Webkit's existing CORS policy implementation since | 342 // We don't use Webkit's existing CORS policy implementation since |
| 337 // their policy works in terms of origins, not sites. For example, | 343 // their policy works in terms of origins, not sites. For example, |
| 338 // when frame is sub.a.com and it is not allowed to access a document | 344 // when frame is sub.a.com and it is not allowed to access a document |
| 339 // with sub1.a.com. But under Site Isolation, it's allowed. | 345 // with sub1.a.com. But under Site Isolation, it's allowed. |
| 340 bool SiteIsolationPolicy::IsValidCorsHeaderSet( | 346 bool CrossSiteDocumentClassifier::IsValidCorsHeaderSet( |
| 341 const GURL& frame_origin, | 347 const GURL& frame_origin, |
| 342 const GURL& website_origin, | 348 const GURL& website_origin, |
| 343 const std::string& access_control_origin) { | 349 const std::string& access_control_origin) { |
| 344 // Many websites are sending back "\"*\"" instead of "*". This is | 350 // Many websites are sending back "\"*\"" instead of "*". This is |
| 345 // non-standard practice, and not supported by Chrome. Refer to | 351 // non-standard practice, and not supported by Chrome. Refer to |
| 346 // CrossOriginAccessControl::passesAccessControlCheck(). | 352 // CrossOriginAccessControl::passesAccessControlCheck(). |
| 347 | 353 |
| 348 // TODO(dsjang): * is not allowed for the response from a request | 354 // TODO(dsjang): * is not allowed for the response from a request |
| 349 // with cookies. This allows for more than what the renderer will | 355 // with cookies. This allows for more than what the renderer will |
| 350 // eventually be able to receive, so we won't see illegal cross-site | 356 // eventually be able to receive, so we won't see illegal cross-site |
| 351 // documents allowed by this. We have to find a way to see if this | 357 // documents allowed by this. We have to find a way to see if this |
| 352 // response is from a cookie-tagged request or not in the future. | 358 // response is from a cookie-tagged request or not in the future. |
| 353 if (access_control_origin == "*") | 359 if (access_control_origin == "*") |
| 354 return true; | 360 return true; |
| 355 | 361 |
| 356 // TODO(dsjang): The CORS spec only treats a fully specified URL, except for | 362 // TODO(dsjang): The CORS spec only treats a fully specified URL, except for |
| 357 // "*", but many websites are using just a domain for access_control_origin, | 363 // "*", but many websites are using just a domain for access_control_origin, |
| 358 // and this is blocked by Webkit's CORS logic here : | 364 // and this is blocked by Webkit's CORS logic here : |
| 359 // CrossOriginAccessControl::passesAccessControlCheck(). GURL is set | 365 // CrossOriginAccessControl::passesAccessControlCheck(). GURL is set |
| 360 // is_valid() to false when it is created from a URL containing * in the | 366 // is_valid() to false when it is created from a URL containing * in the |
| 361 // domain part. | 367 // domain part. |
| 362 | 368 |
| 363 GURL cors_origin(access_control_origin); | 369 GURL cors_origin(access_control_origin); |
| 364 return IsSameSite(frame_origin, cors_origin); | 370 return IsSameSite(frame_origin, cors_origin); |
| 365 } | 371 } |
| 366 | 372 |
| 367 // This function is a slight modification of |net::SniffForHTML|. | 373 // This function is a slight modification of |net::SniffForHTML|. |
| 368 bool SiteIsolationPolicy::SniffForHTML(StringPiece data) { | 374 bool CrossSiteDocumentClassifier::SniffForHTML(StringPiece data) { |
| 369 // The content sniffer used by Chrome and Firefox are using "<!--" | 375 // The content sniffer used by Chrome and Firefox are using "<!--" |
| 370 // as one of the HTML signatures, but it also appears in valid | 376 // as one of the HTML signatures, but it also appears in valid |
| 371 // JavaScript, considered as well-formed JS by the browser. Since | 377 // JavaScript, considered as well-formed JS by the browser. Since |
| 372 // we do not want to block any JS, we exclude it from our HTML | 378 // we do not want to block any JS, we exclude it from our HTML |
| 373 // signatures. This can weaken our document block policy, but we can | 379 // signatures. This can weaken our document block policy, but we can |
| 374 // break less websites. | 380 // break less websites. |
| 375 // TODO(dsjang): parameterize |net::SniffForHTML| with an option | 381 // TODO(dsjang): parameterize |net::SniffForHTML| with an option |
| 376 // that decides whether to include <!-- or not, so that we can | 382 // that decides whether to include <!-- or not, so that we can |
| 377 // remove this function. | 383 // remove this function. |
| 378 // TODO(dsjang): Once SiteIsolationPolicy is moved into the browser | 384 // TODO(dsjang): Once CrossSiteDocumentClassifier is moved into the browser |
| 379 // process, we should do single-thread checking here for the static | 385 // process, we should do single-thread checking here for the static |
| 380 // initializer. | 386 // initializer. |
| 381 static const StringPiece kHtmlSignatures[] = { | 387 static const StringPiece kHtmlSignatures[] = { |
| 382 StringPiece("<!DOCTYPE html"), // HTML5 spec | 388 StringPiece("<!DOCTYPE html"), // HTML5 spec |
| 383 StringPiece("<script"), // HTML5 spec, Mozilla | 389 StringPiece("<script"), // HTML5 spec, Mozilla |
| 384 StringPiece("<html"), // HTML5 spec, Mozilla | 390 StringPiece("<html"), // HTML5 spec, Mozilla |
| 385 StringPiece("<head"), // HTML5 spec, Mozilla | 391 StringPiece("<head"), // HTML5 spec, Mozilla |
| 386 StringPiece("<iframe"), // Mozilla | 392 StringPiece("<iframe"), // Mozilla |
| 387 StringPiece("<h1"), // Mozilla | 393 StringPiece("<h1"), // Mozilla |
| 388 StringPiece("<div"), // Mozilla | 394 StringPiece("<div"), // Mozilla |
| (...skipping 25 matching lines...) Expand all Loading... |
| 414 if (offset == base::StringPiece::npos) | 420 if (offset == base::StringPiece::npos) |
| 415 break; | 421 break; |
| 416 | 422 |
| 417 // Proceed to the index next to the ending comment (-->). | 423 // Proceed to the index next to the ending comment (-->). |
| 418 data.remove_prefix(offset + strlen(kEndComment)); | 424 data.remove_prefix(offset + strlen(kEndComment)); |
| 419 } | 425 } |
| 420 | 426 |
| 421 return false; | 427 return false; |
| 422 } | 428 } |
| 423 | 429 |
| 424 bool SiteIsolationPolicy::SniffForXML(base::StringPiece data) { | 430 bool CrossSiteDocumentClassifier::SniffForXML(base::StringPiece data) { |
| 425 // TODO(dsjang): Chrome's mime_sniffer is using strncasecmp() for | 431 // TODO(dsjang): Chrome's mime_sniffer is using strncasecmp() for |
| 426 // this signature. However, XML is case-sensitive. Don't we have to | 432 // this signature. However, XML is case-sensitive. Don't we have to |
| 427 // be more lenient only to block documents starting with the exact | 433 // be more lenient only to block documents starting with the exact |
| 428 // string <?xml rather than <?XML ? | 434 // string <?xml rather than <?XML ? |
| 429 // TODO(dsjang): Once SiteIsolationPolicy is moved into the browser | 435 // TODO(dsjang): Once CrossSiteDocumentClassifier is moved into the browser |
| 430 // process, we should do single-thread checking here for the static | 436 // process, we should do single-thread checking here for the static |
| 431 // initializer. | 437 // initializer. |
| 432 static const StringPiece kXmlSignatures[] = {StringPiece("<?xml")}; | 438 static const StringPiece kXmlSignatures[] = {StringPiece("<?xml")}; |
| 433 return MatchesSignature(data, kXmlSignatures, arraysize(kXmlSignatures)); | 439 return MatchesSignature(data, kXmlSignatures, arraysize(kXmlSignatures)); |
| 434 } | 440 } |
| 435 | 441 |
| 436 bool SiteIsolationPolicy::SniffForJSON(base::StringPiece data) { | 442 bool CrossSiteDocumentClassifier::SniffForJSON(base::StringPiece data) { |
| 437 // TODO(dsjang): We have to come up with a better way to sniff | 443 // TODO(dsjang): We have to come up with a better way to sniff |
| 438 // JSON. However, even RE cannot help us that much due to the fact | 444 // JSON. However, even RE cannot help us that much due to the fact |
| 439 // that we don't do full parsing. This DFA starts with state 0, and | 445 // that we don't do full parsing. This DFA starts with state 0, and |
| 440 // finds {, "/' and : in that order. We're avoiding adding a | 446 // finds {, "/' and : in that order. We're avoiding adding a |
| 441 // dependency on a regular expression library. | 447 // dependency on a regular expression library. |
| 442 enum { | 448 enum { |
| 443 kStartState, | 449 kStartState, |
| 444 kLeftBraceState, | 450 kLeftBraceState, |
| 445 kLeftQuoteState, | 451 kLeftQuoteState, |
| 446 kColonState, | 452 kColonState, |
| (...skipping 25 matching lines...) Expand all Loading... |
| 472 break; | 478 break; |
| 473 case kColonState: | 479 case kColonState: |
| 474 case kTerminalState: | 480 case kTerminalState: |
| 475 NOTREACHED(); | 481 NOTREACHED(); |
| 476 break; | 482 break; |
| 477 } | 483 } |
| 478 } | 484 } |
| 479 return state == kColonState; | 485 return state == kColonState; |
| 480 } | 486 } |
| 481 | 487 |
| 482 bool SiteIsolationPolicy::SniffForJS(StringPiece data) { | 488 bool SiteIsolationStatsGatherer::SniffForJS(StringPiece data) { |
| 483 // TODO(dsjang): This is a real hack. The only purpose of this function is to | 489 // The purpose of this function is to try to see if there's any possibility |
| 484 // try to see if there's any possibility that this data can be JavaScript | 490 // that this data can be JavaScript (superset of JS). Search for "var " for JS |
| 485 // (superset of JS). This function will be removed once UMA stats are | 491 // detection. This is a real hack and should only be used for stats gathering. |
| 486 // gathered. | |
| 487 | |
| 488 // Search for "var " for JS detection. | |
| 489 return data.find("var ") != base::StringPiece::npos; | 492 return data.find("var ") != base::StringPiece::npos; |
| 490 } | 493 } |
| 491 | 494 |
| 492 } // namespace content | 495 } // namespace content |
| OLD | NEW |