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