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