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