xref: /aosp_15_r20/external/cronet/components/nacl/zygote/nacl_fork_delegate_linux.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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