1 // Copyright 2013 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 // A mini-zygote specifically for Native Client.
11
12 #include "components/nacl/loader/nacl_helper_linux.h"
13
14 #include <errno.h>
15 #include <fcntl.h>
16 #include <link.h>
17 #include <signal.h>
18 #include <stddef.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <sys/socket.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24
25 #include <memory>
26 #include <string>
27 #include <utility>
28 #include <vector>
29
30 #include "base/at_exit.h"
31 #include "base/base_switches.h"
32 #include "base/command_line.h"
33 #include "base/environment.h"
34 #include "base/files/scoped_file.h"
35 #include "base/json/json_reader.h"
36 #include "base/logging.h"
37 #include "base/message_loop/message_pump_type.h"
38 #include "base/metrics/field_trial.h"
39 #include "base/metrics/histogram_shared_memory.h"
40 #include "base/numerics/safe_conversions.h"
41 #include "base/pickle.h"
42 #include "base/posix/eintr_wrapper.h"
43 #include "base/posix/global_descriptors.h"
44 #include "base/posix/unix_domain_socket.h"
45 #include "base/process/kill.h"
46 #include "base/process/process_handle.h"
47 #include "base/rand_util.h"
48 #include "base/task/single_thread_task_executor.h"
49 #include "build/build_config.h"
50 #include "components/nacl/common/nacl_switches.h"
51 #include "components/nacl/loader/nacl_listener.h"
52 #include "components/nacl/loader/sandbox_linux/nacl_sandbox_linux.h"
53 #include "content/public/common/content_descriptors.h"
54 #include "content/public/common/zygote/send_zygote_child_ping_linux.h"
55 #include "content/public/common/zygote/zygote_fork_delegate_linux.h"
56 #include "mojo/core/embedder/embedder.h"
57 #include "sandbox/linux/services/credentials.h"
58 #include "sandbox/linux/services/namespace_sandbox.h"
59 #include "third_party/cros_system_api/switches/chrome_switches.h"
60
61 namespace {
62
63 struct NaClLoaderSystemInfo {
64 size_t prereserved_sandbox_size;
65 long number_of_cores;
66 };
67
68 #if BUILDFLAG(IS_CHROMEOS)
GetCommandLineFeatureFlagChoice(const base::CommandLine * command_line,std::string feature_flag)69 std::string GetCommandLineFeatureFlagChoice(
70 const base::CommandLine* command_line,
71 std::string feature_flag) {
72 std::string encoded =
73 command_line->GetSwitchValueNative(chromeos::switches::kFeatureFlags);
74 if (encoded.empty()) {
75 return "";
76 }
77
78 auto flags_list = base::JSONReader::Read(encoded);
79 if (!flags_list) {
80 LOG(WARNING) << "Failed to parse feature flags configuration";
81 return "";
82 }
83
84 for (const auto& flag : flags_list.value().GetList()) {
85 if (!flag.is_string())
86 continue;
87 std::string flag_string = flag.GetString();
88 if (flag_string.rfind(feature_flag) != std::string::npos)
89 // For option x, this has the form "feature-flag-name@x". Return "x".
90 return flag_string.substr(feature_flag.size() + 1);
91 }
92 return "";
93 }
94
AddVerboseLoggingInNaclSwitch(base::CommandLine * command_line)95 void AddVerboseLoggingInNaclSwitch(base::CommandLine* command_line) {
96 if (command_line->HasSwitch(switches::kVerboseLoggingInNacl))
97 // Flag is already present, nothing to do here.
98 return;
99
100 std::string option = GetCommandLineFeatureFlagChoice(
101 command_line, switches::kVerboseLoggingInNacl);
102
103 // This needs to be kept in sync with the order of choices for
104 // kVerboseLoggingInNacl in chrome/browser/about_flags.cc
105 if (option == "")
106 return;
107 if (option == "1")
108 return command_line->AppendSwitchASCII(
109 switches::kVerboseLoggingInNacl,
110 switches::kVerboseLoggingInNaclChoiceLow);
111 if (option == "2")
112 return command_line->AppendSwitchASCII(
113 switches::kVerboseLoggingInNacl,
114 switches::kVerboseLoggingInNaclChoiceMedium);
115 if (option == "3")
116 return command_line->AppendSwitchASCII(
117 switches::kVerboseLoggingInNacl,
118 switches::kVerboseLoggingInNaclChoiceHigh);
119 if (option == "4")
120 return command_line->AppendSwitchASCII(
121 switches::kVerboseLoggingInNacl,
122 switches::kVerboseLoggingInNaclChoiceHighest);
123 if (option == "5")
124 return command_line->AppendSwitchASCII(
125 switches::kVerboseLoggingInNacl,
126 switches::kVerboseLoggingInNaclChoiceDisabled);
127 }
128 #endif // BUILDFLAG(IS_CHROMEOS)
129
130 // The child must mimic the behavior of zygote_main_linux.cc on the child
131 // side of the fork. See zygote_main_linux.cc:HandleForkRequest from
132 // if (!child) {
BecomeNaClLoader(base::ScopedFD browser_fd,const NaClLoaderSystemInfo & system_info,nacl::NaClSandbox * nacl_sandbox,const std::vector<std::string> & args)133 void BecomeNaClLoader(base::ScopedFD browser_fd,
134 const NaClLoaderSystemInfo& system_info,
135 nacl::NaClSandbox* nacl_sandbox,
136 const std::vector<std::string>& args) {
137 DCHECK(nacl_sandbox);
138 VLOG(1) << "NaCl loader: setting up IPC descriptor";
139 // Close or shutdown IPC channels that we don't need anymore.
140 PCHECK(0 == IGNORE_EINTR(close(kNaClZygoteDescriptor)));
141
142 // Append any passed switches to the forked loader's command line. This is
143 // necessary to get e.g. any field trial handle and feature overrides from
144 // whomever initiated the this fork request.
145 base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess();
146 command_line.AppendArguments(base::CommandLine::FromArgvWithoutProgram(args),
147 /*include_program=*/false);
148 if (command_line.HasSwitch(switches::kVerboseLoggingInNacl)) {
149 base::Environment::Create()->SetVar(
150 "NACLVERBOSITY",
151 command_line.GetSwitchValueASCII(switches::kVerboseLoggingInNacl));
152 }
153
154 // Always ignore SIGPIPE, for consistency with other Chrome processes and
155 // because some IPC code, such as sync_socket_posix.cc, requires this.
156 // We do this before seccomp-bpf is initialized.
157 PCHECK(signal(SIGPIPE, SIG_IGN) != SIG_ERR);
158
159 base::HistogramSharedMemory::InitFromLaunchParameters(command_line);
160
161 base::FieldTrialList field_trial_list;
162 base::FieldTrialList::CreateTrialsInChildProcess(command_line);
163 auto feature_list = std::make_unique<base::FeatureList>();
164 base::FieldTrialList::ApplyFeatureOverridesInChildProcess(feature_list.get());
165 base::FeatureList::SetInstance(std::move(feature_list));
166
167 // Finish layer-1 sandbox initialization and initialize the layer-2 sandbox.
168 CHECK(!nacl_sandbox->HasOpenDirectory());
169 nacl_sandbox->InitializeLayerTwoSandbox();
170 nacl_sandbox->SealLayerOneSandbox();
171 nacl_sandbox->CheckSandboxingStateWithPolicy();
172
173 base::GlobalDescriptors::GetInstance()->Set(kMojoIPCChannel,
174 browser_fd.release());
175
176 // The Mojo EDK must be initialized before using IPC.
177 mojo::core::InitFeatures();
178 mojo::core::Init();
179
180 base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO);
181 NaClListener listener;
182 listener.set_prereserved_sandbox_size(system_info.prereserved_sandbox_size);
183 listener.set_number_of_cores(system_info.number_of_cores);
184 listener.Listen();
185
186 _exit(0);
187 }
188
189 // Start the NaCl loader in a child created by the NaCl loader Zygote.
ChildNaClLoaderInit(std::vector<base::ScopedFD> child_fds,const NaClLoaderSystemInfo & system_info,nacl::NaClSandbox * nacl_sandbox,const std::string & channel_id,const std::vector<std::string> & args)190 void ChildNaClLoaderInit(std::vector<base::ScopedFD> child_fds,
191 const NaClLoaderSystemInfo& system_info,
192 nacl::NaClSandbox* nacl_sandbox,
193 const std::string& channel_id,
194 const std::vector<std::string>& args) {
195 DCHECK(child_fds.size() >
196 std::max(content::ZygoteForkDelegate::kPIDOracleFDIndex,
197 content::ZygoteForkDelegate::kBrowserFDIndex));
198
199 // Ping the PID oracle socket.
200 CHECK(content::SendZygoteChildPing(
201 child_fds[content::ZygoteForkDelegate::kPIDOracleFDIndex].get()));
202
203 // Stash the field trial descriptor in GlobalDescriptors so FieldTrialList
204 // can be initialized later. See BecomeNaClLoader().
205 base::GlobalDescriptors::GetInstance()->Set(
206 kFieldTrialDescriptor,
207 child_fds[content::ZygoteForkDelegate::kFieldTrialFDIndex].release());
208
209 // Stash the histogram descriptor in GlobalDescriptors so the histogram
210 // allocator can be initialized later. See BecomeNaClLoader().
211 // TODO(crbug.com/40109064): Always update mapping once metrics shared memory
212 // region is always passed on startup.
213 if (child_fds.size() > content::ZygoteForkDelegate::kHistogramFDIndex &&
214 child_fds[content::ZygoteForkDelegate::kHistogramFDIndex].is_valid()) {
215 base::GlobalDescriptors::GetInstance()->Set(
216 kHistogramSharedMemoryDescriptor,
217 child_fds[content::ZygoteForkDelegate::kHistogramFDIndex].release());
218 }
219
220 // Save the browser socket and close the rest.
221 base::ScopedFD browser_fd(
222 std::move(child_fds[content::ZygoteForkDelegate::kBrowserFDIndex]));
223 child_fds.clear();
224
225 BecomeNaClLoader(std::move(browser_fd), system_info, nacl_sandbox, args);
226 _exit(1);
227 }
228
229 // Handle a fork request from the Zygote.
230 // Some of this code was lifted from
231 // content/browser/zygote_main_linux.cc:ForkWithRealPid()
HandleForkRequest(std::vector<base::ScopedFD> child_fds,const NaClLoaderSystemInfo & system_info,nacl::NaClSandbox * nacl_sandbox,base::PickleIterator * input_iter,base::Pickle * output_pickle)232 bool HandleForkRequest(std::vector<base::ScopedFD> child_fds,
233 const NaClLoaderSystemInfo& system_info,
234 nacl::NaClSandbox* nacl_sandbox,
235 base::PickleIterator* input_iter,
236 base::Pickle* output_pickle) {
237 std::string channel_id;
238 if (!input_iter->ReadString(&channel_id)) {
239 LOG(ERROR) << "Could not read channel_id string";
240 return false;
241 }
242
243 // Read the args passed by the launcher and prepare to forward them to our own
244 // forked child.
245 int argc;
246 if (!input_iter->ReadInt(&argc) || argc < 0) {
247 LOG(ERROR) << "nacl_helper: Invalid argument list";
248 return false;
249 }
250 std::vector<std::string> args(static_cast<size_t>(argc));
251 for (std::string& arg : args) {
252 if (!input_iter->ReadString(&arg)) {
253 LOG(ERROR) << "nacl_helper: Invalid argument list";
254 return false;
255 }
256 }
257
258 // |child_fds| should contain either kNumPassedFDs or kNumPassedFDs-1 file
259 // descriptors.. The actual size of |child_fds| depends on whether or not the
260 // metrics shared memory region is being passed on startup.
261 // TODO(crbug.com/40109064): Expect a fixed size once passing the metrics
262 // shared memory region on startup has been launched.
263 if (child_fds.size() != content::ZygoteForkDelegate::kNumPassedFDs &&
264 child_fds.size() != content::ZygoteForkDelegate::kNumPassedFDs - 1) {
265 LOG(ERROR) << "nacl_helper: unexpected number of fds, got "
266 << child_fds.size();
267 return false;
268 }
269
270 VLOG(1) << "nacl_helper: forking";
271 pid_t child_pid;
272 if (sandbox::NamespaceSandbox::InNewUserNamespace()) {
273 child_pid = sandbox::NamespaceSandbox::ForkInNewPidNamespace(
274 /*drop_capabilities_in_child=*/true);
275 } else {
276 child_pid = sandbox::Credentials::ForkAndDropCapabilitiesInChild();
277 }
278
279 if (child_pid < 0) {
280 PLOG(ERROR) << "*** fork() failed.";
281 }
282
283 if (child_pid == 0) {
284 ChildNaClLoaderInit(std::move(child_fds), system_info, nacl_sandbox,
285 channel_id, args);
286 NOTREACHED();
287 }
288
289 // I am the parent.
290 // First, close the dummy_fd so the sandbox won't find me when
291 // looking for the child's pid in /proc. Also close other fds.
292 child_fds.clear();
293 VLOG(1) << "nacl_helper: child_pid is " << child_pid;
294
295 // Now send child_pid (eventually -1 if fork failed) to the Chrome Zygote.
296 output_pickle->WriteInt(child_pid);
297 return true;
298 }
299
HandleGetTerminationStatusRequest(base::PickleIterator * input_iter,base::Pickle * output_pickle)300 bool HandleGetTerminationStatusRequest(base::PickleIterator* input_iter,
301 base::Pickle* output_pickle) {
302 pid_t child_to_wait;
303 if (!input_iter->ReadInt(&child_to_wait)) {
304 LOG(ERROR) << "Could not read pid to wait for";
305 return false;
306 }
307
308 bool known_dead;
309 if (!input_iter->ReadBool(&known_dead)) {
310 LOG(ERROR) << "Could not read known_dead status";
311 return false;
312 }
313 // TODO(jln): With NaCl, known_dead seems to never be set to true (unless
314 // called from the Zygote's kZygoteCommandReap command). This means that we
315 // will sometimes detect the process as still running when it's not. Fix
316 // this!
317
318 int exit_code;
319 base::TerminationStatus status;
320 if (known_dead)
321 status = base::GetKnownDeadTerminationStatus(child_to_wait, &exit_code);
322 else
323 status = base::GetTerminationStatus(child_to_wait, &exit_code);
324 output_pickle->WriteInt(static_cast<int>(status));
325 output_pickle->WriteInt(exit_code);
326 return true;
327 }
328
329 // Honor a command |command_type|. Eventual command parameters are
330 // available in |input_iter| and eventual file descriptors attached to
331 // the command are in |attached_fds|.
332 // Reply to the command on |reply_fds|.
HonorRequestAndReply(int reply_fd,int command_type,std::vector<base::ScopedFD> attached_fds,const NaClLoaderSystemInfo & system_info,nacl::NaClSandbox * nacl_sandbox,base::PickleIterator * input_iter)333 bool HonorRequestAndReply(int reply_fd,
334 int command_type,
335 std::vector<base::ScopedFD> attached_fds,
336 const NaClLoaderSystemInfo& system_info,
337 nacl::NaClSandbox* nacl_sandbox,
338 base::PickleIterator* input_iter) {
339 base::Pickle write_pickle;
340 bool have_to_reply = false;
341 // Commands must write anything to send back to |write_pickle|.
342 switch (command_type) {
343 case nacl::kNaClForkRequest:
344 have_to_reply =
345 HandleForkRequest(std::move(attached_fds), system_info, nacl_sandbox,
346 input_iter, &write_pickle);
347 break;
348 case nacl::kNaClGetTerminationStatusRequest:
349 have_to_reply =
350 HandleGetTerminationStatusRequest(input_iter, &write_pickle);
351 break;
352 default:
353 LOG(ERROR) << "Unsupported command from Zygote";
354 return false;
355 }
356 if (!have_to_reply)
357 return false;
358 const std::vector<int> empty; // We never send file descriptors back.
359 if (!base::UnixDomainSocket::SendMsg(reply_fd, write_pickle.data(),
360 write_pickle.size(), empty)) {
361 LOG(ERROR) << "*** send() to zygote failed";
362 return false;
363 }
364 return true;
365 }
366
367 // Read a request from the Zygote from |zygote_ipc_fd| and handle it.
368 // Die on EOF from |zygote_ipc_fd|.
HandleZygoteRequest(int zygote_ipc_fd,const NaClLoaderSystemInfo & system_info,nacl::NaClSandbox * nacl_sandbox)369 bool HandleZygoteRequest(int zygote_ipc_fd,
370 const NaClLoaderSystemInfo& system_info,
371 nacl::NaClSandbox* nacl_sandbox) {
372 std::vector<base::ScopedFD> fds;
373 uint8_t buf[kNaClMaxIPCMessageLength];
374 const ssize_t msglen = base::UnixDomainSocket::RecvMsg(zygote_ipc_fd,
375 &buf, sizeof(buf), &fds);
376 // If the Zygote has started handling requests, we should be sandboxed via
377 // the setuid sandbox.
378 if (!nacl_sandbox->layer_one_enabled()) {
379 LOG(ERROR) << "NaCl helper process running without a sandbox!\n"
380 << "Most likely you need to configure your SUID sandbox "
381 << "correctly";
382 }
383 if (msglen == 0 || (msglen == -1 && errno == ECONNRESET)) {
384 // EOF from the browser. Goodbye!
385 _exit(0);
386 }
387 if (msglen < 0) {
388 PLOG(ERROR) << "nacl_helper: receive from zygote failed";
389 return false;
390 }
391
392 base::Pickle read_pickle = base::Pickle::WithUnownedBuffer(
393 base::span(buf, base::checked_cast<size_t>(msglen)));
394 base::PickleIterator read_iter(read_pickle);
395 int command_type;
396 if (!read_iter.ReadInt(&command_type)) {
397 LOG(ERROR) << "Unable to read command from Zygote";
398 return false;
399 }
400 return HonorRequestAndReply(zygote_ipc_fd, command_type, std::move(fds),
401 system_info, nacl_sandbox, &read_iter);
402 }
403
404 static const char kNaClHelperReservedAtZero[] = "reserved_at_zero";
405 static const char kNaClHelperRDebug[] = "r_debug";
406
407 // Since we were started by nacl_helper_bootstrap rather than in the
408 // usual way, the debugger cannot figure out where our executable
409 // or the dynamic linker or the shared libraries are in memory,
410 // so it won't find any symbols. But we can fake it out to find us.
411 //
412 // The zygote passes --r_debug=0xXXXXXXXXXXXXXXXX.
413 // nacl_helper_bootstrap replaces the Xs with the address of its _r_debug
414 // structure. The debugger will look for that symbol by name to
415 // discover the addresses of key dynamic linker data structures.
416 // Since all it knows about is the original main executable, which
417 // is the bootstrap program, it finds the symbol defined there. The
418 // dynamic linker's structure is somewhere else, but it is filled in
419 // after initialization. The parts that really matter to the
420 // debugger never change. So we just copy the contents of the
421 // dynamic linker's structure into the address provided by the option.
422 // Hereafter, if someone attaches a debugger (or examines a core dump),
423 // the debugger will find all the symbols in the normal way.
CheckRDebug(char * argv0)424 static void CheckRDebug(char* argv0) {
425 std::string r_debug_switch_value =
426 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
427 kNaClHelperRDebug);
428 if (!r_debug_switch_value.empty()) {
429 char* endp;
430 uintptr_t r_debug_addr = strtoul(r_debug_switch_value.c_str(), &endp, 0);
431 if (r_debug_addr != 0 && *endp == '\0') {
432 r_debug* bootstrap_r_debug = reinterpret_cast<r_debug*>(r_debug_addr);
433 *bootstrap_r_debug = _r_debug;
434
435 // Since the main executable (the bootstrap program) does not
436 // have a dynamic section, the debugger will not skip the
437 // first element of the link_map list as it usually would for
438 // an executable or PIE that was loaded normally. But the
439 // dynamic linker has set l_name for the PIE to "" as is
440 // normal for the main executable. So the debugger doesn't
441 // know which file it is. Fill in the actual file name, which
442 // came in as our argv[0].
443 link_map* l = _r_debug.r_map;
444 if (l->l_name[0] == '\0')
445 l->l_name = argv0;
446 }
447 }
448 }
449
450 // The zygote passes --reserved_at_zero=0xXXXXXXXXXXXXXXXX.
451 // nacl_helper_bootstrap replaces the Xs with the amount of prereserved
452 // sandbox memory.
453 //
454 // CheckReservedAtZero parses the value of the argument reserved_at_zero
455 // and returns the amount of prereserved sandbox memory.
CheckReservedAtZero()456 static size_t CheckReservedAtZero() {
457 size_t prereserved_sandbox_size = 0;
458 std::string reserved_at_zero_switch_value =
459 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
460 kNaClHelperReservedAtZero);
461 if (!reserved_at_zero_switch_value.empty()) {
462 char* endp;
463 prereserved_sandbox_size =
464 strtoul(reserved_at_zero_switch_value.c_str(), &endp, 0);
465 if (*endp != '\0')
466 LOG(ERROR) << "Could not parse reserved_at_zero argument value of "
467 << reserved_at_zero_switch_value;
468 }
469 return prereserved_sandbox_size;
470 }
471
472 } // namespace
473
474 #if defined(ADDRESS_SANITIZER)
475 // Do not install the SIGSEGV handler in ASan. This should make the NaCl
476 // platform qualification test pass.
477 // detect_odr_violation=0: http://crbug.com/376306
478 extern const char* kAsanDefaultOptionsNaCl;
479 const char* kAsanDefaultOptionsNaCl = "handle_segv=0:detect_odr_violation=0";
480 #endif
481
main(int argc,char * argv[])482 int main(int argc, char* argv[]) {
483 base::CommandLine::Init(argc, argv);
484 base::AtExitManager exit_manager;
485 base::RandUint64(); // acquire /dev/urandom fd before sandbox is raised
486
487 const NaClLoaderSystemInfo system_info = {CheckReservedAtZero(),
488 sysconf(_SC_NPROCESSORS_ONLN)};
489
490 CheckRDebug(argv[0]);
491
492 #if BUILDFLAG(IS_CHROMEOS)
493 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
494 AddVerboseLoggingInNaclSwitch(command_line);
495 if (command_line->HasSwitch(switches::kVerboseLoggingInNacl)) {
496 if (!freopen("/tmp/nacl.log", "a", stderr))
497 LOG(WARNING) << "Could not open /tmp/nacl.log";
498 }
499 #endif
500
501 std::unique_ptr<nacl::NaClSandbox> nacl_sandbox(new nacl::NaClSandbox);
502 // Make sure that the early initialization did not start any spurious
503 // threads.
504 #if !defined(THREAD_SANITIZER)
505 CHECK(nacl_sandbox->IsSingleThreaded());
506 #endif
507
508 const bool is_init_process = 1 == getpid();
509 nacl_sandbox->InitializeLayerOneSandbox();
510 CHECK_EQ(is_init_process, nacl_sandbox->layer_one_enabled());
511
512 const std::vector<int> empty;
513 // Send the zygote a message to let it know we are ready to help
514 if (!base::UnixDomainSocket::SendMsg(kNaClZygoteDescriptor,
515 kNaClHelperStartupAck,
516 sizeof(kNaClHelperStartupAck), empty)) {
517 LOG(ERROR) << "*** send() to zygote failed";
518 }
519
520 // Now handle requests from the Zygote.
521 while (true) {
522 bool request_handled = HandleZygoteRequest(
523 kNaClZygoteDescriptor, system_info, nacl_sandbox.get());
524 // Do not turn this into a CHECK() without thinking about robustness
525 // against malicious IPC requests.
526 DCHECK(request_handled);
527 }
528 NOTREACHED();
529 }
530