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