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