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