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