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