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