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