diff options
Diffstat (limited to 'webkit/glue/site_isolation_metrics.cc')
-rw-r--r-- | webkit/glue/site_isolation_metrics.cc | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/webkit/glue/site_isolation_metrics.cc b/webkit/glue/site_isolation_metrics.cc new file mode 100644 index 0000000..912263f --- /dev/null +++ b/webkit/glue/site_isolation_metrics.cc @@ -0,0 +1,231 @@ +// Copyright (c) 2010 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 "webkit/glue/site_isolation_metrics.h" + +#include <set> + +#include "base/hash_tables.h" +#include "base/histogram.h" +#include "net/base/mime_sniffer.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSecurityOrigin.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" + +using WebKit::WebFrame; +using WebKit::WebSecurityOrigin; +using WebKit::WebString; +using WebKit::WebURL; +using WebKit::WebURLRequest; +using WebKit::WebURLResponse; + +namespace webkit_glue { + +typedef base::hash_map<unsigned, WebURLRequest::TargetType> TargetTypeMap; +typedef base::hash_map<std::string, int> MimeTypeMap; +typedef std::set<std::string> CrossOriginTextHtmlResponseSet; + +static TargetTypeMap* GetTargetTypeMap() { + static TargetTypeMap target_type_map_; + return &target_type_map_; +} + +// Copied from net/base/mime_util.cc, supported_non_image_types[] +static const char* const kCrossOriginMimeTypesToLog[] = { + "text/cache-manifest", + "text/html", + "text/xml", + "text/xsl", + "text/plain", + "text/vnd.chromium.ftp-dir", + "text/", + "text/css", + "image/svg+xml", + "application/xml", + "application/xhtml+xml", + "application/rss+xml", + "application/atom+xml", + "application/json", + "application/x-x509-user-cert", + "multipart/x-mixed-replace", + "(NONE)" // Keep track of missing MIME types as well +}; + +static MimeTypeMap* GetMimeTypeMap() { + static MimeTypeMap mime_type_map_; + if (!mime_type_map_.size()) { + for (size_t i = 0; i < arraysize(kCrossOriginMimeTypesToLog); ++i) + mime_type_map_[kCrossOriginMimeTypesToLog[i]] = i; + } + return &mime_type_map_; +} + +// This is set is used to keep track of the response urls that we want to +// sniff, since we will have to wait for the payload to arrive. +static CrossOriginTextHtmlResponseSet* GetCrossOriginTextHtmlResponseSet() { + static CrossOriginTextHtmlResponseSet cross_origin_text_html_response_set_; + return &cross_origin_text_html_response_set_; +} + +static void LogVerifiedTextHtmlResponse() { + UMA_HISTOGRAM_COUNTS( + "SiteIsolation.CrossSiteNonFrameResponse_verified_texthtml_BLOCK", 1); +} + +static void LogMislabeledTextHtmlResponse() { + UMA_HISTOGRAM_COUNTS( + "SiteIsolation.CrossSiteNonFrameResponse_mislabeled_texthtml", 1); +} + +void SiteIsolationMetrics::AddRequest(unsigned identifier, + WebURLRequest::TargetType target_type) { + TargetTypeMap& target_type_map = *GetTargetTypeMap(); + target_type_map[identifier] = target_type; +} + +// Check whether the given response is allowed due to access control headers. +// This is basically a copy of the logic of passesAccessControlCheck() in +// WebCore/loader/CrossOriginAccessControl.cpp. +bool SiteIsolationMetrics::AllowedByAccessControlHeader( + WebFrame* frame, const WebURLResponse& response) { + WebString access_control_origin = response.httpHeaderField( + WebString::fromUTF8("Access-Control-Allow-Origin")); + WebSecurityOrigin security_origin = + WebSecurityOrigin::createFromString(access_control_origin); + return access_control_origin == WebString::fromUTF8("*") || + frame->securityOrigin().canAccess(security_origin); +} + +// We want to log any cross-site request that we don't think a renderer should +// be allowed to make. We can safely ignore frame requests (since we'd like +// those to be in a separate renderer) and plugin requests, even if they are +// cross-origin. +// +// For comparison, we keep counts of: +// - All requests made by a renderer +// - All cross-site requests +// +// Then, for cross-site non-frame/plugin requests, we keep track of: +// - Counts for MIME types of interest +// - Counts of those MIME types that carry CORS headers +// - Counts of mislabeled text/html responses (without CORS) +// As well as those we would block: +// - Counts of verified text/html responses (without CORS) +// - Counts of XML/JSON responses (without CORS) +// +// This will let us say what percentage of requests we would end up blocking. +void SiteIsolationMetrics::LogMimeTypeForCrossOriginRequest( + WebFrame* frame, unsigned identifier, const WebURLResponse& response) { + UMA_HISTOGRAM_COUNTS("SiteIsolation.Requests", 1); + + TargetTypeMap& target_type_map = *GetTargetTypeMap(); + TargetTypeMap::iterator iter = target_type_map.find(identifier); + if (iter != target_type_map.end()) { + WebURLRequest::TargetType target_type = iter->second; + target_type_map.erase(iter); + + // Focus on cross-site requests. + if (!frame->securityOrigin().canAccess( + WebSecurityOrigin::create(response.url()))) { + UMA_HISTOGRAM_COUNTS("SiteIsolation.CrossSiteRequests", 1); + + // Now focus on non-frame, non-plugin requests. + if (target_type != WebURLRequest::TargetIsMainFrame && + target_type != WebURLRequest::TargetIsSubFrame && + target_type != WebURLRequest::TargetIsObject) { + // If it is part of a MIME type we might block, log the MIME type. + std::string mime_type = response.mimeType().utf8(); + MimeTypeMap mime_type_map = *GetMimeTypeMap(); + // Also track it if it lacks a MIME type. + // TODO(creis): 304 responses have no MIME type, so we don't handle + // them correctly. Can we look up their MIME type from the cache? + if (mime_type == "") + mime_type = "(NONE)"; + MimeTypeMap::iterator mime_type_iter = mime_type_map.find(mime_type); + if (mime_type_iter != mime_type_map.end()) { + UMA_HISTOGRAM_ENUMERATION( + "SiteIsolation.CrossSiteNonFrameResponse_MIME_Type", + mime_type_iter->second, + arraysize(kCrossOriginMimeTypesToLog)); + + // We also check access control headers, in case this + // cross-origin request has been explicitly permitted. + if (AllowedByAccessControlHeader(frame, response)) { + UMA_HISTOGRAM_ENUMERATION( + "SiteIsolation.CrossSiteNonFrameResponse_With_CORS_MIME_Type", + mime_type_iter->second, + arraysize(kCrossOriginMimeTypesToLog)); + } else { + // Without access control headers, we might block this request. + // Sometimes resources are mislabled as text/html, though, and we + // should only block them if we can verify that. To do so, we sniff + // the content once we have some of the payload. + if (mime_type == "text/html") { + // Remember the response until we can sniff its contents. + GetCrossOriginTextHtmlResponseSet()->insert( + response.url().spec()); + } else if (mime_type == "text/xml" || + mime_type == "text/xsl" || + mime_type == "application/xml" || + mime_type == "application/xhtml+xml" || + mime_type == "application/rss+xml" || + mime_type == "application/atom+xml" || + mime_type == "application/json") { + // We will also block XML and JSON MIME types for cross-site + // non-frame requests without CORS headers. + UMA_HISTOGRAM_COUNTS( + "SiteIsolation.CrossSiteNonFrameResponse_xml_or_json_BLOCK", + 1); + } + } + } + } + } + } +} + +void SiteIsolationMetrics::SniffCrossOriginHTML(const WebURL& response_url, + const char* data, + int len) { + if (!response_url.isValid()) + return; + + // Look up the URL to see if it is a text/html request we are tracking. + CrossOriginTextHtmlResponseSet& cross_origin_text_html_response_set = + *GetCrossOriginTextHtmlResponseSet(); + CrossOriginTextHtmlResponseSet::iterator request_iter = + cross_origin_text_html_response_set.find(response_url.spec()); + if (request_iter != cross_origin_text_html_response_set.end()) { + // Log whether it actually looks like HTML. + std::string sniffed_mime_type; + bool successful = net::SniffMimeType(data, len, response_url, + "", &sniffed_mime_type); + if (successful && sniffed_mime_type == "text/html") + LogVerifiedTextHtmlResponse(); + else + LogMislabeledTextHtmlResponse(); + cross_origin_text_html_response_set.erase(request_iter); + } +} + +void SiteIsolationMetrics::RemoveCompletedResponse( + const WebURL& response_url) { + if (!response_url.isValid()) + return; + + // Ensure we don't leave responses in the set after they've completed. + CrossOriginTextHtmlResponseSet& cross_origin_text_html_response_set = + *GetCrossOriginTextHtmlResponseSet(); + CrossOriginTextHtmlResponseSet::iterator request_iter = + cross_origin_text_html_response_set.find(response_url.spec()); + if (request_iter != cross_origin_text_html_response_set.end()) { + LogMislabeledTextHtmlResponse(); + cross_origin_text_html_response_set.erase(request_iter); + } +} + +} // namespace webkit_glue |