|  | // Copyright 2014 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "components/nacl/renderer/nexe_load_manager.h" | 
|  |  | 
|  | #include <stddef.h> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/command_line.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/metrics/histogram.h" | 
|  | #include "base/strings/string_tokenizer.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "components/nacl/common/nacl_host_messages.h" | 
|  | #include "components/nacl/common/nacl_types.h" | 
|  | #include "components/nacl/renderer/histogram.h" | 
|  | #include "components/nacl/renderer/manifest_service_channel.h" | 
|  | #include "components/nacl/renderer/platform_info.h" | 
|  | #include "components/nacl/renderer/pnacl_translation_resource_host.h" | 
|  | #include "components/nacl/renderer/progress_event.h" | 
|  | #include "components/nacl/renderer/trusted_plugin_channel.h" | 
|  | #include "content/public/common/content_switches.h" | 
|  | #include "content/public/common/sandbox_init.h" | 
|  | #include "content/public/renderer/pepper_plugin_instance.h" | 
|  | #include "content/public/renderer/render_thread.h" | 
|  | #include "content/public/renderer/render_view.h" | 
|  | #include "content/public/renderer/renderer_ppapi_host.h" | 
|  | #include "ppapi/c/pp_bool.h" | 
|  | #include "ppapi/c/private/pp_file_handle.h" | 
|  | #include "ppapi/shared_impl/ppapi_globals.h" | 
|  | #include "ppapi/shared_impl/ppapi_permissions.h" | 
|  | #include "ppapi/shared_impl/ppapi_preferences.h" | 
|  | #include "ppapi/shared_impl/scoped_pp_var.h" | 
|  | #include "ppapi/shared_impl/var.h" | 
|  | #include "ppapi/shared_impl/var_tracker.h" | 
|  | #include "ppapi/thunk/enter.h" | 
|  | #include "third_party/blink/public/web/web_document.h" | 
|  | #include "third_party/blink/public/web/web_plugin_container.h" | 
|  | #include "third_party/blink/public/web/web_view.h" | 
|  | #include "v8/include/v8.h" | 
|  |  | 
|  | namespace nacl { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const char* const kTypeAttribute = "type"; | 
|  | // The "src" attribute of the <embed> tag.  The value is expected to be either | 
|  | // a URL or URI pointing to the manifest file (which is expected to contain | 
|  | // JSON matching ISAs with .nexe URLs). | 
|  | const char* const kSrcManifestAttribute = "src"; | 
|  | // The "nacl" attribute of the <embed> tag.  We use the value of this attribute | 
|  | // to find the manifest file when NaCl is registered as a plugin for another | 
|  | // MIME type because the "src" attribute is used to supply us with the resource | 
|  | // of that MIME type that we're supposed to display. | 
|  | const char* const kNaClManifestAttribute = "nacl"; | 
|  |  | 
|  | const char* const kNaClMIMEType = "application/x-nacl"; | 
|  | const char* const kPNaClMIMEType = "application/x-pnacl"; | 
|  |  | 
|  | static int GetRoutingID(PP_Instance instance) { | 
|  | // Check that we are on the main renderer thread. | 
|  | DCHECK(content::RenderThread::Get()); | 
|  | content::RendererPpapiHost *host = | 
|  | content::RendererPpapiHost::GetForPPInstance(instance); | 
|  | if (!host) | 
|  | return 0; | 
|  | return host->GetRoutingIDForWidget(instance); | 
|  | } | 
|  |  | 
|  | std::string LookupAttribute(const std::map<std::string, std::string>& args, | 
|  | const std::string& key) { | 
|  | std::map<std::string, std::string>::const_iterator it = args.find(key); | 
|  | if (it != args.end()) | 
|  | return it->second; | 
|  | return std::string(); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | NexeLoadManager::NexeLoadManager(PP_Instance pp_instance) | 
|  | : pp_instance_(pp_instance), | 
|  | nacl_ready_state_(PP_NACL_READY_STATE_UNSENT), | 
|  | nexe_error_reported_(false), | 
|  | is_installed_(false), | 
|  | exit_status_(-1), | 
|  | nexe_size_(0), | 
|  | plugin_instance_(content::PepperPluginInstance::Get(pp_instance)), | 
|  | nonsfi_(false), | 
|  | weak_factory_(this) { | 
|  | set_exit_status(-1); | 
|  | SetLastError(""); | 
|  | HistogramEnumerateOsArch(GetSandboxArch()); | 
|  | if (plugin_instance_) { | 
|  | plugin_base_url_ = plugin_instance_->GetContainer()->GetDocument().Url(); | 
|  | } | 
|  | } | 
|  |  | 
|  | NexeLoadManager::~NexeLoadManager() { | 
|  | if (!nexe_error_reported_) { | 
|  | base::TimeDelta uptime = base::Time::Now() - ready_time_; | 
|  | HistogramTimeLarge("NaCl.ModuleUptime.Normal", uptime.InMilliseconds()); | 
|  | } | 
|  | if (base::SharedMemory::IsHandleValid(crash_info_shmem_handle_)) | 
|  | base::SharedMemory::CloseHandle(crash_info_shmem_handle_); | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::NexeFileDidOpen(int32_t pp_error, | 
|  | const base::File& file, | 
|  | int32_t http_status, | 
|  | int64_t nexe_bytes_read, | 
|  | const std::string& url, | 
|  | base::TimeDelta time_since_open) { | 
|  | // Check that we are on the main renderer thread. | 
|  | DCHECK(content::RenderThread::Get()); | 
|  | VLOG(1) << "Plugin::NexeFileDidOpen (pp_error=" << pp_error << ")"; | 
|  | HistogramHTTPStatusCode( | 
|  | is_installed_ ? "NaCl.HttpStatusCodeClass.Nexe.InstalledApp" : | 
|  | "NaCl.HttpStatusCodeClass.Nexe.NotInstalledApp", | 
|  | http_status); | 
|  |  | 
|  | if (pp_error != PP_OK || !file.IsValid()) { | 
|  | if (pp_error == PP_ERROR_ABORTED) { | 
|  | ReportLoadAbort(); | 
|  | } else if (pp_error == PP_ERROR_NOACCESS) { | 
|  | ReportLoadError(PP_NACL_ERROR_NEXE_NOACCESS_URL, | 
|  | "access to nexe url was denied."); | 
|  | } else { | 
|  | ReportLoadError(PP_NACL_ERROR_NEXE_LOAD_URL, | 
|  | "could not load nexe url."); | 
|  | } | 
|  | } else if (nexe_bytes_read == -1) { | 
|  | ReportLoadError(PP_NACL_ERROR_NEXE_STAT, "could not stat nexe file."); | 
|  | } else { | 
|  | // TODO(dmichael): Can we avoid stashing away so much state? | 
|  | nexe_size_ = nexe_bytes_read; | 
|  | HistogramSizeKB("NaCl.Perf.Size.Nexe", | 
|  | static_cast<int32_t>(nexe_size_ / 1024)); | 
|  | HistogramStartupTimeMedium( | 
|  | "NaCl.Perf.StartupTime.NexeDownload", time_since_open, nexe_size_); | 
|  |  | 
|  | // Inform JavaScript that we successfully downloaded the nacl module. | 
|  | ProgressEvent progress_event(PP_NACL_EVENT_PROGRESS, url, true, nexe_size_, | 
|  | nexe_size_); | 
|  | DispatchProgressEvent(pp_instance_, progress_event); | 
|  | load_start_ = base::Time::Now(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::ReportLoadSuccess(const std::string& url, | 
|  | uint64_t loaded_bytes, | 
|  | uint64_t total_bytes) { | 
|  | ready_time_ = base::Time::Now(); | 
|  | if (!IsPNaCl()) { | 
|  | base::TimeDelta load_module_time = ready_time_ - load_start_; | 
|  | HistogramStartupTimeSmall( | 
|  | "NaCl.Perf.StartupTime.LoadModule", load_module_time, nexe_size_); | 
|  | HistogramStartupTimeMedium( | 
|  | "NaCl.Perf.StartupTime.Total", ready_time_ - init_time_, nexe_size_); | 
|  | } | 
|  |  | 
|  | // Check that we are on the main renderer thread. | 
|  | DCHECK(content::RenderThread::Get()); | 
|  | set_nacl_ready_state(PP_NACL_READY_STATE_DONE); | 
|  |  | 
|  | // Inform JavaScript that loading was successful and is complete. | 
|  | ProgressEvent load_event(PP_NACL_EVENT_LOAD, url, true, loaded_bytes, | 
|  | total_bytes); | 
|  | DispatchProgressEvent(pp_instance_, load_event); | 
|  |  | 
|  | ProgressEvent loadend_event(PP_NACL_EVENT_LOADEND, url, true, loaded_bytes, | 
|  | total_bytes); | 
|  | DispatchProgressEvent(pp_instance_, loadend_event); | 
|  |  | 
|  | // UMA | 
|  | HistogramEnumerateLoadStatus(PP_NACL_ERROR_LOAD_SUCCESS, is_installed_); | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::ReportLoadError(PP_NaClError error, | 
|  | const std::string& error_message) { | 
|  | ReportLoadError(error, error_message, error_message); | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::ReportLoadError(PP_NaClError error, | 
|  | const std::string& error_message, | 
|  | const std::string& console_message) { | 
|  | // Check that we are on the main renderer thread. | 
|  | DCHECK(content::RenderThread::Get()); | 
|  |  | 
|  | if (error == PP_NACL_ERROR_MANIFEST_PROGRAM_MISSING_ARCH) { | 
|  | // A special case: the manifest may otherwise be valid but is missing | 
|  | // a program/file compatible with the user's sandbox. | 
|  | IPC::Sender* sender = content::RenderThread::Get(); | 
|  | sender->Send( | 
|  | new NaClHostMsg_MissingArchError(GetRoutingID(pp_instance_))); | 
|  | } | 
|  | set_nacl_ready_state(PP_NACL_READY_STATE_DONE); | 
|  | nexe_error_reported_ = true; | 
|  |  | 
|  | // We must set all properties before calling DispatchEvent so that when an | 
|  | // event handler runs, the properties reflect the current load state. | 
|  | std::string error_string = std::string("NaCl module load failed: ") + | 
|  | std::string(error_message); | 
|  | SetLastError(error_string); | 
|  |  | 
|  | // Inform JavaScript that loading encountered an error and is complete. | 
|  | DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_ERROR)); | 
|  | DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_LOADEND)); | 
|  |  | 
|  | HistogramEnumerateLoadStatus(error, is_installed_); | 
|  | LogToConsole(console_message); | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::ReportLoadAbort() { | 
|  | // Check that we are on the main renderer thread. | 
|  | DCHECK(content::RenderThread::Get()); | 
|  |  | 
|  | // Set the readyState attribute to indicate we need to start over. | 
|  | set_nacl_ready_state(PP_NACL_READY_STATE_DONE); | 
|  | nexe_error_reported_ = true; | 
|  |  | 
|  | // Report an error in lastError and on the JavaScript console. | 
|  | std::string error_string("NaCl module load failed: user aborted"); | 
|  | SetLastError(error_string); | 
|  |  | 
|  | // Inform JavaScript that loading was aborted and is complete. | 
|  | DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_ABORT)); | 
|  | DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_LOADEND)); | 
|  |  | 
|  | HistogramEnumerateLoadStatus(PP_NACL_ERROR_LOAD_ABORTED, is_installed_); | 
|  | LogToConsole(error_string); | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::NexeDidCrash() { | 
|  | VLOG(1) << "Plugin::NexeDidCrash: crash event!"; | 
|  | // The NaCl module voluntarily exited.  However, this is still a | 
|  | // crash from the point of view of Pepper, since PPAPI plugins are | 
|  | // event handlers and should never exit. | 
|  | VLOG_IF(1, exit_status_ != -1) | 
|  | << "Plugin::NexeDidCrash: nexe exited with status " << exit_status_ | 
|  | << " so this is a \"controlled crash\"."; | 
|  | // If the crash occurs during load, we just want to report an error | 
|  | // that fits into our load progress event grammar.  If the crash | 
|  | // occurs after loaded/loadend, then we use ReportDeadNexe to send a | 
|  | // "crash" event. | 
|  | if (nexe_error_reported_) { | 
|  | VLOG(1) << "Plugin::NexeDidCrash: error already reported; suppressing"; | 
|  | } else { | 
|  | if (nacl_ready_state_ == PP_NACL_READY_STATE_DONE) { | 
|  | ReportDeadNexe(); | 
|  | } else { | 
|  | ReportLoadError(PP_NACL_ERROR_START_PROXY_CRASH, | 
|  | "Nexe crashed during startup"); | 
|  | } | 
|  | } | 
|  | // In all cases, try to grab the crash log.  The first error | 
|  | // reported may have come from the start_module RPC reply indicating | 
|  | // a validation error or something similar, which wouldn't grab the | 
|  | // crash log.  In the event that this is called twice, the second | 
|  | // invocation will just be a no-op, since the entire crash log will | 
|  | // have been received and we'll just get an EOF indication. | 
|  |  | 
|  | base::SharedMemory shmem(crash_info_shmem_handle_, true); | 
|  | // When shmem goes out of scope, the handle will be closed. Invalidate | 
|  | // our handle so our destructor doesn't try to close it again. | 
|  | crash_info_shmem_handle_ = base::SharedMemoryHandle(); | 
|  | if (shmem.Map(kNaClCrashInfoShmemSize)) { | 
|  | uint32_t crash_log_length; | 
|  | // We cast the length value to volatile here to prevent the compiler from | 
|  | // reordering instructions in a way that could introduce a TOCTTOU race. | 
|  | crash_log_length = *(static_cast<volatile uint32_t*>(shmem.memory())); | 
|  | crash_log_length = std::min<uint32_t>(crash_log_length, | 
|  | kNaClCrashInfoMaxLogSize); | 
|  |  | 
|  | std::unique_ptr<char[]> crash_log_data(new char[kNaClCrashInfoShmemSize]); | 
|  | memcpy(crash_log_data.get(), | 
|  | static_cast<char*>(shmem.memory()) + sizeof(uint32_t), | 
|  | crash_log_length); | 
|  | std::string crash_log(crash_log_data.get(), crash_log_length); | 
|  | CopyCrashLogToJsConsole(crash_log); | 
|  | } | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::set_trusted_plugin_channel( | 
|  | std::unique_ptr<TrustedPluginChannel> channel) { | 
|  | trusted_plugin_channel_ = std::move(channel); | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::set_manifest_service_channel( | 
|  | std::unique_ptr<ManifestServiceChannel> channel) { | 
|  | manifest_service_channel_ = std::move(channel); | 
|  | } | 
|  |  | 
|  | PP_NaClReadyState NexeLoadManager::nacl_ready_state() { | 
|  | return nacl_ready_state_; | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::set_nacl_ready_state(PP_NaClReadyState ready_state) { | 
|  | nacl_ready_state_ = ready_state; | 
|  | ppapi::ScopedPPVar ready_state_name( | 
|  | ppapi::ScopedPPVar::PassRef(), | 
|  | ppapi::StringVar::StringToPPVar("readyState")); | 
|  | SetReadOnlyProperty(ready_state_name.get(), PP_MakeInt32(ready_state)); | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::SetLastError(const std::string& error) { | 
|  | ppapi::ScopedPPVar error_name_var( | 
|  | ppapi::ScopedPPVar::PassRef(), | 
|  | ppapi::StringVar::StringToPPVar("lastError")); | 
|  | ppapi::ScopedPPVar error_var( | 
|  | ppapi::ScopedPPVar::PassRef(), | 
|  | ppapi::StringVar::StringToPPVar(error)); | 
|  | SetReadOnlyProperty(error_name_var.get(), error_var.get()); | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::SetReadOnlyProperty(PP_Var key, PP_Var value) { | 
|  | plugin_instance_->SetEmbedProperty(key, value); | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::LogToConsole(const std::string& message) { | 
|  | ppapi::PpapiGlobals::Get()->LogWithSource( | 
|  | pp_instance_, PP_LOGLEVEL_LOG, std::string("NativeClient"), message); | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::set_exit_status(int exit_status) { | 
|  | exit_status_ = exit_status; | 
|  | ppapi::ScopedPPVar exit_status_name_var( | 
|  | ppapi::ScopedPPVar::PassRef(), | 
|  | ppapi::StringVar::StringToPPVar("exitStatus")); | 
|  | SetReadOnlyProperty(exit_status_name_var.get(), PP_MakeInt32(exit_status)); | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::InitializePlugin( | 
|  | uint32_t argc, const char* argn[], const char* argv[]) { | 
|  | init_time_ = base::Time::Now(); | 
|  |  | 
|  | for (size_t i = 0; i < argc; ++i) { | 
|  | std::string name(argn[i]); | 
|  | std::string value(argv[i]); | 
|  | args_[name] = value; | 
|  | } | 
|  |  | 
|  | // Store mime_type_ at initialization time since we make it lowercase. | 
|  | mime_type_ = base::ToLowerASCII(LookupAttribute(args_, kTypeAttribute)); | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::ReportStartupOverhead() const { | 
|  | base::TimeDelta overhead = base::Time::Now() - init_time_; | 
|  | HistogramStartupTimeMedium( | 
|  | "NaCl.Perf.StartupTime.NaClOverhead", overhead, nexe_size_); | 
|  | } | 
|  |  | 
|  | bool NexeLoadManager::RequestNaClManifest(const std::string& url) { | 
|  | if (plugin_base_url_.is_valid()) { | 
|  | const GURL& resolved_url = plugin_base_url_.Resolve(url); | 
|  | if (resolved_url.is_valid()) { | 
|  | manifest_base_url_ = resolved_url; | 
|  | is_installed_ = manifest_base_url_.SchemeIs("chrome-extension"); | 
|  | HistogramEnumerateManifestIsDataURI( | 
|  | manifest_base_url_.SchemeIs("data")); | 
|  | set_nacl_ready_state(PP_NACL_READY_STATE_OPENED); | 
|  | DispatchProgressEvent(pp_instance_, | 
|  | ProgressEvent(PP_NACL_EVENT_LOADSTART)); | 
|  | return true; | 
|  | } | 
|  | } | 
|  | ReportLoadError(PP_NACL_ERROR_MANIFEST_RESOLVE_URL, | 
|  | std::string("could not resolve URL \"") + url + | 
|  | "\" relative to \"" + | 
|  | plugin_base_url_.possibly_invalid_spec() + "\"."); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::ProcessNaClManifest(const std::string& program_url) { | 
|  | program_url_ = program_url; | 
|  | GURL gurl(program_url); | 
|  | DCHECK(gurl.is_valid()); | 
|  | if (gurl.is_valid()) | 
|  | is_installed_ = gurl.SchemeIs("chrome-extension"); | 
|  | set_nacl_ready_state(PP_NACL_READY_STATE_LOADING); | 
|  | DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_PROGRESS)); | 
|  | } | 
|  |  | 
|  | std::string NexeLoadManager::GetManifestURLArgument() const { | 
|  | std::string manifest_url; | 
|  |  | 
|  | // If the MIME type is foreign, then this NEXE is being used as a content | 
|  | // type handler rather than directly by an HTML document. | 
|  | bool nexe_is_content_handler = | 
|  | !mime_type_.empty() && | 
|  | mime_type_ != kNaClMIMEType && | 
|  | mime_type_ != kPNaClMIMEType; | 
|  |  | 
|  | if (nexe_is_content_handler) { | 
|  | // For content handlers 'src' will be the URL for the content | 
|  | // and 'nacl' will be the URL for the manifest. | 
|  | manifest_url = LookupAttribute(args_, kNaClManifestAttribute); | 
|  | } else { | 
|  | manifest_url = LookupAttribute(args_, kSrcManifestAttribute); | 
|  | } | 
|  |  | 
|  | if (manifest_url.empty()) { | 
|  | VLOG(1) << "WARNING: no 'src' property, so no manifest loaded."; | 
|  | if (args_.find(kNaClManifestAttribute) != args_.end()) | 
|  | VLOG(1) << "WARNING: 'nacl' property is incorrect. Use 'src'."; | 
|  | } | 
|  | return manifest_url; | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::CloseTrustedPluginChannel() { | 
|  | trusted_plugin_channel_.reset(); | 
|  | } | 
|  |  | 
|  | bool NexeLoadManager::IsPNaCl() const { | 
|  | return mime_type_ == kPNaClMIMEType; | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::ReportDeadNexe() { | 
|  | if (nacl_ready_state_ == PP_NACL_READY_STATE_DONE &&  // After loadEnd | 
|  | !nexe_error_reported_) { | 
|  | // Crashes will be more likely near startup, so use a medium histogram | 
|  | // instead of a large one. | 
|  | base::TimeDelta uptime = base::Time::Now() - ready_time_; | 
|  | HistogramTimeMedium("NaCl.ModuleUptime.Crash", uptime.InMilliseconds()); | 
|  |  | 
|  | std::string message("NaCl module crashed"); | 
|  | SetLastError(message); | 
|  | LogToConsole(message); | 
|  |  | 
|  | DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_CRASH)); | 
|  | nexe_error_reported_ = true; | 
|  | } | 
|  | // else ReportLoadError() and ReportLoadAbort() will be used by loading code | 
|  | // to provide error handling. | 
|  | } | 
|  |  | 
|  | void NexeLoadManager::CopyCrashLogToJsConsole(const std::string& crash_log) { | 
|  | base::StringTokenizer t(crash_log, "\n"); | 
|  | while (t.GetNext()) | 
|  | LogToConsole(t.token()); | 
|  | } | 
|  |  | 
|  | }  // namespace nacl |