OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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 "chrome/browser/nacl_host/nacl_process_host.h" | |
6 | |
7 #include <algorithm> | |
8 #include <string> | |
9 #include <vector> | |
10 | |
11 #include "base/base_switches.h" | |
12 #include "base/bind.h" | |
13 #include "base/command_line.h" | |
14 #include "base/file_util.h" | |
15 #include "base/message_loop/message_loop.h" | |
16 #include "base/metrics/histogram.h" | |
17 #include "base/path_service.h" | |
18 #include "base/process/launch.h" | |
19 #include "base/process/process_iterator.h" | |
20 #include "base/rand_util.h" | |
21 #include "base/strings/string_number_conversions.h" | |
22 #include "base/strings/string_split.h" | |
23 #include "base/strings/string_util.h" | |
24 #include "base/strings/stringprintf.h" | |
25 #include "base/strings/utf_string_conversions.h" | |
26 #include "base/win/windows_version.h" | |
27 #include "build/build_config.h" | |
28 #include "chrome/browser/nacl_host/nacl_host_message_filter.h" | |
29 #include "chrome/common/chrome_switches.h" | |
30 #include "components/nacl/browser/nacl_browser.h" | |
31 #include "components/nacl/common/nacl_cmd_line.h" | |
32 #include "components/nacl/common/nacl_host_messages.h" | |
33 #include "components/nacl/common/nacl_messages.h" | |
34 #include "components/nacl/common/nacl_process_type.h" | |
35 #include "components/nacl/common/nacl_switches.h" | |
36 #include "content/public/browser/browser_child_process_host.h" | |
37 #include "content/public/browser/browser_ppapi_host.h" | |
38 #include "content/public/browser/child_process_data.h" | |
39 #include "content/public/common/child_process_host.h" | |
40 #include "content/public/common/content_switches.h" | |
41 #include "content/public/common/process_type.h" | |
42 #include "ipc/ipc_channel.h" | |
43 #include "ipc/ipc_switches.h" | |
44 #include "native_client/src/shared/imc/nacl_imc_c.h" | |
45 #include "net/base/net_util.h" | |
46 #include "net/socket/tcp_listen_socket.h" | |
47 #include "ppapi/host/host_factory.h" | |
48 #include "ppapi/host/ppapi_host.h" | |
49 #include "ppapi/proxy/ppapi_messages.h" | |
50 #include "ppapi/shared_impl/ppapi_nacl_channel_args.h" | |
51 | |
52 #if defined(OS_POSIX) | |
53 #include <fcntl.h> | |
54 | |
55 #include "ipc/ipc_channel_posix.h" | |
56 #elif defined(OS_WIN) | |
57 #include <windows.h> | |
58 | |
59 #include "base/threading/thread.h" | |
60 #include "base/win/scoped_handle.h" | |
61 #include "chrome/browser/nacl_host/nacl_broker_service_win.h" | |
62 #include "components/nacl/common/nacl_debug_exception_handler_win.h" | |
63 #include "content/public/common/sandbox_init.h" | |
64 #include "content/public/common/sandboxed_process_launcher_delegate.h" | |
65 #endif | |
66 | |
67 using content::BrowserThread; | |
68 using content::ChildProcessData; | |
69 using content::ChildProcessHost; | |
70 using ppapi::proxy::SerializedHandle; | |
71 | |
72 #if defined(OS_WIN) | |
73 | |
74 namespace { | |
75 | |
76 // Looks for the largest contiguous unallocated region of address | |
77 // space and returns it via |*out_addr| and |*out_size|. | |
78 void FindAddressSpace(base::ProcessHandle process, | |
79 char** out_addr, size_t* out_size) { | |
80 *out_addr = NULL; | |
81 *out_size = 0; | |
82 char* addr = 0; | |
83 while (true) { | |
84 MEMORY_BASIC_INFORMATION info; | |
85 size_t result = VirtualQueryEx(process, static_cast<void*>(addr), | |
86 &info, sizeof(info)); | |
87 if (result < sizeof(info)) | |
88 break; | |
89 if (info.State == MEM_FREE && info.RegionSize > *out_size) { | |
90 *out_addr = addr; | |
91 *out_size = info.RegionSize; | |
92 } | |
93 addr += info.RegionSize; | |
94 } | |
95 } | |
96 | |
97 } // namespace | |
98 | |
99 namespace nacl { | |
100 | |
101 // Allocates |size| bytes of address space in the given process at a | |
102 // randomised address. | |
103 void* AllocateAddressSpaceASLR(base::ProcessHandle process, size_t size) { | |
104 char* addr; | |
105 size_t avail_size; | |
106 FindAddressSpace(process, &addr, &avail_size); | |
107 if (avail_size < size) | |
108 return NULL; | |
109 size_t offset = base::RandGenerator(avail_size - size); | |
110 const int kPageSize = 0x10000; | |
111 void* request_addr = | |
112 reinterpret_cast<void*>(reinterpret_cast<uint64>(addr + offset) | |
113 & ~(kPageSize - 1)); | |
114 return VirtualAllocEx(process, request_addr, size, | |
115 MEM_RESERVE, PAGE_NOACCESS); | |
116 } | |
117 | |
118 } // namespace nacl | |
119 | |
120 #endif // defined(OS_WIN) | |
121 | |
122 namespace { | |
123 | |
124 #if defined(OS_WIN) | |
125 bool RunningOnWOW64() { | |
126 return (base::win::OSInfo::GetInstance()->wow64_status() == | |
127 base::win::OSInfo::WOW64_ENABLED); | |
128 } | |
129 | |
130 // NOTE: changes to this class need to be reviewed by the security team. | |
131 class NaClSandboxedProcessLauncherDelegate | |
132 : public content::SandboxedProcessLauncherDelegate { | |
133 public: | |
134 NaClSandboxedProcessLauncherDelegate() {} | |
135 virtual ~NaClSandboxedProcessLauncherDelegate() {} | |
136 | |
137 virtual void PostSpawnTarget(base::ProcessHandle process) { | |
138 // For Native Client sel_ldr processes on 32-bit Windows, reserve 1 GB of | |
139 // address space to prevent later failure due to address space fragmentation | |
140 // from .dll loading. The NaCl process will attempt to locate this space by | |
141 // scanning the address space using VirtualQuery. | |
142 // TODO(bbudge) Handle the --no-sandbox case. | |
143 // http://code.google.com/p/nativeclient/issues/detail?id=2131 | |
144 const SIZE_T kNaClSandboxSize = 1 << 30; | |
145 if (!nacl::AllocateAddressSpaceASLR(process, kNaClSandboxSize)) { | |
146 DLOG(WARNING) << "Failed to reserve address space for Native Client"; | |
147 } | |
148 } | |
149 }; | |
150 | |
151 #endif // OS_WIN | |
152 | |
153 void SetCloseOnExec(NaClHandle fd) { | |
154 #if defined(OS_POSIX) | |
155 int flags = fcntl(fd, F_GETFD); | |
156 CHECK_NE(flags, -1); | |
157 int rc = fcntl(fd, F_SETFD, flags | FD_CLOEXEC); | |
158 CHECK_EQ(rc, 0); | |
159 #endif | |
160 } | |
161 | |
162 bool ShareHandleToSelLdr( | |
163 base::ProcessHandle processh, | |
164 NaClHandle sourceh, | |
165 bool close_source, | |
166 std::vector<nacl::FileDescriptor> *handles_for_sel_ldr) { | |
167 #if defined(OS_WIN) | |
168 HANDLE channel; | |
169 int flags = DUPLICATE_SAME_ACCESS; | |
170 if (close_source) | |
171 flags |= DUPLICATE_CLOSE_SOURCE; | |
172 if (!DuplicateHandle(GetCurrentProcess(), | |
173 reinterpret_cast<HANDLE>(sourceh), | |
174 processh, | |
175 &channel, | |
176 0, // Unused given DUPLICATE_SAME_ACCESS. | |
177 FALSE, | |
178 flags)) { | |
179 LOG(ERROR) << "DuplicateHandle() failed"; | |
180 return false; | |
181 } | |
182 handles_for_sel_ldr->push_back( | |
183 reinterpret_cast<nacl::FileDescriptor>(channel)); | |
184 #else | |
185 nacl::FileDescriptor channel; | |
186 channel.fd = sourceh; | |
187 channel.auto_close = close_source; | |
188 handles_for_sel_ldr->push_back(channel); | |
189 #endif | |
190 return true; | |
191 } | |
192 | |
193 ppapi::PpapiPermissions GetNaClPermissions(uint32 permission_bits) { | |
194 // Only allow NaCl plugins to request certain permissions. We don't want | |
195 // a compromised renderer to be able to start a nacl plugin with e.g. Flash | |
196 // permissions which may expand the surface area of the sandbox. | |
197 uint32 masked_bits = permission_bits & ppapi::PERMISSION_DEV; | |
198 return ppapi::PpapiPermissions::GetForCommandLine(masked_bits); | |
199 } | |
200 | |
201 } // namespace | |
202 | |
203 struct NaClProcessHost::NaClInternal { | |
204 NaClHandle socket_for_renderer; | |
205 NaClHandle socket_for_sel_ldr; | |
206 | |
207 NaClInternal() | |
208 : socket_for_renderer(NACL_INVALID_HANDLE), | |
209 socket_for_sel_ldr(NACL_INVALID_HANDLE) { } | |
210 }; | |
211 | |
212 // ----------------------------------------------------------------------------- | |
213 | |
214 NaClProcessHost::PluginListener::PluginListener(NaClProcessHost* host) | |
215 : host_(host) { | |
216 } | |
217 | |
218 bool NaClProcessHost::PluginListener::OnMessageReceived( | |
219 const IPC::Message& msg) { | |
220 return host_->OnUntrustedMessageForwarded(msg); | |
221 } | |
222 | |
223 NaClProcessHost::NaClProcessHost(const GURL& manifest_url, | |
224 int render_view_id, | |
225 uint32 permission_bits, | |
226 bool uses_irt, | |
227 bool enable_dyncode_syscalls, | |
228 bool enable_exception_handling, | |
229 bool enable_crash_throttling, | |
230 bool off_the_record, | |
231 const base::FilePath& profile_directory) | |
232 : manifest_url_(manifest_url), | |
233 permissions_(GetNaClPermissions(permission_bits)), | |
234 #if defined(OS_WIN) | |
235 process_launched_by_broker_(false), | |
236 #endif | |
237 reply_msg_(NULL), | |
238 #if defined(OS_WIN) | |
239 debug_exception_handler_requested_(false), | |
240 #endif | |
241 internal_(new NaClInternal()), | |
242 weak_factory_(this), | |
243 uses_irt_(uses_irt), | |
244 enable_debug_stub_(false), | |
245 enable_dyncode_syscalls_(enable_dyncode_syscalls), | |
246 enable_exception_handling_(enable_exception_handling), | |
247 enable_crash_throttling_(enable_crash_throttling), | |
248 off_the_record_(off_the_record), | |
249 profile_directory_(profile_directory), | |
250 ipc_plugin_listener_(this), | |
251 render_view_id_(render_view_id) { | |
252 process_.reset(content::BrowserChildProcessHost::Create( | |
253 PROCESS_TYPE_NACL_LOADER, this)); | |
254 | |
255 // Set the display name so the user knows what plugin the process is running. | |
256 // We aren't on the UI thread so getting the pref locale for language | |
257 // formatting isn't possible, so IDN will be lost, but this is probably OK | |
258 // for this use case. | |
259 process_->SetName(net::FormatUrl(manifest_url_, std::string())); | |
260 | |
261 enable_debug_stub_ = CommandLine::ForCurrentProcess()->HasSwitch( | |
262 switches::kEnableNaClDebug); | |
263 } | |
264 | |
265 NaClProcessHost::~NaClProcessHost() { | |
266 // Report exit status only if the process was successfully started. | |
267 if (process_->GetData().handle != base::kNullProcessHandle) { | |
268 int exit_code = 0; | |
269 process_->GetTerminationStatus(false /* known_dead */, &exit_code); | |
270 std::string message = | |
271 base::StringPrintf("NaCl process exited with status %i (0x%x)", | |
272 exit_code, exit_code); | |
273 if (exit_code == 0) { | |
274 LOG(INFO) << message; | |
275 } else { | |
276 LOG(ERROR) << message; | |
277 } | |
278 } | |
279 | |
280 if (internal_->socket_for_renderer != NACL_INVALID_HANDLE) { | |
281 if (NaClClose(internal_->socket_for_renderer) != 0) { | |
282 NOTREACHED() << "NaClClose() failed"; | |
283 } | |
284 } | |
285 | |
286 if (internal_->socket_for_sel_ldr != NACL_INVALID_HANDLE) { | |
287 if (NaClClose(internal_->socket_for_sel_ldr) != 0) { | |
288 NOTREACHED() << "NaClClose() failed"; | |
289 } | |
290 } | |
291 | |
292 if (reply_msg_) { | |
293 // The process failed to launch for some reason. | |
294 // Don't keep the renderer hanging. | |
295 reply_msg_->set_reply_error(); | |
296 nacl_host_message_filter_->Send(reply_msg_); | |
297 } | |
298 #if defined(OS_WIN) | |
299 if (process_launched_by_broker_) { | |
300 NaClBrokerService::GetInstance()->OnLoaderDied(); | |
301 } | |
302 #endif | |
303 } | |
304 | |
305 void NaClProcessHost::OnProcessCrashed(int exit_status) { | |
306 if (enable_crash_throttling_ && | |
307 !CommandLine::ForCurrentProcess()->HasSwitch( | |
308 switches::kDisablePnaclCrashThrottling)) { | |
309 nacl::NaClBrowser::GetInstance()->OnProcessCrashed(); | |
310 } | |
311 } | |
312 | |
313 // This is called at browser startup. | |
314 // static | |
315 void NaClProcessHost::EarlyStartup() { | |
316 nacl::NaClBrowser::GetInstance()->EarlyStartup(); | |
317 #if defined(OS_LINUX) && !defined(OS_CHROMEOS) | |
318 // Open the IRT file early to make sure that it isn't replaced out from | |
319 // under us by autoupdate. | |
320 nacl::NaClBrowser::GetInstance()->EnsureIrtAvailable(); | |
321 #endif | |
322 CommandLine* cmd = CommandLine::ForCurrentProcess(); | |
323 UMA_HISTOGRAM_BOOLEAN( | |
324 "NaCl.nacl-gdb", | |
325 !cmd->GetSwitchValuePath(switches::kNaClGdb).empty()); | |
326 UMA_HISTOGRAM_BOOLEAN( | |
327 "NaCl.nacl-gdb-script", | |
328 !cmd->GetSwitchValuePath(switches::kNaClGdbScript).empty()); | |
329 UMA_HISTOGRAM_BOOLEAN( | |
330 "NaCl.enable-nacl-debug", | |
331 cmd->HasSwitch(switches::kEnableNaClDebug)); | |
332 nacl::NaClBrowser::GetDelegate()->SetDebugPatterns( | |
333 cmd->GetSwitchValueASCII(switches::kNaClDebugMask)); | |
334 } | |
335 | |
336 void NaClProcessHost::Launch( | |
337 NaClHostMessageFilter* nacl_host_message_filter, | |
338 IPC::Message* reply_msg, | |
339 const base::FilePath& manifest_path) { | |
340 nacl_host_message_filter_ = nacl_host_message_filter; | |
341 reply_msg_ = reply_msg; | |
342 manifest_path_ = manifest_path; | |
343 | |
344 // Do not launch the requested NaCl module if NaCl is marked "unstable" due | |
345 // to too many crashes within a given time period. | |
346 if (enable_crash_throttling_ && | |
347 !CommandLine::ForCurrentProcess()->HasSwitch( | |
348 switches::kDisablePnaclCrashThrottling) && | |
349 nacl::NaClBrowser::GetInstance()->IsThrottled()) { | |
350 SendErrorToRenderer("Process creation was throttled due to excessive" | |
351 " crashes"); | |
352 delete this; | |
353 return; | |
354 } | |
355 | |
356 const CommandLine* cmd = CommandLine::ForCurrentProcess(); | |
357 #if defined(OS_WIN) | |
358 if (cmd->HasSwitch(switches::kEnableNaClDebug) && | |
359 !cmd->HasSwitch(switches::kNoSandbox)) { | |
360 // We don't switch off sandbox automatically for security reasons. | |
361 SendErrorToRenderer("NaCl's GDB debug stub requires --no-sandbox flag" | |
362 " on Windows. See crbug.com/265624."); | |
363 delete this; | |
364 return; | |
365 } | |
366 #endif | |
367 if (cmd->HasSwitch(switches::kNaClGdb) && | |
368 !cmd->HasSwitch(switches::kEnableNaClDebug)) { | |
369 LOG(WARNING) << "--nacl-gdb flag requires --enable-nacl-debug flag"; | |
370 } | |
371 | |
372 // Start getting the IRT open asynchronously while we launch the NaCl process. | |
373 // We'll make sure this actually finished in StartWithLaunchedProcess, below. | |
374 nacl::NaClBrowser* nacl_browser = nacl::NaClBrowser::GetInstance(); | |
375 nacl_browser->EnsureAllResourcesAvailable(); | |
376 if (!nacl_browser->IsOk()) { | |
377 SendErrorToRenderer("could not find all the resources needed" | |
378 " to launch the process"); | |
379 delete this; | |
380 return; | |
381 } | |
382 | |
383 // Rather than creating a socket pair in the renderer, and passing | |
384 // one side through the browser to sel_ldr, socket pairs are created | |
385 // in the browser and then passed to the renderer and sel_ldr. | |
386 // | |
387 // This is mainly for the benefit of Windows, where sockets cannot | |
388 // be passed in messages, but are copied via DuplicateHandle(). | |
389 // This means the sandboxed renderer cannot send handles to the | |
390 // browser process. | |
391 | |
392 NaClHandle pair[2]; | |
393 // Create a connected socket | |
394 if (NaClSocketPair(pair) == -1) { | |
395 SendErrorToRenderer("NaClSocketPair() failed"); | |
396 delete this; | |
397 return; | |
398 } | |
399 internal_->socket_for_renderer = pair[0]; | |
400 internal_->socket_for_sel_ldr = pair[1]; | |
401 SetCloseOnExec(pair[0]); | |
402 SetCloseOnExec(pair[1]); | |
403 | |
404 // Launch the process | |
405 if (!LaunchSelLdr()) { | |
406 delete this; | |
407 } | |
408 } | |
409 | |
410 void NaClProcessHost::OnChannelConnected(int32 peer_pid) { | |
411 if (!CommandLine::ForCurrentProcess()->GetSwitchValuePath( | |
412 switches::kNaClGdb).empty()) { | |
413 LaunchNaClGdb(); | |
414 } | |
415 } | |
416 | |
417 #if defined(OS_WIN) | |
418 void NaClProcessHost::OnProcessLaunchedByBroker(base::ProcessHandle handle) { | |
419 process_launched_by_broker_ = true; | |
420 process_->SetHandle(handle); | |
421 if (!StartWithLaunchedProcess()) | |
422 delete this; | |
423 } | |
424 | |
425 void NaClProcessHost::OnDebugExceptionHandlerLaunchedByBroker(bool success) { | |
426 IPC::Message* reply = attach_debug_exception_handler_reply_msg_.release(); | |
427 NaClProcessMsg_AttachDebugExceptionHandler::WriteReplyParams(reply, success); | |
428 Send(reply); | |
429 } | |
430 #endif | |
431 | |
432 // Needed to handle sync messages in OnMessageRecieved. | |
433 bool NaClProcessHost::Send(IPC::Message* msg) { | |
434 return process_->Send(msg); | |
435 } | |
436 | |
437 bool NaClProcessHost::LaunchNaClGdb() { | |
438 #if defined(OS_WIN) | |
439 base::FilePath nacl_gdb = | |
440 CommandLine::ForCurrentProcess()->GetSwitchValuePath(switches::kNaClGdb); | |
441 CommandLine cmd_line(nacl_gdb); | |
442 #else | |
443 CommandLine::StringType nacl_gdb = | |
444 CommandLine::ForCurrentProcess()->GetSwitchValueNative( | |
445 switches::kNaClGdb); | |
446 CommandLine::StringVector argv; | |
447 // We don't support spaces inside arguments in --nacl-gdb switch. | |
448 base::SplitString(nacl_gdb, static_cast<CommandLine::CharType>(' '), &argv); | |
449 CommandLine cmd_line(argv); | |
450 #endif | |
451 cmd_line.AppendArg("--eval-command"); | |
452 base::FilePath::StringType irt_path( | |
453 nacl::NaClBrowser::GetInstance()->GetIrtFilePath().value()); | |
454 // Avoid back slashes because nacl-gdb uses posix escaping rules on Windows. | |
455 // See issue https://code.google.com/p/nativeclient/issues/detail?id=3482. | |
456 std::replace(irt_path.begin(), irt_path.end(), '\\', '/'); | |
457 cmd_line.AppendArgNative(FILE_PATH_LITERAL("nacl-irt \"") + irt_path + | |
458 FILE_PATH_LITERAL("\"")); | |
459 if (!manifest_path_.empty()) { | |
460 cmd_line.AppendArg("--eval-command"); | |
461 base::FilePath::StringType manifest_path_value(manifest_path_.value()); | |
462 std::replace(manifest_path_value.begin(), manifest_path_value.end(), | |
463 '\\', '/'); | |
464 cmd_line.AppendArgNative(FILE_PATH_LITERAL("nacl-manifest \"") + | |
465 manifest_path_value + FILE_PATH_LITERAL("\"")); | |
466 } | |
467 cmd_line.AppendArg("--eval-command"); | |
468 cmd_line.AppendArg("target remote :4014"); | |
469 base::FilePath script = CommandLine::ForCurrentProcess()->GetSwitchValuePath( | |
470 switches::kNaClGdbScript); | |
471 if (!script.empty()) { | |
472 cmd_line.AppendArg("--command"); | |
473 cmd_line.AppendArgNative(script.value()); | |
474 } | |
475 return base::LaunchProcess(cmd_line, base::LaunchOptions(), NULL); | |
476 } | |
477 | |
478 bool NaClProcessHost::LaunchSelLdr() { | |
479 std::string channel_id = process_->GetHost()->CreateChannel(); | |
480 if (channel_id.empty()) { | |
481 SendErrorToRenderer("CreateChannel() failed"); | |
482 return false; | |
483 } | |
484 | |
485 CommandLine::StringType nacl_loader_prefix; | |
486 #if defined(OS_POSIX) | |
487 nacl_loader_prefix = CommandLine::ForCurrentProcess()->GetSwitchValueNative( | |
488 switches::kNaClLoaderCmdPrefix); | |
489 #endif // defined(OS_POSIX) | |
490 | |
491 // Build command line for nacl. | |
492 | |
493 #if defined(OS_MACOSX) | |
494 // The Native Client process needs to be able to allocate a 1GB contiguous | |
495 // region to use as the client environment's virtual address space. ASLR | |
496 // (PIE) interferes with this by making it possible that no gap large enough | |
497 // to accomodate this request will exist in the child process' address | |
498 // space. Disable PIE for NaCl processes. See http://crbug.com/90221 and | |
499 // http://code.google.com/p/nativeclient/issues/detail?id=2043. | |
500 int flags = ChildProcessHost::CHILD_NO_PIE; | |
501 #elif defined(OS_LINUX) | |
502 int flags = nacl_loader_prefix.empty() ? ChildProcessHost::CHILD_ALLOW_SELF : | |
503 ChildProcessHost::CHILD_NORMAL; | |
504 #else | |
505 int flags = ChildProcessHost::CHILD_NORMAL; | |
506 #endif | |
507 | |
508 base::FilePath exe_path = ChildProcessHost::GetChildPath(flags); | |
509 if (exe_path.empty()) | |
510 return false; | |
511 | |
512 #if defined(OS_WIN) | |
513 // On Windows 64-bit NaCl loader is called nacl64.exe instead of chrome.exe | |
514 if (RunningOnWOW64()) { | |
515 if (!nacl::NaClBrowser::GetInstance()->GetNaCl64ExePath(&exe_path)) { | |
516 SendErrorToRenderer("could not get path to nacl64.exe"); | |
517 return false; | |
518 } | |
519 } | |
520 #endif | |
521 | |
522 scoped_ptr<CommandLine> cmd_line(new CommandLine(exe_path)); | |
523 nacl::CopyNaClCommandLineArguments(cmd_line.get()); | |
524 | |
525 cmd_line->AppendSwitchASCII(switches::kProcessType, | |
526 switches::kNaClLoaderProcess); | |
527 cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id); | |
528 if (nacl::NaClBrowser::GetDelegate()->DialogsAreSuppressed()) | |
529 cmd_line->AppendSwitch(switches::kNoErrorDialogs); | |
530 | |
531 if (!nacl_loader_prefix.empty()) | |
532 cmd_line->PrependWrapper(nacl_loader_prefix); | |
533 | |
534 // On Windows we might need to start the broker process to launch a new loader | |
535 #if defined(OS_WIN) | |
536 if (RunningOnWOW64()) { | |
537 if (!NaClBrokerService::GetInstance()->LaunchLoader( | |
538 weak_factory_.GetWeakPtr(), channel_id)) { | |
539 SendErrorToRenderer("broker service did not launch process"); | |
540 return false; | |
541 } | |
542 } else { | |
543 process_->Launch(new NaClSandboxedProcessLauncherDelegate, | |
544 cmd_line.release()); | |
545 } | |
546 #elif defined(OS_POSIX) | |
547 process_->Launch(nacl_loader_prefix.empty(), // use_zygote | |
548 base::EnvironmentMap(), | |
549 cmd_line.release()); | |
550 #endif | |
551 | |
552 return true; | |
553 } | |
554 | |
555 bool NaClProcessHost::OnMessageReceived(const IPC::Message& msg) { | |
556 bool handled = true; | |
557 IPC_BEGIN_MESSAGE_MAP(NaClProcessHost, msg) | |
558 IPC_MESSAGE_HANDLER(NaClProcessMsg_QueryKnownToValidate, | |
559 OnQueryKnownToValidate) | |
560 IPC_MESSAGE_HANDLER(NaClProcessMsg_SetKnownToValidate, | |
561 OnSetKnownToValidate) | |
562 IPC_MESSAGE_HANDLER_DELAY_REPLY(NaClProcessMsg_ResolveFileToken, | |
563 OnResolveFileToken) | |
564 #if defined(OS_WIN) | |
565 IPC_MESSAGE_HANDLER_DELAY_REPLY(NaClProcessMsg_AttachDebugExceptionHandler, | |
566 OnAttachDebugExceptionHandler) | |
567 #endif | |
568 IPC_MESSAGE_HANDLER(NaClProcessHostMsg_PpapiChannelCreated, | |
569 OnPpapiChannelCreated) | |
570 IPC_MESSAGE_UNHANDLED(handled = false) | |
571 IPC_END_MESSAGE_MAP() | |
572 return handled; | |
573 } | |
574 | |
575 void NaClProcessHost::OnProcessLaunched() { | |
576 if (!StartWithLaunchedProcess()) | |
577 delete this; | |
578 } | |
579 | |
580 // Called when the NaClBrowser singleton has been fully initialized. | |
581 void NaClProcessHost::OnResourcesReady() { | |
582 nacl::NaClBrowser* nacl_browser = nacl::NaClBrowser::GetInstance(); | |
583 if (!nacl_browser->IsReady()) { | |
584 SendErrorToRenderer("could not acquire shared resources needed by NaCl"); | |
585 delete this; | |
586 } else if (!SendStart()) { | |
587 delete this; | |
588 } | |
589 } | |
590 | |
591 bool NaClProcessHost::ReplyToRenderer( | |
592 const IPC::ChannelHandle& channel_handle) { | |
593 #if defined(OS_WIN) | |
594 // If we are on 64-bit Windows, the NaCl process's sandbox is | |
595 // managed by a different process from the renderer's sandbox. We | |
596 // need to inform the renderer's sandbox about the NaCl process so | |
597 // that the renderer can send handles to the NaCl process using | |
598 // BrokerDuplicateHandle(). | |
599 if (RunningOnWOW64()) { | |
600 if (!content::BrokerAddTargetPeer(process_->GetData().handle)) { | |
601 SendErrorToRenderer("BrokerAddTargetPeer() failed"); | |
602 return false; | |
603 } | |
604 } | |
605 #endif | |
606 | |
607 nacl::FileDescriptor handle_for_renderer; | |
608 #if defined(OS_WIN) | |
609 // Copy the handle into the renderer process. | |
610 HANDLE handle_in_renderer; | |
611 if (!DuplicateHandle(base::GetCurrentProcessHandle(), | |
612 reinterpret_cast<HANDLE>( | |
613 internal_->socket_for_renderer), | |
614 nacl_host_message_filter_->PeerHandle(), | |
615 &handle_in_renderer, | |
616 0, // Unused given DUPLICATE_SAME_ACCESS. | |
617 FALSE, | |
618 DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { | |
619 SendErrorToRenderer("DuplicateHandle() failed"); | |
620 return false; | |
621 } | |
622 handle_for_renderer = reinterpret_cast<nacl::FileDescriptor>( | |
623 handle_in_renderer); | |
624 #else | |
625 // No need to dup the imc_handle - we don't pass it anywhere else so | |
626 // it cannot be closed. | |
627 nacl::FileDescriptor imc_handle; | |
628 imc_handle.fd = internal_->socket_for_renderer; | |
629 imc_handle.auto_close = true; | |
630 handle_for_renderer = imc_handle; | |
631 #endif | |
632 | |
633 const ChildProcessData& data = process_->GetData(); | |
634 SendMessageToRenderer( | |
635 nacl::NaClLaunchResult(handle_for_renderer, | |
636 channel_handle, | |
637 base::GetProcId(data.handle), | |
638 data.id), | |
639 std::string() /* error_message */); | |
640 internal_->socket_for_renderer = NACL_INVALID_HANDLE; | |
641 return true; | |
642 } | |
643 | |
644 void NaClProcessHost::SendErrorToRenderer(const std::string& error_message) { | |
645 LOG(ERROR) << "NaCl process launch failed: " << error_message; | |
646 SendMessageToRenderer(nacl::NaClLaunchResult(), error_message); | |
647 } | |
648 | |
649 void NaClProcessHost::SendMessageToRenderer( | |
650 const nacl::NaClLaunchResult& result, | |
651 const std::string& error_message) { | |
652 DCHECK(nacl_host_message_filter_); | |
653 DCHECK(reply_msg_); | |
654 if (nacl_host_message_filter_ != NULL && reply_msg_ != NULL) { | |
655 NaClHostMsg_LaunchNaCl::WriteReplyParams( | |
656 reply_msg_, result, error_message); | |
657 nacl_host_message_filter_->Send(reply_msg_); | |
658 nacl_host_message_filter_ = NULL; | |
659 reply_msg_ = NULL; | |
660 } | |
661 } | |
662 | |
663 // TCP port we chose for NaCl debug stub. It can be any other number. | |
664 static const int kDebugStubPort = 4014; | |
665 | |
666 #if defined(OS_POSIX) | |
667 net::SocketDescriptor NaClProcessHost::GetDebugStubSocketHandle() { | |
668 nacl::NaClBrowser* nacl_browser = nacl::NaClBrowser::GetInstance(); | |
669 net::SocketDescriptor s = net::kInvalidSocket; | |
670 // We allocate currently unused TCP port for debug stub tests. The port | |
671 // number is passed to the test via debug stub port listener. | |
672 if (nacl_browser->HasGdbDebugStubPortListener()) { | |
673 int port; | |
674 s = net::TCPListenSocket::CreateAndBindAnyPort("127.0.0.1", &port); | |
675 if (s != net::kInvalidSocket) { | |
676 nacl_browser->FireGdbDebugStubPortOpened(port); | |
677 } | |
678 } else { | |
679 s = net::TCPListenSocket::CreateAndBind("127.0.0.1", kDebugStubPort); | |
680 } | |
681 if (s == net::kInvalidSocket) { | |
682 LOG(ERROR) << "failed to open socket for debug stub"; | |
683 return net::kInvalidSocket; | |
684 } | |
685 if (listen(s, 1)) { | |
686 LOG(ERROR) << "listen() failed on debug stub socket"; | |
687 if (HANDLE_EINTR(close(s)) < 0) | |
688 PLOG(ERROR) << "failed to close debug stub socket"; | |
689 return net::kInvalidSocket; | |
690 } | |
691 return s; | |
692 } | |
693 #endif | |
694 | |
695 bool NaClProcessHost::StartNaClExecution() { | |
696 nacl::NaClBrowser* nacl_browser = nacl::NaClBrowser::GetInstance(); | |
697 | |
698 nacl::NaClStartParams params; | |
699 params.validation_cache_enabled = nacl_browser->ValidationCacheIsEnabled(); | |
700 params.validation_cache_key = nacl_browser->GetValidationCacheKey(); | |
701 params.version = nacl::NaClBrowser::GetDelegate()->GetVersionString(); | |
702 params.enable_exception_handling = enable_exception_handling_; | |
703 params.enable_debug_stub = enable_debug_stub_ && | |
704 nacl::NaClBrowser::GetDelegate()->URLMatchesDebugPatterns(manifest_url_); | |
705 // Enable PPAPI proxy channel creation only for renderer processes. | |
706 params.enable_ipc_proxy = enable_ppapi_proxy(); | |
707 params.uses_irt = uses_irt_; | |
708 params.enable_dyncode_syscalls = enable_dyncode_syscalls_; | |
709 | |
710 const ChildProcessData& data = process_->GetData(); | |
711 if (!ShareHandleToSelLdr(data.handle, | |
712 internal_->socket_for_sel_ldr, true, | |
713 ¶ms.handles)) { | |
714 return false; | |
715 } | |
716 | |
717 if (params.uses_irt) { | |
718 base::PlatformFile irt_file = nacl_browser->IrtFile(); | |
719 CHECK_NE(irt_file, base::kInvalidPlatformFileValue); | |
720 // Send over the IRT file handle. We don't close our own copy! | |
721 if (!ShareHandleToSelLdr(data.handle, irt_file, false, ¶ms.handles)) | |
722 return false; | |
723 } | |
724 | |
725 #if defined(OS_MACOSX) | |
726 // For dynamic loading support, NaCl requires a file descriptor that | |
727 // was created in /tmp, since those created with shm_open() are not | |
728 // mappable with PROT_EXEC. Rather than requiring an extra IPC | |
729 // round trip out of the sandbox, we create an FD here. | |
730 base::SharedMemory memory_buffer; | |
731 base::SharedMemoryCreateOptions options; | |
732 options.size = 1; | |
733 options.executable = true; | |
734 if (!memory_buffer.Create(options)) { | |
735 DLOG(ERROR) << "Failed to allocate memory buffer"; | |
736 return false; | |
737 } | |
738 nacl::FileDescriptor memory_fd; | |
739 memory_fd.fd = dup(memory_buffer.handle().fd); | |
740 if (memory_fd.fd < 0) { | |
741 DLOG(ERROR) << "Failed to dup() a file descriptor"; | |
742 return false; | |
743 } | |
744 memory_fd.auto_close = true; | |
745 params.handles.push_back(memory_fd); | |
746 #endif | |
747 | |
748 #if defined(OS_POSIX) | |
749 if (params.enable_debug_stub) { | |
750 net::SocketDescriptor server_bound_socket = GetDebugStubSocketHandle(); | |
751 if (server_bound_socket != net::kInvalidSocket) { | |
752 params.debug_stub_server_bound_socket = | |
753 nacl::FileDescriptor(server_bound_socket, true); | |
754 } | |
755 } | |
756 #endif | |
757 | |
758 process_->Send(new NaClProcessMsg_Start(params)); | |
759 | |
760 internal_->socket_for_sel_ldr = NACL_INVALID_HANDLE; | |
761 return true; | |
762 } | |
763 | |
764 bool NaClProcessHost::SendStart() { | |
765 if (!enable_ppapi_proxy()) { | |
766 if (!ReplyToRenderer(IPC::ChannelHandle())) | |
767 return false; | |
768 } | |
769 return StartNaClExecution(); | |
770 } | |
771 | |
772 // This method is called when NaClProcessHostMsg_PpapiChannelCreated is | |
773 // received or PpapiHostMsg_ChannelCreated is forwarded by our plugin | |
774 // listener. | |
775 void NaClProcessHost::OnPpapiChannelCreated( | |
776 const IPC::ChannelHandle& channel_handle) { | |
777 // Only renderer processes should create a channel. | |
778 DCHECK(enable_ppapi_proxy()); | |
779 // If the proxy channel is null, this must be the initial NaCl-Browser IPC | |
780 // channel. | |
781 if (!ipc_proxy_channel_.get()) { | |
782 DCHECK_EQ(PROCESS_TYPE_NACL_LOADER, process_->GetData().process_type); | |
783 | |
784 ipc_proxy_channel_.reset( | |
785 new IPC::ChannelProxy(channel_handle, | |
786 IPC::Channel::MODE_CLIENT, | |
787 &ipc_plugin_listener_, | |
788 base::MessageLoopProxy::current().get())); | |
789 // Create the browser ppapi host and enable PPAPI message dispatching to the | |
790 // browser process. | |
791 ppapi_host_.reset(content::BrowserPpapiHost::CreateExternalPluginProcess( | |
792 ipc_proxy_channel_.get(), // sender | |
793 permissions_, | |
794 process_->GetData().handle, | |
795 ipc_proxy_channel_.get(), | |
796 nacl_host_message_filter_->render_process_id(), | |
797 render_view_id_, | |
798 profile_directory_)); | |
799 | |
800 ppapi::PpapiNaClChannelArgs args; | |
801 args.off_the_record = nacl_host_message_filter_->off_the_record(); | |
802 args.permissions = permissions_; | |
803 CommandLine* cmdline = CommandLine::ForCurrentProcess(); | |
804 DCHECK(cmdline); | |
805 std::string flag_whitelist[] = {switches::kV, switches::kVModule}; | |
806 for (size_t i = 0; i < arraysize(flag_whitelist); ++i) { | |
807 std::string value = cmdline->GetSwitchValueASCII(flag_whitelist[i]); | |
808 if (!value.empty()) { | |
809 args.switch_names.push_back(flag_whitelist[i]); | |
810 args.switch_values.push_back(value); | |
811 } | |
812 } | |
813 | |
814 ppapi_host_->GetPpapiHost()->AddHostFactoryFilter( | |
815 scoped_ptr<ppapi::host::HostFactory>( | |
816 nacl::NaClBrowser::GetDelegate()->CreatePpapiHostFactory( | |
817 ppapi_host_.get()))); | |
818 | |
819 // Send a message to create the NaCl-Renderer channel. The handle is just | |
820 // a place holder. | |
821 ipc_proxy_channel_->Send( | |
822 new PpapiMsg_CreateNaClChannel( | |
823 nacl_host_message_filter_->render_process_id(), | |
824 args, | |
825 SerializedHandle(SerializedHandle::CHANNEL_HANDLE, | |
826 IPC::InvalidPlatformFileForTransit()))); | |
827 } else if (reply_msg_) { | |
828 // Otherwise, this must be a renderer channel. | |
829 ReplyToRenderer(channel_handle); | |
830 } else { | |
831 // Attempt to open more than 1 renderer channel is not supported. | |
832 // Shut down the NaCl process. | |
833 process_->GetHost()->ForceShutdown(); | |
834 } | |
835 } | |
836 | |
837 bool NaClProcessHost::OnUntrustedMessageForwarded(const IPC::Message& msg) { | |
838 // Handle messages that have been forwarded from our PluginListener. | |
839 // These messages come from untrusted code so should be handled with care. | |
840 bool handled = true; | |
841 IPC_BEGIN_MESSAGE_MAP(NaClProcessHost, msg) | |
842 IPC_MESSAGE_HANDLER(PpapiHostMsg_ChannelCreated, | |
843 OnPpapiChannelCreated) | |
844 IPC_MESSAGE_UNHANDLED(handled = false) | |
845 IPC_END_MESSAGE_MAP() | |
846 return handled; | |
847 } | |
848 | |
849 bool NaClProcessHost::StartWithLaunchedProcess() { | |
850 nacl::NaClBrowser* nacl_browser = nacl::NaClBrowser::GetInstance(); | |
851 | |
852 if (nacl_browser->IsReady()) { | |
853 return SendStart(); | |
854 } else if (nacl_browser->IsOk()) { | |
855 nacl_browser->WaitForResources( | |
856 base::Bind(&NaClProcessHost::OnResourcesReady, | |
857 weak_factory_.GetWeakPtr())); | |
858 return true; | |
859 } else { | |
860 SendErrorToRenderer("previously failed to acquire shared resources"); | |
861 return false; | |
862 } | |
863 } | |
864 | |
865 void NaClProcessHost::OnQueryKnownToValidate(const std::string& signature, | |
866 bool* result) { | |
867 nacl::NaClBrowser* nacl_browser = nacl::NaClBrowser::GetInstance(); | |
868 *result = nacl_browser->QueryKnownToValidate(signature, off_the_record_); | |
869 } | |
870 | |
871 void NaClProcessHost::OnSetKnownToValidate(const std::string& signature) { | |
872 nacl::NaClBrowser::GetInstance()->SetKnownToValidate( | |
873 signature, off_the_record_); | |
874 } | |
875 | |
876 void NaClProcessHost::FileResolved( | |
877 base::PlatformFile* file, | |
878 const base::FilePath& file_path, | |
879 IPC::Message* reply_msg) { | |
880 if (*file != base::kInvalidPlatformFileValue) { | |
881 IPC::PlatformFileForTransit handle = IPC::GetFileHandleForProcess( | |
882 *file, | |
883 process_->GetData().handle, | |
884 true /* close_source */); | |
885 NaClProcessMsg_ResolveFileToken::WriteReplyParams( | |
886 reply_msg, | |
887 handle, | |
888 file_path); | |
889 } else { | |
890 NaClProcessMsg_ResolveFileToken::WriteReplyParams( | |
891 reply_msg, | |
892 IPC::InvalidPlatformFileForTransit(), | |
893 base::FilePath(FILE_PATH_LITERAL(""))); | |
894 } | |
895 Send(reply_msg); | |
896 } | |
897 | |
898 void NaClProcessHost::OnResolveFileToken(uint64 file_token_lo, | |
899 uint64 file_token_hi, | |
900 IPC::Message* reply_msg) { | |
901 // Was the file registered? | |
902 // | |
903 // Note that the file path cache is of bounded size, and old entries can get | |
904 // evicted. If a large number of NaCl modules are being launched at once, | |
905 // resolving the file_token may fail because the path cache was thrashed | |
906 // while the file_token was in flight. In this case the query fails, and we | |
907 // need to fall back to the slower path. | |
908 // | |
909 // However: each NaCl process will consume 2-3 entries as it starts up, this | |
910 // means that eviction will not happen unless you start up 33+ NaCl processes | |
911 // at the same time, and this still requires worst-case timing. As a | |
912 // practical matter, no entries should be evicted prematurely. | |
913 // The cache itself should take ~ (150 characters * 2 bytes/char + ~60 bytes | |
914 // data structure overhead) * 100 = 35k when full, so making it bigger should | |
915 // not be a problem, if needed. | |
916 // | |
917 // Each NaCl process will consume 2-3 entries because the manifest and main | |
918 // nexe are currently not resolved. Shared libraries will be resolved. They | |
919 // will be loaded sequentially, so they will only consume a single entry | |
920 // while the load is in flight. | |
921 // | |
922 // TODO(ncbray): track behavior with UMA. If entries are getting evicted or | |
923 // bogus keys are getting queried, this would be good to know. | |
924 base::FilePath file_path; | |
925 if (!nacl::NaClBrowser::GetInstance()->GetFilePath( | |
926 file_token_lo, file_token_hi, &file_path)) { | |
927 NaClProcessMsg_ResolveFileToken::WriteReplyParams( | |
928 reply_msg, | |
929 IPC::InvalidPlatformFileForTransit(), | |
930 base::FilePath(FILE_PATH_LITERAL(""))); | |
931 Send(reply_msg); | |
932 return; | |
933 } | |
934 | |
935 // Scratch space to share between the callbacks. | |
936 base::PlatformFile* data = new base::PlatformFile(); | |
937 | |
938 // Open the file. | |
939 if (!content::BrowserThread::PostBlockingPoolTaskAndReply( | |
940 FROM_HERE, | |
941 base::Bind(nacl::OpenNaClExecutableImpl, | |
942 file_path, data), | |
943 base::Bind(&NaClProcessHost::FileResolved, | |
944 weak_factory_.GetWeakPtr(), | |
945 base::Owned(data), | |
946 file_path, | |
947 reply_msg))) { | |
948 NaClProcessMsg_ResolveFileToken::WriteReplyParams( | |
949 reply_msg, | |
950 IPC::InvalidPlatformFileForTransit(), | |
951 base::FilePath(FILE_PATH_LITERAL(""))); | |
952 Send(reply_msg); | |
953 } | |
954 } | |
955 | |
956 #if defined(OS_WIN) | |
957 void NaClProcessHost::OnAttachDebugExceptionHandler(const std::string& info, | |
958 IPC::Message* reply_msg) { | |
959 if (!AttachDebugExceptionHandler(info, reply_msg)) { | |
960 // Send failure message. | |
961 NaClProcessMsg_AttachDebugExceptionHandler::WriteReplyParams(reply_msg, | |
962 false); | |
963 Send(reply_msg); | |
964 } | |
965 } | |
966 | |
967 bool NaClProcessHost::AttachDebugExceptionHandler(const std::string& info, | |
968 IPC::Message* reply_msg) { | |
969 if (!enable_exception_handling_ && !enable_debug_stub_) { | |
970 DLOG(ERROR) << | |
971 "Debug exception handler requested by NaCl process when not enabled"; | |
972 return false; | |
973 } | |
974 if (debug_exception_handler_requested_) { | |
975 // The NaCl process should not request this multiple times. | |
976 DLOG(ERROR) << "Multiple AttachDebugExceptionHandler requests received"; | |
977 return false; | |
978 } | |
979 debug_exception_handler_requested_ = true; | |
980 | |
981 base::ProcessId nacl_pid = base::GetProcId(process_->GetData().handle); | |
982 base::win::ScopedHandle process_handle; | |
983 // We cannot use process_->GetData().handle because it does not have | |
984 // the necessary access rights. We open the new handle here rather | |
985 // than in the NaCl broker process in case the NaCl loader process | |
986 // dies before the NaCl broker process receives the message we send. | |
987 // The debug exception handler uses DebugActiveProcess() to attach, | |
988 // but this takes a PID. We need to prevent the NaCl loader's PID | |
989 // from being reused before DebugActiveProcess() is called, and | |
990 // holding a process handle open achieves this. | |
991 if (!base::OpenProcessHandleWithAccess( | |
992 nacl_pid, | |
993 base::kProcessAccessQueryInformation | | |
994 base::kProcessAccessSuspendResume | | |
995 base::kProcessAccessTerminate | | |
996 base::kProcessAccessVMOperation | | |
997 base::kProcessAccessVMRead | | |
998 base::kProcessAccessVMWrite | | |
999 base::kProcessAccessDuplicateHandle | | |
1000 base::kProcessAccessWaitForTermination, | |
1001 process_handle.Receive())) { | |
1002 LOG(ERROR) << "Failed to get process handle"; | |
1003 return false; | |
1004 } | |
1005 | |
1006 attach_debug_exception_handler_reply_msg_.reset(reply_msg); | |
1007 // If the NaCl loader is 64-bit, the process running its debug | |
1008 // exception handler must be 64-bit too, so we use the 64-bit NaCl | |
1009 // broker process for this. Otherwise, on a 32-bit system, we use | |
1010 // the 32-bit browser process to run the debug exception handler. | |
1011 if (RunningOnWOW64()) { | |
1012 return NaClBrokerService::GetInstance()->LaunchDebugExceptionHandler( | |
1013 weak_factory_.GetWeakPtr(), nacl_pid, process_handle, info); | |
1014 } else { | |
1015 NaClStartDebugExceptionHandlerThread( | |
1016 process_handle.Take(), info, | |
1017 base::MessageLoopProxy::current(), | |
1018 base::Bind(&NaClProcessHost::OnDebugExceptionHandlerLaunchedByBroker, | |
1019 weak_factory_.GetWeakPtr())); | |
1020 return true; | |
1021 } | |
1022 } | |
1023 #endif | |
OLD | NEW |