Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / components / nacl / zygote / nacl_fork_delegate_linux.cc
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/third_party/dynamic_annotations/dynamic_annotations.h"
30 #include "components/nacl/common/nacl_paths.h"
31 #include "components/nacl/common/nacl_switches.h"
32 #include "components/nacl/loader/nacl_helper_linux.h"
33 #include "content/public/common/content_descriptors.h"
34 #include "content/public/common/content_switches.h"
35 #include "sandbox/linux/suid/client/setuid_sandbox_client.h"
36
37 namespace {
38
39 // Note these need to match up with their counterparts in nacl_helper_linux.c
40 // and nacl_helper_bootstrap_linux.c.
41 const char kNaClHelperReservedAtZero[] =
42     "--reserved_at_zero=0xXXXXXXXXXXXXXXXX";
43 const char kNaClHelperRDebug[] = "--r_debug=0xXXXXXXXXXXXXXXXX";
44
45 #if defined(ARCH_CPU_X86)
46 bool NonZeroSegmentBaseIsSlow() {
47   base::CPU cpuid;
48   // Using a non-zero segment base is known to be very slow on Intel
49   // Atom CPUs.  See "Segmentation-based Memory Protection Mechanism
50   // on Intel Atom Microarchitecture: Coding Optimizations" (Leonardo
51   // Potenza, Intel).
52   //
53   // The following list of CPU model numbers is taken from:
54   // "Intel 64 and IA-32 Architectures Software Developer's Manual"
55   // (http://download.intel.com/products/processor/manual/325462.pdf),
56   // "Table 35-1. CPUID Signature Values of DisplayFamily_DisplayModel"
57   // (Volume 3C, 35-1), which contains:
58   //   "06_36H - Intel Atom S Processor Family
59   //    06_1CH, 06_26H, 06_27H, 06_35, 06_36 - Intel Atom Processor Family"
60   if (cpuid.family() == 6) {
61     switch (cpuid.model()) {
62       case 0x1c:
63       case 0x26:
64       case 0x27:
65       case 0x35:
66       case 0x36:
67         return true;
68     }
69   }
70   return false;
71 }
72 #endif
73
74 // Send an IPC request on |ipc_channel|. The request is contained in
75 // |request_pickle| and can have file descriptors attached in |attached_fds|.
76 // |reply_data_buffer| must be allocated by the caller and will contain the
77 // reply. The size of the reply will be written to |reply_size|.
78 // This code assumes that only one thread can write to |ipc_channel| to make
79 // requests.
80 bool SendIPCRequestAndReadReply(int ipc_channel,
81                                 const std::vector<int>& attached_fds,
82                                 const Pickle& request_pickle,
83                                 char* reply_data_buffer,
84                                 size_t reply_data_buffer_size,
85                                 ssize_t* reply_size) {
86   DCHECK_LE(static_cast<size_t>(kNaClMaxIPCMessageLength),
87             reply_data_buffer_size);
88   DCHECK(reply_size);
89
90   if (!UnixDomainSocket::SendMsg(ipc_channel, request_pickle.data(),
91                                  request_pickle.size(), attached_fds)) {
92     LOG(ERROR) << "SendIPCRequestAndReadReply: SendMsg failed";
93     return false;
94   }
95
96   // Then read the remote reply.
97   ScopedVector<base::ScopedFD> received_fds;
98   const ssize_t msg_len =
99       UnixDomainSocket::RecvMsg(ipc_channel, reply_data_buffer,
100                                 reply_data_buffer_size, &received_fds);
101   if (msg_len <= 0) {
102     LOG(ERROR) << "SendIPCRequestAndReadReply: RecvMsg failed";
103     return false;
104   }
105   *reply_size = msg_len;
106   return true;
107 }
108
109 }  // namespace.
110
111 NaClForkDelegate::NaClForkDelegate()
112     : status_(kNaClHelperUnused),
113       fd_(-1) {}
114
115 void NaClForkDelegate::Init(const int sandboxdesc,
116                             const bool enable_layer1_sandbox) {
117   VLOG(1) << "NaClForkDelegate::Init()";
118   int fds[2];
119
120   scoped_ptr<sandbox::SetuidSandboxClient> setuid_sandbox_client(
121       sandbox::SetuidSandboxClient::Create());
122
123   // For communications between the NaCl loader process and
124   // the SUID sandbox.
125   int nacl_sandbox_descriptor =
126       base::GlobalDescriptors::kBaseDescriptor + kSandboxIPCChannel;
127   // Confirm a hard-wired assumption.
128   DCHECK_EQ(sandboxdesc, nacl_sandbox_descriptor);
129
130   CHECK(socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds) == 0);
131   base::FileHandleMappingVector fds_to_map;
132   fds_to_map.push_back(std::make_pair(fds[1], kNaClZygoteDescriptor));
133   fds_to_map.push_back(std::make_pair(sandboxdesc, nacl_sandbox_descriptor));
134
135   // Using nacl_helper_bootstrap is not necessary on x86-64 because
136   // NaCl's x86-64 sandbox is not zero-address-based.  Starting
137   // nacl_helper through nacl_helper_bootstrap works on x86-64, but it
138   // leaves nacl_helper_bootstrap mapped at a fixed address at the
139   // bottom of the address space, which is undesirable because it
140   // effectively defeats ASLR.
141 #if defined(ARCH_CPU_X86_64)
142   bool kUseNaClBootstrap = false;
143 #elif defined(ARCH_CPU_X86)
144   // Performance vs. security trade-off: We prefer using a
145   // non-zero-address-based sandbox on x86-32 because it provides some
146   // ASLR and so is more secure.  However, on Atom CPUs, using a
147   // non-zero segment base is very slow, so we use a zero-based
148   // sandbox on those.
149   bool kUseNaClBootstrap = NonZeroSegmentBaseIsSlow();
150 #else
151   bool kUseNaClBootstrap = true;
152 #endif
153
154   status_ = kNaClHelperUnused;
155   base::FilePath helper_exe;
156   base::FilePath helper_bootstrap_exe;
157   if (!PathService::Get(nacl::FILE_NACL_HELPER, &helper_exe)) {
158     status_ = kNaClHelperMissing;
159   } else if (kUseNaClBootstrap &&
160              !PathService::Get(nacl::FILE_NACL_HELPER_BOOTSTRAP,
161                                &helper_bootstrap_exe)) {
162     status_ = kNaClHelperBootstrapMissing;
163   } else if (RunningOnValgrind()) {
164     status_ = kNaClHelperValgrind;
165   } else {
166     CommandLine::StringVector argv_to_launch;
167     {
168       CommandLine cmd_line(CommandLine::NO_PROGRAM);
169       if (kUseNaClBootstrap)
170         cmd_line.SetProgram(helper_bootstrap_exe);
171       else
172         cmd_line.SetProgram(helper_exe);
173
174       // Append any switches that need to be forwarded to the NaCl helper.
175       static const char* kForwardSwitches[] = {
176         switches::kDisableSeccompFilterSandbox,
177         switches::kNaClDangerousNoSandboxNonSfi,
178         switches::kNoSandbox,
179       };
180       const CommandLine& current_cmd_line = *CommandLine::ForCurrentProcess();
181       cmd_line.CopySwitchesFrom(current_cmd_line, kForwardSwitches,
182                                 arraysize(kForwardSwitches));
183
184       // The command line needs to be tightly controlled to use
185       // |helper_bootstrap_exe|. So from now on, argv_to_launch should be
186       // modified directly.
187       argv_to_launch = cmd_line.argv();
188     }
189     if (kUseNaClBootstrap) {
190       // Arguments to the bootstrap helper which need to be at the start
191       // of the command line, right after the helper's path.
192       CommandLine::StringVector bootstrap_prepend;
193       bootstrap_prepend.push_back(helper_exe.value());
194       bootstrap_prepend.push_back(kNaClHelperReservedAtZero);
195       bootstrap_prepend.push_back(kNaClHelperRDebug);
196       argv_to_launch.insert(argv_to_launch.begin() + 1,
197                             bootstrap_prepend.begin(),
198                             bootstrap_prepend.end());
199     }
200
201     base::LaunchOptions options;
202
203     base::ScopedFD dummy_fd;
204     if (enable_layer1_sandbox) {
205       // NaCl needs to keep tight control of the cmd_line, so prepend the
206       // setuid sandbox wrapper manually.
207       base::FilePath sandbox_path =
208           setuid_sandbox_client->GetSandboxBinaryPath();
209       argv_to_launch.insert(argv_to_launch.begin(), sandbox_path.value());
210       setuid_sandbox_client->SetupLaunchOptions(
211           &options, &fds_to_map, &dummy_fd);
212       setuid_sandbox_client->SetupLaunchEnvironment();
213     }
214
215     options.fds_to_remap = &fds_to_map;
216
217     // The NaCl processes spawned may need to exceed the ambient soft limit
218     // on RLIMIT_AS to allocate the untrusted address space and its guard
219     // regions.  The nacl_helper itself cannot just raise its own limit,
220     // because the existing limit may prevent the initial exec of
221     // nacl_helper_bootstrap from succeeding, with its large address space
222     // reservation.
223     std::vector<int> max_these_limits;
224     max_these_limits.push_back(RLIMIT_AS);
225     options.maximize_rlimits = &max_these_limits;
226
227     if (!base::LaunchProcess(argv_to_launch, options, NULL))
228       status_ = kNaClHelperLaunchFailed;
229     // parent and error cases are handled below
230
231     if (enable_layer1_sandbox) {
232       // Sanity check that dummy_fd was kept alive for LaunchProcess.
233       DCHECK(dummy_fd.is_valid());
234     }
235   }
236   if (IGNORE_EINTR(close(fds[1])) != 0)
237     LOG(ERROR) << "close(fds[1]) failed";
238   if (status_ == kNaClHelperUnused) {
239     const ssize_t kExpectedLength = strlen(kNaClHelperStartupAck);
240     char buf[kExpectedLength];
241
242     // Wait for ack from nacl_helper, indicating it is ready to help
243     const ssize_t nread = HANDLE_EINTR(read(fds[0], buf, sizeof(buf)));
244     if (nread == kExpectedLength &&
245         memcmp(buf, kNaClHelperStartupAck, nread) == 0) {
246       // all is well
247       status_ = kNaClHelperSuccess;
248       fd_ = fds[0];
249       return;
250     }
251
252     status_ = kNaClHelperAckFailed;
253     LOG(ERROR) << "Bad NaCl helper startup ack (" << nread << " bytes)";
254   }
255   // TODO(bradchen): Make this LOG(ERROR) when the NaCl helper
256   // becomes the default.
257   fd_ = -1;
258   if (IGNORE_EINTR(close(fds[0])) != 0)
259     LOG(ERROR) << "close(fds[0]) failed";
260 }
261
262 void NaClForkDelegate::InitialUMA(std::string* uma_name,
263                                   int* uma_sample,
264                                   int* uma_boundary_value) {
265   *uma_name = "NaCl.Client.Helper.InitState";
266   *uma_sample = status_;
267   *uma_boundary_value = kNaClHelperStatusBoundary;
268 }
269
270 NaClForkDelegate::~NaClForkDelegate() {
271   // side effect of close: delegate process will terminate
272   if (status_ == kNaClHelperSuccess) {
273     if (IGNORE_EINTR(close(fd_)) != 0)
274       LOG(ERROR) << "close(fd_) failed";
275   }
276 }
277
278 bool NaClForkDelegate::CanHelp(const std::string& process_type,
279                                std::string* uma_name,
280                                int* uma_sample,
281                                int* uma_boundary_value) {
282   if (process_type != switches::kNaClLoaderProcess &&
283       process_type != switches::kNaClLoaderNonSfiProcess)
284     return false;
285   *uma_name = "NaCl.Client.Helper.StateOnFork";
286   *uma_sample = status_;
287   *uma_boundary_value = kNaClHelperStatusBoundary;
288   return true;
289 }
290
291 pid_t NaClForkDelegate::Fork(const std::string& process_type,
292                              const std::vector<int>& fds,
293                              const std::string& channel_id) {
294   VLOG(1) << "NaClForkDelegate::Fork";
295
296   DCHECK(fds.size() == kNumPassedFDs);
297
298   if (status_ != kNaClHelperSuccess) {
299     LOG(ERROR) << "Cannot launch NaCl process: nacl_helper failed to start";
300     return -1;
301   }
302
303   // First, send a remote fork request.
304   Pickle write_pickle;
305   write_pickle.WriteInt(nacl::kNaClForkRequest);
306   // TODO(hamaji): When we split the helper binary for non-SFI mode
307   // from nacl_helper, stop sending this information.
308   const bool uses_nonsfi_mode =
309     process_type == switches::kNaClLoaderNonSfiProcess;
310   write_pickle.WriteBool(uses_nonsfi_mode);
311   write_pickle.WriteString(channel_id);
312
313   char reply_buf[kNaClMaxIPCMessageLength];
314   ssize_t reply_size = 0;
315   bool got_reply =
316       SendIPCRequestAndReadReply(fd_, fds, write_pickle,
317                                  reply_buf, sizeof(reply_buf), &reply_size);
318   if (!got_reply) {
319     LOG(ERROR) << "Could not perform remote fork.";
320     return -1;
321   }
322
323   // Now see if the other end managed to fork.
324   Pickle reply_pickle(reply_buf, reply_size);
325   PickleIterator iter(reply_pickle);
326   pid_t nacl_child;
327   if (!iter.ReadInt(&nacl_child)) {
328     LOG(ERROR) << "NaClForkDelegate::Fork: pickle failed";
329     return -1;
330   }
331   VLOG(1) << "nacl_child is " << nacl_child;
332   return nacl_child;
333 }
334
335 bool NaClForkDelegate::GetTerminationStatus(pid_t pid, bool known_dead,
336                                             base::TerminationStatus* status,
337                                             int* exit_code) {
338   VLOG(1) << "NaClForkDelegate::GetTerminationStatus";
339   DCHECK(status);
340   DCHECK(exit_code);
341
342   Pickle write_pickle;
343   write_pickle.WriteInt(nacl::kNaClGetTerminationStatusRequest);
344   write_pickle.WriteInt(pid);
345   write_pickle.WriteBool(known_dead);
346
347   const std::vector<int> empty_fds;
348   char reply_buf[kNaClMaxIPCMessageLength];
349   ssize_t reply_size = 0;
350   bool got_reply =
351       SendIPCRequestAndReadReply(fd_, empty_fds, write_pickle,
352                                  reply_buf, sizeof(reply_buf), &reply_size);
353   if (!got_reply) {
354     LOG(ERROR) << "Could not perform remote GetTerminationStatus.";
355     return false;
356   }
357
358   Pickle reply_pickle(reply_buf, reply_size);
359   PickleIterator iter(reply_pickle);
360   int termination_status;
361   if (!iter.ReadInt(&termination_status) ||
362       termination_status < 0 ||
363       termination_status >= base::TERMINATION_STATUS_MAX_ENUM) {
364     LOG(ERROR) << "GetTerminationStatus: pickle failed";
365     return false;
366   }
367
368   int remote_exit_code;
369   if (!iter.ReadInt(&remote_exit_code)) {
370     LOG(ERROR) << "GetTerminationStatus: pickle failed";
371     return false;
372   }
373
374   *status = static_cast<base::TerminationStatus>(termination_status);
375   *exit_code = remote_exit_code;
376   return true;
377 }