Home | History | Annotate | Download | only in renderer
      1 // Copyright 2014 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/renderer/nexe_load_manager.h"
      6 
      7 #include "base/command_line.h"
      8 #include "base/containers/scoped_ptr_hash_map.h"
      9 #include "base/lazy_instance.h"
     10 #include "base/logging.h"
     11 #include "base/metrics/histogram.h"
     12 #include "base/strings/string_tokenizer.h"
     13 #include "base/strings/string_util.h"
     14 #include "components/nacl/common/nacl_host_messages.h"
     15 #include "components/nacl/common/nacl_types.h"
     16 #include "components/nacl/renderer/histogram.h"
     17 #include "components/nacl/renderer/manifest_service_channel.h"
     18 #include "components/nacl/renderer/platform_info.h"
     19 #include "components/nacl/renderer/pnacl_translation_resource_host.h"
     20 #include "components/nacl/renderer/progress_event.h"
     21 #include "components/nacl/renderer/trusted_plugin_channel.h"
     22 #include "content/public/common/content_client.h"
     23 #include "content/public/common/content_switches.h"
     24 #include "content/public/common/sandbox_init.h"
     25 #include "content/public/renderer/pepper_plugin_instance.h"
     26 #include "content/public/renderer/render_thread.h"
     27 #include "content/public/renderer/render_view.h"
     28 #include "content/public/renderer/renderer_ppapi_host.h"
     29 #include "ppapi/c/pp_bool.h"
     30 #include "ppapi/c/private/pp_file_handle.h"
     31 #include "ppapi/shared_impl/ppapi_globals.h"
     32 #include "ppapi/shared_impl/ppapi_permissions.h"
     33 #include "ppapi/shared_impl/ppapi_preferences.h"
     34 #include "ppapi/shared_impl/scoped_pp_var.h"
     35 #include "ppapi/shared_impl/var.h"
     36 #include "ppapi/shared_impl/var_tracker.h"
     37 #include "ppapi/thunk/enter.h"
     38 #include "third_party/WebKit/public/web/WebDocument.h"
     39 #include "third_party/WebKit/public/web/WebElement.h"
     40 #include "third_party/WebKit/public/web/WebPluginContainer.h"
     41 #include "third_party/WebKit/public/web/WebView.h"
     42 #include "v8/include/v8.h"
     43 
     44 namespace nacl {
     45 
     46 namespace {
     47 
     48 const char* const kTypeAttribute = "type";
     49 // The "src" attribute of the <embed> tag.  The value is expected to be either
     50 // a URL or URI pointing to the manifest file (which is expected to contain
     51 // JSON matching ISAs with .nexe URLs).
     52 const char* const kSrcManifestAttribute = "src";
     53 // The "nacl" attribute of the <embed> tag.  We use the value of this attribute
     54 // to find the manifest file when NaCl is registered as a plug-in for another
     55 // MIME type because the "src" attribute is used to supply us with the resource
     56 // of that MIME type that we're supposed to display.
     57 const char* const kNaClManifestAttribute = "nacl";
     58 // Define an argument name to enable 'dev' interfaces. To make sure it doesn't
     59 // collide with any user-defined HTML attribute, make the first character '@'.
     60 const char* const kDevAttribute = "@dev";
     61 
     62 const char* const kNaClMIMEType = "application/x-nacl";
     63 const char* const kPNaClMIMEType = "application/x-pnacl";
     64 
     65 static int GetRoutingID(PP_Instance instance) {
     66   // Check that we are on the main renderer thread.
     67   DCHECK(content::RenderThread::Get());
     68   content::RendererPpapiHost *host =
     69       content::RendererPpapiHost::GetForPPInstance(instance);
     70   if (!host)
     71     return 0;
     72   return host->GetRoutingIDForWidget(instance);
     73 }
     74 
     75 std::string LookupAttribute(const std::map<std::string, std::string>& args,
     76                             const std::string& key) {
     77   std::map<std::string, std::string>::const_iterator it = args.find(key);
     78   if (it != args.end())
     79     return it->second;
     80   return std::string();
     81 }
     82 
     83 typedef base::ScopedPtrHashMap<PP_Instance, NexeLoadManager> NexeLoadManagerMap;
     84 base::LazyInstance<NexeLoadManagerMap> g_load_manager_map =
     85     LAZY_INSTANCE_INITIALIZER;
     86 
     87 }  // namespace
     88 
     89 NexeLoadManager::NexeLoadManager(
     90     PP_Instance pp_instance)
     91     : pp_instance_(pp_instance),
     92       nacl_ready_state_(PP_NACL_READY_STATE_UNSENT),
     93       nexe_error_reported_(false),
     94       is_installed_(false),
     95       exit_status_(-1),
     96       nexe_size_(0),
     97       plugin_instance_(content::PepperPluginInstance::Get(pp_instance)),
     98       crash_info_shmem_handle_(base::SharedMemory::NULLHandle()),
     99       weak_factory_(this) {
    100   set_exit_status(-1);
    101   SetLastError("");
    102   HistogramEnumerateOsArch(GetSandboxArch());
    103   if (plugin_instance_) {
    104     plugin_base_url_ =
    105         plugin_instance_->GetContainer()->element().document().url();
    106   }
    107 }
    108 
    109 NexeLoadManager::~NexeLoadManager() {
    110   if (!nexe_error_reported_) {
    111     base::TimeDelta uptime = base::Time::Now() - ready_time_;
    112     HistogramTimeLarge("NaCl.ModuleUptime.Normal", uptime.InMilliseconds());
    113   }
    114   if (base::SharedMemory::IsHandleValid(crash_info_shmem_handle_))
    115     base::SharedMemory::CloseHandle(crash_info_shmem_handle_);
    116 }
    117 
    118 void NexeLoadManager::Create(PP_Instance instance) {
    119   scoped_ptr<NexeLoadManager> new_load_manager(new NexeLoadManager(instance));
    120   NexeLoadManagerMap& map = g_load_manager_map.Get();
    121   DLOG_IF(ERROR, map.count(instance) != 0) << "Instance count should be 0";
    122   map.add(instance, new_load_manager.Pass());
    123 }
    124 
    125 NexeLoadManager* NexeLoadManager::Get(PP_Instance instance) {
    126   NexeLoadManagerMap& map = g_load_manager_map.Get();
    127   NexeLoadManagerMap::iterator iter = map.find(instance);
    128   if (iter != map.end())
    129     return iter->second;
    130   return NULL;
    131 }
    132 
    133 void NexeLoadManager::Delete(PP_Instance instance) {
    134   NexeLoadManagerMap& map = g_load_manager_map.Get();
    135   // The erase may call NexeLoadManager's destructor prior to removing it from
    136   // the map. In that case, it is possible for the trusted Plugin to re-enter
    137   // the NexeLoadManager (e.g., by calling ReportLoadError). Passing out the
    138   // NexeLoadManager to a local scoped_ptr just ensures that its entry is gone
    139   // from the map prior to the destructor being invoked.
    140   scoped_ptr<NexeLoadManager> temp(map.take(instance));
    141   map.erase(instance);
    142 }
    143 
    144 void NexeLoadManager::NexeFileDidOpen(int32_t pp_error,
    145                                       const base::File& file,
    146                                       int32_t http_status,
    147                                       int64_t nexe_bytes_read,
    148                                       const std::string& url,
    149                                       base::TimeDelta time_since_open) {
    150   // Check that we are on the main renderer thread.
    151   DCHECK(content::RenderThread::Get());
    152   VLOG(1) << "Plugin::NexeFileDidOpen (pp_error=" << pp_error << ")";
    153   HistogramHTTPStatusCode(
    154       is_installed_ ? "NaCl.HttpStatusCodeClass.Nexe.InstalledApp" :
    155                       "NaCl.HttpStatusCodeClass.Nexe.NotInstalledApp",
    156       http_status);
    157 
    158   if (pp_error != PP_OK || !file.IsValid()) {
    159     if (pp_error == PP_ERROR_ABORTED) {
    160       ReportLoadAbort();
    161     } else if (pp_error == PP_ERROR_NOACCESS) {
    162       ReportLoadError(PP_NACL_ERROR_NEXE_NOACCESS_URL,
    163                       "access to nexe url was denied.");
    164     } else {
    165       ReportLoadError(PP_NACL_ERROR_NEXE_LOAD_URL,
    166                       "could not load nexe url.");
    167     }
    168   } else if (nexe_bytes_read == -1) {
    169     ReportLoadError(PP_NACL_ERROR_NEXE_STAT, "could not stat nexe file.");
    170   } else {
    171     // TODO(dmichael): Can we avoid stashing away so much state?
    172     nexe_size_ = nexe_bytes_read;
    173     HistogramSizeKB("NaCl.Perf.Size.Nexe",
    174                     static_cast<int32_t>(nexe_size_ / 1024));
    175     HistogramStartupTimeMedium(
    176         "NaCl.Perf.StartupTime.NexeDownload", time_since_open, nexe_size_);
    177 
    178     // Inform JavaScript that we successfully downloaded the nacl module.
    179     ProgressEvent progress_event(PP_NACL_EVENT_PROGRESS, url, true, nexe_size_,
    180                                  nexe_size_);
    181     DispatchProgressEvent(pp_instance_, progress_event);
    182     load_start_ = base::Time::Now();
    183   }
    184 }
    185 
    186 void NexeLoadManager::ReportLoadSuccess(const std::string& url,
    187                                         uint64_t loaded_bytes,
    188                                         uint64_t total_bytes) {
    189   ready_time_ = base::Time::Now();
    190   if (!IsPNaCl()) {
    191     base::TimeDelta load_module_time = ready_time_ - load_start_;
    192     HistogramStartupTimeSmall(
    193         "NaCl.Perf.StartupTime.LoadModule", load_module_time, nexe_size_);
    194     HistogramStartupTimeMedium(
    195         "NaCl.Perf.StartupTime.Total", ready_time_ - init_time_, nexe_size_);
    196   }
    197 
    198   // Check that we are on the main renderer thread.
    199   DCHECK(content::RenderThread::Get());
    200   set_nacl_ready_state(PP_NACL_READY_STATE_DONE);
    201 
    202   // Inform JavaScript that loading was successful and is complete.
    203   ProgressEvent load_event(PP_NACL_EVENT_LOAD, url, true, loaded_bytes,
    204                            total_bytes);
    205   DispatchProgressEvent(pp_instance_, load_event);
    206 
    207   ProgressEvent loadend_event(PP_NACL_EVENT_LOADEND, url, true, loaded_bytes,
    208                               total_bytes);
    209   DispatchProgressEvent(pp_instance_, loadend_event);
    210 
    211   // UMA
    212   HistogramEnumerateLoadStatus(PP_NACL_ERROR_LOAD_SUCCESS, is_installed_);
    213 }
    214 
    215 void NexeLoadManager::ReportLoadError(PP_NaClError error,
    216                                       const std::string& error_message) {
    217   ReportLoadError(error, error_message, error_message);
    218 }
    219 
    220 void NexeLoadManager::ReportLoadError(PP_NaClError error,
    221                                       const std::string& error_message,
    222                                       const std::string& console_message) {
    223   // Check that we are on the main renderer thread.
    224   DCHECK(content::RenderThread::Get());
    225 
    226   if (error == PP_NACL_ERROR_MANIFEST_PROGRAM_MISSING_ARCH) {
    227     // A special case: the manifest may otherwise be valid but is missing
    228     // a program/file compatible with the user's sandbox.
    229     IPC::Sender* sender = content::RenderThread::Get();
    230     sender->Send(
    231         new NaClHostMsg_MissingArchError(GetRoutingID(pp_instance_)));
    232   }
    233   set_nacl_ready_state(PP_NACL_READY_STATE_DONE);
    234   nexe_error_reported_ = true;
    235 
    236   // We must set all properties before calling DispatchEvent so that when an
    237   // event handler runs, the properties reflect the current load state.
    238   std::string error_string = std::string("NaCl module load failed: ") +
    239       std::string(error_message);
    240   SetLastError(error_string);
    241 
    242   // Inform JavaScript that loading encountered an error and is complete.
    243   DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_ERROR));
    244   DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_LOADEND));
    245 
    246   HistogramEnumerateLoadStatus(error, is_installed_);
    247   LogToConsole(console_message);
    248 }
    249 
    250 void NexeLoadManager::ReportLoadAbort() {
    251   // Check that we are on the main renderer thread.
    252   DCHECK(content::RenderThread::Get());
    253 
    254   // Set the readyState attribute to indicate we need to start over.
    255   set_nacl_ready_state(PP_NACL_READY_STATE_DONE);
    256   nexe_error_reported_ = true;
    257 
    258   // Report an error in lastError and on the JavaScript console.
    259   std::string error_string("NaCl module load failed: user aborted");
    260   SetLastError(error_string);
    261 
    262   // Inform JavaScript that loading was aborted and is complete.
    263   DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_ABORT));
    264   DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_LOADEND));
    265 
    266   HistogramEnumerateLoadStatus(PP_NACL_ERROR_LOAD_ABORTED, is_installed_);
    267   LogToConsole(error_string);
    268 }
    269 
    270 void NexeLoadManager::NexeDidCrash() {
    271   VLOG(1) << "Plugin::NexeDidCrash: crash event!";
    272     // The NaCl module voluntarily exited.  However, this is still a
    273     // crash from the point of view of Pepper, since PPAPI plugins are
    274     // event handlers and should never exit.
    275   VLOG_IF(1, exit_status_ != -1)
    276       << "Plugin::NexeDidCrash: nexe exited with status " << exit_status_
    277       << " so this is a \"controlled crash\".";
    278   // If the crash occurs during load, we just want to report an error
    279   // that fits into our load progress event grammar.  If the crash
    280   // occurs after loaded/loadend, then we use ReportDeadNexe to send a
    281   // "crash" event.
    282   if (nexe_error_reported_) {
    283     VLOG(1) << "Plugin::NexeDidCrash: error already reported; suppressing";
    284   } else {
    285     if (nacl_ready_state_ == PP_NACL_READY_STATE_DONE) {
    286       ReportDeadNexe();
    287     } else {
    288       ReportLoadError(PP_NACL_ERROR_START_PROXY_CRASH,
    289                       "Nexe crashed during startup");
    290     }
    291   }
    292   // In all cases, try to grab the crash log.  The first error
    293   // reported may have come from the start_module RPC reply indicating
    294   // a validation error or something similar, which wouldn't grab the
    295   // crash log.  In the event that this is called twice, the second
    296   // invocation will just be a no-op, since the entire crash log will
    297   // have been received and we'll just get an EOF indication.
    298 
    299   base::SharedMemory shmem(crash_info_shmem_handle_, true);
    300   if (shmem.Map(kNaClCrashInfoShmemSize)) {
    301     uint32_t crash_log_length;
    302     // We cast the length value to volatile here to prevent the compiler from
    303     // reordering instructions in a way that could introduce a TOCTTOU race.
    304     crash_log_length = *(static_cast<volatile uint32_t*>(shmem.memory()));
    305     crash_log_length = std::min<uint32_t>(crash_log_length,
    306                                           kNaClCrashInfoMaxLogSize);
    307 
    308     scoped_ptr<char[]> crash_log_data(new char[kNaClCrashInfoShmemSize]);
    309     memcpy(crash_log_data.get(),
    310            static_cast<char*>(shmem.memory()) + sizeof(uint32_t),
    311            crash_log_length);
    312     std::string crash_log(crash_log_data.get(), crash_log_length);
    313     CopyCrashLogToJsConsole(crash_log);
    314   }
    315 }
    316 
    317 void NexeLoadManager::set_trusted_plugin_channel(
    318     scoped_ptr<TrustedPluginChannel> channel) {
    319   trusted_plugin_channel_ = channel.Pass();
    320 }
    321 
    322 void NexeLoadManager::set_manifest_service_channel(
    323     scoped_ptr<ManifestServiceChannel> channel) {
    324   manifest_service_channel_ = channel.Pass();
    325 }
    326 
    327 PP_NaClReadyState NexeLoadManager::nacl_ready_state() {
    328   return nacl_ready_state_;
    329 }
    330 
    331 void NexeLoadManager::set_nacl_ready_state(PP_NaClReadyState ready_state) {
    332   nacl_ready_state_ = ready_state;
    333   ppapi::ScopedPPVar ready_state_name(
    334       ppapi::ScopedPPVar::PassRef(),
    335       ppapi::StringVar::StringToPPVar("readyState"));
    336   SetReadOnlyProperty(ready_state_name.get(), PP_MakeInt32(ready_state));
    337 }
    338 
    339 void NexeLoadManager::SetLastError(const std::string& error) {
    340   ppapi::ScopedPPVar error_name_var(
    341       ppapi::ScopedPPVar::PassRef(),
    342       ppapi::StringVar::StringToPPVar("lastError"));
    343   ppapi::ScopedPPVar error_var(
    344       ppapi::ScopedPPVar::PassRef(),
    345       ppapi::StringVar::StringToPPVar(error));
    346   SetReadOnlyProperty(error_name_var.get(), error_var.get());
    347 }
    348 
    349 void NexeLoadManager::SetReadOnlyProperty(PP_Var key, PP_Var value) {
    350   plugin_instance_->SetEmbedProperty(key, value);
    351 }
    352 
    353 void NexeLoadManager::LogToConsole(const std::string& message) {
    354   ppapi::PpapiGlobals::Get()->LogWithSource(
    355       pp_instance_, PP_LOGLEVEL_LOG, std::string("NativeClient"), message);
    356 }
    357 
    358 void NexeLoadManager::set_exit_status(int exit_status) {
    359   exit_status_ = exit_status;
    360   ppapi::ScopedPPVar exit_status_name_var(
    361       ppapi::ScopedPPVar::PassRef(),
    362       ppapi::StringVar::StringToPPVar("exitStatus"));
    363   SetReadOnlyProperty(exit_status_name_var.get(), PP_MakeInt32(exit_status));
    364 }
    365 
    366 void NexeLoadManager::InitializePlugin(
    367     uint32_t argc, const char* argn[], const char* argv[]) {
    368   init_time_ = base::Time::Now();
    369 
    370   for (size_t i = 0; i < argc; ++i) {
    371     std::string name(argn[i]);
    372     std::string value(argv[i]);
    373     args_[name] = value;
    374   }
    375 
    376   // Store mime_type_ at initialization time since we make it lowercase.
    377   mime_type_ = base::StringToLowerASCII(LookupAttribute(args_, kTypeAttribute));
    378 }
    379 
    380 void NexeLoadManager::ReportStartupOverhead() const {
    381   base::TimeDelta overhead = base::Time::Now() - init_time_;
    382   HistogramStartupTimeMedium(
    383       "NaCl.Perf.StartupTime.NaClOverhead", overhead, nexe_size_);
    384 }
    385 
    386 bool NexeLoadManager::RequestNaClManifest(const std::string& url) {
    387   if (plugin_base_url_.is_valid()) {
    388     const GURL& resolved_url = plugin_base_url_.Resolve(url);
    389     if (resolved_url.is_valid()) {
    390       manifest_base_url_ = resolved_url;
    391       is_installed_ = manifest_base_url_.SchemeIs("chrome-extension");
    392       HistogramEnumerateManifestIsDataURI(
    393           manifest_base_url_.SchemeIs("data"));
    394       set_nacl_ready_state(PP_NACL_READY_STATE_OPENED);
    395       DispatchProgressEvent(pp_instance_,
    396                             ProgressEvent(PP_NACL_EVENT_LOADSTART));
    397       return true;
    398     }
    399   }
    400   ReportLoadError(PP_NACL_ERROR_MANIFEST_RESOLVE_URL,
    401                   std::string("could not resolve URL \"") + url +
    402                   "\" relative to \"" +
    403                   plugin_base_url_.possibly_invalid_spec() + "\".");
    404   return false;
    405 }
    406 
    407 void NexeLoadManager::ProcessNaClManifest(const std::string& program_url) {
    408   program_url_ = program_url;
    409   GURL gurl(program_url);
    410   DCHECK(gurl.is_valid());
    411   if (gurl.is_valid())
    412     is_installed_ = gurl.SchemeIs("chrome-extension");
    413   set_nacl_ready_state(PP_NACL_READY_STATE_LOADING);
    414   DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_PROGRESS));
    415 }
    416 
    417 std::string NexeLoadManager::GetManifestURLArgument() const {
    418   std::string manifest_url;
    419 
    420   // If the MIME type is foreign, then this NEXE is being used as a content
    421   // type handler rather than directly by an HTML document.
    422   bool nexe_is_content_handler =
    423       !mime_type_.empty() &&
    424       mime_type_ != kNaClMIMEType &&
    425       mime_type_ != kPNaClMIMEType;
    426 
    427   if (nexe_is_content_handler) {
    428     // For content handlers 'src' will be the URL for the content
    429     // and 'nacl' will be the URL for the manifest.
    430     manifest_url = LookupAttribute(args_, kNaClManifestAttribute);
    431   } else {
    432     manifest_url = LookupAttribute(args_, kSrcManifestAttribute);
    433   }
    434 
    435   if (manifest_url.empty()) {
    436     VLOG(1) << "WARNING: no 'src' property, so no manifest loaded.";
    437     if (args_.find(kNaClManifestAttribute) != args_.end())
    438       VLOG(1) << "WARNING: 'nacl' property is incorrect. Use 'src'.";
    439   }
    440   return manifest_url;
    441 }
    442 
    443 bool NexeLoadManager::IsPNaCl() const {
    444   return mime_type_ == kPNaClMIMEType;
    445 }
    446 
    447 bool NexeLoadManager::DevInterfacesEnabled() const {
    448   // Look for the developer attribute; if it's present, enable 'dev'
    449   // interfaces.
    450   return args_.find(kDevAttribute) != args_.end();
    451 }
    452 
    453 void NexeLoadManager::ReportDeadNexe() {
    454   if (nacl_ready_state_ == PP_NACL_READY_STATE_DONE &&  // After loadEnd
    455       !nexe_error_reported_) {
    456     // Crashes will be more likely near startup, so use a medium histogram
    457     // instead of a large one.
    458     base::TimeDelta uptime = base::Time::Now() - ready_time_;
    459     HistogramTimeMedium("NaCl.ModuleUptime.Crash", uptime.InMilliseconds());
    460 
    461     std::string message("NaCl module crashed");
    462     SetLastError(message);
    463     LogToConsole(message);
    464 
    465     DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_CRASH));
    466     nexe_error_reported_ = true;
    467   }
    468   // else ReportLoadError() and ReportLoadAbort() will be used by loading code
    469   // to provide error handling.
    470 }
    471 
    472 void NexeLoadManager::CopyCrashLogToJsConsole(const std::string& crash_log) {
    473   base::StringTokenizer t(crash_log, "\n");
    474   while (t.GetNext())
    475     LogToConsole(t.token());
    476 }
    477 
    478 }  // namespace nacl
    479