Upstream version 9.38.198.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/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
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     : weak_factory_(this),
141       irt_filepath_(),
142       irt_state_(NaClResourceUninitialized),
143       validation_cache_file_path_(),
144       validation_cache_is_enabled_(
145           CheckEnvVar("NACL_VALIDATION_CACHE",
146                       kValidationCacheEnabledByDefault)),
147       validation_cache_is_modified_(false),
148       validation_cache_state_(NaClResourceUninitialized),
149       path_cache_(kFilePathCacheSize),
150       ok_(true) {
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