// 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 #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 TargetTypeMap; typedef base::hash_map MimeTypeMap; typedef std::set 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" }; 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", 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; } void SiteIsolationMetrics::LogMimeTypeForCrossOriginRequest( WebFrame* frame, unsigned identifier, const WebURLResponse& response) { 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); // 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. if (target_type != WebURLRequest::TargetIsMainFrame && target_type != WebURLRequest::TargetIsSubFrame && target_type != WebURLRequest::TargetIsObject && !frame->securityOrigin().canAccess( WebSecurityOrigin::create(response.url()))) { std::string mime_type = response.mimeType().utf8(); MimeTypeMap mime_type_map = *GetMimeTypeMap(); 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 should check access control headers, in case this // cross-origin request has been explicitly permitted. // This is basically a copy of the logic of passesAccessControlCheck() // in WebCore/loader/CrossOriginAccessControl.cpp. WebString access_control_origin = response.httpHeaderField( WebString::fromUTF8("Access-Control-Allow-Origin")); if (access_control_origin != WebString::fromUTF8("*") && !frame->securityOrigin().canAccess( WebSecurityOrigin::createFromString(access_control_origin))) { UMA_HISTOGRAM_ENUMERATION( "SiteIsolation.CrossSiteNonFrameResponse_With_CORS_MIME_Type", mime_type_iter->second, arraysize(kCrossOriginMimeTypesToLog)); } // Sometimes resources are mislabeled as text/html. Once we have some // of the payload, we want to determine if this was actually text/html. if (mime_type == "text/html") GetCrossOriginTextHtmlResponseSet()->insert(response.url().spec()); } } } } void SiteIsolationMetrics::SniffCrossOriginHTML(const WebURL& response_url, const char* data, int len) { if (!response_url.isValid()) return; 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()) { 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