1 // Copyright (c) 2012 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 "chrome/browser/nacl_host/nacl_browser.h" 6 7 #include "base/command_line.h" 8 #include "base/file_util.h" 9 #include "base/message_loop/message_loop.h" 10 #include "base/metrics/histogram.h" 11 #include "base/path_service.h" 12 #include "base/pickle.h" 13 #include "base/rand_util.h" 14 #include "base/strings/string_split.h" 15 #include "base/win/windows_version.h" 16 #include "build/build_config.h" 17 #include "content/public/browser/browser_thread.h" 18 #include "extensions/common/url_pattern.h" 19 #include "url/gurl.h" 20 21 namespace { 22 23 // An arbitrary delay to coalesce multiple writes to the cache. 24 const int kValidationCacheCoalescingTimeMS = 6000; 25 const char kValidationCacheSequenceName[] = "NaClValidationCache"; 26 const base::FilePath::CharType kValidationCacheFileName[] = 27 FILE_PATH_LITERAL("nacl_validation_cache.bin"); 28 29 const bool kValidationCacheEnabledByDefault = true; 30 31 enum ValidationCacheStatus { 32 CACHE_MISS = 0, 33 CACHE_HIT, 34 CACHE_MAX 35 }; 36 37 // Keep the cache bounded to an arbitrary size. If it's too small, useful 38 // entries could be evicted when multiple .nexes are loaded at once. On the 39 // other hand, entries are not always claimed (and hence removed), so the size 40 // of the cache will likely saturate at its maximum size. 41 // Entries may not be claimed for two main reasons. 1) the NaCl process could 42 // be killed while it is loading. 2) the trusted NaCl plugin opens files using 43 // the code path but doesn't resolve them. 44 // TODO(ncbray) don't cache files that the plugin will not resolve. 45 const int kFilePathCacheSize = 100; 46 47 const base::FilePath::StringType NaClIrtName() { 48 base::FilePath::StringType irt_name(FILE_PATH_LITERAL("nacl_irt_")); 49 50 #if defined(ARCH_CPU_X86_FAMILY) 51 #if defined(ARCH_CPU_X86_64) 52 bool is64 = true; 53 #elif defined(OS_WIN) 54 bool is64 = (base::win::OSInfo::GetInstance()->wow64_status() == 55 base::win::OSInfo::WOW64_ENABLED); 56 #else 57 bool is64 = false; 58 #endif 59 if (is64) 60 irt_name.append(FILE_PATH_LITERAL("x86_64")); 61 else 62 irt_name.append(FILE_PATH_LITERAL("x86_32")); 63 64 #elif defined(ARCH_CPU_ARMEL) 65 // TODO(mcgrathr): Eventually we'll need to distinguish arm32 vs thumb2. 66 // That may need to be based on the actual nexe rather than a static 67 // choice, which would require substantial refactoring. 68 irt_name.append(FILE_PATH_LITERAL("arm")); 69 #elif defined(ARCH_CPU_MIPSEL) 70 irt_name.append(FILE_PATH_LITERAL("mips32")); 71 #else 72 #error Add support for your architecture to NaCl IRT file selection 73 #endif 74 irt_name.append(FILE_PATH_LITERAL(".nexe")); 75 return irt_name; 76 } 77 78 bool CheckEnvVar(const char* name, bool default_value) { 79 bool result = default_value; 80 const char* var = getenv(name); 81 if (var && strlen(var) > 0) { 82 result = var[0] != '0'; 83 } 84 return result; 85 } 86 87 void ReadCache(const base::FilePath& filename, std::string* data) { 88 if (!file_util::ReadFileToString(filename, data)) { 89 // Zero-size data used as an in-band error code. 90 data->clear(); 91 } 92 } 93 94 void WriteCache(const base::FilePath& filename, const Pickle* pickle) { 95 file_util::WriteFile(filename, static_cast<const char*>(pickle->data()), 96 pickle->size()); 97 } 98 99 void RemoveCache(const base::FilePath& filename, 100 const base::Closure& callback) { 101 base::DeleteFile(filename, false); 102 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, 103 callback); 104 } 105 106 void LogCacheQuery(ValidationCacheStatus status) { 107 UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Query", status, CACHE_MAX); 108 } 109 110 void LogCacheSet(ValidationCacheStatus status) { 111 // Bucket zero is reserved for future use. 112 UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Set", status, CACHE_MAX); 113 } 114 115 } // namespace 116 117 namespace nacl { 118 119 void OpenNaClExecutableImpl(const base::FilePath& file_path, 120 base::PlatformFile* file) { 121 // Get a file descriptor. On Windows, we need 'GENERIC_EXECUTE' in order to 122 // memory map the executable. 123 // IMPORTANT: This file descriptor must not have write access - that could 124 // allow a NaCl inner sandbox escape. 125 base::PlatformFileError error_code; 126 *file = base::CreatePlatformFile( 127 file_path, 128 (base::PLATFORM_FILE_OPEN | 129 base::PLATFORM_FILE_READ | 130 base::PLATFORM_FILE_EXECUTE), // Windows only flag. 131 NULL, 132 &error_code); 133 if (error_code != base::PLATFORM_FILE_OK) { 134 *file = base::kInvalidPlatformFileValue; 135 return; 136 } 137 // Check that the file does not reference a directory. Returning a descriptor 138 // to an extension directory could allow an outer sandbox escape. openat(...) 139 // could be used to traverse into the file system. 140 base::PlatformFileInfo file_info; 141 if (!base::GetPlatformFileInfo(*file, &file_info) || 142 file_info.is_directory) { 143 base::ClosePlatformFile(*file); 144 *file = base::kInvalidPlatformFileValue; 145 return; 146 } 147 } 148 149 } // namespace nacl 150 151 NaClBrowser::NaClBrowser() 152 : weak_factory_(this), 153 irt_platform_file_(base::kInvalidPlatformFileValue), 154 irt_filepath_(), 155 irt_state_(NaClResourceUninitialized), 156 debug_patterns_(), 157 inverse_debug_patterns_(false), 158 validation_cache_file_path_(), 159 validation_cache_is_enabled_( 160 CheckEnvVar("NACL_VALIDATION_CACHE", 161 kValidationCacheEnabledByDefault)), 162 validation_cache_is_modified_(false), 163 validation_cache_state_(NaClResourceUninitialized), 164 path_cache_(kFilePathCacheSize), 165 ok_(true) { 166 } 167 168 void NaClBrowser::SetDelegate(NaClBrowserDelegate* delegate) { 169 NaClBrowser* nacl_browser = NaClBrowser::GetInstance(); 170 nacl_browser->browser_delegate_.reset(delegate); 171 } 172 173 NaClBrowserDelegate* NaClBrowser::GetDelegate() { 174 DCHECK(GetInstance()->browser_delegate_.get() != NULL); 175 return GetInstance()->browser_delegate_.get(); 176 } 177 178 void NaClBrowser::EarlyStartup() { 179 InitIrtFilePath(); 180 InitValidationCacheFilePath(); 181 } 182 183 NaClBrowser::~NaClBrowser() { 184 if (irt_platform_file_ != base::kInvalidPlatformFileValue) 185 base::ClosePlatformFile(irt_platform_file_); 186 } 187 188 void NaClBrowser::InitIrtFilePath() { 189 // Allow the IRT library to be overridden via an environment 190 // variable. This allows the NaCl/Chromium integration bot to 191 // specify a newly-built IRT rather than using a prebuilt one 192 // downloaded via Chromium's DEPS file. We use the same environment 193 // variable that the standalone NaCl PPAPI plugin accepts. 194 const char* irt_path_var = getenv("NACL_IRT_LIBRARY"); 195 if (irt_path_var != NULL) { 196 base::FilePath::StringType path_string( 197 irt_path_var, const_cast<const char*>(strchr(irt_path_var, '\0'))); 198 irt_filepath_ = base::FilePath(path_string); 199 } else { 200 base::FilePath plugin_dir; 201 if (!browser_delegate_->GetPluginDirectory(&plugin_dir)) { 202 DLOG(ERROR) << "Failed to locate the plugins directory, NaCl disabled."; 203 MarkAsFailed(); 204 return; 205 } 206 irt_filepath_ = plugin_dir.Append(NaClIrtName()); 207 } 208 } 209 210 #if defined(OS_WIN) 211 bool NaClBrowser::GetNaCl64ExePath(base::FilePath* exe_path) { 212 base::FilePath module_path; 213 if (!PathService::Get(base::FILE_MODULE, &module_path)) { 214 LOG(ERROR) << "NaCl process launch failed: could not resolve module"; 215 return false; 216 } 217 *exe_path = module_path.DirName().Append(L"nacl64"); 218 return true; 219 } 220 #endif 221 222 NaClBrowser* NaClBrowser::GetInstance() { 223 return Singleton<NaClBrowser>::get(); 224 } 225 226 bool NaClBrowser::IsReady() const { 227 return (IsOk() && 228 irt_state_ == NaClResourceReady && 229 validation_cache_state_ == NaClResourceReady); 230 } 231 232 bool NaClBrowser::IsOk() const { 233 return ok_; 234 } 235 236 base::PlatformFile NaClBrowser::IrtFile() const { 237 CHECK_EQ(irt_state_, NaClResourceReady); 238 CHECK_NE(irt_platform_file_, base::kInvalidPlatformFileValue); 239 return irt_platform_file_; 240 } 241 242 void NaClBrowser::EnsureAllResourcesAvailable() { 243 EnsureIrtAvailable(); 244 EnsureValidationCacheAvailable(); 245 } 246 247 // Load the IRT async. 248 void NaClBrowser::EnsureIrtAvailable() { 249 if (IsOk() && irt_state_ == NaClResourceUninitialized) { 250 irt_state_ = NaClResourceRequested; 251 // TODO(ncbray) use blocking pool. 252 if (!base::FileUtilProxy::CreateOrOpen( 253 content::BrowserThread::GetMessageLoopProxyForThread( 254 content::BrowserThread::FILE) 255 .get(), 256 irt_filepath_, 257 base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, 258 base::Bind(&NaClBrowser::OnIrtOpened, 259 weak_factory_.GetWeakPtr()))) { 260 LOG(ERROR) << "Internal error, NaCl disabled."; 261 MarkAsFailed(); 262 } 263 } 264 } 265 266 void NaClBrowser::OnIrtOpened(base::PlatformFileError error_code, 267 base::PassPlatformFile file, 268 bool created) { 269 DCHECK_EQ(irt_state_, NaClResourceRequested); 270 DCHECK(!created); 271 if (error_code == base::PLATFORM_FILE_OK) { 272 irt_platform_file_ = file.ReleaseValue(); 273 } else { 274 LOG(ERROR) << "Failed to open NaCl IRT file \"" 275 << irt_filepath_.LossyDisplayName() 276 << "\": " << error_code; 277 MarkAsFailed(); 278 } 279 irt_state_ = NaClResourceReady; 280 CheckWaiting(); 281 } 282 283 void NaClBrowser::SetDebugPatterns(std::string debug_patterns) { 284 if (!debug_patterns.empty() && debug_patterns[0] == '!') { 285 inverse_debug_patterns_ = true; 286 debug_patterns.erase(0, 1); 287 } 288 if (debug_patterns.empty()) { 289 return; 290 } 291 std::vector<std::string> patterns; 292 base::SplitString(debug_patterns, ',', &patterns); 293 for (std::vector<std::string>::iterator iter = patterns.begin(); 294 iter != patterns.end(); ++iter) { 295 URLPattern pattern; 296 if (pattern.Parse(*iter) == URLPattern::PARSE_SUCCESS) { 297 // If URL pattern has scheme equal to *, Parse method resets valid 298 // schemes mask to http and https only, so we need to reset it after 299 // Parse to include chrome-extension scheme that can be used by NaCl 300 // manifest files. 301 pattern.SetValidSchemes(URLPattern::SCHEME_ALL); 302 debug_patterns_.push_back(pattern); 303 } 304 } 305 } 306 307 bool NaClBrowser::URLMatchesDebugPatterns(GURL manifest_url) { 308 // Empty patterns are forbidden so we ignore them. 309 if (debug_patterns_.empty()) { 310 return true; 311 } 312 bool matches = false; 313 for (std::vector<URLPattern>::iterator iter = debug_patterns_.begin(); 314 iter != debug_patterns_.end(); ++iter) { 315 if (iter->MatchesURL(manifest_url)) { 316 matches = true; 317 break; 318 } 319 } 320 if (inverse_debug_patterns_) { 321 return !matches; 322 } else { 323 return matches; 324 } 325 } 326 327 void NaClBrowser::FireGdbDebugStubPortOpened(int port) { 328 content::BrowserThread::PostTask( 329 content::BrowserThread::IO, 330 FROM_HERE, 331 base::Bind(debug_stub_port_listener_, port)); 332 } 333 334 bool NaClBrowser::HasGdbDebugStubPortListener() { 335 return !debug_stub_port_listener_.is_null(); 336 } 337 338 void NaClBrowser::SetGdbDebugStubPortListener( 339 base::Callback<void(int)> listener) { 340 debug_stub_port_listener_ = listener; 341 } 342 343 void NaClBrowser::ClearGdbDebugStubPortListener() { 344 debug_stub_port_listener_.Reset(); 345 } 346 347 void NaClBrowser::InitValidationCacheFilePath() { 348 // Determine where the validation cache resides in the file system. It 349 // exists in Chrome's cache directory and is not tied to any specific 350 // profile. 351 // Start by finding the user data directory. 352 base::FilePath user_data_dir; 353 if (!browser_delegate_->GetUserDirectory(&user_data_dir)) { 354 RunWithoutValidationCache(); 355 return; 356 } 357 // The cache directory may or may not be the user data directory. 358 base::FilePath cache_file_path; 359 browser_delegate_->GetCacheDirectory(&cache_file_path); 360 // Append the base file name to the cache directory. 361 362 validation_cache_file_path_ = 363 cache_file_path.Append(kValidationCacheFileName); 364 } 365 366 void NaClBrowser::EnsureValidationCacheAvailable() { 367 if (IsOk() && validation_cache_state_ == NaClResourceUninitialized) { 368 if (ValidationCacheIsEnabled()) { 369 validation_cache_state_ = NaClResourceRequested; 370 371 // Structure for carrying data between the callbacks. 372 std::string* data = new std::string(); 373 // We can get away not giving this a sequence ID because this is the first 374 // task and further file access will not occur until after we get a 375 // response. 376 if (!content::BrowserThread::PostBlockingPoolTaskAndReply( 377 FROM_HERE, 378 base::Bind(ReadCache, validation_cache_file_path_, data), 379 base::Bind(&NaClBrowser::OnValidationCacheLoaded, 380 weak_factory_.GetWeakPtr(), 381 base::Owned(data)))) { 382 RunWithoutValidationCache(); 383 } 384 } else { 385 RunWithoutValidationCache(); 386 } 387 } 388 } 389 390 void NaClBrowser::OnValidationCacheLoaded(const std::string *data) { 391 // Did the cache get cleared before the load completed? If so, ignore the 392 // incoming data. 393 if (validation_cache_state_ == NaClResourceReady) 394 return; 395 396 if (data->size() == 0) { 397 // No file found. 398 validation_cache_.Reset(); 399 } else { 400 Pickle pickle(data->data(), data->size()); 401 validation_cache_.Deserialize(&pickle); 402 } 403 validation_cache_state_ = NaClResourceReady; 404 CheckWaiting(); 405 } 406 407 void NaClBrowser::RunWithoutValidationCache() { 408 // Be paranoid. 409 validation_cache_.Reset(); 410 validation_cache_is_enabled_ = false; 411 validation_cache_state_ = NaClResourceReady; 412 CheckWaiting(); 413 } 414 415 void NaClBrowser::CheckWaiting() { 416 if (!IsOk() || IsReady()) { 417 // Queue the waiting tasks into the message loop. This helps avoid 418 // re-entrancy problems that could occur if the closure was invoked 419 // directly. For example, this could result in use-after-free of the 420 // process host. 421 for (std::vector<base::Closure>::iterator iter = waiting_.begin(); 422 iter != waiting_.end(); ++iter) { 423 base::MessageLoop::current()->PostTask(FROM_HERE, *iter); 424 } 425 waiting_.clear(); 426 } 427 } 428 429 void NaClBrowser::MarkAsFailed() { 430 ok_ = false; 431 CheckWaiting(); 432 } 433 434 void NaClBrowser::WaitForResources(const base::Closure& reply) { 435 waiting_.push_back(reply); 436 EnsureAllResourcesAvailable(); 437 CheckWaiting(); 438 } 439 440 const base::FilePath& NaClBrowser::GetIrtFilePath() { 441 return irt_filepath_; 442 } 443 444 void NaClBrowser::PutFilePath(const base::FilePath& path, uint64* file_token_lo, 445 uint64* file_token_hi) { 446 while (true) { 447 uint64 file_token[2] = {base::RandUint64(), base::RandUint64()}; 448 // A zero file_token indicates there is no file_token, if we get zero, ask 449 // for another number. 450 if (file_token[0] != 0 || file_token[1] != 0) { 451 // If the file_token is in use, ask for another number. 452 std::string key(reinterpret_cast<char*>(file_token), sizeof(file_token)); 453 PathCacheType::iterator iter = path_cache_.Peek(key); 454 if (iter == path_cache_.end()) { 455 path_cache_.Put(key, path); 456 *file_token_lo = file_token[0]; 457 *file_token_hi = file_token[1]; 458 break; 459 } 460 } 461 } 462 } 463 464 bool NaClBrowser::GetFilePath(uint64 file_token_lo, uint64 file_token_hi, 465 base::FilePath* path) { 466 uint64 file_token[2] = {file_token_lo, file_token_hi}; 467 std::string key(reinterpret_cast<char*>(file_token), sizeof(file_token)); 468 PathCacheType::iterator iter = path_cache_.Peek(key); 469 if (iter == path_cache_.end()) { 470 *path = base::FilePath(FILE_PATH_LITERAL("")); 471 return false; 472 } 473 *path = iter->second; 474 path_cache_.Erase(iter); 475 return true; 476 } 477 478 479 bool NaClBrowser::QueryKnownToValidate(const std::string& signature, 480 bool off_the_record) { 481 if (off_the_record) { 482 // If we're off the record, don't reorder the main cache. 483 return validation_cache_.QueryKnownToValidate(signature, false) || 484 off_the_record_validation_cache_.QueryKnownToValidate(signature, true); 485 } else { 486 bool result = validation_cache_.QueryKnownToValidate(signature, true); 487 LogCacheQuery(result ? CACHE_HIT : CACHE_MISS); 488 // Queries can modify the MRU order of the cache. 489 MarkValidationCacheAsModified(); 490 return result; 491 } 492 } 493 494 void NaClBrowser::SetKnownToValidate(const std::string& signature, 495 bool off_the_record) { 496 if (off_the_record) { 497 off_the_record_validation_cache_.SetKnownToValidate(signature); 498 } else { 499 validation_cache_.SetKnownToValidate(signature); 500 // The number of sets should be equal to the number of cache misses, minus 501 // validation failures and successful validations where stubout occurs. 502 LogCacheSet(CACHE_HIT); 503 MarkValidationCacheAsModified(); 504 } 505 } 506 507 void NaClBrowser::ClearValidationCache(const base::Closure& callback) { 508 // Note: this method may be called before EnsureValidationCacheAvailable has 509 // been invoked. In other words, this method may be called before any NaCl 510 // processes have been created. This method must succeed and invoke the 511 // callback in such a case. If it does not invoke the callback, Chrome's UI 512 // will hang in that case. 513 validation_cache_.Reset(); 514 off_the_record_validation_cache_.Reset(); 515 516 if (validation_cache_file_path_.empty()) { 517 // Can't figure out what file to remove, but don't drop the callback. 518 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, 519 callback); 520 } else { 521 // Delegate the removal of the cache from the filesystem to another thread 522 // to avoid blocking the IO thread. 523 // This task is dispatched immediately, not delayed and coalesced, because 524 // the user interface for cache clearing is likely waiting for the callback. 525 // In addition, we need to make sure the cache is actually cleared before 526 // invoking the callback to meet the implicit guarantees of the UI. 527 content::BrowserThread::PostBlockingPoolSequencedTask( 528 kValidationCacheSequenceName, 529 FROM_HERE, 530 base::Bind(RemoveCache, validation_cache_file_path_, callback)); 531 } 532 533 // Make sure any delayed tasks to persist the cache to the filesystem are 534 // squelched. 535 validation_cache_is_modified_ = false; 536 537 // If the cache is cleared before it is loaded from the filesystem, act as if 538 // we just loaded an empty cache. 539 if (validation_cache_state_ != NaClResourceReady) { 540 validation_cache_state_ = NaClResourceReady; 541 CheckWaiting(); 542 } 543 } 544 545 void NaClBrowser::MarkValidationCacheAsModified() { 546 if (!validation_cache_is_modified_) { 547 // Wait before persisting to disk. This can coalesce multiple cache 548 // modifications info a single disk write. 549 base::MessageLoop::current()->PostDelayedTask( 550 FROM_HERE, 551 base::Bind(&NaClBrowser::PersistValidationCache, 552 weak_factory_.GetWeakPtr()), 553 base::TimeDelta::FromMilliseconds(kValidationCacheCoalescingTimeMS)); 554 validation_cache_is_modified_ = true; 555 } 556 } 557 558 void NaClBrowser::PersistValidationCache() { 559 // validation_cache_is_modified_ may be false if the cache was cleared while 560 // this delayed task was pending. 561 // validation_cache_file_path_ may be empty if something went wrong during 562 // initialization. 563 if (validation_cache_is_modified_ && !validation_cache_file_path_.empty()) { 564 Pickle* pickle = new Pickle(); 565 validation_cache_.Serialize(pickle); 566 567 // Pass the serialized data to another thread to write to disk. File IO is 568 // not allowed on the IO thread (which is the thread this method runs on) 569 // because it can degrade the responsiveness of the browser. 570 // The task is sequenced so that multiple writes happen in order. 571 content::BrowserThread::PostBlockingPoolSequencedTask( 572 kValidationCacheSequenceName, 573 FROM_HERE, 574 base::Bind(WriteCache, validation_cache_file_path_, 575 base::Owned(pickle))); 576 } 577 validation_cache_is_modified_ = false; 578 } 579