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