Modify isolate_driver.py to always generate a wrapping .isolate.
[chromium-blink-merge.git] / components / nacl / zygote / nacl_fork_delegate_linux.cc
blobae625643171ab6304bfcc2b0aafdbd7251b4657d
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.
5 #include "components/nacl/zygote/nacl_fork_delegate_linux.h"
7 #include <signal.h>
8 #include <stdlib.h>
9 #include <sys/resource.h>
10 #include <sys/socket.h>
12 #include <set>
14 #include "base/basictypes.h"
15 #include "base/command_line.h"
16 #include "base/cpu.h"
17 #include "base/files/file_path.h"
18 #include "base/files/scoped_file.h"
19 #include "base/logging.h"
20 #include "base/memory/scoped_ptr.h"
21 #include "base/memory/scoped_vector.h"
22 #include "base/path_service.h"
23 #include "base/pickle.h"
24 #include "base/posix/eintr_wrapper.h"
25 #include "base/posix/global_descriptors.h"
26 #include "base/posix/unix_domain_socket_linux.h"
27 #include "base/process/kill.h"
28 #include "base/process/launch.h"
29 #include "base/strings/string_split.h"
30 #include "base/third_party/dynamic_annotations/dynamic_annotations.h"
31 #include "build/build_config.h"
32 #include "components/nacl/common/nacl_nonsfi_util.h"
33 #include "components/nacl/common/nacl_paths.h"
34 #include "components/nacl/common/nacl_switches.h"
35 #include "components/nacl/loader/nacl_helper_linux.h"
36 #include "content/public/common/content_descriptors.h"
37 #include "content/public/common/content_switches.h"
38 #include "sandbox/linux/services/namespace_sandbox.h"
39 #include "sandbox/linux/suid/client/setuid_sandbox_client.h"
40 #include "sandbox/linux/suid/client/setuid_sandbox_host.h"
41 #include "sandbox/linux/suid/common/sandbox.h"
43 namespace {
45 // Note these need to match up with their counterparts in nacl_helper_linux.c
46 // and nacl_helper_bootstrap_linux.c.
47 const char kNaClHelperReservedAtZero[] =
48 "--reserved_at_zero=0xXXXXXXXXXXXXXXXX";
49 const char kNaClHelperRDebug[] = "--r_debug=0xXXXXXXXXXXXXXXXX";
51 // This is an environment variable which controls which (if any) other
52 // environment variables are passed through to NaCl processes. e.g.,
53 // NACL_ENV_PASSTHROUGH="PATH,CWD" would pass both $PATH and $CWD to the child
54 // process.
55 const char kNaClEnvPassthrough[] = "NACL_ENV_PASSTHROUGH";
56 char kNaClEnvPassthroughDelimiter = ',';
58 // The following environment variables are always passed through if they exist
59 // in the parent process.
60 const char kNaClExeStderr[] = "NACL_EXE_STDERR";
61 const char kNaClExeStdout[] = "NACL_EXE_STDOUT";
62 const char kNaClVerbosity[] = "NACLVERBOSITY";
64 #if defined(ARCH_CPU_X86)
65 bool NonZeroSegmentBaseIsSlow() {
66 base::CPU cpuid;
67 // Using a non-zero segment base is known to be very slow on Intel
68 // Atom CPUs. See "Segmentation-based Memory Protection Mechanism
69 // on Intel Atom Microarchitecture: Coding Optimizations" (Leonardo
70 // Potenza, Intel).
72 // The following list of CPU model numbers is taken from:
73 // "Intel 64 and IA-32 Architectures Software Developer's Manual"
74 // (http://download.intel.com/products/processor/manual/325462.pdf),
75 // "Table 35-1. CPUID Signature Values of DisplayFamily_DisplayModel"
76 // (Volume 3C, 35-1), which contains:
77 // "06_36H - Intel Atom S Processor Family
78 // 06_1CH, 06_26H, 06_27H, 06_35, 06_36 - Intel Atom Processor Family"
79 if (cpuid.family() == 6) {
80 switch (cpuid.model()) {
81 case 0x1c:
82 case 0x26:
83 case 0x27:
84 case 0x35:
85 case 0x36:
86 return true;
89 return false;
91 #endif
93 // Send an IPC request on |ipc_channel|. The request is contained in
94 // |request_pickle| and can have file descriptors attached in |attached_fds|.
95 // |reply_data_buffer| must be allocated by the caller and will contain the
96 // reply. The size of the reply will be written to |reply_size|.
97 // This code assumes that only one thread can write to |ipc_channel| to make
98 // requests.
99 bool SendIPCRequestAndReadReply(int ipc_channel,
100 const std::vector<int>& attached_fds,
101 const base::Pickle& request_pickle,
102 char* reply_data_buffer,
103 size_t reply_data_buffer_size,
104 ssize_t* reply_size) {
105 DCHECK_LE(static_cast<size_t>(kNaClMaxIPCMessageLength),
106 reply_data_buffer_size);
107 DCHECK(reply_size);
109 if (!base::UnixDomainSocket::SendMsg(ipc_channel, request_pickle.data(),
110 request_pickle.size(), attached_fds)) {
111 LOG(ERROR) << "SendIPCRequestAndReadReply: SendMsg failed";
112 return false;
115 // Then read the remote reply.
116 ScopedVector<base::ScopedFD> received_fds;
117 const ssize_t msg_len =
118 base::UnixDomainSocket::RecvMsg(ipc_channel, reply_data_buffer,
119 reply_data_buffer_size, &received_fds);
120 if (msg_len <= 0) {
121 LOG(ERROR) << "SendIPCRequestAndReadReply: RecvMsg failed";
122 return false;
124 *reply_size = msg_len;
125 return true;
128 } // namespace.
130 namespace nacl {
132 void AddNaClZygoteForkDelegates(
133 ScopedVector<content::ZygoteForkDelegate>* delegates) {
134 delegates->push_back(new NaClForkDelegate(false /* nonsfi_mode */));
135 delegates->push_back(new NaClForkDelegate(true /* nonsfi_mode */));
138 NaClForkDelegate::NaClForkDelegate(bool nonsfi_mode)
139 : nonsfi_mode_(nonsfi_mode), status_(kNaClHelperUnused), fd_(-1) {
142 void NaClForkDelegate::Init(const int sandboxdesc,
143 const bool enable_layer1_sandbox) {
144 VLOG(1) << "NaClForkDelegate::Init()";
146 // Only launch the non-SFI helper process if non-SFI mode is enabled.
147 if (nonsfi_mode_ && !IsNonSFIModeEnabled()) {
148 return;
151 // TODO(rickyz): Make IsSuidSandboxChild a static function.
152 scoped_ptr<sandbox::SetuidSandboxClient> setuid_sandbox_client(
153 sandbox::SetuidSandboxClient::Create());
154 const bool using_setuid_sandbox = setuid_sandbox_client->IsSuidSandboxChild();
155 const bool using_namespace_sandbox =
156 sandbox::NamespaceSandbox::InNewUserNamespace();
158 CHECK(!(using_setuid_sandbox && using_namespace_sandbox));
159 if (enable_layer1_sandbox) {
160 CHECK(using_setuid_sandbox || using_namespace_sandbox);
163 scoped_ptr<sandbox::SetuidSandboxHost> setuid_sandbox_host(
164 sandbox::SetuidSandboxHost::Create());
166 // For communications between the NaCl loader process and
167 // the browser process.
168 int nacl_sandbox_descriptor =
169 base::GlobalDescriptors::kBaseDescriptor + kSandboxIPCChannel;
170 // Confirm a hard-wired assumption.
171 DCHECK_EQ(sandboxdesc, nacl_sandbox_descriptor);
173 int fds[2];
174 PCHECK(0 == socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds));
175 base::FileHandleMappingVector fds_to_map;
176 fds_to_map.push_back(std::make_pair(fds[1], kNaClZygoteDescriptor));
177 fds_to_map.push_back(std::make_pair(sandboxdesc, nacl_sandbox_descriptor));
179 bool use_nacl_bootstrap = false;
180 // For non-SFI mode, we do not use fixed address space.
181 if (!nonsfi_mode_) {
182 // Using nacl_helper_bootstrap is not necessary on x86-64 because
183 // NaCl's x86-64 sandbox is not zero-address-based. Starting
184 // nacl_helper through nacl_helper_bootstrap works on x86-64, but it
185 // leaves nacl_helper_bootstrap mapped at a fixed address at the
186 // bottom of the address space, which is undesirable because it
187 // effectively defeats ASLR.
188 #if defined(ARCH_CPU_X86_64)
189 use_nacl_bootstrap = false;
190 #elif defined(ARCH_CPU_X86)
191 // Performance vs. security trade-off: We prefer using a
192 // non-zero-address-based sandbox on x86-32 because it provides some
193 // ASLR and so is more secure. However, on Atom CPUs, using a
194 // non-zero segment base is very slow, so we use a zero-based
195 // sandbox on those.
196 use_nacl_bootstrap = NonZeroSegmentBaseIsSlow();
197 #else
198 use_nacl_bootstrap = true;
199 #endif
202 status_ = kNaClHelperUnused;
203 base::FilePath helper_exe;
204 base::FilePath helper_bootstrap_exe;
205 if (!PathService::Get(
206 nonsfi_mode_ ? nacl::FILE_NACL_HELPER_NONSFI : nacl::FILE_NACL_HELPER,
207 &helper_exe)) {
208 status_ = kNaClHelperMissing;
209 } else if (use_nacl_bootstrap &&
210 !PathService::Get(nacl::FILE_NACL_HELPER_BOOTSTRAP,
211 &helper_bootstrap_exe)) {
212 status_ = kNaClHelperBootstrapMissing;
213 } else if (RunningOnValgrind()) {
214 status_ = kNaClHelperValgrind;
215 } else {
216 base::CommandLine::StringVector argv_to_launch;
218 base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM);
219 if (use_nacl_bootstrap)
220 cmd_line.SetProgram(helper_bootstrap_exe);
221 else
222 cmd_line.SetProgram(helper_exe);
224 // Append any switches that need to be forwarded to the NaCl helper.
225 static const char* kForwardSwitches[] = {
226 switches::kAllowSandboxDebugging,
227 switches::kDisableSeccompFilterSandbox,
228 switches::kEnableNaClDebug,
229 switches::kNaClDangerousNoSandboxNonSfi,
230 switches::kNoSandbox,
232 const base::CommandLine& current_cmd_line =
233 *base::CommandLine::ForCurrentProcess();
234 cmd_line.CopySwitchesFrom(current_cmd_line, kForwardSwitches,
235 arraysize(kForwardSwitches));
237 // The command line needs to be tightly controlled to use
238 // |helper_bootstrap_exe|. So from now on, argv_to_launch should be
239 // modified directly.
240 argv_to_launch = cmd_line.argv();
242 if (use_nacl_bootstrap) {
243 // Arguments to the bootstrap helper which need to be at the start
244 // of the command line, right after the helper's path.
245 base::CommandLine::StringVector bootstrap_prepend;
246 bootstrap_prepend.push_back(helper_exe.value());
247 bootstrap_prepend.push_back(kNaClHelperReservedAtZero);
248 bootstrap_prepend.push_back(kNaClHelperRDebug);
249 argv_to_launch.insert(argv_to_launch.begin() + 1,
250 bootstrap_prepend.begin(),
251 bootstrap_prepend.end());
254 base::LaunchOptions options;
256 base::ScopedFD dummy_fd;
257 if (using_setuid_sandbox) {
258 // NaCl needs to keep tight control of the cmd_line, so prepend the
259 // setuid sandbox wrapper manually.
260 base::FilePath sandbox_path = setuid_sandbox_host->GetSandboxBinaryPath();
261 argv_to_launch.insert(argv_to_launch.begin(), sandbox_path.value());
262 setuid_sandbox_host->SetupLaunchOptions(&options, &fds_to_map, &dummy_fd);
263 setuid_sandbox_host->SetupLaunchEnvironment();
266 options.fds_to_remap = &fds_to_map;
268 // The NaCl processes spawned may need to exceed the ambient soft limit
269 // on RLIMIT_AS to allocate the untrusted address space and its guard
270 // regions. The nacl_helper itself cannot just raise its own limit,
271 // because the existing limit may prevent the initial exec of
272 // nacl_helper_bootstrap from succeeding, with its large address space
273 // reservation.
274 std::vector<int> max_these_limits;
275 max_these_limits.push_back(RLIMIT_AS);
276 options.maximize_rlimits = &max_these_limits;
278 // To avoid information leaks in Non-SFI mode, clear the environment for
279 // the NaCl Helper process.
280 options.clear_environ = true;
281 AddPassthroughEnvToOptions(&options);
283 base::Process process =
284 using_namespace_sandbox
285 ? sandbox::NamespaceSandbox::LaunchProcess(argv_to_launch, options)
286 : base::LaunchProcess(argv_to_launch, options);
288 if (!process.IsValid())
289 status_ = kNaClHelperLaunchFailed;
290 // parent and error cases are handled below
292 if (using_setuid_sandbox) {
293 // Sanity check that dummy_fd was kept alive for LaunchProcess.
294 DCHECK(dummy_fd.is_valid());
297 if (IGNORE_EINTR(close(fds[1])) != 0)
298 LOG(ERROR) << "close(fds[1]) failed";
299 if (status_ == kNaClHelperUnused) {
300 const ssize_t kExpectedLength = strlen(kNaClHelperStartupAck);
301 char buf[kExpectedLength];
303 // Wait for ack from nacl_helper, indicating it is ready to help
304 const ssize_t nread = HANDLE_EINTR(read(fds[0], buf, sizeof(buf)));
305 if (nread == kExpectedLength &&
306 memcmp(buf, kNaClHelperStartupAck, nread) == 0) {
307 // all is well
308 status_ = kNaClHelperSuccess;
309 fd_ = fds[0];
310 return;
313 status_ = kNaClHelperAckFailed;
314 LOG(ERROR) << "Bad NaCl helper startup ack (" << nread << " bytes)";
316 // TODO(bradchen): Make this LOG(ERROR) when the NaCl helper
317 // becomes the default.
318 fd_ = -1;
319 if (IGNORE_EINTR(close(fds[0])) != 0)
320 LOG(ERROR) << "close(fds[0]) failed";
323 void NaClForkDelegate::InitialUMA(std::string* uma_name,
324 int* uma_sample,
325 int* uma_boundary_value) {
326 *uma_name = nonsfi_mode_ ? "NaCl.Client.HelperNonSFI.InitState"
327 : "NaCl.Client.Helper.InitState";
328 *uma_sample = status_;
329 *uma_boundary_value = kNaClHelperStatusBoundary;
332 NaClForkDelegate::~NaClForkDelegate() {
333 // side effect of close: delegate process will terminate
334 if (status_ == kNaClHelperSuccess) {
335 if (IGNORE_EINTR(close(fd_)) != 0)
336 LOG(ERROR) << "close(fd_) failed";
340 bool NaClForkDelegate::CanHelp(const std::string& process_type,
341 std::string* uma_name,
342 int* uma_sample,
343 int* uma_boundary_value) {
344 // We can only help with a specific process type depending on nonsfi_mode_.
345 const char* helpable_process_type = nonsfi_mode_
346 ? switches::kNaClLoaderNonSfiProcess
347 : switches::kNaClLoaderProcess;
348 if (process_type != helpable_process_type)
349 return false;
350 *uma_name = nonsfi_mode_ ? "NaCl.Client.HelperNonSFI.StateOnFork"
351 : "NaCl.Client.Helper.StateOnFork";
352 *uma_sample = status_;
353 *uma_boundary_value = kNaClHelperStatusBoundary;
354 return true;
357 pid_t NaClForkDelegate::Fork(const std::string& process_type,
358 const std::vector<int>& fds,
359 const std::string& channel_id) {
360 VLOG(1) << "NaClForkDelegate::Fork";
362 DCHECK(fds.size() == kNumPassedFDs);
364 if (status_ != kNaClHelperSuccess) {
365 LOG(ERROR) << "Cannot launch NaCl process: nacl_helper failed to start";
366 return -1;
369 // First, send a remote fork request.
370 base::Pickle write_pickle;
371 write_pickle.WriteInt(nacl::kNaClForkRequest);
372 // TODO(hamaji): When we split the helper binary for non-SFI mode
373 // from nacl_helper, stop sending this information.
374 write_pickle.WriteBool(nonsfi_mode_);
375 write_pickle.WriteString(channel_id);
377 char reply_buf[kNaClMaxIPCMessageLength];
378 ssize_t reply_size = 0;
379 bool got_reply =
380 SendIPCRequestAndReadReply(fd_, fds, write_pickle,
381 reply_buf, sizeof(reply_buf), &reply_size);
382 if (!got_reply) {
383 LOG(ERROR) << "Could not perform remote fork.";
384 return -1;
387 // Now see if the other end managed to fork.
388 base::Pickle reply_pickle(reply_buf, reply_size);
389 base::PickleIterator iter(reply_pickle);
390 pid_t nacl_child;
391 if (!iter.ReadInt(&nacl_child)) {
392 LOG(ERROR) << "NaClForkDelegate::Fork: pickle failed";
393 return -1;
395 VLOG(1) << "nacl_child is " << nacl_child;
396 return nacl_child;
399 bool NaClForkDelegate::GetTerminationStatus(pid_t pid, bool known_dead,
400 base::TerminationStatus* status,
401 int* exit_code) {
402 VLOG(1) << "NaClForkDelegate::GetTerminationStatus";
403 DCHECK(status);
404 DCHECK(exit_code);
406 base::Pickle write_pickle;
407 write_pickle.WriteInt(nacl::kNaClGetTerminationStatusRequest);
408 write_pickle.WriteInt(pid);
409 write_pickle.WriteBool(known_dead);
411 const std::vector<int> empty_fds;
412 char reply_buf[kNaClMaxIPCMessageLength];
413 ssize_t reply_size = 0;
414 bool got_reply =
415 SendIPCRequestAndReadReply(fd_, empty_fds, write_pickle,
416 reply_buf, sizeof(reply_buf), &reply_size);
417 if (!got_reply) {
418 LOG(ERROR) << "Could not perform remote GetTerminationStatus.";
419 return false;
422 base::Pickle reply_pickle(reply_buf, reply_size);
423 base::PickleIterator iter(reply_pickle);
424 int termination_status;
425 if (!iter.ReadInt(&termination_status) ||
426 termination_status < 0 ||
427 termination_status >= base::TERMINATION_STATUS_MAX_ENUM) {
428 LOG(ERROR) << "GetTerminationStatus: pickle failed";
429 return false;
432 int remote_exit_code;
433 if (!iter.ReadInt(&remote_exit_code)) {
434 LOG(ERROR) << "GetTerminationStatus: pickle failed";
435 return false;
438 *status = static_cast<base::TerminationStatus>(termination_status);
439 *exit_code = remote_exit_code;
440 return true;
443 // static
444 void NaClForkDelegate::AddPassthroughEnvToOptions(
445 base::LaunchOptions* options) {
446 scoped_ptr<base::Environment> env(base::Environment::Create());
447 std::string pass_through_string;
448 std::vector<std::string> pass_through_vars;
449 if (env->GetVar(kNaClEnvPassthrough, &pass_through_string)) {
450 pass_through_vars = base::SplitString(
451 pass_through_string, std::string(1, kNaClEnvPassthroughDelimiter),
452 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
454 pass_through_vars.push_back(kNaClExeStderr);
455 pass_through_vars.push_back(kNaClExeStdout);
456 pass_through_vars.push_back(kNaClVerbosity);
457 pass_through_vars.push_back(sandbox::kSandboxEnvironmentApiRequest);
458 for (size_t i = 0; i < pass_through_vars.size(); ++i) {
459 std::string temp;
460 if (env->GetVar(pass_through_vars[i].c_str(), &temp))
461 options->environ[pass_through_vars[i]] = temp;
465 } // namespace nacl