Home | History | Annotate | Download | only in browser
      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