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.
5 #include "components/nacl/browser/nacl_browser.h"
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"
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");
28 const bool kValidationCacheEnabledByDefault = true;
30 enum ValidationCacheStatus {
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;
46 const base::FilePath::StringType NaClIrtName() {
47 base::FilePath::StringType irt_name(FILE_PATH_LITERAL("nacl_irt_"));
49 #if defined(ARCH_CPU_X86_FAMILY)
50 #if defined(ARCH_CPU_X86_64)
53 bool is64 = (base::win::OSInfo::GetInstance()->wow64_status() ==
54 base::win::OSInfo::WOW64_ENABLED);
59 irt_name.append(FILE_PATH_LITERAL("x86_64"));
61 irt_name.append(FILE_PATH_LITERAL("x86_32"));
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"));
68 #error Add support for your architecture to NaCl IRT file selection
70 irt_name.append(FILE_PATH_LITERAL(".nexe"));
74 bool 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';
83 void 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.
90 void WriteCache(const base::FilePath& filename, const Pickle* pickle) {
91 base::WriteFile(filename, static_cast<const char*>(pickle->data()),
95 void RemoveCache(const base::FilePath& filename,
96 const base::Closure& callback) {
97 base::DeleteFile(filename, false);
98 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
102 void LogCacheQuery(ValidationCacheStatus status) {
103 UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Query", status, CACHE_MAX);
106 void LogCacheSet(ValidationCacheStatus status) {
107 // Bucket zero is reserved for future use.
108 UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Set", status, CACHE_MAX);
111 // Crash throttling parameters.
112 const size_t kMaxCrashesPerInterval = 3;
113 const int64 kCrashesIntervalInSeconds = 120;
119 base::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.
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)
141 NaClBrowser::NaClBrowser()
142 : weak_factory_(this),
143 irt_platform_file_(base::kInvalidPlatformFileValue),
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),
156 void NaClBrowser::SetDelegate(NaClBrowserDelegate* delegate) {
157 NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
158 nacl_browser->browser_delegate_.reset(delegate);
161 NaClBrowserDelegate* 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();
168 void NaClBrowser::EarlyStartup() {
169 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
171 InitValidationCacheFilePath();
174 NaClBrowser::~NaClBrowser() {
175 if (irt_platform_file_ != base::kInvalidPlatformFileValue)
176 base::ClosePlatformFile(irt_platform_file_);
179 void 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);
191 base::FilePath plugin_dir;
192 if (!browser_delegate_->GetPluginDirectory(&plugin_dir)) {
193 DLOG(ERROR) << "Failed to locate the plugins directory, NaCl disabled.";
197 irt_filepath_ = plugin_dir.Append(NaClIrtName());
202 bool 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";
208 *exe_path = module_path.DirName().Append(L"nacl64");
213 NaClBrowser* NaClBrowser::GetInstance() {
214 return Singleton<NaClBrowser>::get();
217 bool NaClBrowser::IsReady() const {
219 irt_state_ == NaClResourceReady &&
220 validation_cache_state_ == NaClResourceReady);
223 bool NaClBrowser::IsOk() const {
227 base::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_;
234 void NaClBrowser::EnsureAllResourcesAvailable() {
235 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
236 EnsureIrtAvailable();
237 EnsureValidationCacheAvailable();
240 // Load the IRT async.
241 void 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)
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.";
260 void NaClBrowser::OnIrtOpened(base::File::Error error_code,
261 base::PassPlatformFile file,
263 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
264 DCHECK_EQ(irt_state_, NaClResourceRequested);
266 if (error_code == base::File::FILE_OK) {
267 irt_platform_file_ = file.ReleaseValue();
269 LOG(ERROR) << "Failed to open NaCl IRT file \""
270 << irt_filepath_.LossyDisplayName()
271 << "\": " << error_code;
274 irt_state_ = NaClResourceReady;
278 void NaClBrowser::FireGdbDebugStubPortOpened(int port) {
279 content::BrowserThread::PostTask(
280 content::BrowserThread::IO,
282 base::Bind(debug_stub_port_listener_, port));
285 bool NaClBrowser::HasGdbDebugStubPortListener() {
286 return !debug_stub_port_listener_.is_null();
289 void NaClBrowser::SetGdbDebugStubPortListener(
290 base::Callback<void(int)> listener) {
291 debug_stub_port_listener_ = listener;
294 void NaClBrowser::ClearGdbDebugStubPortListener() {
295 debug_stub_port_listener_.Reset();
298 void 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
302 // Start by finding the user data directory.
303 base::FilePath user_data_dir;
304 if (!browser_delegate_->GetUserDirectory(&user_data_dir)) {
305 RunWithoutValidationCache();
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.
313 validation_cache_file_path_ =
314 cache_file_path.Append(kValidationCacheFileName);
317 void 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;
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
328 if (!content::BrowserThread::PostBlockingPoolTaskAndReply(
330 base::Bind(ReadCache, validation_cache_file_path_, data),
331 base::Bind(&NaClBrowser::OnValidationCacheLoaded,
332 weak_factory_.GetWeakPtr(),
333 base::Owned(data)))) {
334 RunWithoutValidationCache();
337 RunWithoutValidationCache();
342 void 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
346 if (validation_cache_state_ == NaClResourceReady)
349 if (data->size() == 0) {
351 validation_cache_.Reset();
353 Pickle pickle(data->data(), data->size());
354 validation_cache_.Deserialize(&pickle);
356 validation_cache_state_ = NaClResourceReady;
360 void NaClBrowser::RunWithoutValidationCache() {
362 validation_cache_.Reset();
363 validation_cache_is_enabled_ = false;
364 validation_cache_state_ = NaClResourceReady;
368 void 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
375 for (std::vector<base::Closure>::iterator iter = waiting_.begin();
376 iter != waiting_.end(); ++iter) {
377 base::MessageLoop::current()->PostTask(FROM_HERE, *iter);
383 void NaClBrowser::MarkAsFailed() {
388 void NaClBrowser::WaitForResources(const base::Closure& reply) {
389 waiting_.push_back(reply);
390 EnsureAllResourcesAvailable();
394 const base::FilePath& NaClBrowser::GetIrtFilePath() {
395 return irt_filepath_;
398 void NaClBrowser::PutFilePath(const base::FilePath& path, uint64* file_token_lo,
399 uint64* file_token_hi) {
400 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
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];
419 bool 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(""));
429 *path = iter->second;
430 path_cache_.Erase(iter);
435 bool 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);
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();
451 void 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);
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();
465 void 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();
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,
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,
489 base::Bind(RemoveCache, validation_cache_file_path_, callback));
492 // Make sure any delayed tasks to persist the cache to the filesystem are
494 validation_cache_is_modified_ = false;
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;
504 void 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(
510 base::Bind(&NaClBrowser::PersistValidationCache,
511 weak_factory_.GetWeakPtr()),
512 base::TimeDelta::FromMilliseconds(kValidationCacheCoalescingTimeMS));
513 validation_cache_is_modified_ = true;
517 void 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
523 if (validation_cache_is_modified_ && !validation_cache_file_path_.empty()) {
524 Pickle* pickle = new Pickle();
525 validation_cache_.Serialize(pickle);
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,
534 base::Bind(WriteCache, validation_cache_file_path_,
535 base::Owned(pickle)));
537 validation_cache_is_modified_ = false;
540 void NaClBrowser::OnProcessCrashed() {
541 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
542 if (crash_times_.size() == kMaxCrashesPerInterval) {
543 crash_times_.pop_front();
545 base::Time time = base::Time::Now();
546 crash_times_.push_back(time);
549 bool NaClBrowser::IsThrottled() {
550 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
551 if (crash_times_.size() != kMaxCrashesPerInterval) {
554 base::TimeDelta delta = base::Time::Now() - crash_times_.front();
555 return delta.InSeconds() <= kCrashesIntervalInSeconds;