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/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   // TODO(mcgrathr): Eventually we'll need to distinguish arm32 vs thumb2.
     65   // That may need to be based on the actual nexe rather than a static
     66   // choice, which would require substantial refactoring.
     67   irt_name.append(FILE_PATH_LITERAL("arm"));
     68 #elif defined(ARCH_CPU_MIPSEL)
     69   irt_name.append(FILE_PATH_LITERAL("mips32"));
     70 #else
     71 #error Add support for your architecture to NaCl IRT file selection
     72 #endif
     73   irt_name.append(FILE_PATH_LITERAL(".nexe"));
     74   return irt_name;
     75 }
     76 
     77 bool CheckEnvVar(const char* name, bool default_value) {
     78   bool result = default_value;
     79   const char* var = getenv(name);
     80   if (var && strlen(var) > 0) {
     81     result = var[0] != '0';
     82   }
     83   return result;
     84 }
     85 
     86 void ReadCache(const base::FilePath& filename, std::string* data) {
     87   if (!base::ReadFileToString(filename, data)) {
     88     // Zero-size data used as an in-band error code.
     89     data->clear();
     90   }
     91 }
     92 
     93 void WriteCache(const base::FilePath& filename, const Pickle* pickle) {
     94   file_util::WriteFile(filename, static_cast<const char*>(pickle->data()),
     95                        pickle->size());
     96 }
     97 
     98 void RemoveCache(const base::FilePath& filename,
     99                  const base::Closure& callback) {
    100   base::DeleteFile(filename, false);
    101   content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
    102                                    callback);
    103 }
    104 
    105 void LogCacheQuery(ValidationCacheStatus status) {
    106   UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Query", status, CACHE_MAX);
    107 }
    108 
    109 void LogCacheSet(ValidationCacheStatus status) {
    110   // Bucket zero is reserved for future use.
    111   UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Set", status, CACHE_MAX);
    112 }
    113 
    114 // Crash throttling parameters.
    115 const size_t kMaxCrashesPerInterval = 3;
    116 const int64 kCrashesIntervalInSeconds = 120;
    117 
    118 }  // namespace
    119 
    120 namespace nacl {
    121 
    122 base::PlatformFile OpenNaClExecutableImpl(const base::FilePath& file_path) {
    123   // Get a file descriptor. On Windows, we need 'GENERIC_EXECUTE' in order to
    124   // memory map the executable.
    125   // IMPORTANT: This file descriptor must not have write access - that could
    126   // allow a NaCl inner sandbox escape.
    127   base::PlatformFile file;
    128   base::PlatformFileError error_code;
    129   file = base::CreatePlatformFile(
    130       file_path,
    131       (base::PLATFORM_FILE_OPEN |
    132        base::PLATFORM_FILE_READ |
    133        base::PLATFORM_FILE_EXECUTE),  // Windows only flag.
    134       NULL,
    135       &error_code);
    136   if (error_code != base::PLATFORM_FILE_OK)
    137     return base::kInvalidPlatformFileValue;
    138 
    139   // Check that the file does not reference a directory. Returning a descriptor
    140   // to an extension directory could allow an outer sandbox escape. openat(...)
    141   // could be used to traverse into the file system.
    142   base::PlatformFileInfo file_info;
    143   if (!base::GetPlatformFileInfo(file, &file_info) || file_info.is_directory) {
    144     base::ClosePlatformFile(file);
    145     return base::kInvalidPlatformFileValue;
    146   }
    147   return file;
    148 }
    149 
    150 NaClBrowser::NaClBrowser()
    151     : weak_factory_(this),
    152       irt_platform_file_(base::kInvalidPlatformFileValue),
    153       irt_filepath_(),
    154       irt_state_(NaClResourceUninitialized),
    155       validation_cache_file_path_(),
    156       validation_cache_is_enabled_(
    157           CheckEnvVar("NACL_VALIDATION_CACHE",
    158                       kValidationCacheEnabledByDefault)),
    159       validation_cache_is_modified_(false),
    160       validation_cache_state_(NaClResourceUninitialized),
    161       path_cache_(kFilePathCacheSize),
    162       ok_(true) {
    163 }
    164 
    165 void NaClBrowser::SetDelegate(NaClBrowserDelegate* delegate) {
    166   NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
    167   nacl_browser->browser_delegate_.reset(delegate);
    168 }
    169 
    170 NaClBrowserDelegate* NaClBrowser::GetDelegate() {
    171   // The delegate is not owned by the IO thread.  This accessor method can be
    172   // called from other threads.
    173   DCHECK(GetInstance()->browser_delegate_.get() != NULL);
    174   return GetInstance()->browser_delegate_.get();
    175 }
    176 
    177 void NaClBrowser::EarlyStartup() {
    178   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
    179   InitIrtFilePath();
    180   InitValidationCacheFilePath();
    181 }
    182 
    183 NaClBrowser::~NaClBrowser() {
    184   if (irt_platform_file_ != base::kInvalidPlatformFileValue)
    185     base::ClosePlatformFile(irt_platform_file_);
    186 }
    187 
    188 void NaClBrowser::InitIrtFilePath() {
    189   // Allow the IRT library to be overridden via an environment
    190   // variable.  This allows the NaCl/Chromium integration bot to
    191   // specify a newly-built IRT rather than using a prebuilt one
    192   // downloaded via Chromium's DEPS file.  We use the same environment
    193   // variable that the standalone NaCl PPAPI plugin accepts.
    194   const char* irt_path_var = getenv("NACL_IRT_LIBRARY");
    195   if (irt_path_var != NULL) {
    196     base::FilePath::StringType path_string(
    197         irt_path_var, const_cast<const char*>(strchr(irt_path_var, '\0')));
    198     irt_filepath_ = base::FilePath(path_string);
    199   } else {
    200     base::FilePath plugin_dir;
    201     if (!browser_delegate_->GetPluginDirectory(&plugin_dir)) {
    202       DLOG(ERROR) << "Failed to locate the plugins directory, NaCl disabled.";
    203       MarkAsFailed();
    204       return;
    205     }
    206     irt_filepath_ = plugin_dir.Append(NaClIrtName());
    207   }
    208 }
    209 
    210 #if defined(OS_WIN)
    211 bool NaClBrowser::GetNaCl64ExePath(base::FilePath* exe_path) {
    212   base::FilePath module_path;
    213   if (!PathService::Get(base::FILE_MODULE, &module_path)) {
    214     LOG(ERROR) << "NaCl process launch failed: could not resolve module";
    215     return false;
    216   }
    217   *exe_path = module_path.DirName().Append(L"nacl64");
    218   return true;
    219 }
    220 #endif
    221 
    222 NaClBrowser* NaClBrowser::GetInstance() {
    223   return Singleton<NaClBrowser>::get();
    224 }
    225 
    226 bool NaClBrowser::IsReady() const {
    227   return (IsOk() &&
    228           irt_state_ == NaClResourceReady &&
    229           validation_cache_state_ == NaClResourceReady);
    230 }
    231 
    232 bool NaClBrowser::IsOk() const {
    233   return ok_;
    234 }
    235 
    236 base::PlatformFile NaClBrowser::IrtFile() const {
    237   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
    238   CHECK_EQ(irt_state_, NaClResourceReady);
    239   CHECK_NE(irt_platform_file_, base::kInvalidPlatformFileValue);
    240   return irt_platform_file_;
    241 }
    242 
    243 void NaClBrowser::EnsureAllResourcesAvailable() {
    244   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
    245   EnsureIrtAvailable();
    246   EnsureValidationCacheAvailable();
    247 }
    248 
    249 // Load the IRT async.
    250 void NaClBrowser::EnsureIrtAvailable() {
    251   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
    252   if (IsOk() && irt_state_ == NaClResourceUninitialized) {
    253     irt_state_ = NaClResourceRequested;
    254     // TODO(ncbray) use blocking pool.
    255     if (!base::FileUtilProxy::CreateOrOpen(
    256             content::BrowserThread::GetMessageLoopProxyForThread(
    257                 content::BrowserThread::FILE)
    258                 .get(),
    259             irt_filepath_,
    260             base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
    261             base::Bind(&NaClBrowser::OnIrtOpened,
    262                        weak_factory_.GetWeakPtr()))) {
    263       LOG(ERROR) << "Internal error, NaCl disabled.";
    264       MarkAsFailed();
    265     }
    266   }
    267 }
    268 
    269 void NaClBrowser::OnIrtOpened(base::PlatformFileError error_code,
    270                               base::PassPlatformFile file,
    271                               bool created) {
    272   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
    273   DCHECK_EQ(irt_state_, NaClResourceRequested);
    274   DCHECK(!created);
    275   if (error_code == base::PLATFORM_FILE_OK) {
    276     irt_platform_file_ = file.ReleaseValue();
    277   } else {
    278     LOG(ERROR) << "Failed to open NaCl IRT file \""
    279                << irt_filepath_.LossyDisplayName()
    280                << "\": " << error_code;
    281     MarkAsFailed();
    282   }
    283   irt_state_ = NaClResourceReady;
    284   CheckWaiting();
    285 }
    286 
    287 void NaClBrowser::FireGdbDebugStubPortOpened(int port) {
    288   content::BrowserThread::PostTask(
    289       content::BrowserThread::IO,
    290       FROM_HERE,
    291       base::Bind(debug_stub_port_listener_, port));
    292 }
    293 
    294 bool NaClBrowser::HasGdbDebugStubPortListener() {
    295   return !debug_stub_port_listener_.is_null();
    296 }
    297 
    298 void NaClBrowser::SetGdbDebugStubPortListener(
    299     base::Callback<void(int)> listener) {
    300   debug_stub_port_listener_ = listener;
    301 }
    302 
    303 void NaClBrowser::ClearGdbDebugStubPortListener() {
    304   debug_stub_port_listener_.Reset();
    305 }
    306 
    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;
    316   }
    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.
    321 
    322   validation_cache_file_path_ =
    323       cache_file_path.Append(kValidationCacheFileName);
    324 }
    325 
    326 void NaClBrowser::EnsureValidationCacheAvailable() {
    327   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
    328   if (IsOk() && validation_cache_state_ == NaClResourceUninitialized) {
    329     if (ValidationCacheIsEnabled()) {
    330       validation_cache_state_ = NaClResourceRequested;
    331 
    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();
    344       }
    345     } else {
    346       RunWithoutValidationCache();
    347     }
    348   }
    349 }
    350 
    351 void NaClBrowser::OnValidationCacheLoaded(const std::string *data) {
    352   DCHECK(content::BrowserThread::CurrentlyOn(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;
    357 
    358   if (data->size() == 0) {
    359     // No file found.
    360     validation_cache_.Reset();
    361   } else {
    362     Pickle pickle(data->data(), data->size());
    363     validation_cache_.Deserialize(&pickle);
    364   }
    365   validation_cache_state_ = NaClResourceReady;
    366   CheckWaiting();
    367 }
    368 
    369 void NaClBrowser::RunWithoutValidationCache() {
    370   // Be paranoid.
    371   validation_cache_.Reset();
    372   validation_cache_is_enabled_ = false;
    373   validation_cache_state_ = NaClResourceReady;
    374   CheckWaiting();
    375 }
    376 
    377 void NaClBrowser::CheckWaiting() {
    378   DCHECK(content::BrowserThread::CurrentlyOn(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::MessageLoop::current()->PostTask(FROM_HERE, *iter);
    387     }
    388     waiting_.clear();
    389   }
    390 }
    391 
    392 void NaClBrowser::MarkAsFailed() {
    393   ok_ = false;
    394   CheckWaiting();
    395 }
    396 
    397 void NaClBrowser::WaitForResources(const base::Closure& reply) {
    398   waiting_.push_back(reply);
    399   EnsureAllResourcesAvailable();
    400   CheckWaiting();
    401 }
    402 
    403 const base::FilePath& NaClBrowser::GetIrtFilePath() {
    404   return irt_filepath_;
    405 }
    406 
    407 void NaClBrowser::PutFilePath(const base::FilePath& path, uint64* file_token_lo,
    408                               uint64* file_token_hi) {
    409   DCHECK(content::BrowserThread::CurrentlyOn(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;
    423       }
    424     }
    425   }
    426 }
    427 
    428 bool NaClBrowser::GetFilePath(uint64 file_token_lo, uint64 file_token_hi,
    429                               base::FilePath* path) {
    430   DCHECK(content::BrowserThread::CurrentlyOn(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;
    437   }
    438   *path = iter->second;
    439   path_cache_.Erase(iter);
    440   return true;
    441 }
    442 
    443 
    444 bool NaClBrowser::QueryKnownToValidate(const std::string& signature,
    445                                        bool off_the_record) {
    446   DCHECK(content::BrowserThread::CurrentlyOn(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;
    457   }
    458 }
    459 
    460 void NaClBrowser::SetKnownToValidate(const std::string& signature,
    461                                      bool off_the_record) {
    462   DCHECK(content::BrowserThread::CurrentlyOn(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();
    471   }
    472 }
    473 
    474 void NaClBrowser::ClearValidationCache(const base::Closure& callback) {
    475   DCHECK(content::BrowserThread::CurrentlyOn(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();
    483 
    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));
    499   }
    500 
    501   // Make sure any delayed tasks to persist the cache to the filesystem are
    502   // squelched.
    503   validation_cache_is_modified_ = false;
    504 
    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();
    510   }
    511 }
    512 
    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::MessageLoop::current()->PostDelayedTask(
    518          FROM_HERE,
    519          base::Bind(&NaClBrowser::PersistValidationCache,
    520                     weak_factory_.GetWeakPtr()),
    521          base::TimeDelta::FromMilliseconds(kValidationCacheCoalescingTimeMS));
    522     validation_cache_is_modified_ = true;
    523   }
    524 }
    525 
    526 void NaClBrowser::PersistValidationCache() {
    527   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
    528   // validation_cache_is_modified_ may be false if the cache was cleared while
    529   // this delayed task was pending.
    530   // validation_cache_file_path_ may be empty if something went wrong during
    531   // initialization.
    532   if (validation_cache_is_modified_ && !validation_cache_file_path_.empty()) {
    533     Pickle* pickle = new Pickle();
    534     validation_cache_.Serialize(pickle);
    535 
    536     // Pass the serialized data to another thread to write to disk.  File IO is
    537     // not allowed on the IO thread (which is the thread this method runs on)
    538     // because it can degrade the responsiveness of the browser.
    539     // The task is sequenced so that multiple writes happen in order.
    540     content::BrowserThread::PostBlockingPoolSequencedTask(
    541         kValidationCacheSequenceName,
    542         FROM_HERE,
    543         base::Bind(WriteCache, validation_cache_file_path_,
    544                    base::Owned(pickle)));
    545   }
    546   validation_cache_is_modified_ = false;
    547 }
    548 
    549 void NaClBrowser::OnProcessCrashed() {
    550   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
    551   if (crash_times_.size() == kMaxCrashesPerInterval) {
    552     crash_times_.pop_front();
    553   }
    554   base::Time time = base::Time::Now();
    555   crash_times_.push_back(time);
    556 }
    557 
    558 bool NaClBrowser::IsThrottled() {
    559   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
    560   if (crash_times_.size() != kMaxCrashesPerInterval) {
    561     return false;
    562   }
    563   base::TimeDelta delta = base::Time::Now() - crash_times_.front();
    564   return delta.InSeconds() <= kCrashesIntervalInSeconds;
    565 }
    566 
    567 }  // namespace nacl
    568