Home | History | Annotate | Download | only in nacl_host
      1 // Copyright (c) 2012 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 "chrome/browser/nacl_host/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/strings/string_split.h"
     15 #include "base/win/windows_version.h"
     16 #include "build/build_config.h"
     17 #include "content/public/browser/browser_thread.h"
     18 #include "extensions/common/url_pattern.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 enum ValidationCacheStatus {
     32   CACHE_MISS = 0,
     33   CACHE_HIT,
     34   CACHE_MAX
     35 };
     36 
     37 // Keep the cache bounded to an arbitrary size.  If it's too small, useful
     38 // entries could be evicted when multiple .nexes are loaded at once.  On the
     39 // other hand, entries are not always claimed (and hence removed), so the size
     40 // of the cache will likely saturate at its maximum size.
     41 // Entries may not be claimed for two main reasons. 1) the NaCl process could
     42 // be killed while it is loading.  2) the trusted NaCl plugin opens files using
     43 // the code path but doesn't resolve them.
     44 // TODO(ncbray) don't cache files that the plugin will not resolve.
     45 const int kFilePathCacheSize = 100;
     46 
     47 const base::FilePath::StringType NaClIrtName() {
     48   base::FilePath::StringType irt_name(FILE_PATH_LITERAL("nacl_irt_"));
     49 
     50 #if defined(ARCH_CPU_X86_FAMILY)
     51 #if defined(ARCH_CPU_X86_64)
     52   bool is64 = true;
     53 #elif defined(OS_WIN)
     54   bool is64 = (base::win::OSInfo::GetInstance()->wow64_status() ==
     55                base::win::OSInfo::WOW64_ENABLED);
     56 #else
     57   bool is64 = false;
     58 #endif
     59   if (is64)
     60     irt_name.append(FILE_PATH_LITERAL("x86_64"));
     61   else
     62     irt_name.append(FILE_PATH_LITERAL("x86_32"));
     63 
     64 #elif defined(ARCH_CPU_ARMEL)
     65   // TODO(mcgrathr): Eventually we'll need to distinguish arm32 vs thumb2.
     66   // That may need to be based on the actual nexe rather than a static
     67   // choice, which would require substantial refactoring.
     68   irt_name.append(FILE_PATH_LITERAL("arm"));
     69 #elif defined(ARCH_CPU_MIPSEL)
     70   irt_name.append(FILE_PATH_LITERAL("mips32"));
     71 #else
     72 #error Add support for your architecture to NaCl IRT file selection
     73 #endif
     74   irt_name.append(FILE_PATH_LITERAL(".nexe"));
     75   return irt_name;
     76 }
     77 
     78 bool CheckEnvVar(const char* name, bool default_value) {
     79   bool result = default_value;
     80   const char* var = getenv(name);
     81   if (var && strlen(var) > 0) {
     82     result = var[0] != '0';
     83   }
     84   return result;
     85 }
     86 
     87 void ReadCache(const base::FilePath& filename, std::string* data) {
     88   if (!file_util::ReadFileToString(filename, data)) {
     89     // Zero-size data used as an in-band error code.
     90     data->clear();
     91   }
     92 }
     93 
     94 void WriteCache(const base::FilePath& filename, const Pickle* pickle) {
     95   file_util::WriteFile(filename, static_cast<const char*>(pickle->data()),
     96                        pickle->size());
     97 }
     98 
     99 void RemoveCache(const base::FilePath& filename,
    100                  const base::Closure& callback) {
    101   base::DeleteFile(filename, false);
    102   content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
    103                                    callback);
    104 }
    105 
    106 void LogCacheQuery(ValidationCacheStatus status) {
    107   UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Query", status, CACHE_MAX);
    108 }
    109 
    110 void LogCacheSet(ValidationCacheStatus status) {
    111   // Bucket zero is reserved for future use.
    112   UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Set", status, CACHE_MAX);
    113 }
    114 
    115 }  // namespace
    116 
    117 namespace nacl {
    118 
    119 void OpenNaClExecutableImpl(const base::FilePath& file_path,
    120                             base::PlatformFile* file) {
    121   // Get a file descriptor. On Windows, we need 'GENERIC_EXECUTE' in order to
    122   // memory map the executable.
    123   // IMPORTANT: This file descriptor must not have write access - that could
    124   // allow a NaCl inner sandbox escape.
    125   base::PlatformFileError error_code;
    126   *file = base::CreatePlatformFile(
    127       file_path,
    128       (base::PLATFORM_FILE_OPEN |
    129        base::PLATFORM_FILE_READ |
    130        base::PLATFORM_FILE_EXECUTE),  // Windows only flag.
    131       NULL,
    132       &error_code);
    133   if (error_code != base::PLATFORM_FILE_OK) {
    134     *file = base::kInvalidPlatformFileValue;
    135     return;
    136   }
    137   // Check that the file does not reference a directory. Returning a descriptor
    138   // to an extension directory could allow an outer sandbox escape. openat(...)
    139   // could be used to traverse into the file system.
    140   base::PlatformFileInfo file_info;
    141   if (!base::GetPlatformFileInfo(*file, &file_info) ||
    142       file_info.is_directory) {
    143     base::ClosePlatformFile(*file);
    144     *file = base::kInvalidPlatformFileValue;
    145     return;
    146   }
    147 }
    148 
    149 } // namespace nacl
    150 
    151 NaClBrowser::NaClBrowser()
    152     : weak_factory_(this),
    153       irt_platform_file_(base::kInvalidPlatformFileValue),
    154       irt_filepath_(),
    155       irt_state_(NaClResourceUninitialized),
    156       debug_patterns_(),
    157       inverse_debug_patterns_(false),
    158       validation_cache_file_path_(),
    159       validation_cache_is_enabled_(
    160           CheckEnvVar("NACL_VALIDATION_CACHE",
    161                       kValidationCacheEnabledByDefault)),
    162       validation_cache_is_modified_(false),
    163       validation_cache_state_(NaClResourceUninitialized),
    164       path_cache_(kFilePathCacheSize),
    165       ok_(true) {
    166 }
    167 
    168 void NaClBrowser::SetDelegate(NaClBrowserDelegate* delegate) {
    169   NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
    170   nacl_browser->browser_delegate_.reset(delegate);
    171 }
    172 
    173 NaClBrowserDelegate* NaClBrowser::GetDelegate() {
    174   DCHECK(GetInstance()->browser_delegate_.get() != NULL);
    175   return GetInstance()->browser_delegate_.get();
    176 }
    177 
    178 void NaClBrowser::EarlyStartup() {
    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   CHECK_EQ(irt_state_, NaClResourceReady);
    238   CHECK_NE(irt_platform_file_, base::kInvalidPlatformFileValue);
    239   return irt_platform_file_;
    240 }
    241 
    242 void NaClBrowser::EnsureAllResourcesAvailable() {
    243   EnsureIrtAvailable();
    244   EnsureValidationCacheAvailable();
    245 }
    246 
    247 // Load the IRT async.
    248 void NaClBrowser::EnsureIrtAvailable() {
    249   if (IsOk() && irt_state_ == NaClResourceUninitialized) {
    250     irt_state_ = NaClResourceRequested;
    251     // TODO(ncbray) use blocking pool.
    252     if (!base::FileUtilProxy::CreateOrOpen(
    253             content::BrowserThread::GetMessageLoopProxyForThread(
    254                 content::BrowserThread::FILE)
    255                 .get(),
    256             irt_filepath_,
    257             base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
    258             base::Bind(&NaClBrowser::OnIrtOpened,
    259                        weak_factory_.GetWeakPtr()))) {
    260       LOG(ERROR) << "Internal error, NaCl disabled.";
    261       MarkAsFailed();
    262     }
    263   }
    264 }
    265 
    266 void NaClBrowser::OnIrtOpened(base::PlatformFileError error_code,
    267                               base::PassPlatformFile file,
    268                               bool created) {
    269   DCHECK_EQ(irt_state_, NaClResourceRequested);
    270   DCHECK(!created);
    271   if (error_code == base::PLATFORM_FILE_OK) {
    272     irt_platform_file_ = file.ReleaseValue();
    273   } else {
    274     LOG(ERROR) << "Failed to open NaCl IRT file \""
    275                << irt_filepath_.LossyDisplayName()
    276                << "\": " << error_code;
    277     MarkAsFailed();
    278   }
    279   irt_state_ = NaClResourceReady;
    280   CheckWaiting();
    281 }
    282 
    283 void NaClBrowser::SetDebugPatterns(std::string debug_patterns) {
    284   if (!debug_patterns.empty() && debug_patterns[0] == '!') {
    285     inverse_debug_patterns_ = true;
    286     debug_patterns.erase(0, 1);
    287   }
    288   if (debug_patterns.empty()) {
    289     return;
    290   }
    291   std::vector<std::string> patterns;
    292   base::SplitString(debug_patterns, ',', &patterns);
    293   for (std::vector<std::string>::iterator iter = patterns.begin();
    294        iter != patterns.end(); ++iter) {
    295     URLPattern pattern;
    296     if (pattern.Parse(*iter) == URLPattern::PARSE_SUCCESS) {
    297       // If URL pattern has scheme equal to *, Parse method resets valid
    298       // schemes mask to http and https only, so we need to reset it after
    299       // Parse to include chrome-extension scheme that can be used by NaCl
    300       // manifest files.
    301       pattern.SetValidSchemes(URLPattern::SCHEME_ALL);
    302       debug_patterns_.push_back(pattern);
    303     }
    304   }
    305 }
    306 
    307 bool NaClBrowser::URLMatchesDebugPatterns(GURL manifest_url) {
    308   // Empty patterns are forbidden so we ignore them.
    309   if (debug_patterns_.empty()) {
    310     return true;
    311   }
    312   bool matches = false;
    313   for (std::vector<URLPattern>::iterator iter = debug_patterns_.begin();
    314        iter != debug_patterns_.end(); ++iter) {
    315     if (iter->MatchesURL(manifest_url)) {
    316       matches = true;
    317       break;
    318     }
    319   }
    320   if (inverse_debug_patterns_) {
    321     return !matches;
    322   } else {
    323     return matches;
    324   }
    325 }
    326 
    327 void NaClBrowser::FireGdbDebugStubPortOpened(int port) {
    328   content::BrowserThread::PostTask(
    329       content::BrowserThread::IO,
    330       FROM_HERE,
    331       base::Bind(debug_stub_port_listener_, port));
    332 }
    333 
    334 bool NaClBrowser::HasGdbDebugStubPortListener() {
    335   return !debug_stub_port_listener_.is_null();
    336 }
    337 
    338 void NaClBrowser::SetGdbDebugStubPortListener(
    339     base::Callback<void(int)> listener) {
    340   debug_stub_port_listener_ = listener;
    341 }
    342 
    343 void NaClBrowser::ClearGdbDebugStubPortListener() {
    344   debug_stub_port_listener_.Reset();
    345 }
    346 
    347 void NaClBrowser::InitValidationCacheFilePath() {
    348   // Determine where the validation cache resides in the file system.  It
    349   // exists in Chrome's cache directory and is not tied to any specific
    350   // profile.
    351   // Start by finding the user data directory.
    352   base::FilePath user_data_dir;
    353   if (!browser_delegate_->GetUserDirectory(&user_data_dir)) {
    354     RunWithoutValidationCache();
    355     return;
    356   }
    357   // The cache directory may or may not be the user data directory.
    358   base::FilePath cache_file_path;
    359   browser_delegate_->GetCacheDirectory(&cache_file_path);
    360   // Append the base file name to the cache directory.
    361 
    362   validation_cache_file_path_ =
    363       cache_file_path.Append(kValidationCacheFileName);
    364 }
    365 
    366 void NaClBrowser::EnsureValidationCacheAvailable() {
    367   if (IsOk() && validation_cache_state_ == NaClResourceUninitialized) {
    368     if (ValidationCacheIsEnabled()) {
    369       validation_cache_state_ = NaClResourceRequested;
    370 
    371       // Structure for carrying data between the callbacks.
    372       std::string* data = new std::string();
    373       // We can get away not giving this a sequence ID because this is the first
    374       // task and further file access will not occur until after we get a
    375       // response.
    376       if (!content::BrowserThread::PostBlockingPoolTaskAndReply(
    377               FROM_HERE,
    378               base::Bind(ReadCache, validation_cache_file_path_, data),
    379               base::Bind(&NaClBrowser::OnValidationCacheLoaded,
    380                          weak_factory_.GetWeakPtr(),
    381                          base::Owned(data)))) {
    382         RunWithoutValidationCache();
    383       }
    384     } else {
    385       RunWithoutValidationCache();
    386     }
    387   }
    388 }
    389 
    390 void NaClBrowser::OnValidationCacheLoaded(const std::string *data) {
    391   // Did the cache get cleared before the load completed?  If so, ignore the
    392   // incoming data.
    393   if (validation_cache_state_ == NaClResourceReady)
    394     return;
    395 
    396   if (data->size() == 0) {
    397     // No file found.
    398     validation_cache_.Reset();
    399   } else {
    400     Pickle pickle(data->data(), data->size());
    401     validation_cache_.Deserialize(&pickle);
    402   }
    403   validation_cache_state_ = NaClResourceReady;
    404   CheckWaiting();
    405 }
    406 
    407 void NaClBrowser::RunWithoutValidationCache() {
    408   // Be paranoid.
    409   validation_cache_.Reset();
    410   validation_cache_is_enabled_ = false;
    411   validation_cache_state_ = NaClResourceReady;
    412   CheckWaiting();
    413 }
    414 
    415 void NaClBrowser::CheckWaiting() {
    416   if (!IsOk() || IsReady()) {
    417     // Queue the waiting tasks into the message loop.  This helps avoid
    418     // re-entrancy problems that could occur if the closure was invoked
    419     // directly.  For example, this could result in use-after-free of the
    420     // process host.
    421     for (std::vector<base::Closure>::iterator iter = waiting_.begin();
    422          iter != waiting_.end(); ++iter) {
    423       base::MessageLoop::current()->PostTask(FROM_HERE, *iter);
    424     }
    425     waiting_.clear();
    426   }
    427 }
    428 
    429 void NaClBrowser::MarkAsFailed() {
    430   ok_ = false;
    431   CheckWaiting();
    432 }
    433 
    434 void NaClBrowser::WaitForResources(const base::Closure& reply) {
    435   waiting_.push_back(reply);
    436   EnsureAllResourcesAvailable();
    437   CheckWaiting();
    438 }
    439 
    440 const base::FilePath& NaClBrowser::GetIrtFilePath() {
    441   return irt_filepath_;
    442 }
    443 
    444 void NaClBrowser::PutFilePath(const base::FilePath& path, uint64* file_token_lo,
    445                               uint64* file_token_hi) {
    446   while (true) {
    447     uint64 file_token[2] = {base::RandUint64(), base::RandUint64()};
    448     // A zero file_token indicates there is no file_token, if we get zero, ask
    449     // for another number.
    450     if (file_token[0] != 0 || file_token[1] != 0) {
    451       // If the file_token is in use, ask for another number.
    452       std::string key(reinterpret_cast<char*>(file_token), sizeof(file_token));
    453       PathCacheType::iterator iter = path_cache_.Peek(key);
    454       if (iter == path_cache_.end()) {
    455         path_cache_.Put(key, path);
    456         *file_token_lo = file_token[0];
    457         *file_token_hi = file_token[1];
    458         break;
    459       }
    460     }
    461   }
    462 }
    463 
    464 bool NaClBrowser::GetFilePath(uint64 file_token_lo, uint64 file_token_hi,
    465                               base::FilePath* path) {
    466   uint64 file_token[2] = {file_token_lo, file_token_hi};
    467   std::string key(reinterpret_cast<char*>(file_token), sizeof(file_token));
    468   PathCacheType::iterator iter = path_cache_.Peek(key);
    469   if (iter == path_cache_.end()) {
    470     *path = base::FilePath(FILE_PATH_LITERAL(""));
    471     return false;
    472   }
    473   *path = iter->second;
    474   path_cache_.Erase(iter);
    475   return true;
    476 }
    477 
    478 
    479 bool NaClBrowser::QueryKnownToValidate(const std::string& signature,
    480                                        bool off_the_record) {
    481   if (off_the_record) {
    482     // If we're off the record, don't reorder the main cache.
    483     return validation_cache_.QueryKnownToValidate(signature, false) ||
    484         off_the_record_validation_cache_.QueryKnownToValidate(signature, true);
    485   } else {
    486     bool result = validation_cache_.QueryKnownToValidate(signature, true);
    487     LogCacheQuery(result ? CACHE_HIT : CACHE_MISS);
    488     // Queries can modify the MRU order of the cache.
    489     MarkValidationCacheAsModified();
    490     return result;
    491   }
    492 }
    493 
    494 void NaClBrowser::SetKnownToValidate(const std::string& signature,
    495                                      bool off_the_record) {
    496   if (off_the_record) {
    497     off_the_record_validation_cache_.SetKnownToValidate(signature);
    498   } else {
    499     validation_cache_.SetKnownToValidate(signature);
    500     // The number of sets should be equal to the number of cache misses, minus
    501     // validation failures and successful validations where stubout occurs.
    502     LogCacheSet(CACHE_HIT);
    503     MarkValidationCacheAsModified();
    504   }
    505 }
    506 
    507 void NaClBrowser::ClearValidationCache(const base::Closure& callback) {
    508   // Note: this method may be called before EnsureValidationCacheAvailable has
    509   // been invoked.  In other words, this method may be called before any NaCl
    510   // processes have been created.  This method must succeed and invoke the
    511   // callback in such a case.  If it does not invoke the callback, Chrome's UI
    512   // will hang in that case.
    513   validation_cache_.Reset();
    514   off_the_record_validation_cache_.Reset();
    515 
    516   if (validation_cache_file_path_.empty()) {
    517     // Can't figure out what file to remove, but don't drop the callback.
    518     content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
    519                                      callback);
    520   } else {
    521     // Delegate the removal of the cache from the filesystem to another thread
    522     // to avoid blocking the IO thread.
    523     // This task is dispatched immediately, not delayed and coalesced, because
    524     // the user interface for cache clearing is likely waiting for the callback.
    525     // In addition, we need to make sure the cache is actually cleared before
    526     // invoking the callback to meet the implicit guarantees of the UI.
    527     content::BrowserThread::PostBlockingPoolSequencedTask(
    528         kValidationCacheSequenceName,
    529         FROM_HERE,
    530         base::Bind(RemoveCache, validation_cache_file_path_, callback));
    531   }
    532 
    533   // Make sure any delayed tasks to persist the cache to the filesystem are
    534   // squelched.
    535   validation_cache_is_modified_ = false;
    536 
    537   // If the cache is cleared before it is loaded from the filesystem, act as if
    538   // we just loaded an empty cache.
    539   if (validation_cache_state_ != NaClResourceReady) {
    540     validation_cache_state_ = NaClResourceReady;
    541     CheckWaiting();
    542   }
    543 }
    544 
    545 void NaClBrowser::MarkValidationCacheAsModified() {
    546   if (!validation_cache_is_modified_) {
    547     // Wait before persisting to disk.  This can coalesce multiple cache
    548     // modifications info a single disk write.
    549     base::MessageLoop::current()->PostDelayedTask(
    550          FROM_HERE,
    551          base::Bind(&NaClBrowser::PersistValidationCache,
    552                     weak_factory_.GetWeakPtr()),
    553          base::TimeDelta::FromMilliseconds(kValidationCacheCoalescingTimeMS));
    554     validation_cache_is_modified_ = true;
    555   }
    556 }
    557 
    558 void NaClBrowser::PersistValidationCache() {
    559   // validation_cache_is_modified_ may be false if the cache was cleared while
    560   // this delayed task was pending.
    561   // validation_cache_file_path_ may be empty if something went wrong during
    562   // initialization.
    563   if (validation_cache_is_modified_ && !validation_cache_file_path_.empty()) {
    564     Pickle* pickle = new Pickle();
    565     validation_cache_.Serialize(pickle);
    566 
    567     // Pass the serialized data to another thread to write to disk.  File IO is
    568     // not allowed on the IO thread (which is the thread this method runs on)
    569     // because it can degrade the responsiveness of the browser.
    570     // The task is sequenced so that multiple writes happen in order.
    571     content::BrowserThread::PostBlockingPoolSequencedTask(
    572         kValidationCacheSequenceName,
    573         FROM_HERE,
    574         base::Bind(WriteCache, validation_cache_file_path_,
    575                    base::Owned(pickle)));
    576   }
    577   validation_cache_is_modified_ = false;
    578 }
    579