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