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