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"
9 #include <sys/resource.h>
10 #include <sys/socket.h>
14 #include "base/basictypes.h"
15 #include "base/command_line.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/suid/client/setuid_sandbox_client.h"
39 #include "sandbox/linux/suid/common/sandbox.h"
43 // Note these need to match up with their counterparts in nacl_helper_linux.c
44 // and nacl_helper_bootstrap_linux.c.
45 const char kNaClHelperReservedAtZero
[] =
46 "--reserved_at_zero=0xXXXXXXXXXXXXXXXX";
47 const char kNaClHelperRDebug
[] = "--r_debug=0xXXXXXXXXXXXXXXXX";
49 // This is an environment variable which controls which (if any) other
50 // environment variables are passed through to NaCl processes. e.g.,
51 // NACL_ENV_PASSTHROUGH="PATH,CWD" would pass both $PATH and $CWD to the child
53 const char kNaClEnvPassthrough
[] = "NACL_ENV_PASSTHROUGH";
54 char kNaClEnvPassthroughDelimiter
= ',';
56 // The following environment variables are always passed through if they exist
57 // in the parent process.
58 const char kNaClExeStderr
[] = "NACL_EXE_STDERR";
59 const char kNaClExeStdout
[] = "NACL_EXE_STDOUT";
60 const char kNaClVerbosity
[] = "NACLVERBOSITY";
62 #if defined(ARCH_CPU_X86)
63 bool NonZeroSegmentBaseIsSlow() {
65 // Using a non-zero segment base is known to be very slow on Intel
66 // Atom CPUs. See "Segmentation-based Memory Protection Mechanism
67 // on Intel Atom Microarchitecture: Coding Optimizations" (Leonardo
70 // The following list of CPU model numbers is taken from:
71 // "Intel 64 and IA-32 Architectures Software Developer's Manual"
72 // (http://download.intel.com/products/processor/manual/325462.pdf),
73 // "Table 35-1. CPUID Signature Values of DisplayFamily_DisplayModel"
74 // (Volume 3C, 35-1), which contains:
75 // "06_36H - Intel Atom S Processor Family
76 // 06_1CH, 06_26H, 06_27H, 06_35, 06_36 - Intel Atom Processor Family"
77 if (cpuid
.family() == 6) {
78 switch (cpuid
.model()) {
91 // Send an IPC request on |ipc_channel|. The request is contained in
92 // |request_pickle| and can have file descriptors attached in |attached_fds|.
93 // |reply_data_buffer| must be allocated by the caller and will contain the
94 // reply. The size of the reply will be written to |reply_size|.
95 // This code assumes that only one thread can write to |ipc_channel| to make
97 bool SendIPCRequestAndReadReply(int ipc_channel
,
98 const std::vector
<int>& attached_fds
,
99 const Pickle
& request_pickle
,
100 char* reply_data_buffer
,
101 size_t reply_data_buffer_size
,
102 ssize_t
* reply_size
) {
103 DCHECK_LE(static_cast<size_t>(kNaClMaxIPCMessageLength
),
104 reply_data_buffer_size
);
107 if (!UnixDomainSocket::SendMsg(ipc_channel
, request_pickle
.data(),
108 request_pickle
.size(), attached_fds
)) {
109 LOG(ERROR
) << "SendIPCRequestAndReadReply: SendMsg failed";
113 // Then read the remote reply.
114 ScopedVector
<base::ScopedFD
> received_fds
;
115 const ssize_t msg_len
=
116 UnixDomainSocket::RecvMsg(ipc_channel
, reply_data_buffer
,
117 reply_data_buffer_size
, &received_fds
);
119 LOG(ERROR
) << "SendIPCRequestAndReadReply: RecvMsg failed";
122 *reply_size
= msg_len
;
130 void AddNaClZygoteForkDelegates(
131 ScopedVector
<content::ZygoteForkDelegate
>* delegates
) {
132 delegates
->push_back(new NaClForkDelegate(false /* nonsfi_mode */));
133 delegates
->push_back(new NaClForkDelegate(true /* nonsfi_mode */));
136 NaClForkDelegate::NaClForkDelegate(bool nonsfi_mode
)
137 : nonsfi_mode_(nonsfi_mode
), status_(kNaClHelperUnused
), fd_(-1) {
140 void NaClForkDelegate::Init(const int sandboxdesc
,
141 const bool enable_layer1_sandbox
) {
142 VLOG(1) << "NaClForkDelegate::Init()";
144 // Only launch the non-SFI helper process if non-SFI mode is enabled.
145 if (nonsfi_mode_
&& !IsNonSFIModeEnabled()) {
149 scoped_ptr
<sandbox::SetuidSandboxClient
> setuid_sandbox_client(
150 sandbox::SetuidSandboxClient::Create());
152 // For communications between the NaCl loader process and
154 int nacl_sandbox_descriptor
=
155 base::GlobalDescriptors::kBaseDescriptor
+ kSandboxIPCChannel
;
156 // Confirm a hard-wired assumption.
157 DCHECK_EQ(sandboxdesc
, nacl_sandbox_descriptor
);
160 PCHECK(0 == socketpair(PF_UNIX
, SOCK_SEQPACKET
, 0, fds
));
161 base::FileHandleMappingVector fds_to_map
;
162 fds_to_map
.push_back(std::make_pair(fds
[1], kNaClZygoteDescriptor
));
163 fds_to_map
.push_back(std::make_pair(sandboxdesc
, nacl_sandbox_descriptor
));
165 bool use_nacl_bootstrap
= false;
166 // For non-SFI mode, we do not use fixed address space.
168 // Using nacl_helper_bootstrap is not necessary on x86-64 because
169 // NaCl's x86-64 sandbox is not zero-address-based. Starting
170 // nacl_helper through nacl_helper_bootstrap works on x86-64, but it
171 // leaves nacl_helper_bootstrap mapped at a fixed address at the
172 // bottom of the address space, which is undesirable because it
173 // effectively defeats ASLR.
174 #if defined(ARCH_CPU_X86_64)
175 use_nacl_bootstrap
= false;
176 #elif defined(ARCH_CPU_X86)
177 // Performance vs. security trade-off: We prefer using a
178 // non-zero-address-based sandbox on x86-32 because it provides some
179 // ASLR and so is more secure. However, on Atom CPUs, using a
180 // non-zero segment base is very slow, so we use a zero-based
182 use_nacl_bootstrap
= NonZeroSegmentBaseIsSlow();
184 use_nacl_bootstrap
= true;
188 status_
= kNaClHelperUnused
;
189 base::FilePath helper_exe
;
190 base::FilePath helper_bootstrap_exe
;
191 if (!PathService::Get(
192 nonsfi_mode_
? nacl::FILE_NACL_HELPER_NONSFI
: nacl::FILE_NACL_HELPER
,
194 status_
= kNaClHelperMissing
;
195 } else if (use_nacl_bootstrap
&&
196 !PathService::Get(nacl::FILE_NACL_HELPER_BOOTSTRAP
,
197 &helper_bootstrap_exe
)) {
198 status_
= kNaClHelperBootstrapMissing
;
199 } else if (RunningOnValgrind()) {
200 status_
= kNaClHelperValgrind
;
202 CommandLine::StringVector argv_to_launch
;
204 CommandLine
cmd_line(CommandLine::NO_PROGRAM
);
205 if (use_nacl_bootstrap
)
206 cmd_line
.SetProgram(helper_bootstrap_exe
);
208 cmd_line
.SetProgram(helper_exe
);
210 // Append any switches that need to be forwarded to the NaCl helper.
211 static const char* kForwardSwitches
[] = {
212 switches::kDisableSeccompFilterSandbox
,
213 switches::kEnableNaClDebug
,
214 switches::kNaClDangerousNoSandboxNonSfi
,
215 switches::kNoSandbox
,
217 const CommandLine
& current_cmd_line
= *CommandLine::ForCurrentProcess();
218 cmd_line
.CopySwitchesFrom(current_cmd_line
, kForwardSwitches
,
219 arraysize(kForwardSwitches
));
221 // The command line needs to be tightly controlled to use
222 // |helper_bootstrap_exe|. So from now on, argv_to_launch should be
223 // modified directly.
224 argv_to_launch
= cmd_line
.argv();
226 if (use_nacl_bootstrap
) {
227 // Arguments to the bootstrap helper which need to be at the start
228 // of the command line, right after the helper's path.
229 CommandLine::StringVector bootstrap_prepend
;
230 bootstrap_prepend
.push_back(helper_exe
.value());
231 bootstrap_prepend
.push_back(kNaClHelperReservedAtZero
);
232 bootstrap_prepend
.push_back(kNaClHelperRDebug
);
233 argv_to_launch
.insert(argv_to_launch
.begin() + 1,
234 bootstrap_prepend
.begin(),
235 bootstrap_prepend
.end());
238 base::LaunchOptions options
;
240 base::ScopedFD dummy_fd
;
241 if (enable_layer1_sandbox
) {
242 // NaCl needs to keep tight control of the cmd_line, so prepend the
243 // setuid sandbox wrapper manually.
244 base::FilePath sandbox_path
=
245 setuid_sandbox_client
->GetSandboxBinaryPath();
246 argv_to_launch
.insert(argv_to_launch
.begin(), sandbox_path
.value());
247 setuid_sandbox_client
->SetupLaunchOptions(
248 &options
, &fds_to_map
, &dummy_fd
);
249 setuid_sandbox_client
->SetupLaunchEnvironment();
252 options
.fds_to_remap
= &fds_to_map
;
254 // The NaCl processes spawned may need to exceed the ambient soft limit
255 // on RLIMIT_AS to allocate the untrusted address space and its guard
256 // regions. The nacl_helper itself cannot just raise its own limit,
257 // because the existing limit may prevent the initial exec of
258 // nacl_helper_bootstrap from succeeding, with its large address space
260 std::vector
<int> max_these_limits
;
261 max_these_limits
.push_back(RLIMIT_AS
);
262 options
.maximize_rlimits
= &max_these_limits
;
264 // To avoid information leaks in Non-SFI mode, clear the environment for
265 // the NaCl Helper process.
266 options
.clear_environ
= true;
267 AddPassthroughEnvToOptions(&options
);
269 if (!base::LaunchProcess(argv_to_launch
, options
, NULL
))
270 status_
= kNaClHelperLaunchFailed
;
271 // parent and error cases are handled below
273 if (enable_layer1_sandbox
) {
274 // Sanity check that dummy_fd was kept alive for LaunchProcess.
275 DCHECK(dummy_fd
.is_valid());
278 if (IGNORE_EINTR(close(fds
[1])) != 0)
279 LOG(ERROR
) << "close(fds[1]) failed";
280 if (status_
== kNaClHelperUnused
) {
281 const ssize_t kExpectedLength
= strlen(kNaClHelperStartupAck
);
282 char buf
[kExpectedLength
];
284 // Wait for ack from nacl_helper, indicating it is ready to help
285 const ssize_t nread
= HANDLE_EINTR(read(fds
[0], buf
, sizeof(buf
)));
286 if (nread
== kExpectedLength
&&
287 memcmp(buf
, kNaClHelperStartupAck
, nread
) == 0) {
289 status_
= kNaClHelperSuccess
;
294 status_
= kNaClHelperAckFailed
;
295 LOG(ERROR
) << "Bad NaCl helper startup ack (" << nread
<< " bytes)";
297 // TODO(bradchen): Make this LOG(ERROR) when the NaCl helper
298 // becomes the default.
300 if (IGNORE_EINTR(close(fds
[0])) != 0)
301 LOG(ERROR
) << "close(fds[0]) failed";
304 void NaClForkDelegate::InitialUMA(std::string
* uma_name
,
306 int* uma_boundary_value
) {
307 *uma_name
= nonsfi_mode_
? "NaCl.Client.HelperNonSFI.InitState"
308 : "NaCl.Client.Helper.InitState";
309 *uma_sample
= status_
;
310 *uma_boundary_value
= kNaClHelperStatusBoundary
;
313 NaClForkDelegate::~NaClForkDelegate() {
314 // side effect of close: delegate process will terminate
315 if (status_
== kNaClHelperSuccess
) {
316 if (IGNORE_EINTR(close(fd_
)) != 0)
317 LOG(ERROR
) << "close(fd_) failed";
321 bool NaClForkDelegate::CanHelp(const std::string
& process_type
,
322 std::string
* uma_name
,
324 int* uma_boundary_value
) {
325 // We can only help with a specific process type depending on nonsfi_mode_.
326 const char* helpable_process_type
= nonsfi_mode_
327 ? switches::kNaClLoaderNonSfiProcess
328 : switches::kNaClLoaderProcess
;
329 if (process_type
!= helpable_process_type
)
331 *uma_name
= nonsfi_mode_
? "NaCl.Client.HelperNonSFI.StateOnFork"
332 : "NaCl.Client.Helper.StateOnFork";
333 *uma_sample
= status_
;
334 *uma_boundary_value
= kNaClHelperStatusBoundary
;
338 pid_t
NaClForkDelegate::Fork(const std::string
& process_type
,
339 const std::vector
<int>& fds
,
340 const std::string
& channel_id
) {
341 VLOG(1) << "NaClForkDelegate::Fork";
343 DCHECK(fds
.size() == kNumPassedFDs
);
345 if (status_
!= kNaClHelperSuccess
) {
346 LOG(ERROR
) << "Cannot launch NaCl process: nacl_helper failed to start";
350 // First, send a remote fork request.
352 write_pickle
.WriteInt(nacl::kNaClForkRequest
);
353 // TODO(hamaji): When we split the helper binary for non-SFI mode
354 // from nacl_helper, stop sending this information.
355 write_pickle
.WriteBool(nonsfi_mode_
);
356 write_pickle
.WriteString(channel_id
);
358 char reply_buf
[kNaClMaxIPCMessageLength
];
359 ssize_t reply_size
= 0;
361 SendIPCRequestAndReadReply(fd_
, fds
, write_pickle
,
362 reply_buf
, sizeof(reply_buf
), &reply_size
);
364 LOG(ERROR
) << "Could not perform remote fork.";
368 // Now see if the other end managed to fork.
369 Pickle
reply_pickle(reply_buf
, reply_size
);
370 PickleIterator
iter(reply_pickle
);
372 if (!iter
.ReadInt(&nacl_child
)) {
373 LOG(ERROR
) << "NaClForkDelegate::Fork: pickle failed";
376 VLOG(1) << "nacl_child is " << nacl_child
;
380 bool NaClForkDelegate::GetTerminationStatus(pid_t pid
, bool known_dead
,
381 base::TerminationStatus
* status
,
383 VLOG(1) << "NaClForkDelegate::GetTerminationStatus";
388 write_pickle
.WriteInt(nacl::kNaClGetTerminationStatusRequest
);
389 write_pickle
.WriteInt(pid
);
390 write_pickle
.WriteBool(known_dead
);
392 const std::vector
<int> empty_fds
;
393 char reply_buf
[kNaClMaxIPCMessageLength
];
394 ssize_t reply_size
= 0;
396 SendIPCRequestAndReadReply(fd_
, empty_fds
, write_pickle
,
397 reply_buf
, sizeof(reply_buf
), &reply_size
);
399 LOG(ERROR
) << "Could not perform remote GetTerminationStatus.";
403 Pickle
reply_pickle(reply_buf
, reply_size
);
404 PickleIterator
iter(reply_pickle
);
405 int termination_status
;
406 if (!iter
.ReadInt(&termination_status
) ||
407 termination_status
< 0 ||
408 termination_status
>= base::TERMINATION_STATUS_MAX_ENUM
) {
409 LOG(ERROR
) << "GetTerminationStatus: pickle failed";
413 int remote_exit_code
;
414 if (!iter
.ReadInt(&remote_exit_code
)) {
415 LOG(ERROR
) << "GetTerminationStatus: pickle failed";
419 *status
= static_cast<base::TerminationStatus
>(termination_status
);
420 *exit_code
= remote_exit_code
;
425 void NaClForkDelegate::AddPassthroughEnvToOptions(
426 base::LaunchOptions
* options
) {
427 scoped_ptr
<base::Environment
> env(base::Environment::Create());
428 std::string pass_through_string
;
429 std::vector
<std::string
> pass_through_vars
;
430 if (env
->GetVar(kNaClEnvPassthrough
, &pass_through_string
)) {
432 pass_through_string
, kNaClEnvPassthroughDelimiter
, &pass_through_vars
);
434 pass_through_vars
.push_back(kNaClExeStderr
);
435 pass_through_vars
.push_back(kNaClExeStdout
);
436 pass_through_vars
.push_back(kNaClVerbosity
);
437 pass_through_vars
.push_back(sandbox::kSandboxEnvironmentApiRequest
);
438 for (size_t i
= 0; i
< pass_through_vars
.size(); ++i
) {
440 if (env
->GetVar(pass_through_vars
[i
].c_str(), &temp
))
441 options
->environ
[pass_through_vars
[i
]] = temp
;