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
44namespace nacl {
45
46namespace {
47
48const 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).
52const 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.
57const 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 '@'.
60const char* const kDevAttribute = "@dev";
61
62const char* const kNaClMIMEType = "application/x-nacl";
63const char* const kPNaClMIMEType = "application/x-pnacl";
64
65static 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
75std::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
83typedef base::ScopedPtrHashMap<PP_Instance, NexeLoadManager> NexeLoadManagerMap;
84base::LazyInstance<NexeLoadManagerMap> g_load_manager_map =
85    LAZY_INSTANCE_INITIALIZER;
86
87}  // namespace
88
89NexeLoadManager::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
109NexeLoadManager::~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
118void 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
125NexeLoadManager* 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
133void 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
144void 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
186void 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
215void NexeLoadManager::ReportLoadError(PP_NaClError error,
216                                      const std::string& error_message) {
217  ReportLoadError(error, error_message, error_message);
218}
219
220void 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
250void 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
270void 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
317void NexeLoadManager::set_trusted_plugin_channel(
318    scoped_ptr<TrustedPluginChannel> channel) {
319  trusted_plugin_channel_ = channel.Pass();
320}
321
322void NexeLoadManager::set_manifest_service_channel(
323    scoped_ptr<ManifestServiceChannel> channel) {
324  manifest_service_channel_ = channel.Pass();
325}
326
327PP_NaClReadyState NexeLoadManager::nacl_ready_state() {
328  return nacl_ready_state_;
329}
330
331void 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
339void 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
349void NexeLoadManager::SetReadOnlyProperty(PP_Var key, PP_Var value) {
350  plugin_instance_->SetEmbedProperty(key, value);
351}
352
353void NexeLoadManager::LogToConsole(const std::string& message) {
354  ppapi::PpapiGlobals::Get()->LogWithSource(
355      pp_instance_, PP_LOGLEVEL_LOG, std::string("NativeClient"), message);
356}
357
358void 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
366void 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
380void NexeLoadManager::ReportStartupOverhead() const {
381  base::TimeDelta overhead = base::Time::Now() - init_time_;
382  HistogramStartupTimeMedium(
383      "NaCl.Perf.StartupTime.NaClOverhead", overhead, nexe_size_);
384}
385
386bool 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
407void 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
417std::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
443bool NexeLoadManager::IsPNaCl() const {
444  return mime_type_ == kPNaClMIMEType;
445}
446
447bool 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
453void 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
472void 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