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