1 // Copyright 2014 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 "extensions/browser/extension_protocols.h" 6 7 #include <algorithm> 8 #include <string> 9 #include <vector> 10 11 #include "base/base64.h" 12 #include "base/compiler_specific.h" 13 #include "base/files/file_path.h" 14 #include "base/files/file_util.h" 15 #include "base/format_macros.h" 16 #include "base/logging.h" 17 #include "base/memory/weak_ptr.h" 18 #include "base/message_loop/message_loop.h" 19 #include "base/metrics/field_trial.h" 20 #include "base/metrics/histogram.h" 21 #include "base/metrics/sparse_histogram.h" 22 #include "base/path_service.h" 23 #include "base/sha1.h" 24 #include "base/strings/string_number_conversions.h" 25 #include "base/strings/string_util.h" 26 #include "base/strings/stringprintf.h" 27 #include "base/strings/utf_string_conversions.h" 28 #include "base/threading/sequenced_worker_pool.h" 29 #include "base/threading/thread_restrictions.h" 30 #include "base/timer/elapsed_timer.h" 31 #include "build/build_config.h" 32 #include "content/public/browser/browser_thread.h" 33 #include "content/public/browser/resource_request_info.h" 34 #include "crypto/secure_hash.h" 35 #include "crypto/sha2.h" 36 #include "extensions/browser/content_verifier.h" 37 #include "extensions/browser/content_verify_job.h" 38 #include "extensions/browser/extensions_browser_client.h" 39 #include "extensions/browser/info_map.h" 40 #include "extensions/browser/url_request_util.h" 41 #include "extensions/common/constants.h" 42 #include "extensions/common/extension.h" 43 #include "extensions/common/extension_resource.h" 44 #include "extensions/common/file_util.h" 45 #include "extensions/common/manifest_handlers/background_info.h" 46 #include "extensions/common/manifest_handlers/csp_info.h" 47 #include "extensions/common/manifest_handlers/icons_handler.h" 48 #include "extensions/common/manifest_handlers/incognito_info.h" 49 #include "extensions/common/manifest_handlers/shared_module_info.h" 50 #include "extensions/common/manifest_handlers/web_accessible_resources_info.h" 51 #include "net/base/io_buffer.h" 52 #include "net/base/net_errors.h" 53 #include "net/http/http_request_headers.h" 54 #include "net/http/http_response_headers.h" 55 #include "net/http/http_response_info.h" 56 #include "net/url_request/url_request_error_job.h" 57 #include "net/url_request/url_request_file_job.h" 58 #include "net/url_request/url_request_simple_job.h" 59 #include "url/url_util.h" 60 61 using content::BrowserThread; 62 using content::ResourceRequestInfo; 63 using content::ResourceType; 64 using extensions::Extension; 65 using extensions::SharedModuleInfo; 66 67 namespace extensions { 68 namespace { 69 70 class GeneratedBackgroundPageJob : public net::URLRequestSimpleJob { 71 public: 72 GeneratedBackgroundPageJob(net::URLRequest* request, 73 net::NetworkDelegate* network_delegate, 74 const scoped_refptr<const Extension> extension, 75 const std::string& content_security_policy) 76 : net::URLRequestSimpleJob(request, network_delegate), 77 extension_(extension) { 78 const bool send_cors_headers = false; 79 // Leave cache headers out of generated background page jobs. 80 response_info_.headers = BuildHttpHeaders(content_security_policy, 81 send_cors_headers, 82 base::Time()); 83 } 84 85 // Overridden from URLRequestSimpleJob: 86 virtual int GetData(std::string* mime_type, 87 std::string* charset, 88 std::string* data, 89 const net::CompletionCallback& callback) const OVERRIDE { 90 *mime_type = "text/html"; 91 *charset = "utf-8"; 92 93 *data = "<!DOCTYPE html>\n<body>\n"; 94 const std::vector<std::string>& background_scripts = 95 extensions::BackgroundInfo::GetBackgroundScripts(extension_.get()); 96 for (size_t i = 0; i < background_scripts.size(); ++i) { 97 *data += "<script src=\""; 98 *data += background_scripts[i]; 99 *data += "\"></script>\n"; 100 } 101 102 return net::OK; 103 } 104 105 virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE { 106 *info = response_info_; 107 } 108 109 private: 110 virtual ~GeneratedBackgroundPageJob() {} 111 112 scoped_refptr<const Extension> extension_; 113 net::HttpResponseInfo response_info_; 114 }; 115 116 base::Time GetFileLastModifiedTime(const base::FilePath& filename) { 117 if (base::PathExists(filename)) { 118 base::File::Info info; 119 if (base::GetFileInfo(filename, &info)) 120 return info.last_modified; 121 } 122 return base::Time(); 123 } 124 125 base::Time GetFileCreationTime(const base::FilePath& filename) { 126 if (base::PathExists(filename)) { 127 base::File::Info info; 128 if (base::GetFileInfo(filename, &info)) 129 return info.creation_time; 130 } 131 return base::Time(); 132 } 133 134 void ReadResourceFilePathAndLastModifiedTime( 135 const extensions::ExtensionResource& resource, 136 const base::FilePath& directory, 137 base::FilePath* file_path, 138 base::Time* last_modified_time) { 139 *file_path = resource.GetFilePath(); 140 *last_modified_time = GetFileLastModifiedTime(*file_path); 141 // While we're here, log the delta between extension directory 142 // creation time and the resource's last modification time. 143 base::ElapsedTimer query_timer; 144 base::Time dir_creation_time = GetFileCreationTime(directory); 145 UMA_HISTOGRAM_TIMES("Extensions.ResourceDirectoryTimestampQueryLatency", 146 query_timer.Elapsed()); 147 int64 delta_seconds = (*last_modified_time - dir_creation_time).InSeconds(); 148 if (delta_seconds >= 0) { 149 UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedDelta", 150 delta_seconds, 151 0, 152 base::TimeDelta::FromDays(30).InSeconds(), 153 50); 154 } else { 155 UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedNegativeDelta", 156 -delta_seconds, 157 1, 158 base::TimeDelta::FromDays(30).InSeconds(), 159 50); 160 } 161 } 162 163 class URLRequestExtensionJob : public net::URLRequestFileJob { 164 public: 165 URLRequestExtensionJob(net::URLRequest* request, 166 net::NetworkDelegate* network_delegate, 167 const std::string& extension_id, 168 const base::FilePath& directory_path, 169 const base::FilePath& relative_path, 170 const std::string& content_security_policy, 171 bool send_cors_header, 172 bool follow_symlinks_anywhere, 173 ContentVerifyJob* verify_job) 174 : net::URLRequestFileJob( 175 request, 176 network_delegate, 177 base::FilePath(), 178 BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior( 179 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)), 180 verify_job_(verify_job), 181 seek_position_(0), 182 bytes_read_(0), 183 directory_path_(directory_path), 184 // TODO(tc): Move all of these files into resources.pak so we don't 185 // break when updating on Linux. 186 resource_(extension_id, directory_path, relative_path), 187 content_security_policy_(content_security_policy), 188 send_cors_header_(send_cors_header), 189 weak_factory_(this) { 190 if (follow_symlinks_anywhere) { 191 resource_.set_follow_symlinks_anywhere(); 192 } 193 } 194 195 virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE { 196 *info = response_info_; 197 } 198 199 virtual void Start() OVERRIDE { 200 request_timer_.reset(new base::ElapsedTimer()); 201 base::FilePath* read_file_path = new base::FilePath; 202 base::Time* last_modified_time = new base::Time(); 203 bool posted = BrowserThread::PostBlockingPoolTaskAndReply( 204 FROM_HERE, 205 base::Bind(&ReadResourceFilePathAndLastModifiedTime, 206 resource_, 207 directory_path_, 208 base::Unretained(read_file_path), 209 base::Unretained(last_modified_time)), 210 base::Bind(&URLRequestExtensionJob::OnFilePathAndLastModifiedTimeRead, 211 weak_factory_.GetWeakPtr(), 212 base::Owned(read_file_path), 213 base::Owned(last_modified_time))); 214 DCHECK(posted); 215 } 216 217 virtual bool IsRedirectResponse(GURL* location, 218 int* http_status_code) override { 219 return false; 220 } 221 222 virtual void SetExtraRequestHeaders( 223 const net::HttpRequestHeaders& headers) OVERRIDE { 224 // TODO(asargent) - we'll need to add proper support for range headers. 225 // crbug.com/369895. 226 std::string range_header; 227 if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) { 228 if (verify_job_.get()) 229 verify_job_ = NULL; 230 } 231 URLRequestFileJob::SetExtraRequestHeaders(headers); 232 } 233 234 virtual void OnSeekComplete(int64 result) OVERRIDE { 235 DCHECK_EQ(seek_position_, 0); 236 seek_position_ = result; 237 // TODO(asargent) - we'll need to add proper support for range headers. 238 // crbug.com/369895. 239 if (result > 0 && verify_job_.get()) 240 verify_job_ = NULL; 241 } 242 243 virtual void OnReadComplete(net::IOBuffer* buffer, int result) OVERRIDE { 244 if (result >= 0) 245 UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.OnReadCompleteResult", result); 246 else 247 UMA_HISTOGRAM_SPARSE_SLOWLY("ExtensionUrlRequest.OnReadCompleteError", 248 -result); 249 if (result > 0) { 250 bytes_read_ += result; 251 if (verify_job_.get()) { 252 verify_job_->BytesRead(result, buffer->data()); 253 if (!remaining_bytes()) 254 verify_job_->DoneReading(); 255 } 256 } 257 } 258 259 private: 260 virtual ~URLRequestExtensionJob() { 261 UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.TotalKbRead", bytes_read_ / 1024); 262 UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.SeekPosition", seek_position_); 263 if (request_timer_.get()) 264 UMA_HISTOGRAM_TIMES("ExtensionUrlRequest.Latency", 265 request_timer_->Elapsed()); 266 } 267 268 void OnFilePathAndLastModifiedTimeRead(base::FilePath* read_file_path, 269 base::Time* last_modified_time) { 270 file_path_ = *read_file_path; 271 response_info_.headers = BuildHttpHeaders( 272 content_security_policy_, 273 send_cors_header_, 274 *last_modified_time); 275 URLRequestFileJob::Start(); 276 } 277 278 scoped_refptr<ContentVerifyJob> verify_job_; 279 280 scoped_ptr<base::ElapsedTimer> request_timer_; 281 282 // The position we seeked to in the file. 283 int64 seek_position_; 284 285 // The number of bytes of content we read from the file. 286 int bytes_read_; 287 288 net::HttpResponseInfo response_info_; 289 base::FilePath directory_path_; 290 extensions::ExtensionResource resource_; 291 std::string content_security_policy_; 292 bool send_cors_header_; 293 base::WeakPtrFactory<URLRequestExtensionJob> weak_factory_; 294 }; 295 296 bool ExtensionCanLoadInIncognito(const ResourceRequestInfo* info, 297 const std::string& extension_id, 298 extensions::InfoMap* extension_info_map) { 299 if (!extension_info_map->IsIncognitoEnabled(extension_id)) 300 return false; 301 302 // Only allow incognito toplevel navigations to extension resources in 303 // split mode. In spanning mode, the extension must run in a single process, 304 // and an incognito tab prevents that. 305 if (info->GetResourceType() == content::RESOURCE_TYPE_MAIN_FRAME) { 306 const Extension* extension = 307 extension_info_map->extensions().GetByID(extension_id); 308 return extension && extensions::IncognitoInfo::IsSplitMode(extension); 309 } 310 311 return true; 312 } 313 314 // Returns true if an chrome-extension:// resource should be allowed to load. 315 // Pass true for |is_incognito| only for incognito profiles and not Chrome OS 316 // guest mode profiles. 317 // TODO(aa): This should be moved into ExtensionResourceRequestPolicy, but we 318 // first need to find a way to get CanLoadInIncognito state into the renderers. 319 bool AllowExtensionResourceLoad(net::URLRequest* request, 320 bool is_incognito, 321 const Extension* extension, 322 extensions::InfoMap* extension_info_map) { 323 const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request); 324 325 // We have seen crashes where info is NULL: crbug.com/52374. 326 if (!info) { 327 LOG(ERROR) << "Allowing load of " << request->url().spec() 328 << "from unknown origin. Could not find user data for " 329 << "request."; 330 return true; 331 } 332 333 if (is_incognito && !ExtensionCanLoadInIncognito( 334 info, request->url().host(), extension_info_map)) { 335 return false; 336 } 337 338 // The following checks are meant to replicate similar set of checks in the 339 // renderer process, performed by ResourceRequestPolicy::CanRequestResource. 340 // These are not exactly equivalent, because we don't have the same bits of 341 // information. The two checks need to be kept in sync as much as possible, as 342 // an exploited renderer can bypass the checks in ResourceRequestPolicy. 343 344 // Check if the extension for which this request is made is indeed loaded in 345 // the process sending the request. If not, we need to explicitly check if 346 // the resource is explicitly accessible or fits in a set of exception cases. 347 // Note: This allows a case where two extensions execute in the same renderer 348 // process to request each other's resources. We can't do a more precise 349 // check, since the renderer can lie about which extension has made the 350 // request. 351 if (extension_info_map->process_map().Contains( 352 request->url().host(), info->GetChildID())) { 353 return true; 354 } 355 356 // Allow the extension module embedder to grant permission for loads. 357 if (ExtensionsBrowserClient::Get()->AllowCrossRendererResourceLoad( 358 request, is_incognito, extension, extension_info_map)) { 359 return true; 360 } 361 362 // No special exceptions for cross-process loading. Block the load. 363 return false; 364 } 365 366 // Returns true if the given URL references an icon in the given extension. 367 bool URLIsForExtensionIcon(const GURL& url, const Extension* extension) { 368 DCHECK(url.SchemeIs(extensions::kExtensionScheme)); 369 370 if (!extension) 371 return false; 372 373 std::string path = url.path(); 374 DCHECK_EQ(url.host(), extension->id()); 375 DCHECK(path.length() > 0 && path[0] == '/'); 376 path = path.substr(1); 377 return extensions::IconsInfo::GetIcons(extension).ContainsPath(path); 378 } 379 380 class ExtensionProtocolHandler 381 : public net::URLRequestJobFactory::ProtocolHandler { 382 public: 383 ExtensionProtocolHandler(bool is_incognito, 384 extensions::InfoMap* extension_info_map) 385 : is_incognito_(is_incognito), extension_info_map_(extension_info_map) {} 386 387 virtual ~ExtensionProtocolHandler() {} 388 389 virtual net::URLRequestJob* MaybeCreateJob( 390 net::URLRequest* request, 391 net::NetworkDelegate* network_delegate) const OVERRIDE; 392 393 private: 394 const bool is_incognito_; 395 extensions::InfoMap* const extension_info_map_; 396 DISALLOW_COPY_AND_ASSIGN(ExtensionProtocolHandler); 397 }; 398 399 // Creates URLRequestJobs for extension:// URLs. 400 net::URLRequestJob* 401 ExtensionProtocolHandler::MaybeCreateJob( 402 net::URLRequest* request, net::NetworkDelegate* network_delegate) const { 403 // chrome-extension://extension-id/resource/path.js 404 std::string extension_id = request->url().host(); 405 const Extension* extension = 406 extension_info_map_->extensions().GetByID(extension_id); 407 408 // TODO(mpcomplete): better error code. 409 if (!AllowExtensionResourceLoad( 410 request, is_incognito_, extension, extension_info_map_)) { 411 return new net::URLRequestErrorJob( 412 request, network_delegate, net::ERR_ADDRESS_UNREACHABLE); 413 } 414 415 // If this is a disabled extension only allow the icon to load. 416 base::FilePath directory_path; 417 if (extension) 418 directory_path = extension->path(); 419 if (directory_path.value().empty()) { 420 const Extension* disabled_extension = 421 extension_info_map_->disabled_extensions().GetByID(extension_id); 422 if (URLIsForExtensionIcon(request->url(), disabled_extension)) 423 directory_path = disabled_extension->path(); 424 if (directory_path.value().empty()) { 425 LOG(WARNING) << "Failed to GetPathForExtension: " << extension_id; 426 return NULL; 427 } 428 } 429 430 // Set up content security policy. 431 std::string content_security_policy; 432 bool send_cors_header = false; 433 bool follow_symlinks_anywhere = false; 434 435 if (extension) { 436 std::string resource_path = request->url().path(); 437 438 // Use default CSP for <webview>. 439 if (!url_request_util::IsWebViewRequest(request)) { 440 content_security_policy = 441 extensions::CSPInfo::GetResourceContentSecurityPolicy(extension, 442 resource_path); 443 } 444 445 if ((extension->manifest_version() >= 2 || 446 extensions::WebAccessibleResourcesInfo::HasWebAccessibleResources( 447 extension)) && 448 extensions::WebAccessibleResourcesInfo::IsResourceWebAccessible( 449 extension, resource_path)) { 450 send_cors_header = true; 451 } 452 453 follow_symlinks_anywhere = 454 (extension->creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE) 455 != 0; 456 } 457 458 // Create a job for a generated background page. 459 std::string path = request->url().path(); 460 if (path.size() > 1 && 461 path.substr(1) == extensions::kGeneratedBackgroundPageFilename) { 462 return new GeneratedBackgroundPageJob( 463 request, network_delegate, extension, content_security_policy); 464 } 465 466 // Component extension resources may be part of the embedder's resource files, 467 // for example component_extension_resources.pak in Chrome. 468 net::URLRequestJob* resource_bundle_job = 469 extensions::ExtensionsBrowserClient::Get() 470 ->MaybeCreateResourceBundleRequestJob(request, 471 network_delegate, 472 directory_path, 473 content_security_policy, 474 send_cors_header); 475 if (resource_bundle_job) 476 return resource_bundle_job; 477 478 base::FilePath relative_path = 479 extensions::file_util::ExtensionURLToRelativeFilePath(request->url()); 480 481 // Handle shared resources (extension A loading resources out of extension B). 482 if (SharedModuleInfo::IsImportedPath(path)) { 483 std::string new_extension_id; 484 std::string new_relative_path; 485 SharedModuleInfo::ParseImportedPath(path, &new_extension_id, 486 &new_relative_path); 487 const Extension* new_extension = 488 extension_info_map_->extensions().GetByID(new_extension_id); 489 490 bool first_party_in_import = false; 491 // NB: This first_party_for_cookies call is not for security, it is only 492 // used so an exported extension can limit the visible surface to the 493 // extension that imports it, more or less constituting its API. 494 const std::string& first_party_path = 495 request->first_party_for_cookies().path(); 496 if (SharedModuleInfo::IsImportedPath(first_party_path)) { 497 std::string first_party_id; 498 std::string dummy; 499 SharedModuleInfo::ParseImportedPath(first_party_path, &first_party_id, 500 &dummy); 501 if (first_party_id == new_extension_id) { 502 first_party_in_import = true; 503 } 504 } 505 506 if (SharedModuleInfo::ImportsExtensionById(extension, new_extension_id) && 507 new_extension && 508 (first_party_in_import || 509 SharedModuleInfo::IsExportAllowed(new_extension, new_relative_path))) { 510 directory_path = new_extension->path(); 511 extension_id = new_extension_id; 512 relative_path = base::FilePath::FromUTF8Unsafe(new_relative_path); 513 } else { 514 return NULL; 515 } 516 } 517 ContentVerifyJob* verify_job = NULL; 518 ContentVerifier* verifier = extension_info_map_->content_verifier(); 519 if (verifier) { 520 verify_job = 521 verifier->CreateJobFor(extension_id, directory_path, relative_path); 522 if (verify_job) 523 verify_job->Start(); 524 } 525 526 return new URLRequestExtensionJob(request, 527 network_delegate, 528 extension_id, 529 directory_path, 530 relative_path, 531 content_security_policy, 532 send_cors_header, 533 follow_symlinks_anywhere, 534 verify_job); 535 } 536 537 } // namespace 538 539 net::HttpResponseHeaders* BuildHttpHeaders( 540 const std::string& content_security_policy, 541 bool send_cors_header, 542 const base::Time& last_modified_time) { 543 std::string raw_headers; 544 raw_headers.append("HTTP/1.1 200 OK"); 545 if (!content_security_policy.empty()) { 546 raw_headers.append(1, '\0'); 547 raw_headers.append("Content-Security-Policy: "); 548 raw_headers.append(content_security_policy); 549 } 550 551 if (send_cors_header) { 552 raw_headers.append(1, '\0'); 553 raw_headers.append("Access-Control-Allow-Origin: *"); 554 } 555 556 if (!last_modified_time.is_null()) { 557 // Hash the time and make an etag to avoid exposing the exact 558 // user installation time of the extension. 559 std::string hash = 560 base::StringPrintf("%" PRId64, last_modified_time.ToInternalValue()); 561 hash = base::SHA1HashString(hash); 562 std::string etag; 563 base::Base64Encode(hash, &etag); 564 raw_headers.append(1, '\0'); 565 raw_headers.append("ETag: \""); 566 raw_headers.append(etag); 567 raw_headers.append("\""); 568 // Also force revalidation. 569 raw_headers.append(1, '\0'); 570 raw_headers.append("cache-control: no-cache"); 571 } 572 573 raw_headers.append(2, '\0'); 574 return new net::HttpResponseHeaders(raw_headers); 575 } 576 577 net::URLRequestJobFactory::ProtocolHandler* CreateExtensionProtocolHandler( 578 bool is_incognito, 579 extensions::InfoMap* extension_info_map) { 580 return new ExtensionProtocolHandler(is_incognito, extension_info_map); 581 } 582 583 } // namespace extensions 584