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