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 "components/nacl/zygote/nacl_fork_delegate_linux.h"
6
7#include <signal.h>
8#include <stdlib.h>
9#include <sys/resource.h>
10#include <sys/socket.h>
11
12#include <set>
13
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/suid/client/setuid_sandbox_client.h"
39#include "sandbox/linux/suid/common/sandbox.h"
40
41namespace {
42
43// Note these need to match up with their counterparts in nacl_helper_linux.c
44// and nacl_helper_bootstrap_linux.c.
45const char kNaClHelperReservedAtZero[] =
46    "--reserved_at_zero=0xXXXXXXXXXXXXXXXX";
47const char kNaClHelperRDebug[] = "--r_debug=0xXXXXXXXXXXXXXXXX";
48
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
52// process.
53const char kNaClEnvPassthrough[] = "NACL_ENV_PASSTHROUGH";
54char kNaClEnvPassthroughDelimiter = ',';
55
56// The following environment variables are always passed through if they exist
57// in the parent process.
58const char kNaClExeStderr[] = "NACL_EXE_STDERR";
59const char kNaClExeStdout[] = "NACL_EXE_STDOUT";
60const char kNaClVerbosity[] = "NACLVERBOSITY";
61
62#if defined(ARCH_CPU_X86)
63bool NonZeroSegmentBaseIsSlow() {
64  base::CPU cpuid;
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
68  // Potenza, Intel).
69  //
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()) {
79      case 0x1c:
80      case 0x26:
81      case 0x27:
82      case 0x35:
83      case 0x36:
84        return true;
85    }
86  }
87  return false;
88}
89#endif
90
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
96// requests.
97bool 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);
105  DCHECK(reply_size);
106
107  if (!UnixDomainSocket::SendMsg(ipc_channel, request_pickle.data(),
108                                 request_pickle.size(), attached_fds)) {
109    LOG(ERROR) << "SendIPCRequestAndReadReply: SendMsg failed";
110    return false;
111  }
112
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);
118  if (msg_len <= 0) {
119    LOG(ERROR) << "SendIPCRequestAndReadReply: RecvMsg failed";
120    return false;
121  }
122  *reply_size = msg_len;
123  return true;
124}
125
126}  // namespace.
127
128namespace nacl {
129
130void AddNaClZygoteForkDelegates(
131    ScopedVector<content::ZygoteForkDelegate>* delegates) {
132  delegates->push_back(new NaClForkDelegate(false /* nonsfi_mode */));
133  delegates->push_back(new NaClForkDelegate(true /* nonsfi_mode */));
134}
135
136NaClForkDelegate::NaClForkDelegate(bool nonsfi_mode)
137    : nonsfi_mode_(nonsfi_mode), status_(kNaClHelperUnused), fd_(-1) {
138}
139
140void NaClForkDelegate::Init(const int sandboxdesc,
141                            const bool enable_layer1_sandbox) {
142  VLOG(1) << "NaClForkDelegate::Init()";
143
144  // Only launch the non-SFI helper process if non-SFI mode is enabled.
145  if (nonsfi_mode_ && !IsNonSFIModeEnabled()) {
146    return;
147  }
148
149  scoped_ptr<sandbox::SetuidSandboxClient> setuid_sandbox_client(
150      sandbox::SetuidSandboxClient::Create());
151
152  // For communications between the NaCl loader process and
153  // the SUID sandbox.
154  int nacl_sandbox_descriptor =
155      base::GlobalDescriptors::kBaseDescriptor + kSandboxIPCChannel;
156  // Confirm a hard-wired assumption.
157  DCHECK_EQ(sandboxdesc, nacl_sandbox_descriptor);
158
159  int fds[2];
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));
164
165  bool use_nacl_bootstrap = false;
166  // For non-SFI mode, we do not use fixed address space.
167  if (!nonsfi_mode_) {
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
181    // sandbox on those.
182    use_nacl_bootstrap = NonZeroSegmentBaseIsSlow();
183#else
184    use_nacl_bootstrap = true;
185#endif
186  }
187
188  status_ = kNaClHelperUnused;
189  base::FilePath helper_exe;
190  base::FilePath helper_bootstrap_exe;
191  if (!PathService::Get(nacl::FILE_NACL_HELPER, &helper_exe)) {
192    status_ = kNaClHelperMissing;
193  } else if (use_nacl_bootstrap &&
194             !PathService::Get(nacl::FILE_NACL_HELPER_BOOTSTRAP,
195                               &helper_bootstrap_exe)) {
196    status_ = kNaClHelperBootstrapMissing;
197  } else if (RunningOnValgrind()) {
198    status_ = kNaClHelperValgrind;
199  } else {
200    CommandLine::StringVector argv_to_launch;
201    {
202      CommandLine cmd_line(CommandLine::NO_PROGRAM);
203      if (use_nacl_bootstrap)
204        cmd_line.SetProgram(helper_bootstrap_exe);
205      else
206        cmd_line.SetProgram(helper_exe);
207
208      // Append any switches that need to be forwarded to the NaCl helper.
209      static const char* kForwardSwitches[] = {
210        switches::kDisableSeccompFilterSandbox,
211        switches::kNaClDangerousNoSandboxNonSfi,
212        switches::kNoSandbox,
213      };
214      const CommandLine& current_cmd_line = *CommandLine::ForCurrentProcess();
215      cmd_line.CopySwitchesFrom(current_cmd_line, kForwardSwitches,
216                                arraysize(kForwardSwitches));
217
218      // The command line needs to be tightly controlled to use
219      // |helper_bootstrap_exe|. So from now on, argv_to_launch should be
220      // modified directly.
221      argv_to_launch = cmd_line.argv();
222    }
223    if (use_nacl_bootstrap) {
224      // Arguments to the bootstrap helper which need to be at the start
225      // of the command line, right after the helper's path.
226      CommandLine::StringVector bootstrap_prepend;
227      bootstrap_prepend.push_back(helper_exe.value());
228      bootstrap_prepend.push_back(kNaClHelperReservedAtZero);
229      bootstrap_prepend.push_back(kNaClHelperRDebug);
230      argv_to_launch.insert(argv_to_launch.begin() + 1,
231                            bootstrap_prepend.begin(),
232                            bootstrap_prepend.end());
233    }
234
235    base::LaunchOptions options;
236
237    base::ScopedFD dummy_fd;
238    if (enable_layer1_sandbox) {
239      // NaCl needs to keep tight control of the cmd_line, so prepend the
240      // setuid sandbox wrapper manually.
241      base::FilePath sandbox_path =
242          setuid_sandbox_client->GetSandboxBinaryPath();
243      argv_to_launch.insert(argv_to_launch.begin(), sandbox_path.value());
244      setuid_sandbox_client->SetupLaunchOptions(
245          &options, &fds_to_map, &dummy_fd);
246      setuid_sandbox_client->SetupLaunchEnvironment();
247    }
248
249    options.fds_to_remap = &fds_to_map;
250
251    // The NaCl processes spawned may need to exceed the ambient soft limit
252    // on RLIMIT_AS to allocate the untrusted address space and its guard
253    // regions.  The nacl_helper itself cannot just raise its own limit,
254    // because the existing limit may prevent the initial exec of
255    // nacl_helper_bootstrap from succeeding, with its large address space
256    // reservation.
257    std::vector<int> max_these_limits;
258    max_these_limits.push_back(RLIMIT_AS);
259    options.maximize_rlimits = &max_these_limits;
260
261    // To avoid information leaks in Non-SFI mode, clear the environment for
262    // the NaCl Helper process.
263    options.clear_environ = true;
264    AddPassthroughEnvToOptions(&options);
265
266    if (!base::LaunchProcess(argv_to_launch, options, NULL))
267      status_ = kNaClHelperLaunchFailed;
268    // parent and error cases are handled below
269
270    if (enable_layer1_sandbox) {
271      // Sanity check that dummy_fd was kept alive for LaunchProcess.
272      DCHECK(dummy_fd.is_valid());
273    }
274  }
275  if (IGNORE_EINTR(close(fds[1])) != 0)
276    LOG(ERROR) << "close(fds[1]) failed";
277  if (status_ == kNaClHelperUnused) {
278    const ssize_t kExpectedLength = strlen(kNaClHelperStartupAck);
279    char buf[kExpectedLength];
280
281    // Wait for ack from nacl_helper, indicating it is ready to help
282    const ssize_t nread = HANDLE_EINTR(read(fds[0], buf, sizeof(buf)));
283    if (nread == kExpectedLength &&
284        memcmp(buf, kNaClHelperStartupAck, nread) == 0) {
285      // all is well
286      status_ = kNaClHelperSuccess;
287      fd_ = fds[0];
288      return;
289    }
290
291    status_ = kNaClHelperAckFailed;
292    LOG(ERROR) << "Bad NaCl helper startup ack (" << nread << " bytes)";
293  }
294  // TODO(bradchen): Make this LOG(ERROR) when the NaCl helper
295  // becomes the default.
296  fd_ = -1;
297  if (IGNORE_EINTR(close(fds[0])) != 0)
298    LOG(ERROR) << "close(fds[0]) failed";
299}
300
301void NaClForkDelegate::InitialUMA(std::string* uma_name,
302                                  int* uma_sample,
303                                  int* uma_boundary_value) {
304  *uma_name = nonsfi_mode_ ? "NaCl.Client.HelperNonSFI.InitState"
305                           : "NaCl.Client.Helper.InitState";
306  *uma_sample = status_;
307  *uma_boundary_value = kNaClHelperStatusBoundary;
308}
309
310NaClForkDelegate::~NaClForkDelegate() {
311  // side effect of close: delegate process will terminate
312  if (status_ == kNaClHelperSuccess) {
313    if (IGNORE_EINTR(close(fd_)) != 0)
314      LOG(ERROR) << "close(fd_) failed";
315  }
316}
317
318bool NaClForkDelegate::CanHelp(const std::string& process_type,
319                               std::string* uma_name,
320                               int* uma_sample,
321                               int* uma_boundary_value) {
322  // We can only help with a specific process type depending on nonsfi_mode_.
323  const char* helpable_process_type = nonsfi_mode_
324                                          ? switches::kNaClLoaderNonSfiProcess
325                                          : switches::kNaClLoaderProcess;
326  if (process_type != helpable_process_type)
327    return false;
328  *uma_name = nonsfi_mode_ ? "NaCl.Client.HelperNonSFI.StateOnFork"
329                           : "NaCl.Client.Helper.StateOnFork";
330  *uma_sample = status_;
331  *uma_boundary_value = kNaClHelperStatusBoundary;
332  return true;
333}
334
335pid_t NaClForkDelegate::Fork(const std::string& process_type,
336                             const std::vector<int>& fds,
337                             const std::string& channel_id) {
338  VLOG(1) << "NaClForkDelegate::Fork";
339
340  DCHECK(fds.size() == kNumPassedFDs);
341
342  if (status_ != kNaClHelperSuccess) {
343    LOG(ERROR) << "Cannot launch NaCl process: nacl_helper failed to start";
344    return -1;
345  }
346
347  // First, send a remote fork request.
348  Pickle write_pickle;
349  write_pickle.WriteInt(nacl::kNaClForkRequest);
350  // TODO(hamaji): When we split the helper binary for non-SFI mode
351  // from nacl_helper, stop sending this information.
352  write_pickle.WriteBool(nonsfi_mode_);
353  write_pickle.WriteString(channel_id);
354
355  char reply_buf[kNaClMaxIPCMessageLength];
356  ssize_t reply_size = 0;
357  bool got_reply =
358      SendIPCRequestAndReadReply(fd_, fds, write_pickle,
359                                 reply_buf, sizeof(reply_buf), &reply_size);
360  if (!got_reply) {
361    LOG(ERROR) << "Could not perform remote fork.";
362    return -1;
363  }
364
365  // Now see if the other end managed to fork.
366  Pickle reply_pickle(reply_buf, reply_size);
367  PickleIterator iter(reply_pickle);
368  pid_t nacl_child;
369  if (!iter.ReadInt(&nacl_child)) {
370    LOG(ERROR) << "NaClForkDelegate::Fork: pickle failed";
371    return -1;
372  }
373  VLOG(1) << "nacl_child is " << nacl_child;
374  return nacl_child;
375}
376
377bool NaClForkDelegate::GetTerminationStatus(pid_t pid, bool known_dead,
378                                            base::TerminationStatus* status,
379                                            int* exit_code) {
380  VLOG(1) << "NaClForkDelegate::GetTerminationStatus";
381  DCHECK(status);
382  DCHECK(exit_code);
383
384  Pickle write_pickle;
385  write_pickle.WriteInt(nacl::kNaClGetTerminationStatusRequest);
386  write_pickle.WriteInt(pid);
387  write_pickle.WriteBool(known_dead);
388
389  const std::vector<int> empty_fds;
390  char reply_buf[kNaClMaxIPCMessageLength];
391  ssize_t reply_size = 0;
392  bool got_reply =
393      SendIPCRequestAndReadReply(fd_, empty_fds, write_pickle,
394                                 reply_buf, sizeof(reply_buf), &reply_size);
395  if (!got_reply) {
396    LOG(ERROR) << "Could not perform remote GetTerminationStatus.";
397    return false;
398  }
399
400  Pickle reply_pickle(reply_buf, reply_size);
401  PickleIterator iter(reply_pickle);
402  int termination_status;
403  if (!iter.ReadInt(&termination_status) ||
404      termination_status < 0 ||
405      termination_status >= base::TERMINATION_STATUS_MAX_ENUM) {
406    LOG(ERROR) << "GetTerminationStatus: pickle failed";
407    return false;
408  }
409
410  int remote_exit_code;
411  if (!iter.ReadInt(&remote_exit_code)) {
412    LOG(ERROR) << "GetTerminationStatus: pickle failed";
413    return false;
414  }
415
416  *status = static_cast<base::TerminationStatus>(termination_status);
417  *exit_code = remote_exit_code;
418  return true;
419}
420
421// static
422void NaClForkDelegate::AddPassthroughEnvToOptions(
423    base::LaunchOptions* options) {
424  scoped_ptr<base::Environment> env(base::Environment::Create());
425  std::string pass_through_string;
426  std::vector<std::string> pass_through_vars;
427  if (env->GetVar(kNaClEnvPassthrough, &pass_through_string)) {
428    base::SplitString(
429        pass_through_string, kNaClEnvPassthroughDelimiter, &pass_through_vars);
430  }
431  pass_through_vars.push_back(kNaClExeStderr);
432  pass_through_vars.push_back(kNaClExeStdout);
433  pass_through_vars.push_back(kNaClVerbosity);
434  pass_through_vars.push_back(sandbox::kSandboxEnvironmentApiRequest);
435  for (size_t i = 0; i < pass_through_vars.size(); ++i) {
436    std::string temp;
437    if (env->GetVar(pass_through_vars[i].c_str(), &temp))
438      options->environ[pass_through_vars[i]] = temp;
439  }
440}
441
442}  // namespace nacl
443