Upstream version 6.35.121.0
[platform/framework/web/crosswalk.git] / src / components / nacl / browser / nacl_browser.cc
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
20 namespace {
21
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");
27
28 const bool kValidationCacheEnabledByDefault = true;
29
30 enum 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.
44 const int kFilePathCacheSize = 100;
45
46 const 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
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';
79   }
80   return result;
81 }
82
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.
86     data->clear();
87   }
88 }
89
90 void WriteCache(const base::FilePath& filename, const Pickle* pickle) {
91   base::WriteFile(filename, static_cast<const char*>(pickle->data()),
92                        pickle->size());
93 }
94
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,
99                                    callback);
100 }
101
102 void LogCacheQuery(ValidationCacheStatus status) {
103   UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Query", status, CACHE_MAX);
104 }
105
106 void 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.
112 const size_t kMaxCrashesPerInterval = 3;
113 const int64 kCrashesIntervalInSeconds = 120;
114
115 }  // namespace
116
117 namespace nacl {
118
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.
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
141 NaClBrowser::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
156 void NaClBrowser::SetDelegate(NaClBrowserDelegate* delegate) {
157   NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
158   nacl_browser->browser_delegate_.reset(delegate);
159 }
160
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();
166 }
167
168 void NaClBrowser::EarlyStartup() {
169   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
170   InitIrtFilePath();
171   InitValidationCacheFilePath();
172 }
173
174 NaClBrowser::~NaClBrowser() {
175   if (irt_platform_file_ != base::kInvalidPlatformFileValue)
176     base::ClosePlatformFile(irt_platform_file_);
177 }
178
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);
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)
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";
206     return false;
207   }
208   *exe_path = module_path.DirName().Append(L"nacl64");
209   return true;
210 }
211 #endif
212
213 NaClBrowser* NaClBrowser::GetInstance() {
214   return Singleton<NaClBrowser>::get();
215 }
216
217 bool NaClBrowser::IsReady() const {
218   return (IsOk() &&
219           irt_state_ == NaClResourceReady &&
220           validation_cache_state_ == NaClResourceReady);
221 }
222
223 bool NaClBrowser::IsOk() const {
224   return ok_;
225 }
226
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_;
232 }
233
234 void NaClBrowser::EnsureAllResourcesAvailable() {
235   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
236   EnsureIrtAvailable();
237   EnsureValidationCacheAvailable();
238 }
239
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)
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
260 void 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
278 void 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
285 bool NaClBrowser::HasGdbDebugStubPortListener() {
286   return !debug_stub_port_listener_.is_null();
287 }
288
289 void NaClBrowser::SetGdbDebugStubPortListener(
290     base::Callback<void(int)> listener) {
291   debug_stub_port_listener_ = listener;
292 }
293
294 void NaClBrowser::ClearGdbDebugStubPortListener() {
295   debug_stub_port_listener_.Reset();
296 }
297
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
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
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;
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
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
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
360 void NaClBrowser::RunWithoutValidationCache() {
361   // Be paranoid.
362   validation_cache_.Reset();
363   validation_cache_is_enabled_ = false;
364   validation_cache_state_ = NaClResourceReady;
365   CheckWaiting();
366 }
367
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
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
383 void NaClBrowser::MarkAsFailed() {
384   ok_ = false;
385   CheckWaiting();
386 }
387
388 void NaClBrowser::WaitForResources(const base::Closure& reply) {
389   waiting_.push_back(reply);
390   EnsureAllResourcesAvailable();
391   CheckWaiting();
392 }
393
394 const base::FilePath& NaClBrowser::GetIrtFilePath() {
395   return irt_filepath_;
396 }
397
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));
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
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(""));
427     return false;
428   }
429   *path = iter->second;
430   path_cache_.Erase(iter);
431   return true;
432 }
433
434
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);
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
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);
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
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();
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
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(
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
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
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
540 void 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
549 bool 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