1 // Copyright 2012 The Chromium Authors
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 <stddef.h>
9 #include <stdlib.h>
10 #include <sys/resource.h>
11 #include <sys/socket.h>
12
13 #include <memory>
14 #include <set>
15
16 #include "base/command_line.h"
17 #include "base/cpu.h"
18 #include "base/files/file_path.h"
19 #include "base/files/scoped_file.h"
20 #include "base/logging.h"
21 #include "base/numerics/safe_conversions.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.h"
27 #include "base/process/kill.h"
28 #include "base/process/launch.h"
29 #include "base/strings/string_split.h"
30 #include "build/build_config.h"
31 #include "components/nacl/common/nacl_paths.h"
32 #include "components/nacl/common/nacl_switches.h"
33 #include "components/nacl/loader/nacl_helper_linux.h"
34 #include "content/public/common/content_descriptors.h"
35 #include "content/public/common/content_switches.h"
36 #include "mojo/core/embedder/embedder.h"
37 #include "sandbox/linux/services/namespace_sandbox.h"
38 #include "sandbox/linux/suid/client/setuid_sandbox_client.h"
39 #include "sandbox/linux/suid/client/setuid_sandbox_host.h"
40 #include "sandbox/linux/suid/common/sandbox.h"
41 #include "sandbox/policy/switches.h"
42 #include "third_party/cros_system_api/switches/chrome_switches.h"
43
44 namespace {
45
46 // Note these need to match up with their counterparts in nacl_helper_linux.c
47 // and nacl_helper_bootstrap_linux.c.
48 const char kNaClHelperReservedAtZero[] =
49 "--reserved_at_zero=0xXXXXXXXXXXXXXXXX";
50 const char kNaClHelperRDebug[] = "--r_debug=0xXXXXXXXXXXXXXXXX";
51
52 // This is an environment variable which controls which (if any) other
53 // environment variables are passed through to NaCl processes. e.g.,
54 // NACL_ENV_PASSTHROUGH="PATH,CWD" would pass both $PATH and $CWD to the child
55 // process.
56 const char kNaClEnvPassthrough[] = "NACL_ENV_PASSTHROUGH";
57 char kNaClEnvPassthroughDelimiter = ',';
58
59 // The following environment variables are always passed through if they exist
60 // in the parent process.
61 const char kNaClExeStderr[] = "NACL_EXE_STDERR";
62 const char kNaClExeStdout[] = "NACL_EXE_STDOUT";
63 const char kNaClVerbosity[] = "NACLVERBOSITY";
64
65 #if defined(ARCH_CPU_X86)
NonZeroSegmentBaseIsSlow()66 bool NonZeroSegmentBaseIsSlow() {
67 base::CPU cpuid;
68 // Using a non-zero segment base is known to be very slow on Intel
69 // Atom CPUs. See "Segmentation-based Memory Protection Mechanism
70 // on Intel Atom Microarchitecture: Coding Optimizations" (Leonardo
71 // Potenza, Intel).
72 //
73 // The following list of CPU model numbers is taken from:
74 // "Intel 64 and IA-32 Architectures Software Developer's Manual"
75 // (http://download.intel.com/products/processor/manual/325462.pdf),
76 // "Table 35-1. CPUID Signature Values of DisplayFamily_DisplayModel"
77 // (Volume 3C, 35-1), which contains:
78 // "06_36H - Intel Atom S Processor Family
79 // 06_1CH, 06_26H, 06_27H, 06_35, 06_36 - Intel Atom Processor Family"
80 if (cpuid.family() == 6) {
81 switch (cpuid.model()) {
82 case 0x1c:
83 case 0x26:
84 case 0x27:
85 case 0x35:
86 case 0x36:
87 return true;
88 }
89 }
90 return false;
91 }
92 #endif
93
94 // Send an IPC request on |ipc_channel|. The request is contained in
95 // |request_pickle| and can have file descriptors attached in |attached_fds|.
96 // |reply_data_buffer| must be allocated by the caller and will contain the
97 // reply. The size of the reply will be written to |reply_size|.
98 // This code assumes that only one thread can write to |ipc_channel| to make
99 // requests.
SendIPCRequestAndReadReply(int ipc_channel,const std::vector<int> & attached_fds,const base::Pickle & request_pickle,char * reply_data_buffer,size_t reply_data_buffer_size,ssize_t * reply_size)100 bool SendIPCRequestAndReadReply(int ipc_channel,
101 const std::vector<int>& attached_fds,
102 const base::Pickle& request_pickle,
103 char* reply_data_buffer,
104 size_t reply_data_buffer_size,
105 ssize_t* reply_size) {
106 DCHECK_LE(static_cast<size_t>(kNaClMaxIPCMessageLength),
107 reply_data_buffer_size);
108 DCHECK(reply_size);
109
110 if (!base::UnixDomainSocket::SendMsg(ipc_channel, request_pickle.data(),
111 request_pickle.size(), attached_fds)) {
112 LOG(ERROR) << "SendIPCRequestAndReadReply: SendMsg failed";
113 return false;
114 }
115
116 // Then read the remote reply.
117 std::vector<base::ScopedFD> received_fds;
118 const ssize_t msg_len =
119 base::UnixDomainSocket::RecvMsg(ipc_channel, reply_data_buffer,
120 reply_data_buffer_size, &received_fds);
121 if (msg_len <= 0) {
122 LOG(ERROR) << "SendIPCRequestAndReadReply: RecvMsg failed";
123 return false;
124 }
125 *reply_size = msg_len;
126 return true;
127 }
128
129 } // namespace.
130
131 namespace nacl {
132
AddNaClZygoteForkDelegates(std::vector<std::unique_ptr<content::ZygoteForkDelegate>> * delegates)133 void AddNaClZygoteForkDelegates(
134 std::vector<std::unique_ptr<content::ZygoteForkDelegate>>* delegates) {
135 // We don't need the delegates for the unsandboxed zygote since NaCl always
136 // starts from the sandboxed zygote.
137 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
138 sandbox::policy::switches::kNoZygoteSandbox)) {
139 return;
140 }
141
142 delegates->push_back(std::make_unique<NaClForkDelegate>());
143 }
144
NaClForkDelegate()145 NaClForkDelegate::NaClForkDelegate() : status_(kNaClHelperUnused), fd_(-1) {}
146
Init(const int sandboxdesc,const bool enable_layer1_sandbox)147 void NaClForkDelegate::Init(const int sandboxdesc,
148 const bool enable_layer1_sandbox) {
149 VLOG(1) << "NaClForkDelegate::Init()";
150
151 // TODO(rickyz): Make IsSuidSandboxChild a static function.
152 std::unique_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();
157
158 CHECK(!(using_setuid_sandbox && using_namespace_sandbox));
159 if (enable_layer1_sandbox) {
160 CHECK(using_setuid_sandbox || using_namespace_sandbox);
161 }
162
163 std::unique_ptr<sandbox::SetuidSandboxHost> setuid_sandbox_host(
164 sandbox::SetuidSandboxHost::Create());
165
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);
172
173 int fds[2];
174 PCHECK(0 == socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds));
175
176 bool use_nacl_bootstrap = true;
177 #if defined(ARCH_CPU_X86_64)
178 // Using nacl_helper_bootstrap is not necessary on x86-64 because
179 // NaCl's x86-64 sandbox is not zero-address-based. Starting
180 // nacl_helper through nacl_helper_bootstrap works on x86-64, but it
181 // leaves nacl_helper_bootstrap mapped at a fixed address at the
182 // bottom of the address space, which is undesirable because it
183 // effectively defeats ASLR.
184 use_nacl_bootstrap = false;
185 #elif defined(ARCH_CPU_X86)
186 // Performance vs. security trade-off: We prefer using a
187 // non-zero-address-based sandbox on x86-32 because it provides some
188 // ASLR and so is more secure. However, on Atom CPUs, using a
189 // non-zero segment base is very slow, so we use a zero-based
190 // sandbox on those.
191 use_nacl_bootstrap = NonZeroSegmentBaseIsSlow();
192 #endif
193
194 status_ = kNaClHelperUnused;
195 base::FilePath helper_exe;
196 base::FilePath helper_bootstrap_exe;
197 if (!base::PathService::Get(nacl::FILE_NACL_HELPER, &helper_exe)) {
198 status_ = kNaClHelperMissing;
199 } else if (use_nacl_bootstrap &&
200 !base::PathService::Get(nacl::FILE_NACL_HELPER_BOOTSTRAP,
201 &helper_bootstrap_exe)) {
202 status_ = kNaClHelperBootstrapMissing;
203 } else {
204 base::CommandLine::StringVector argv_to_launch;
205 {
206 base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM);
207 if (use_nacl_bootstrap)
208 cmd_line.SetProgram(helper_bootstrap_exe);
209 else
210 cmd_line.SetProgram(helper_exe);
211
212 // Append any switches that need to be forwarded to the NaCl helper.
213 static constexpr const char* kForwardSwitches[] = {
214 sandbox::policy::switches::kAllowSandboxDebugging,
215 sandbox::policy::switches::kDisableSeccompFilterSandbox,
216 sandbox::policy::switches::kNoSandbox,
217 switches::kEnableNaClDebug,
218 switches::kVerboseLoggingInNacl,
219 chromeos::switches::kFeatureFlags,
220 };
221 const base::CommandLine& current_cmd_line =
222 *base::CommandLine::ForCurrentProcess();
223 cmd_line.CopySwitchesFrom(current_cmd_line, kForwardSwitches);
224
225 // The command line needs to be tightly controlled to use
226 // |helper_bootstrap_exe|. So from now on, argv_to_launch should be
227 // modified directly.
228 argv_to_launch = cmd_line.argv();
229 }
230 if (use_nacl_bootstrap) {
231 // Arguments to the bootstrap helper which need to be at the start
232 // of the command line, right after the helper's path.
233 base::CommandLine::StringVector bootstrap_prepend;
234 bootstrap_prepend.push_back(helper_exe.value());
235 bootstrap_prepend.push_back(kNaClHelperReservedAtZero);
236 bootstrap_prepend.push_back(kNaClHelperRDebug);
237 argv_to_launch.insert(argv_to_launch.begin() + 1,
238 bootstrap_prepend.begin(),
239 bootstrap_prepend.end());
240 }
241
242 std::vector<int> max_these_limits; // must outlive `options`
243 base::LaunchOptions options;
244 options.maximize_rlimits = &max_these_limits;
245 options.fds_to_remap.push_back(
246 std::make_pair(fds[1], kNaClZygoteDescriptor));
247 options.fds_to_remap.push_back(
248 std::make_pair(sandboxdesc, nacl_sandbox_descriptor));
249
250 base::ScopedFD dummy_fd;
251 if (using_setuid_sandbox) {
252 // NaCl needs to keep tight control of the cmd_line, so prepend the
253 // setuid sandbox wrapper manually.
254 base::FilePath sandbox_path = setuid_sandbox_host->GetSandboxBinaryPath();
255 argv_to_launch.insert(argv_to_launch.begin(), sandbox_path.value());
256 setuid_sandbox_host->SetupLaunchOptions(&options, &dummy_fd);
257 setuid_sandbox_host->SetupLaunchEnvironment();
258 }
259
260 // The NaCl processes spawned may need to exceed the ambient soft limit
261 // on RLIMIT_AS to allocate the untrusted address space and its guard
262 // regions. The nacl_helper itself cannot just raise its own limit,
263 // because the existing limit may prevent the initial exec of
264 // nacl_helper_bootstrap from succeeding, with its large address space
265 // reservation.
266 max_these_limits.push_back(RLIMIT_AS);
267
268 // Clear the environment for the NaCl Helper process.
269 options.clear_environment = true;
270 AddPassthroughEnvToOptions(&options);
271
272 #ifdef COMPONENT_BUILD
273 // In component build, nacl_helper loads libgnutls.so.
274 // Newer versions of libgnutls do implicit initialization when loaded that
275 // leaves an additional /dev/urandom file descriptor open. Passing the
276 // following env var asks libgnutls not to do that implicit initialization.
277 // (crbug.com/973024)
278 options.environment["GNUTLS_NO_EXPLICIT_INIT"] = "1";
279 #endif
280
281 base::Process process =
282 using_namespace_sandbox
283 ? sandbox::NamespaceSandbox::LaunchProcess(argv_to_launch, options)
284 : base::LaunchProcess(argv_to_launch, options);
285
286 if (!process.IsValid())
287 status_ = kNaClHelperLaunchFailed;
288 // parent and error cases are handled below
289
290 if (using_setuid_sandbox) {
291 // Sanity check that dummy_fd was kept alive for LaunchProcess.
292 DCHECK(dummy_fd.is_valid());
293 }
294 }
295 if (IGNORE_EINTR(close(fds[1])) != 0)
296 LOG(ERROR) << "close(fds[1]) failed";
297 if (status_ == kNaClHelperUnused) {
298 const ssize_t kExpectedLength = strlen(kNaClHelperStartupAck);
299 char buf[kExpectedLength];
300
301 // Wait for ack from nacl_helper, indicating it is ready to help
302 const ssize_t nread = HANDLE_EINTR(read(fds[0], buf, sizeof(buf)));
303 if (nread == kExpectedLength &&
304 memcmp(buf, kNaClHelperStartupAck, nread) == 0) {
305 // all is well
306 status_ = kNaClHelperSuccess;
307 fd_ = fds[0];
308 return;
309 }
310
311 status_ = kNaClHelperAckFailed;
312 LOG(ERROR) << "Bad NaCl helper startup ack (" << nread << " bytes)";
313 }
314 // TODO(bradchen): Make this LOG(ERROR) when the NaCl helper
315 // becomes the default.
316 fd_ = -1;
317 if (IGNORE_EINTR(close(fds[0])) != 0)
318 LOG(ERROR) << "close(fds[0]) failed";
319 }
320
InitialUMA(std::string * uma_name,int * uma_sample,int * uma_boundary_value)321 void NaClForkDelegate::InitialUMA(std::string* uma_name,
322 int* uma_sample,
323 int* uma_boundary_value) {
324 *uma_name = "NaCl.Client.Helper.InitState";
325 *uma_sample = status_;
326 *uma_boundary_value = kNaClHelperStatusBoundary;
327 }
328
~NaClForkDelegate()329 NaClForkDelegate::~NaClForkDelegate() {
330 // side effect of close: delegate process will terminate
331 if (status_ == kNaClHelperSuccess) {
332 if (IGNORE_EINTR(close(fd_)) != 0)
333 LOG(ERROR) << "close(fd_) failed";
334 }
335 }
336
CanHelp(const std::string & process_type,std::string * uma_name,int * uma_sample,int * uma_boundary_value)337 bool NaClForkDelegate::CanHelp(const std::string& process_type,
338 std::string* uma_name,
339 int* uma_sample,
340 int* uma_boundary_value) {
341 if (process_type != switches::kNaClLoaderProcess)
342 return false;
343 *uma_name = "NaCl.Client.Helper.StateOnFork";
344 *uma_sample = status_;
345 *uma_boundary_value = kNaClHelperStatusBoundary;
346 return true;
347 }
348
Fork(const std::string & process_type,const std::vector<std::string> & args,const std::vector<int> & fds,const std::string & channel_id)349 pid_t NaClForkDelegate::Fork(const std::string& process_type,
350 const std::vector<std::string>& args,
351 const std::vector<int>& fds,
352 const std::string& channel_id) {
353 VLOG(1) << "NaClForkDelegate::Fork";
354
355 // The metrics shared memory handle may or may not be in |fds|, depending on
356 // whether the feature flag to pass the handle on startup was enabled in the
357 // parent; there should either be kNumPassedFDs or kNumPassedFDs-1 present.
358 // TODO(crbug/1028263): Only check for kNumPassedFDs once passing the metrics
359 // shared memory handle on startup is launched.
360 DCHECK(fds.size() == kNumPassedFDs || fds.size() == kNumPassedFDs - 1);
361
362 if (status_ != kNaClHelperSuccess) {
363 LOG(ERROR) << "Cannot launch NaCl process: nacl_helper failed to start";
364 return -1;
365 }
366
367 // First, send a remote fork request.
368 base::Pickle write_pickle;
369 write_pickle.WriteInt(nacl::kNaClForkRequest);
370 write_pickle.WriteString(channel_id);
371 write_pickle.WriteInt(base::checked_cast<int>(args.size()));
372 for (const std::string& arg : args) {
373 write_pickle.WriteString(arg);
374 }
375
376 char reply_buf[kNaClMaxIPCMessageLength];
377 ssize_t reply_size = 0;
378 bool got_reply =
379 SendIPCRequestAndReadReply(fd_, fds, write_pickle,
380 reply_buf, sizeof(reply_buf), &reply_size);
381 if (!got_reply) {
382 LOG(ERROR) << "Could not perform remote fork.";
383 return -1;
384 }
385
386 // Now see if the other end managed to fork.
387 base::Pickle reply_pickle = base::Pickle::WithUnownedBuffer(
388 base::span(reinterpret_cast<uint8_t*>(reply_buf),
389 base::checked_cast<size_t>(reply_size)));
390 base::PickleIterator iter(reply_pickle);
391 pid_t nacl_child;
392 if (!iter.ReadInt(&nacl_child)) {
393 LOG(ERROR) << "NaClForkDelegate::Fork: pickle failed";
394 return -1;
395 }
396 VLOG(1) << "nacl_child is " << nacl_child;
397 return nacl_child;
398 }
399
GetTerminationStatus(pid_t pid,bool known_dead,base::TerminationStatus * status,int * exit_code)400 bool NaClForkDelegate::GetTerminationStatus(pid_t pid, bool known_dead,
401 base::TerminationStatus* status,
402 int* exit_code) {
403 VLOG(1) << "NaClForkDelegate::GetTerminationStatus";
404 DCHECK(status);
405 DCHECK(exit_code);
406
407 base::Pickle write_pickle;
408 write_pickle.WriteInt(nacl::kNaClGetTerminationStatusRequest);
409 write_pickle.WriteInt(pid);
410 write_pickle.WriteBool(known_dead);
411
412 const std::vector<int> empty_fds;
413 char reply_buf[kNaClMaxIPCMessageLength];
414 ssize_t reply_size = 0;
415 bool got_reply =
416 SendIPCRequestAndReadReply(fd_, empty_fds, write_pickle,
417 reply_buf, sizeof(reply_buf), &reply_size);
418 if (!got_reply) {
419 LOG(ERROR) << "Could not perform remote GetTerminationStatus.";
420 return false;
421 }
422
423 base::Pickle reply_pickle = base::Pickle::WithUnownedBuffer(
424 base::span(reinterpret_cast<uint8_t*>(reply_buf),
425 base::checked_cast<size_t>(reply_size)));
426 base::PickleIterator iter(reply_pickle);
427 int termination_status;
428 if (!iter.ReadInt(&termination_status) ||
429 termination_status < 0 ||
430 termination_status >= base::TERMINATION_STATUS_MAX_ENUM) {
431 LOG(ERROR) << "GetTerminationStatus: pickle failed";
432 return false;
433 }
434
435 int remote_exit_code;
436 if (!iter.ReadInt(&remote_exit_code)) {
437 LOG(ERROR) << "GetTerminationStatus: pickle failed";
438 return false;
439 }
440
441 *status = static_cast<base::TerminationStatus>(termination_status);
442 *exit_code = remote_exit_code;
443 return true;
444 }
445
446 // static
AddPassthroughEnvToOptions(base::LaunchOptions * options)447 void NaClForkDelegate::AddPassthroughEnvToOptions(
448 base::LaunchOptions* options) {
449 std::unique_ptr<base::Environment> env(base::Environment::Create());
450 std::string pass_through_string;
451 std::vector<std::string> pass_through_vars;
452 if (env->GetVar(kNaClEnvPassthrough, &pass_through_string)) {
453 pass_through_vars = base::SplitString(
454 pass_through_string, std::string(1, kNaClEnvPassthroughDelimiter),
455 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
456 }
457 pass_through_vars.push_back(kNaClExeStderr);
458 pass_through_vars.push_back(kNaClExeStdout);
459 pass_through_vars.push_back(kNaClVerbosity);
460 pass_through_vars.push_back(sandbox::kSandboxEnvironmentApiRequest);
461 for (size_t i = 0; i < pass_through_vars.size(); ++i) {
462 std::string temp;
463 if (env->GetVar(pass_through_vars[i], &temp))
464 options->environment[pass_through_vars[i]] = temp;
465 }
466 }
467
468 } // namespace nacl
469