nacl_browser.cc revision 0f1bc08d4cfcc34181b0b5cbf065c40f687bf740
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 20namespace { 21 22// An arbitrary delay to coalesce multiple writes to the cache. 23const int kValidationCacheCoalescingTimeMS = 6000; 24const char kValidationCacheSequenceName[] = "NaClValidationCache"; 25const base::FilePath::CharType kValidationCacheFileName[] = 26 FILE_PATH_LITERAL("nacl_validation_cache.bin"); 27 28const bool kValidationCacheEnabledByDefault = true; 29 30enum 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. 44const int kFilePathCacheSize = 100; 45 46const 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 77bool 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 86void 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 93void WriteCache(const base::FilePath& filename, const Pickle* pickle) { 94 file_util::WriteFile(filename, static_cast<const char*>(pickle->data()), 95 pickle->size()); 96} 97 98void 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 105void LogCacheQuery(ValidationCacheStatus status) { 106 UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Query", status, CACHE_MAX); 107} 108 109void 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. 115const size_t kMaxCrashesPerInterval = 3; 116const int64 kCrashesIntervalInSeconds = 120; 117 118} // namespace 119 120namespace nacl { 121 122void OpenNaClExecutableImpl(const base::FilePath& file_path, 123 base::PlatformFile* file) { 124 // Get a file descriptor. On Windows, we need 'GENERIC_EXECUTE' in order to 125 // memory map the executable. 126 // IMPORTANT: This file descriptor must not have write access - that could 127 // allow a NaCl inner sandbox escape. 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 *file = base::kInvalidPlatformFileValue; 138 return; 139 } 140 // Check that the file does not reference a directory. Returning a descriptor 141 // to an extension directory could allow an outer sandbox escape. openat(...) 142 // could be used to traverse into the file system. 143 base::PlatformFileInfo file_info; 144 if (!base::GetPlatformFileInfo(*file, &file_info) || 145 file_info.is_directory) { 146 base::ClosePlatformFile(*file); 147 *file = base::kInvalidPlatformFileValue; 148 return; 149 } 150} 151 152NaClBrowser::NaClBrowser() 153 : weak_factory_(this), 154 irt_platform_file_(base::kInvalidPlatformFileValue), 155 irt_filepath_(), 156 irt_state_(NaClResourceUninitialized), 157 validation_cache_file_path_(), 158 validation_cache_is_enabled_( 159 CheckEnvVar("NACL_VALIDATION_CACHE", 160 kValidationCacheEnabledByDefault)), 161 validation_cache_is_modified_(false), 162 validation_cache_state_(NaClResourceUninitialized), 163 path_cache_(kFilePathCacheSize), 164 ok_(true) { 165} 166 167void NaClBrowser::SetDelegate(NaClBrowserDelegate* delegate) { 168 NaClBrowser* nacl_browser = NaClBrowser::GetInstance(); 169 nacl_browser->browser_delegate_.reset(delegate); 170} 171 172NaClBrowserDelegate* NaClBrowser::GetDelegate() { 173 // The delegate is not owned by the IO thread. This accessor method can be 174 // called from other threads. 175 DCHECK(GetInstance()->browser_delegate_.get() != NULL); 176 return GetInstance()->browser_delegate_.get(); 177} 178 179void NaClBrowser::EarlyStartup() { 180 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 181 InitIrtFilePath(); 182 InitValidationCacheFilePath(); 183} 184 185NaClBrowser::~NaClBrowser() { 186 if (irt_platform_file_ != base::kInvalidPlatformFileValue) 187 base::ClosePlatformFile(irt_platform_file_); 188} 189 190void NaClBrowser::InitIrtFilePath() { 191 // Allow the IRT library to be overridden via an environment 192 // variable. This allows the NaCl/Chromium integration bot to 193 // specify a newly-built IRT rather than using a prebuilt one 194 // downloaded via Chromium's DEPS file. We use the same environment 195 // variable that the standalone NaCl PPAPI plugin accepts. 196 const char* irt_path_var = getenv("NACL_IRT_LIBRARY"); 197 if (irt_path_var != NULL) { 198 base::FilePath::StringType path_string( 199 irt_path_var, const_cast<const char*>(strchr(irt_path_var, '\0'))); 200 irt_filepath_ = base::FilePath(path_string); 201 } else { 202 base::FilePath plugin_dir; 203 if (!browser_delegate_->GetPluginDirectory(&plugin_dir)) { 204 DLOG(ERROR) << "Failed to locate the plugins directory, NaCl disabled."; 205 MarkAsFailed(); 206 return; 207 } 208 irt_filepath_ = plugin_dir.Append(NaClIrtName()); 209 } 210} 211 212#if defined(OS_WIN) 213bool NaClBrowser::GetNaCl64ExePath(base::FilePath* exe_path) { 214 base::FilePath module_path; 215 if (!PathService::Get(base::FILE_MODULE, &module_path)) { 216 LOG(ERROR) << "NaCl process launch failed: could not resolve module"; 217 return false; 218 } 219 *exe_path = module_path.DirName().Append(L"nacl64"); 220 return true; 221} 222#endif 223 224NaClBrowser* NaClBrowser::GetInstance() { 225 return Singleton<NaClBrowser>::get(); 226} 227 228bool NaClBrowser::IsReady() const { 229 return (IsOk() && 230 irt_state_ == NaClResourceReady && 231 validation_cache_state_ == NaClResourceReady); 232} 233 234bool NaClBrowser::IsOk() const { 235 return ok_; 236} 237 238base::PlatformFile NaClBrowser::IrtFile() const { 239 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 240 CHECK_EQ(irt_state_, NaClResourceReady); 241 CHECK_NE(irt_platform_file_, base::kInvalidPlatformFileValue); 242 return irt_platform_file_; 243} 244 245void NaClBrowser::EnsureAllResourcesAvailable() { 246 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 247 EnsureIrtAvailable(); 248 EnsureValidationCacheAvailable(); 249} 250 251// Load the IRT async. 252void NaClBrowser::EnsureIrtAvailable() { 253 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 254 if (IsOk() && irt_state_ == NaClResourceUninitialized) { 255 irt_state_ = NaClResourceRequested; 256 // TODO(ncbray) use blocking pool. 257 if (!base::FileUtilProxy::CreateOrOpen( 258 content::BrowserThread::GetMessageLoopProxyForThread( 259 content::BrowserThread::FILE) 260 .get(), 261 irt_filepath_, 262 base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, 263 base::Bind(&NaClBrowser::OnIrtOpened, 264 weak_factory_.GetWeakPtr()))) { 265 LOG(ERROR) << "Internal error, NaCl disabled."; 266 MarkAsFailed(); 267 } 268 } 269} 270 271void NaClBrowser::OnIrtOpened(base::PlatformFileError error_code, 272 base::PassPlatformFile file, 273 bool created) { 274 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 275 DCHECK_EQ(irt_state_, NaClResourceRequested); 276 DCHECK(!created); 277 if (error_code == base::PLATFORM_FILE_OK) { 278 irt_platform_file_ = file.ReleaseValue(); 279 } else { 280 LOG(ERROR) << "Failed to open NaCl IRT file \"" 281 << irt_filepath_.LossyDisplayName() 282 << "\": " << error_code; 283 MarkAsFailed(); 284 } 285 irt_state_ = NaClResourceReady; 286 CheckWaiting(); 287} 288 289void NaClBrowser::FireGdbDebugStubPortOpened(int port) { 290 content::BrowserThread::PostTask( 291 content::BrowserThread::IO, 292 FROM_HERE, 293 base::Bind(debug_stub_port_listener_, port)); 294} 295 296bool NaClBrowser::HasGdbDebugStubPortListener() { 297 return !debug_stub_port_listener_.is_null(); 298} 299 300void NaClBrowser::SetGdbDebugStubPortListener( 301 base::Callback<void(int)> listener) { 302 debug_stub_port_listener_ = listener; 303} 304 305void NaClBrowser::ClearGdbDebugStubPortListener() { 306 debug_stub_port_listener_.Reset(); 307} 308 309void NaClBrowser::InitValidationCacheFilePath() { 310 // Determine where the validation cache resides in the file system. It 311 // exists in Chrome's cache directory and is not tied to any specific 312 // profile. 313 // Start by finding the user data directory. 314 base::FilePath user_data_dir; 315 if (!browser_delegate_->GetUserDirectory(&user_data_dir)) { 316 RunWithoutValidationCache(); 317 return; 318 } 319 // The cache directory may or may not be the user data directory. 320 base::FilePath cache_file_path; 321 browser_delegate_->GetCacheDirectory(&cache_file_path); 322 // Append the base file name to the cache directory. 323 324 validation_cache_file_path_ = 325 cache_file_path.Append(kValidationCacheFileName); 326} 327 328void NaClBrowser::EnsureValidationCacheAvailable() { 329 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 330 if (IsOk() && validation_cache_state_ == NaClResourceUninitialized) { 331 if (ValidationCacheIsEnabled()) { 332 validation_cache_state_ = NaClResourceRequested; 333 334 // Structure for carrying data between the callbacks. 335 std::string* data = new std::string(); 336 // We can get away not giving this a sequence ID because this is the first 337 // task and further file access will not occur until after we get a 338 // response. 339 if (!content::BrowserThread::PostBlockingPoolTaskAndReply( 340 FROM_HERE, 341 base::Bind(ReadCache, validation_cache_file_path_, data), 342 base::Bind(&NaClBrowser::OnValidationCacheLoaded, 343 weak_factory_.GetWeakPtr(), 344 base::Owned(data)))) { 345 RunWithoutValidationCache(); 346 } 347 } else { 348 RunWithoutValidationCache(); 349 } 350 } 351} 352 353void NaClBrowser::OnValidationCacheLoaded(const std::string *data) { 354 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 355 // Did the cache get cleared before the load completed? If so, ignore the 356 // incoming data. 357 if (validation_cache_state_ == NaClResourceReady) 358 return; 359 360 if (data->size() == 0) { 361 // No file found. 362 validation_cache_.Reset(); 363 } else { 364 Pickle pickle(data->data(), data->size()); 365 validation_cache_.Deserialize(&pickle); 366 } 367 validation_cache_state_ = NaClResourceReady; 368 CheckWaiting(); 369} 370 371void NaClBrowser::RunWithoutValidationCache() { 372 // Be paranoid. 373 validation_cache_.Reset(); 374 validation_cache_is_enabled_ = false; 375 validation_cache_state_ = NaClResourceReady; 376 CheckWaiting(); 377} 378 379void NaClBrowser::CheckWaiting() { 380 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 381 if (!IsOk() || IsReady()) { 382 // Queue the waiting tasks into the message loop. This helps avoid 383 // re-entrancy problems that could occur if the closure was invoked 384 // directly. For example, this could result in use-after-free of the 385 // process host. 386 for (std::vector<base::Closure>::iterator iter = waiting_.begin(); 387 iter != waiting_.end(); ++iter) { 388 base::MessageLoop::current()->PostTask(FROM_HERE, *iter); 389 } 390 waiting_.clear(); 391 } 392} 393 394void NaClBrowser::MarkAsFailed() { 395 ok_ = false; 396 CheckWaiting(); 397} 398 399void NaClBrowser::WaitForResources(const base::Closure& reply) { 400 waiting_.push_back(reply); 401 EnsureAllResourcesAvailable(); 402 CheckWaiting(); 403} 404 405const base::FilePath& NaClBrowser::GetIrtFilePath() { 406 return irt_filepath_; 407} 408 409void NaClBrowser::PutFilePath(const base::FilePath& path, uint64* file_token_lo, 410 uint64* file_token_hi) { 411 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 412 while (true) { 413 uint64 file_token[2] = {base::RandUint64(), base::RandUint64()}; 414 // A zero file_token indicates there is no file_token, if we get zero, ask 415 // for another number. 416 if (file_token[0] != 0 || file_token[1] != 0) { 417 // If the file_token is in use, ask for another number. 418 std::string key(reinterpret_cast<char*>(file_token), sizeof(file_token)); 419 PathCacheType::iterator iter = path_cache_.Peek(key); 420 if (iter == path_cache_.end()) { 421 path_cache_.Put(key, path); 422 *file_token_lo = file_token[0]; 423 *file_token_hi = file_token[1]; 424 break; 425 } 426 } 427 } 428} 429 430bool NaClBrowser::GetFilePath(uint64 file_token_lo, uint64 file_token_hi, 431 base::FilePath* path) { 432 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 433 uint64 file_token[2] = {file_token_lo, file_token_hi}; 434 std::string key(reinterpret_cast<char*>(file_token), sizeof(file_token)); 435 PathCacheType::iterator iter = path_cache_.Peek(key); 436 if (iter == path_cache_.end()) { 437 *path = base::FilePath(FILE_PATH_LITERAL("")); 438 return false; 439 } 440 *path = iter->second; 441 path_cache_.Erase(iter); 442 return true; 443} 444 445 446bool NaClBrowser::QueryKnownToValidate(const std::string& signature, 447 bool off_the_record) { 448 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 449 if (off_the_record) { 450 // If we're off the record, don't reorder the main cache. 451 return validation_cache_.QueryKnownToValidate(signature, false) || 452 off_the_record_validation_cache_.QueryKnownToValidate(signature, true); 453 } else { 454 bool result = validation_cache_.QueryKnownToValidate(signature, true); 455 LogCacheQuery(result ? CACHE_HIT : CACHE_MISS); 456 // Queries can modify the MRU order of the cache. 457 MarkValidationCacheAsModified(); 458 return result; 459 } 460} 461 462void NaClBrowser::SetKnownToValidate(const std::string& signature, 463 bool off_the_record) { 464 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 465 if (off_the_record) { 466 off_the_record_validation_cache_.SetKnownToValidate(signature); 467 } else { 468 validation_cache_.SetKnownToValidate(signature); 469 // The number of sets should be equal to the number of cache misses, minus 470 // validation failures and successful validations where stubout occurs. 471 LogCacheSet(CACHE_HIT); 472 MarkValidationCacheAsModified(); 473 } 474} 475 476void NaClBrowser::ClearValidationCache(const base::Closure& callback) { 477 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 478 // Note: this method may be called before EnsureValidationCacheAvailable has 479 // been invoked. In other words, this method may be called before any NaCl 480 // processes have been created. This method must succeed and invoke the 481 // callback in such a case. If it does not invoke the callback, Chrome's UI 482 // will hang in that case. 483 validation_cache_.Reset(); 484 off_the_record_validation_cache_.Reset(); 485 486 if (validation_cache_file_path_.empty()) { 487 // Can't figure out what file to remove, but don't drop the callback. 488 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, 489 callback); 490 } else { 491 // Delegate the removal of the cache from the filesystem to another thread 492 // to avoid blocking the IO thread. 493 // This task is dispatched immediately, not delayed and coalesced, because 494 // the user interface for cache clearing is likely waiting for the callback. 495 // In addition, we need to make sure the cache is actually cleared before 496 // invoking the callback to meet the implicit guarantees of the UI. 497 content::BrowserThread::PostBlockingPoolSequencedTask( 498 kValidationCacheSequenceName, 499 FROM_HERE, 500 base::Bind(RemoveCache, validation_cache_file_path_, callback)); 501 } 502 503 // Make sure any delayed tasks to persist the cache to the filesystem are 504 // squelched. 505 validation_cache_is_modified_ = false; 506 507 // If the cache is cleared before it is loaded from the filesystem, act as if 508 // we just loaded an empty cache. 509 if (validation_cache_state_ != NaClResourceReady) { 510 validation_cache_state_ = NaClResourceReady; 511 CheckWaiting(); 512 } 513} 514 515void NaClBrowser::MarkValidationCacheAsModified() { 516 if (!validation_cache_is_modified_) { 517 // Wait before persisting to disk. This can coalesce multiple cache 518 // modifications info a single disk write. 519 base::MessageLoop::current()->PostDelayedTask( 520 FROM_HERE, 521 base::Bind(&NaClBrowser::PersistValidationCache, 522 weak_factory_.GetWeakPtr()), 523 base::TimeDelta::FromMilliseconds(kValidationCacheCoalescingTimeMS)); 524 validation_cache_is_modified_ = true; 525 } 526} 527 528void NaClBrowser::PersistValidationCache() { 529 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 530 // validation_cache_is_modified_ may be false if the cache was cleared while 531 // this delayed task was pending. 532 // validation_cache_file_path_ may be empty if something went wrong during 533 // initialization. 534 if (validation_cache_is_modified_ && !validation_cache_file_path_.empty()) { 535 Pickle* pickle = new Pickle(); 536 validation_cache_.Serialize(pickle); 537 538 // Pass the serialized data to another thread to write to disk. File IO is 539 // not allowed on the IO thread (which is the thread this method runs on) 540 // because it can degrade the responsiveness of the browser. 541 // The task is sequenced so that multiple writes happen in order. 542 content::BrowserThread::PostBlockingPoolSequencedTask( 543 kValidationCacheSequenceName, 544 FROM_HERE, 545 base::Bind(WriteCache, validation_cache_file_path_, 546 base::Owned(pickle))); 547 } 548 validation_cache_is_modified_ = false; 549} 550 551void NaClBrowser::OnProcessCrashed() { 552 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 553 if (crash_times_.size() == kMaxCrashesPerInterval) { 554 crash_times_.pop_front(); 555 } 556 base::Time time = base::Time::Now(); 557 crash_times_.push_back(time); 558} 559 560bool NaClBrowser::IsThrottled() { 561 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 562 if (crash_times_.size() != kMaxCrashesPerInterval) { 563 return false; 564 } 565 base::TimeDelta delta = base::Time::Now() - crash_times_.front(); 566 return delta.InSeconds() <= kCrashesIntervalInSeconds; 567} 568 569} // namespace nacl 570