1 // Copyright 2012 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/browser/nacl_process_host.h"
6
7 #include <string.h>
8
9 #include <algorithm>
10 #include <memory>
11 #include <string>
12 #include <utility>
13 #include <vector>
14
15 #include "base/base_switches.h"
16 #include "base/command_line.h"
17 #include "base/feature_list.h"
18 #include "base/files/file_util.h"
19 #include "base/functional/bind.h"
20 #include "base/location.h"
21 #include "base/metrics/histogram_macros.h"
22 #include "base/path_service.h"
23 #include "base/process/launch.h"
24 #include "base/process/process_iterator.h"
25 #include "base/rand_util.h"
26 #include "base/strings/string_number_conversions.h"
27 #include "base/strings/string_split.h"
28 #include "base/strings/string_util.h"
29 #include "base/strings/stringprintf.h"
30 #include "base/strings/utf_string_conversions.h"
31 #include "base/sys_byteorder.h"
32 #include "base/task/single_thread_task_runner.h"
33 #include "base/task/thread_pool.h"
34 #include "build/build_config.h"
35 #include "build/chromeos_buildflags.h"
36 #include "components/nacl/browser/nacl_browser.h"
37 #include "components/nacl/browser/nacl_browser_delegate.h"
38 #include "components/nacl/browser/nacl_host_message_filter.h"
39 #include "components/nacl/common/nacl_cmd_line.h"
40 #include "components/nacl/common/nacl_constants.h"
41 #include "components/nacl/common/nacl_host_messages.h"
42 #include "components/nacl/common/nacl_messages.h"
43 #include "components/nacl/common/nacl_process_type.h"
44 #include "components/nacl/common/nacl_switches.h"
45 #include "components/url_formatter/url_formatter.h"
46 #include "content/public/browser/browser_child_process_host.h"
47 #include "content/public/browser/browser_ppapi_host.h"
48 #include "content/public/browser/child_process_data.h"
49 #include "content/public/browser/child_process_host.h"
50 #include "content/public/browser/plugin_service.h"
51 #include "content/public/browser/render_process_host.h"
52 #include "content/public/browser/web_contents.h"
53 #include "content/public/common/content_switches.h"
54 #include "content/public/common/process_type.h"
55 #include "content/public/common/sandboxed_process_launcher_delegate.h"
56 #include "content/public/common/zygote/zygote_buildflags.h"
57 #include "ipc/ipc_channel.h"
58 #include "mojo/public/cpp/system/invitation.h"
59 #include "net/socket/socket_descriptor.h"
60 #include "ppapi/host/host_factory.h"
61 #include "ppapi/host/ppapi_host.h"
62 #include "ppapi/proxy/ppapi_messages.h"
63 #include "ppapi/shared_impl/ppapi_nacl_plugin_args.h"
64 #include "sandbox/policy/mojom/sandbox.mojom.h"
65 #include "sandbox/policy/switches.h"
66
67 #if BUILDFLAG(USE_ZYGOTE)
68 #include "content/public/common/zygote/zygote_handle.h" // nogncheck
69 #endif // BUILDFLAG(USE_ZYGOTE)
70
71 #if BUILDFLAG(IS_POSIX)
72
73 #include <arpa/inet.h>
74 #include <fcntl.h>
75 #include <netinet/in.h>
76 #include <sys/socket.h>
77
78 #endif
79
80 using content::BrowserThread;
81 using content::ChildProcessData;
82 using content::ChildProcessHost;
83 using ppapi::proxy::SerializedHandle;
84
85 namespace nacl {
86 namespace {
87
88 // NOTE: changes to this class need to be reviewed by the security team.
89 class NaClSandboxedProcessLauncherDelegate
90 : public content::SandboxedProcessLauncherDelegate {
91 public:
NaClSandboxedProcessLauncherDelegate()92 NaClSandboxedProcessLauncherDelegate() {}
93
94 #if BUILDFLAG(USE_ZYGOTE)
GetZygote()95 content::ZygoteCommunication* GetZygote() override {
96 return content::GetGenericZygote();
97 }
98 #endif // BUILDFLAG(USE_ZYGOTE)
99
GetSandboxType()100 sandbox::mojom::Sandbox GetSandboxType() override {
101 return sandbox::mojom::Sandbox::kPpapi;
102 }
103 };
104
CloseFile(base::File file)105 void CloseFile(base::File file) {
106 // The base::File destructor will close the file for us.
107 }
108
109 } // namespace
110
NaClProcessHost(const GURL & manifest_url,base::File nexe_file,const NaClFileToken & nexe_token,const std::vector<NaClResourcePrefetchResult> & prefetched_resource_files,ppapi::PpapiPermissions permissions,uint32_t permission_bits,bool off_the_record,NaClAppProcessType process_type,const base::FilePath & profile_directory)111 NaClProcessHost::NaClProcessHost(
112 const GURL& manifest_url,
113 base::File nexe_file,
114 const NaClFileToken& nexe_token,
115 const std::vector<NaClResourcePrefetchResult>& prefetched_resource_files,
116 ppapi::PpapiPermissions permissions,
117 uint32_t permission_bits,
118 bool off_the_record,
119 NaClAppProcessType process_type,
120 const base::FilePath& profile_directory)
121 : manifest_url_(manifest_url),
122 nexe_file_(std::move(nexe_file)),
123 nexe_token_(nexe_token),
124 prefetched_resource_files_(prefetched_resource_files),
125 permissions_(permissions),
126 reply_msg_(nullptr),
127 enable_debug_stub_(false),
128 enable_crash_throttling_(false),
129 off_the_record_(off_the_record),
130 process_type_(process_type),
131 profile_directory_(profile_directory) {
132 process_ = content::BrowserChildProcessHost::Create(
133 static_cast<content::ProcessType>(PROCESS_TYPE_NACL_LOADER), this,
134 content::ChildProcessHost::IpcMode::kLegacy);
135 process_->SetMetricsName("NaCl Loader");
136
137 // Set the display name so the user knows what plugin the process is running.
138 // We aren't on the UI thread so getting the pref locale for language
139 // formatting isn't possible, so IDN will be lost, but this is probably OK
140 // for this use case.
141 process_->SetName(url_formatter::FormatUrl(manifest_url_));
142
143 enable_debug_stub_ = base::CommandLine::ForCurrentProcess()->HasSwitch(
144 switches::kEnableNaClDebug);
145 DCHECK(process_type_ != kUnknownNaClProcessType);
146 enable_crash_throttling_ = process_type_ != kNativeNaClProcessType;
147 }
148
~NaClProcessHost()149 NaClProcessHost::~NaClProcessHost() {
150 // Report exit status only if the process was successfully started.
151 if (process_->GetData().GetProcess().IsValid()) {
152 content::ChildProcessTerminationInfo info =
153 process_->GetTerminationInfo(false /* known_dead */);
154 std::string message =
155 base::StringPrintf("NaCl process exited with status %i (0x%x)",
156 info.exit_code, info.exit_code);
157 if (info.exit_code == 0) {
158 VLOG(1) << message;
159 } else {
160 LOG(ERROR) << message;
161 }
162 NaClBrowser::GetInstance()->OnProcessEnd(process_->GetData().id);
163 }
164
165 for (size_t i = 0; i < prefetched_resource_files_.size(); ++i) {
166 // The process failed to launch for some reason. Close resource file
167 // handles.
168 base::File file(IPC::PlatformFileForTransitToFile(
169 prefetched_resource_files_[i].file));
170 base::ThreadPool::PostTask(
171 FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
172 base::BindOnce(&CloseFile, std::move(file)));
173 }
174 // Open files need to be closed on the blocking pool.
175 if (nexe_file_.IsValid()) {
176 base::ThreadPool::PostTask(
177 FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
178 base::BindOnce(&CloseFile, std::move(nexe_file_)));
179 }
180
181 if (reply_msg_) {
182 // The process failed to launch for some reason.
183 // Don't keep the renderer hanging.
184 reply_msg_->set_reply_error();
185 nacl_host_message_filter_->Send(reply_msg_);
186 }
187 }
188
OnProcessCrashed(int exit_status)189 void NaClProcessHost::OnProcessCrashed(int exit_status) {
190 if (enable_crash_throttling_ &&
191 !base::CommandLine::ForCurrentProcess()->HasSwitch(
192 switches::kDisablePnaclCrashThrottling)) {
193 NaClBrowser::GetInstance()->OnProcessCrashed();
194 }
195 }
196
197 // This is called at browser startup.
198 // static
EarlyStartup()199 void NaClProcessHost::EarlyStartup() {
200 NaClBrowser::GetInstance()->EarlyStartup();
201 // Inform NaClBrowser that we exist and will have a debug port at some point.
202 // TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
203 // of lacros-chrome is complete.
204 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
205 // Open the IRT file early to make sure that it isn't replaced out from
206 // under us by autoupdate.
207 NaClBrowser::GetInstance()->EnsureIrtAvailable();
208 #endif
209 base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
210 std::string nacl_debug_mask =
211 cmd->GetSwitchValueASCII(switches::kNaClDebugMask);
212 // By default, exclude debugging SSH and the PNaCl translator.
213 // about::flags only allows empty flags as the default, so replace
214 // the empty setting with the default. To debug all apps, use a wild-card.
215 if (nacl_debug_mask.empty()) {
216 nacl_debug_mask = "!*://*/*ssh_client.nmf,chrome://pnacl-translator/*";
217 }
218 NaClBrowser::GetDelegate()->SetDebugPatterns(nacl_debug_mask);
219 }
220
Launch(NaClHostMessageFilter * nacl_host_message_filter,IPC::Message * reply_msg,const base::FilePath & manifest_path)221 void NaClProcessHost::Launch(
222 NaClHostMessageFilter* nacl_host_message_filter,
223 IPC::Message* reply_msg,
224 const base::FilePath& manifest_path) {
225 nacl_host_message_filter_ = nacl_host_message_filter;
226 reply_msg_ = reply_msg;
227 manifest_path_ = manifest_path;
228
229 // Do not launch the requested NaCl module if NaCl is marked "unstable" due
230 // to too many crashes within a given time period.
231 if (enable_crash_throttling_ &&
232 !base::CommandLine::ForCurrentProcess()->HasSwitch(
233 switches::kDisablePnaclCrashThrottling) &&
234 NaClBrowser::GetInstance()->IsThrottled()) {
235 SendErrorToRenderer("Process creation was throttled due to excessive"
236 " crashes");
237 delete this;
238 return;
239 }
240
241 const base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
242 if (cmd->HasSwitch(switches::kNaClGdb) &&
243 !cmd->HasSwitch(switches::kEnableNaClDebug)) {
244 LOG(WARNING) << "--nacl-gdb flag requires --enable-nacl-debug flag";
245 }
246
247 // Start getting the IRT open asynchronously while we launch the NaCl process.
248 // We'll make sure this actually finished in StartWithLaunchedProcess, below.
249 NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
250 nacl_browser->EnsureAllResourcesAvailable();
251 if (!nacl_browser->IsOk()) {
252 SendErrorToRenderer("could not find all the resources needed"
253 " to launch the process");
254 delete this;
255 return;
256 }
257
258 // Launch the process
259 if (!LaunchSelLdr()) {
260 delete this;
261 }
262 }
263
OnChannelConnected(int32_t peer_pid)264 void NaClProcessHost::OnChannelConnected(int32_t peer_pid) {
265 if (!base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
266 switches::kNaClGdb).empty()) {
267 LaunchNaClGdb();
268 }
269 }
270
271 // Needed to handle sync messages in OnMessageReceived.
Send(IPC::Message * msg)272 bool NaClProcessHost::Send(IPC::Message* msg) {
273 return process_->Send(msg);
274 }
275
LaunchNaClGdb()276 void NaClProcessHost::LaunchNaClGdb() {
277 const base::CommandLine& command_line =
278 *base::CommandLine::ForCurrentProcess();
279 base::CommandLine::StringType nacl_gdb =
280 command_line.GetSwitchValueNative(switches::kNaClGdb);
281 // We don't support spaces inside arguments in --nacl-gdb switch.
282 base::CommandLine cmd_line(base::SplitString(
283 nacl_gdb, base::CommandLine::StringType(1, ' '),
284 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL));
285 cmd_line.AppendArg("--eval-command");
286 base::FilePath::StringType irt_path(
287 NaClBrowser::GetInstance()->GetIrtFilePath().value());
288 // Avoid back slashes because nacl-gdb uses posix escaping rules on Windows.
289 // See issue https://code.google.com/p/nativeclient/issues/detail?id=3482.
290 std::replace(irt_path.begin(), irt_path.end(), '\\', '/');
291 cmd_line.AppendArgNative(FILE_PATH_LITERAL("nacl-irt \"") + irt_path +
292 FILE_PATH_LITERAL("\""));
293 if (!manifest_path_.empty()) {
294 cmd_line.AppendArg("--eval-command");
295 base::FilePath::StringType manifest_path_value(manifest_path_.value());
296 std::replace(manifest_path_value.begin(), manifest_path_value.end(),
297 '\\', '/');
298 cmd_line.AppendArgNative(FILE_PATH_LITERAL("nacl-manifest \"") +
299 manifest_path_value + FILE_PATH_LITERAL("\""));
300 }
301 cmd_line.AppendArg("--eval-command");
302 cmd_line.AppendArg("target remote :4014");
303 base::FilePath script =
304 command_line.GetSwitchValuePath(switches::kNaClGdbScript);
305 if (!script.empty()) {
306 cmd_line.AppendArg("--command");
307 cmd_line.AppendArgNative(script.value());
308 }
309 base::LaunchProcess(cmd_line, base::LaunchOptions());
310 }
311
LaunchSelLdr()312 bool NaClProcessHost::LaunchSelLdr() {
313 process_->GetHost()->CreateChannelMojo();
314
315 // Build command line for nacl.
316
317 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
318 int flags = ChildProcessHost::CHILD_ALLOW_SELF;
319 #else
320 int flags = ChildProcessHost::CHILD_NORMAL;
321 #endif
322
323 base::FilePath exe_path = ChildProcessHost::GetChildPath(flags);
324 if (exe_path.empty())
325 return false;
326
327 std::unique_ptr<base::CommandLine> cmd_line(new base::CommandLine(exe_path));
328 CopyNaClCommandLineArguments(cmd_line.get());
329
330 cmd_line->AppendSwitchASCII(switches::kProcessType,
331 switches::kNaClLoaderProcess);
332 if (NaClBrowser::GetDelegate()->DialogsAreSuppressed())
333 cmd_line->AppendSwitch(switches::kNoErrorDialogs);
334
335 process_->Launch(std::make_unique<NaClSandboxedProcessLauncherDelegate>(),
336 std::move(cmd_line), true);
337 return true;
338 }
339
OnMessageReceived(const IPC::Message & msg)340 bool NaClProcessHost::OnMessageReceived(const IPC::Message& msg) {
341 bool handled = true;
342 IPC_BEGIN_MESSAGE_MAP(NaClProcessHost, msg)
343 IPC_MESSAGE_HANDLER(NaClProcessMsg_QueryKnownToValidate,
344 OnQueryKnownToValidate)
345 IPC_MESSAGE_HANDLER(NaClProcessMsg_SetKnownToValidate,
346 OnSetKnownToValidate)
347 IPC_MESSAGE_HANDLER(NaClProcessMsg_ResolveFileToken,
348 OnResolveFileToken)
349 IPC_MESSAGE_HANDLER(NaClProcessHostMsg_PpapiChannelsCreated,
350 OnPpapiChannelsCreated)
351 IPC_MESSAGE_UNHANDLED(handled = false)
352 IPC_END_MESSAGE_MAP()
353 return handled;
354 }
355
OnProcessLaunched()356 void NaClProcessHost::OnProcessLaunched() {
357 if (!StartWithLaunchedProcess())
358 delete this;
359 }
360
361 // Called when the NaClBrowser singleton has been fully initialized.
OnResourcesReady()362 void NaClProcessHost::OnResourcesReady() {
363 NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
364 if (!nacl_browser->IsReady()) {
365 SendErrorToRenderer("could not acquire shared resources needed by NaCl");
366 delete this;
367 } else if (!StartNaClExecution()) {
368 delete this;
369 }
370 }
371
ReplyToRenderer(mojo::ScopedMessagePipeHandle ppapi_channel_handle,mojo::ScopedMessagePipeHandle trusted_channel_handle,mojo::ScopedMessagePipeHandle manifest_service_channel_handle,base::ReadOnlySharedMemoryRegion crash_info_shmem_region)372 void NaClProcessHost::ReplyToRenderer(
373 mojo::ScopedMessagePipeHandle ppapi_channel_handle,
374 mojo::ScopedMessagePipeHandle trusted_channel_handle,
375 mojo::ScopedMessagePipeHandle manifest_service_channel_handle,
376 base::ReadOnlySharedMemoryRegion crash_info_shmem_region) {
377 // Hereafter, we always send an IPC message with handles created above
378 // which, on Windows, are not closable in this process.
379 std::string error_message;
380 if (!crash_info_shmem_region.IsValid()) {
381 // On error, we do not send "IPC::ChannelHandle"s to the renderer process.
382 // Note that some other FDs/handles still get sent to the renderer, but
383 // will be closed there.
384 ppapi_channel_handle.reset();
385 trusted_channel_handle.reset();
386 manifest_service_channel_handle.reset();
387 error_message = "shared memory region not valid";
388 }
389
390 const ChildProcessData& data = process_->GetData();
391 SendMessageToRenderer(
392 NaClLaunchResult(
393 ppapi_channel_handle.release(), trusted_channel_handle.release(),
394 manifest_service_channel_handle.release(), data.GetProcess().Pid(),
395 data.id, std::move(crash_info_shmem_region)),
396 error_message);
397 }
398
SendErrorToRenderer(const std::string & error_message)399 void NaClProcessHost::SendErrorToRenderer(const std::string& error_message) {
400 LOG(ERROR) << "NaCl process launch failed: " << error_message;
401 SendMessageToRenderer(NaClLaunchResult(), error_message);
402 }
403
SendMessageToRenderer(const NaClLaunchResult & result,const std::string & error_message)404 void NaClProcessHost::SendMessageToRenderer(
405 const NaClLaunchResult& result,
406 const std::string& error_message) {
407 DCHECK(nacl_host_message_filter_.get());
408 DCHECK(reply_msg_);
409 if (!nacl_host_message_filter_.get() || !reply_msg_) {
410 // As DCHECKed above, this case should not happen in general.
411 // Though, in this case, unfortunately there is no proper way to release
412 // resources which are already created in |result|. We just give up on
413 // releasing them, and leak them.
414 return;
415 }
416
417 NaClHostMsg_LaunchNaCl::WriteReplyParams(reply_msg_, result, error_message);
418 nacl_host_message_filter_->Send(reply_msg_);
419 nacl_host_message_filter_.reset();
420 reply_msg_ = nullptr;
421 }
422
SetDebugStubPort(int port)423 void NaClProcessHost::SetDebugStubPort(int port) {
424 NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
425 nacl_browser->SetProcessGdbDebugStubPort(process_->GetData().id, port);
426 }
427
428 #if BUILDFLAG(IS_POSIX)
429 // TCP port we chose for NaCl debug stub. It can be any other number.
430 static const uint16_t kInitialDebugStubPort = 4014;
431
GetDebugStubSocketHandle()432 net::SocketDescriptor NaClProcessHost::GetDebugStubSocketHandle() {
433 // We always try to allocate the default port first. If this fails, we then
434 // allocate any available port.
435 // On success, if the test system has register a handler
436 // (GdbDebugStubPortListener), we fire a notification.
437 uint16_t port = kInitialDebugStubPort;
438 net::SocketDescriptor s =
439 net::CreatePlatformSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
440 if (s != net::kInvalidSocket) {
441 // Allow rapid reuse.
442 static const int kOn = 1;
443 setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn));
444
445 sockaddr_in addr;
446 memset(&addr, 0, sizeof(addr));
447 addr.sin_family = AF_INET;
448 addr.sin_addr.s_addr = inet_addr("127.0.0.1");
449 addr.sin_port = base::HostToNet16(port);
450 if (bind(s, reinterpret_cast<sockaddr*>(&addr), sizeof(addr))) {
451 // Try allocate any available port.
452 addr.sin_port = base::HostToNet16(0);
453 if (bind(s, reinterpret_cast<sockaddr*>(&addr), sizeof(addr))) {
454 close(s);
455 LOG(ERROR) << "Could not bind socket to port" << port;
456 s = net::kInvalidSocket;
457 } else {
458 sockaddr_in sock_addr;
459 socklen_t sock_addr_size = sizeof(sock_addr);
460 if (getsockname(s, reinterpret_cast<struct sockaddr*>(&sock_addr),
461 &sock_addr_size) != 0 ||
462 sock_addr_size != sizeof(sock_addr)) {
463 LOG(ERROR) << "Could not determine bound port, getsockname() failed";
464 close(s);
465 s = net::kInvalidSocket;
466 } else {
467 port = base::NetToHost16(sock_addr.sin_port);
468 }
469 }
470 }
471 }
472
473 if (s != net::kInvalidSocket) {
474 SetDebugStubPort(port);
475 }
476 if (s == net::kInvalidSocket) {
477 LOG(ERROR) << "failed to open socket for debug stub";
478 return net::kInvalidSocket;
479 }
480 LOG(WARNING) << "debug stub on port " << port;
481 if (listen(s, 1)) {
482 LOG(ERROR) << "listen() failed on debug stub socket";
483 if (IGNORE_EINTR(close(s)) < 0)
484 PLOG(ERROR) << "failed to close debug stub socket";
485 return net::kInvalidSocket;
486 }
487 return s;
488 }
489 #endif
490
StartNaClExecution()491 bool NaClProcessHost::StartNaClExecution() {
492 NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
493
494 NaClStartParams params;
495
496 params.process_type = process_type_;
497 bool enable_nacl_debug = enable_debug_stub_ &&
498 NaClBrowser::GetDelegate()->URLMatchesDebugPatterns(manifest_url_);
499 params.validation_cache_enabled = nacl_browser->ValidationCacheIsEnabled();
500 params.validation_cache_key = nacl_browser->GetValidationCacheKey();
501 params.version = NaClBrowser::GetDelegate()->GetVersionString();
502 params.enable_debug_stub = enable_nacl_debug;
503
504 const base::File& irt_file = nacl_browser->IrtFile();
505 CHECK(irt_file.IsValid());
506 // Send over the IRT file handle. We don't close our own copy!
507 params.irt_handle =
508 IPC::GetPlatformFileForTransit(irt_file.GetPlatformFile(), false);
509 if (params.irt_handle == IPC::InvalidPlatformFileForTransit()) {
510 return false;
511 }
512
513 #if BUILDFLAG(IS_POSIX)
514 if (params.enable_debug_stub) {
515 net::SocketDescriptor server_bound_socket = GetDebugStubSocketHandle();
516 if (server_bound_socket != net::kInvalidSocket) {
517 params.debug_stub_server_bound_socket =
518 IPC::GetPlatformFileForTransit(server_bound_socket, true);
519 }
520 }
521 #endif
522
523 // Create a shared memory region that the renderer and the plugin share to
524 // report crash information.
525 params.crash_info_shmem_region =
526 base::WritableSharedMemoryRegion::Create(kNaClCrashInfoShmemSize);
527 if (!params.crash_info_shmem_region.IsValid()) {
528 DLOG(ERROR) << "Failed to create a shared memory buffer";
529 return false;
530 }
531
532 // Pass the pre-opened resource files to the loader. We do not have to reopen
533 // resource files here because the descriptors are not from a renderer.
534 for (size_t i = 0; i < prefetched_resource_files_.size(); ++i) {
535 process_->Send(
536 new NaClProcessMsg_AddPrefetchedResource(NaClResourcePrefetchResult(
537 prefetched_resource_files_[i].file,
538 prefetched_resource_files_[i].file_path_metadata,
539 prefetched_resource_files_[i].file_key)));
540 }
541 prefetched_resource_files_.clear();
542
543 base::FilePath file_path;
544 if (NaClBrowser::GetInstance()->GetFilePath(nexe_token_.lo, nexe_token_.hi,
545 &file_path)) {
546 // We have to reopen the file in the browser process; we don't want a
547 // compromised renderer to pass an arbitrary fd that could get loaded
548 // into the plugin process.
549 base::ThreadPool::PostTaskAndReplyWithResult(
550 FROM_HERE,
551 // USER_BLOCKING because it is on the critical path of displaying the
552 // official virtual keyboard on Chrome OS. https://crbug.com/976542
553 {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
554 base::BindOnce(OpenNaClReadExecImpl, file_path,
555 true /* is_executable */),
556 base::BindOnce(&NaClProcessHost::StartNaClFileResolved,
557 weak_factory_.GetWeakPtr(), std::move(params),
558 file_path));
559 return true;
560 }
561
562 StartNaClFileResolved(std::move(params), base::FilePath(), base::File());
563 return true;
564 }
565
StartNaClFileResolved(NaClStartParams params,const base::FilePath & file_path,base::File checked_nexe_file)566 void NaClProcessHost::StartNaClFileResolved(
567 NaClStartParams params,
568 const base::FilePath& file_path,
569 base::File checked_nexe_file) {
570 if (checked_nexe_file.IsValid()) {
571 // Release the file received from the renderer. This has to be done on a
572 // thread where IO is permitted, though.
573 base::ThreadPool::PostTask(
574 FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
575 base::BindOnce(&CloseFile, std::move(nexe_file_)));
576 params.nexe_file_path_metadata = file_path;
577 params.nexe_file =
578 IPC::TakePlatformFileForTransit(std::move(checked_nexe_file));
579 } else {
580 params.nexe_file = IPC::TakePlatformFileForTransit(std::move(nexe_file_));
581 }
582
583 process_->Send(new NaClProcessMsg_Start(std::move(params)));
584 }
585
StartPPAPIProxy(mojo::ScopedMessagePipeHandle channel_handle)586 bool NaClProcessHost::StartPPAPIProxy(
587 mojo::ScopedMessagePipeHandle channel_handle) {
588 if (ipc_proxy_channel_.get()) {
589 // Attempt to open more than 1 browser channel is not supported.
590 // Shut down the NaCl process.
591 process_->GetHost()->ForceShutdown();
592 return false;
593 }
594
595 DCHECK_EQ(PROCESS_TYPE_NACL_LOADER, process_->GetData().process_type);
596
597 ipc_proxy_channel_ = IPC::ChannelProxy::Create(
598 channel_handle.release(), IPC::Channel::MODE_CLIENT, nullptr,
599 base::SingleThreadTaskRunner::GetCurrentDefault().get(),
600 base::SingleThreadTaskRunner::GetCurrentDefault().get());
601 // Create the browser ppapi host and enable PPAPI message dispatching to the
602 // browser process.
603 ppapi_host_.reset(content::BrowserPpapiHost::CreateExternalPluginProcess(
604 ipc_proxy_channel_.get(), // sender
605 permissions_, process_->GetData().GetProcess().Duplicate(),
606 ipc_proxy_channel_.get(), profile_directory_));
607
608 ppapi::PpapiNaClPluginArgs args;
609 args.off_the_record = nacl_host_message_filter_->off_the_record();
610 args.permissions = permissions_;
611 base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
612 DCHECK(cmdline);
613 std::string flag_allowlist[] = {
614 switches::kV,
615 switches::kVModule,
616 };
617 for (size_t i = 0; i < std::size(flag_allowlist); ++i) {
618 std::string value = cmdline->GetSwitchValueASCII(flag_allowlist[i]);
619 if (!value.empty()) {
620 args.switch_names.push_back(flag_allowlist[i]);
621 args.switch_values.push_back(value);
622 }
623 }
624
625 std::string enabled_features;
626 std::string disabled_features;
627 base::FeatureList::GetInstance()->GetFeatureOverrides(&enabled_features,
628 &disabled_features);
629 if (!enabled_features.empty()) {
630 args.switch_names.push_back(switches::kEnableFeatures);
631 args.switch_values.push_back(enabled_features);
632 }
633 if (!disabled_features.empty()) {
634 args.switch_names.push_back(switches::kDisableFeatures);
635 args.switch_values.push_back(disabled_features);
636 }
637
638 ppapi_host_->GetPpapiHost()->AddHostFactoryFilter(
639 std::unique_ptr<ppapi::host::HostFactory>(
640 NaClBrowser::GetDelegate()->CreatePpapiHostFactory(
641 ppapi_host_.get())));
642
643 // Send a message to initialize the IPC dispatchers in the NaCl plugin.
644 ipc_proxy_channel_->Send(new PpapiMsg_InitializeNaClDispatcher(args));
645 return true;
646 }
647
648 // This method is called when NaClProcessHostMsg_PpapiChannelCreated is
649 // received.
OnPpapiChannelsCreated(const IPC::ChannelHandle & raw_ppapi_browser_channel_handle,const IPC::ChannelHandle & raw_ppapi_renderer_channel_handle,const IPC::ChannelHandle & raw_trusted_renderer_channel_handle,const IPC::ChannelHandle & raw_manifest_service_channel_handle,base::ReadOnlySharedMemoryRegion crash_info_shmem_region)650 void NaClProcessHost::OnPpapiChannelsCreated(
651 const IPC::ChannelHandle& raw_ppapi_browser_channel_handle,
652 const IPC::ChannelHandle& raw_ppapi_renderer_channel_handle,
653 const IPC::ChannelHandle& raw_trusted_renderer_channel_handle,
654 const IPC::ChannelHandle& raw_manifest_service_channel_handle,
655 base::ReadOnlySharedMemoryRegion crash_info_shmem_region) {
656 DCHECK(raw_ppapi_browser_channel_handle.is_mojo_channel_handle());
657 DCHECK(raw_ppapi_renderer_channel_handle.is_mojo_channel_handle());
658 DCHECK(raw_trusted_renderer_channel_handle.is_mojo_channel_handle());
659 DCHECK(raw_manifest_service_channel_handle.is_mojo_channel_handle());
660
661 mojo::ScopedMessagePipeHandle ppapi_browser_channel_handle(
662 raw_ppapi_browser_channel_handle.mojo_handle);
663 mojo::ScopedMessagePipeHandle ppapi_renderer_channel_handle(
664 raw_ppapi_renderer_channel_handle.mojo_handle);
665 mojo::ScopedMessagePipeHandle trusted_renderer_channel_handle(
666 raw_trusted_renderer_channel_handle.mojo_handle);
667 mojo::ScopedMessagePipeHandle manifest_service_channel_handle(
668 raw_manifest_service_channel_handle.mojo_handle);
669
670 if (!StartPPAPIProxy(std::move(ppapi_browser_channel_handle))) {
671 SendErrorToRenderer("Browser PPAPI proxy could not start.");
672 return;
673 }
674
675 // Let the renderer know that the IPC channels are established.
676 ReplyToRenderer(std::move(ppapi_renderer_channel_handle),
677 std::move(trusted_renderer_channel_handle),
678 std::move(manifest_service_channel_handle),
679 std::move(crash_info_shmem_region));
680 }
681
StartWithLaunchedProcess()682 bool NaClProcessHost::StartWithLaunchedProcess() {
683 NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
684
685 if (nacl_browser->IsReady())
686 return StartNaClExecution();
687 if (nacl_browser->IsOk()) {
688 nacl_browser->WaitForResources(base::BindOnce(
689 &NaClProcessHost::OnResourcesReady, weak_factory_.GetWeakPtr()));
690 return true;
691 }
692 SendErrorToRenderer("previously failed to acquire shared resources");
693 return false;
694 }
695
OnQueryKnownToValidate(const std::string & signature,bool * result)696 void NaClProcessHost::OnQueryKnownToValidate(const std::string& signature,
697 bool* result) {
698 NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
699 *result = nacl_browser->QueryKnownToValidate(signature, off_the_record_);
700 }
701
OnSetKnownToValidate(const std::string & signature)702 void NaClProcessHost::OnSetKnownToValidate(const std::string& signature) {
703 NaClBrowser::GetInstance()->SetKnownToValidate(
704 signature, off_the_record_);
705 }
706
OnResolveFileToken(uint64_t file_token_lo,uint64_t file_token_hi)707 void NaClProcessHost::OnResolveFileToken(uint64_t file_token_lo,
708 uint64_t file_token_hi) {
709 // Was the file registered?
710 //
711 // Note that the file path cache is of bounded size, and old entries can get
712 // evicted. If a large number of NaCl modules are being launched at once,
713 // resolving the file_token may fail because the path cache was thrashed
714 // while the file_token was in flight. In this case the query fails, and we
715 // need to fall back to the slower path.
716 //
717 // However: each NaCl process will consume 2-3 entries as it starts up, this
718 // means that eviction will not happen unless you start up 33+ NaCl processes
719 // at the same time, and this still requires worst-case timing. As a
720 // practical matter, no entries should be evicted prematurely.
721 // The cache itself should take ~ (150 characters * 2 bytes/char + ~60 bytes
722 // data structure overhead) * 100 = 35k when full, so making it bigger should
723 // not be a problem, if needed.
724 //
725 // Each NaCl process will consume 2-3 entries because the manifest and main
726 // nexe are currently not resolved. Shared libraries will be resolved. They
727 // will be loaded sequentially, so they will only consume a single entry
728 // while the load is in flight.
729 //
730 // TODO(ncbray): track behavior with UMA. If entries are getting evicted or
731 // bogus keys are getting queried, this would be good to know.
732 base::FilePath file_path;
733 if (!NaClBrowser::GetInstance()->GetFilePath(
734 file_token_lo, file_token_hi, &file_path)) {
735 Send(new NaClProcessMsg_ResolveFileTokenReply(
736 file_token_lo,
737 file_token_hi,
738 IPC::PlatformFileForTransit(),
739 base::FilePath()));
740 return;
741 }
742
743 // Open the file.
744 base::ThreadPool::PostTaskAndReplyWithResult(
745 FROM_HERE,
746 // USER_BLOCKING because it is on the critical path of displaying the
747 // official virtual keyboard on Chrome OS. https://crbug.com/976542
748 {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
749 base::BindOnce(OpenNaClReadExecImpl, file_path, true /* is_executable */),
750 base::BindOnce(&NaClProcessHost::FileResolved, weak_factory_.GetWeakPtr(),
751 file_token_lo, file_token_hi, file_path));
752 }
753
FileResolved(uint64_t file_token_lo,uint64_t file_token_hi,const base::FilePath & file_path,base::File file)754 void NaClProcessHost::FileResolved(
755 uint64_t file_token_lo,
756 uint64_t file_token_hi,
757 const base::FilePath& file_path,
758 base::File file) {
759 base::FilePath out_file_path;
760 IPC::PlatformFileForTransit out_handle;
761 if (file.IsValid()) {
762 out_file_path = file_path;
763 out_handle = IPC::TakePlatformFileForTransit(std::move(file));
764 } else {
765 out_handle = IPC::InvalidPlatformFileForTransit();
766 }
767 Send(new NaClProcessMsg_ResolveFileTokenReply(
768 file_token_lo,
769 file_token_hi,
770 out_handle,
771 out_file_path));
772 }
773
774 } // namespace nacl
775