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 "content/zygote/zygote_main.h" 6 7 #include <dlfcn.h> 8 #include <fcntl.h> 9 #include <pthread.h> 10 #include <string.h> 11 #include <sys/socket.h> 12 #include <sys/types.h> 13 #include <unistd.h> 14 15 #include "base/basictypes.h" 16 #include "base/bind.h" 17 #include "base/command_line.h" 18 #include "base/compiler_specific.h" 19 #include "base/memory/scoped_vector.h" 20 #include "base/native_library.h" 21 #include "base/pickle.h" 22 #include "base/posix/eintr_wrapper.h" 23 #include "base/posix/unix_domain_socket_linux.h" 24 #include "base/rand_util.h" 25 #include "base/strings/string_number_conversions.h" 26 #include "base/sys_info.h" 27 #include "build/build_config.h" 28 #include "content/common/child_process_sandbox_support_impl_linux.h" 29 #include "content/common/font_config_ipc_linux.h" 30 #include "content/common/pepper_plugin_list.h" 31 #include "content/common/sandbox_linux/sandbox_linux.h" 32 #include "content/common/zygote_commands_linux.h" 33 #include "content/public/common/content_switches.h" 34 #include "content/public/common/main_function_params.h" 35 #include "content/public/common/pepper_plugin_info.h" 36 #include "content/public/common/sandbox_linux.h" 37 #include "content/public/common/zygote_fork_delegate_linux.h" 38 #include "content/zygote/zygote_linux.h" 39 #include "crypto/nss_util.h" 40 #include "sandbox/linux/services/init_process_reaper.h" 41 #include "sandbox/linux/services/libc_urandom_override.h" 42 #include "sandbox/linux/suid/client/setuid_sandbox_client.h" 43 #include "third_party/icu/source/i18n/unicode/timezone.h" 44 #include "third_party/skia/include/ports/SkFontConfigInterface.h" 45 46 #if defined(OS_LINUX) 47 #include <sys/prctl.h> 48 #endif 49 50 #if defined(ENABLE_WEBRTC) 51 #include "third_party/libjingle/overrides/init_webrtc.h" 52 #endif 53 54 #if defined(ADDRESS_SANITIZER) 55 #include <sanitizer/asan_interface.h> 56 #endif 57 58 namespace content { 59 60 // See http://code.google.com/p/chromium/wiki/LinuxZygote 61 62 static void ProxyLocaltimeCallToBrowser(time_t input, struct tm* output, 63 char* timezone_out, 64 size_t timezone_out_len) { 65 Pickle request; 66 request.WriteInt(LinuxSandbox::METHOD_LOCALTIME); 67 request.WriteString( 68 std::string(reinterpret_cast<char*>(&input), sizeof(input))); 69 70 uint8_t reply_buf[512]; 71 const ssize_t r = UnixDomainSocket::SendRecvMsg( 72 GetSandboxFD(), reply_buf, sizeof(reply_buf), NULL, 73 request); 74 if (r == -1) { 75 memset(output, 0, sizeof(struct tm)); 76 return; 77 } 78 79 Pickle reply(reinterpret_cast<char*>(reply_buf), r); 80 PickleIterator iter(reply); 81 std::string result, timezone; 82 if (!reply.ReadString(&iter, &result) || 83 !reply.ReadString(&iter, &timezone) || 84 result.size() != sizeof(struct tm)) { 85 memset(output, 0, sizeof(struct tm)); 86 return; 87 } 88 89 memcpy(output, result.data(), sizeof(struct tm)); 90 if (timezone_out_len) { 91 const size_t copy_len = std::min(timezone_out_len - 1, timezone.size()); 92 memcpy(timezone_out, timezone.data(), copy_len); 93 timezone_out[copy_len] = 0; 94 output->tm_zone = timezone_out; 95 } else { 96 output->tm_zone = NULL; 97 } 98 } 99 100 static bool g_am_zygote_or_renderer = false; 101 102 // Sandbox interception of libc calls. 103 // 104 // Because we are running in a sandbox certain libc calls will fail (localtime 105 // being the motivating example - it needs to read /etc/localtime). We need to 106 // intercept these calls and proxy them to the browser. However, these calls 107 // may come from us or from our libraries. In some cases we can't just change 108 // our code. 109 // 110 // It's for these cases that we have the following setup: 111 // 112 // We define global functions for those functions which we wish to override. 113 // Since we will be first in the dynamic resolution order, the dynamic linker 114 // will point callers to our versions of these functions. However, we have the 115 // same binary for both the browser and the renderers, which means that our 116 // overrides will apply in the browser too. 117 // 118 // The global |g_am_zygote_or_renderer| is true iff we are in a zygote or 119 // renderer process. It's set in ZygoteMain and inherited by the renderers when 120 // they fork. (This means that it'll be incorrect for global constructor 121 // functions and before ZygoteMain is called - beware). 122 // 123 // Our replacement functions can check this global and either proxy 124 // the call to the browser over the sandbox IPC 125 // (http://code.google.com/p/chromium/wiki/LinuxSandboxIPC) or they can use 126 // dlsym with RTLD_NEXT to resolve the symbol, ignoring any symbols in the 127 // current module. 128 // 129 // Other avenues: 130 // 131 // Our first attempt involved some assembly to patch the GOT of the current 132 // module. This worked, but was platform specific and doesn't catch the case 133 // where a library makes a call rather than current module. 134 // 135 // We also considered patching the function in place, but this would again by 136 // platform specific and the above technique seems to work well enough. 137 138 typedef struct tm* (*LocaltimeFunction)(const time_t* timep); 139 typedef struct tm* (*LocaltimeRFunction)(const time_t* timep, 140 struct tm* result); 141 142 static pthread_once_t g_libc_localtime_funcs_guard = PTHREAD_ONCE_INIT; 143 static LocaltimeFunction g_libc_localtime; 144 static LocaltimeFunction g_libc_localtime64; 145 static LocaltimeRFunction g_libc_localtime_r; 146 static LocaltimeRFunction g_libc_localtime64_r; 147 148 static void InitLibcLocaltimeFunctions() { 149 g_libc_localtime = reinterpret_cast<LocaltimeFunction>( 150 dlsym(RTLD_NEXT, "localtime")); 151 g_libc_localtime64 = reinterpret_cast<LocaltimeFunction>( 152 dlsym(RTLD_NEXT, "localtime64")); 153 g_libc_localtime_r = reinterpret_cast<LocaltimeRFunction>( 154 dlsym(RTLD_NEXT, "localtime_r")); 155 g_libc_localtime64_r = reinterpret_cast<LocaltimeRFunction>( 156 dlsym(RTLD_NEXT, "localtime64_r")); 157 158 if (!g_libc_localtime || !g_libc_localtime_r) { 159 // http://code.google.com/p/chromium/issues/detail?id=16800 160 // 161 // Nvidia's libGL.so overrides dlsym for an unknown reason and replaces 162 // it with a version which doesn't work. In this case we'll get a NULL 163 // result. There's not a lot we can do at this point, so we just bodge it! 164 LOG(ERROR) << "Your system is broken: dlsym doesn't work! This has been " 165 "reported to be caused by Nvidia's libGL. You should expect" 166 " time related functions to misbehave. " 167 "http://code.google.com/p/chromium/issues/detail?id=16800"; 168 } 169 170 if (!g_libc_localtime) 171 g_libc_localtime = gmtime; 172 if (!g_libc_localtime64) 173 g_libc_localtime64 = g_libc_localtime; 174 if (!g_libc_localtime_r) 175 g_libc_localtime_r = gmtime_r; 176 if (!g_libc_localtime64_r) 177 g_libc_localtime64_r = g_libc_localtime_r; 178 } 179 180 // Define localtime_override() function with asm name "localtime", so that all 181 // references to localtime() will resolve to this function. Notice that we need 182 // to set visibility attribute to "default" to export the symbol, as it is set 183 // to "hidden" by default in chrome per build/common.gypi. 184 __attribute__ ((__visibility__("default"))) 185 struct tm* localtime_override(const time_t* timep) __asm__ ("localtime"); 186 187 __attribute__ ((__visibility__("default"))) 188 struct tm* localtime_override(const time_t* timep) { 189 if (g_am_zygote_or_renderer) { 190 static struct tm time_struct; 191 static char timezone_string[64]; 192 ProxyLocaltimeCallToBrowser(*timep, &time_struct, timezone_string, 193 sizeof(timezone_string)); 194 return &time_struct; 195 } else { 196 CHECK_EQ(0, pthread_once(&g_libc_localtime_funcs_guard, 197 InitLibcLocaltimeFunctions)); 198 struct tm* res = g_libc_localtime(timep); 199 #if defined(MEMORY_SANITIZER) 200 if (res) __msan_unpoison(res, sizeof(*res)); 201 if (res->tm_zone) __msan_unpoison_string(res->tm_zone); 202 #endif 203 return res; 204 } 205 } 206 207 // Use same trick to override localtime64(), localtime_r() and localtime64_r(). 208 __attribute__ ((__visibility__("default"))) 209 struct tm* localtime64_override(const time_t* timep) __asm__ ("localtime64"); 210 211 __attribute__ ((__visibility__("default"))) 212 struct tm* localtime64_override(const time_t* timep) { 213 if (g_am_zygote_or_renderer) { 214 static struct tm time_struct; 215 static char timezone_string[64]; 216 ProxyLocaltimeCallToBrowser(*timep, &time_struct, timezone_string, 217 sizeof(timezone_string)); 218 return &time_struct; 219 } else { 220 CHECK_EQ(0, pthread_once(&g_libc_localtime_funcs_guard, 221 InitLibcLocaltimeFunctions)); 222 struct tm* res = g_libc_localtime64(timep); 223 #if defined(MEMORY_SANITIZER) 224 if (res) __msan_unpoison(res, sizeof(*res)); 225 if (res->tm_zone) __msan_unpoison_string(res->tm_zone); 226 #endif 227 return res; 228 } 229 } 230 231 __attribute__ ((__visibility__("default"))) 232 struct tm* localtime_r_override(const time_t* timep, 233 struct tm* result) __asm__ ("localtime_r"); 234 235 __attribute__ ((__visibility__("default"))) 236 struct tm* localtime_r_override(const time_t* timep, struct tm* result) { 237 if (g_am_zygote_or_renderer) { 238 ProxyLocaltimeCallToBrowser(*timep, result, NULL, 0); 239 return result; 240 } else { 241 CHECK_EQ(0, pthread_once(&g_libc_localtime_funcs_guard, 242 InitLibcLocaltimeFunctions)); 243 struct tm* res = g_libc_localtime_r(timep, result); 244 #if defined(MEMORY_SANITIZER) 245 if (res) __msan_unpoison(res, sizeof(*res)); 246 if (res->tm_zone) __msan_unpoison_string(res->tm_zone); 247 #endif 248 return res; 249 } 250 } 251 252 __attribute__ ((__visibility__("default"))) 253 struct tm* localtime64_r_override(const time_t* timep, 254 struct tm* result) __asm__ ("localtime64_r"); 255 256 __attribute__ ((__visibility__("default"))) 257 struct tm* localtime64_r_override(const time_t* timep, struct tm* result) { 258 if (g_am_zygote_or_renderer) { 259 ProxyLocaltimeCallToBrowser(*timep, result, NULL, 0); 260 return result; 261 } else { 262 CHECK_EQ(0, pthread_once(&g_libc_localtime_funcs_guard, 263 InitLibcLocaltimeFunctions)); 264 struct tm* res = g_libc_localtime64_r(timep, result); 265 #if defined(MEMORY_SANITIZER) 266 if (res) __msan_unpoison(res, sizeof(*res)); 267 if (res->tm_zone) __msan_unpoison_string(res->tm_zone); 268 #endif 269 return res; 270 } 271 } 272 273 #if defined(ENABLE_PLUGINS) 274 // Loads the (native) libraries but does not initialize them (i.e., does not 275 // call PPP_InitializeModule). This is needed by the zygote on Linux to get 276 // access to the plugins before entering the sandbox. 277 void PreloadPepperPlugins() { 278 std::vector<PepperPluginInfo> plugins; 279 ComputePepperPluginList(&plugins); 280 for (size_t i = 0; i < plugins.size(); ++i) { 281 if (!plugins[i].is_internal && plugins[i].is_sandboxed) { 282 base::NativeLibraryLoadError error; 283 base::NativeLibrary library = base::LoadNativeLibrary(plugins[i].path, 284 &error); 285 VLOG_IF(1, !library) << "Unable to load plugin " 286 << plugins[i].path.value() << " " 287 << error.ToString(); 288 289 (void)library; // Prevent release-mode warning. 290 } 291 } 292 } 293 #endif 294 295 // This function triggers the static and lazy construction of objects that need 296 // to be created before imposing the sandbox. 297 static void ZygotePreSandboxInit() { 298 base::RandUint64(); 299 300 base::SysInfo::AmountOfPhysicalMemory(); 301 base::SysInfo::MaxSharedMemorySize(); 302 base::SysInfo::NumberOfProcessors(); 303 304 // ICU DateFormat class (used in base/time_format.cc) needs to get the 305 // Olson timezone ID by accessing the zoneinfo files on disk. After 306 // TimeZone::createDefault is called once here, the timezone ID is 307 // cached and there's no more need to access the file system. 308 scoped_ptr<icu::TimeZone> zone(icu::TimeZone::createDefault()); 309 310 #if defined(USE_NSS) 311 // NSS libraries are loaded before sandbox is activated. This is to allow 312 // successful initialization of NSS which tries to load extra library files. 313 crypto::LoadNSSLibraries(); 314 #elif defined(USE_OPENSSL) 315 // OpenSSL is intentionally not supported in the sandboxed processes, see 316 // http://crbug.com/99163. If that ever changes we'll likely need to init 317 // OpenSSL here (at least, load the library and error strings). 318 #else 319 // It's possible that another hypothetical crypto stack would not require 320 // pre-sandbox init, but more likely this is just a build configuration error. 321 #error Which SSL library are you using? 322 #endif 323 #if defined(ENABLE_PLUGINS) 324 // Ensure access to the Pepper plugins before the sandbox is turned on. 325 PreloadPepperPlugins(); 326 #endif 327 #if defined(ENABLE_WEBRTC) 328 InitializeWebRtcModule(); 329 #endif 330 SkFontConfigInterface::SetGlobal( 331 new FontConfigIPC(GetSandboxFD()))->unref(); 332 } 333 334 static bool CreateInitProcessReaper(base::Closure* post_fork_parent_callback) { 335 // The current process becomes init(1), this function returns from a 336 // newly created process. 337 const bool init_created = 338 sandbox::CreateInitProcessReaper(post_fork_parent_callback); 339 if (!init_created) { 340 LOG(ERROR) << "Error creating an init process to reap zombies"; 341 return false; 342 } 343 return true; 344 } 345 346 // Enter the setuid sandbox. This requires the current process to have been 347 // created through the setuid sandbox. 348 static bool EnterSuidSandbox(sandbox::SetuidSandboxClient* setuid_sandbox, 349 base::Closure* post_fork_parent_callback) { 350 DCHECK(setuid_sandbox); 351 DCHECK(setuid_sandbox->IsSuidSandboxChild()); 352 353 // Use the SUID sandbox. This still allows the seccomp sandbox to 354 // be enabled by the process later. 355 356 if (!setuid_sandbox->IsSuidSandboxUpToDate()) { 357 LOG(WARNING) << 358 "You are using a wrong version of the setuid binary!\n" 359 "Please read " 360 "https://code.google.com/p/chromium/wiki/LinuxSUIDSandboxDevelopment." 361 "\n\n"; 362 } 363 364 if (!setuid_sandbox->ChrootMe()) 365 return false; 366 367 if (setuid_sandbox->IsInNewPIDNamespace()) { 368 CHECK_EQ(1, getpid()) 369 << "The SUID sandbox created a new PID namespace but Zygote " 370 "is not the init process. Please, make sure the SUID " 371 "binary is up to date."; 372 } 373 374 if (getpid() == 1) { 375 // The setuid sandbox has created a new PID namespace and we need 376 // to assume the role of init. 377 CHECK(CreateInitProcessReaper(post_fork_parent_callback)); 378 } 379 380 #if !defined(OS_OPENBSD) 381 // Previously, we required that the binary be non-readable. This causes the 382 // kernel to mark the process as non-dumpable at startup. The thinking was 383 // that, although we were putting the renderers into a PID namespace (with 384 // the SUID sandbox), they would nonetheless be in the /same/ PID 385 // namespace. So they could ptrace each other unless they were non-dumpable. 386 // 387 // If the binary was readable, then there would be a window between process 388 // startup and the point where we set the non-dumpable flag in which a 389 // compromised renderer could ptrace attach. 390 // 391 // However, now that we have a zygote model, only the (trusted) zygote 392 // exists at this point and we can set the non-dumpable flag which is 393 // inherited by all our renderer children. 394 // 395 // Note: a non-dumpable process can't be debugged. To debug sandbox-related 396 // issues, one can specify --allow-sandbox-debugging to let the process be 397 // dumpable. 398 const CommandLine& command_line = *CommandLine::ForCurrentProcess(); 399 if (!command_line.HasSwitch(switches::kAllowSandboxDebugging)) { 400 prctl(PR_SET_DUMPABLE, 0, 0, 0, 0); 401 if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0)) { 402 LOG(ERROR) << "Failed to set non-dumpable flag"; 403 return false; 404 } 405 } 406 #endif 407 408 return true; 409 } 410 411 #if defined(ADDRESS_SANITIZER) 412 const size_t kSanitizerMaxMessageLength = 1 * 1024 * 1024; 413 414 // A helper process which collects code coverage data from the renderers over a 415 // socket and dumps it to a file. See http://crbug.com/336212 for discussion. 416 static void SanitizerCoverageHelper(int socket_fd, int file_fd) { 417 scoped_ptr<char[]> buffer(new char[kSanitizerMaxMessageLength]); 418 while (true) { 419 ssize_t received_size = HANDLE_EINTR( 420 recv(socket_fd, buffer.get(), kSanitizerMaxMessageLength, 0)); 421 PCHECK(received_size >= 0); 422 if (received_size == 0) 423 // All clients have closed the socket. We should die. 424 _exit(0); 425 PCHECK(file_fd >= 0); 426 ssize_t written_size = 0; 427 while (written_size < received_size) { 428 ssize_t write_res = 429 HANDLE_EINTR(write(file_fd, buffer.get() + written_size, 430 received_size - written_size)); 431 PCHECK(write_res >= 0); 432 written_size += write_res; 433 } 434 PCHECK(0 == HANDLE_EINTR(fsync(file_fd))); 435 } 436 } 437 438 // fds[0] is the read end, fds[1] is the write end. 439 static void CreateSanitizerCoverageSocketPair(int fds[2]) { 440 PCHECK(0 == socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); 441 PCHECK(0 == shutdown(fds[0], SHUT_WR)); 442 PCHECK(0 == shutdown(fds[1], SHUT_RD)); 443 } 444 445 static pid_t ForkSanitizerCoverageHelper(int child_fd, int parent_fd, 446 base::ScopedFD file_fd) { 447 pid_t pid = fork(); 448 PCHECK(pid >= 0); 449 if (pid == 0) { 450 // In the child. 451 PCHECK(0 == IGNORE_EINTR(close(parent_fd))); 452 SanitizerCoverageHelper(child_fd, file_fd.get()); 453 _exit(0); 454 } else { 455 // In the parent. 456 PCHECK(0 == IGNORE_EINTR(close(child_fd))); 457 return pid; 458 } 459 } 460 461 void CloseFdPair(const int fds[2]) { 462 PCHECK(0 == IGNORE_EINTR(close(fds[0]))); 463 PCHECK(0 == IGNORE_EINTR(close(fds[1]))); 464 } 465 #endif // defined(ADDRESS_SANITIZER) 466 467 // If |is_suid_sandbox_child|, then make sure that the setuid sandbox is 468 // engaged. 469 static void EnterLayerOneSandbox(LinuxSandbox* linux_sandbox, 470 bool is_suid_sandbox_child, 471 base::Closure* post_fork_parent_callback) { 472 DCHECK(linux_sandbox); 473 474 ZygotePreSandboxInit(); 475 476 // Check that the pre-sandbox initialization didn't spawn threads. 477 #if !defined(THREAD_SANITIZER) 478 DCHECK(linux_sandbox->IsSingleThreaded()); 479 #endif 480 481 sandbox::SetuidSandboxClient* setuid_sandbox = 482 linux_sandbox->setuid_sandbox_client(); 483 484 if (is_suid_sandbox_child) { 485 CHECK(EnterSuidSandbox(setuid_sandbox, post_fork_parent_callback)) 486 << "Failed to enter setuid sandbox"; 487 } 488 } 489 490 bool ZygoteMain(const MainFunctionParams& params, 491 ScopedVector<ZygoteForkDelegate> fork_delegates) { 492 g_am_zygote_or_renderer = true; 493 sandbox::InitLibcUrandomOverrides(); 494 495 base::Closure *post_fork_parent_callback = NULL; 496 497 LinuxSandbox* linux_sandbox = LinuxSandbox::GetInstance(); 498 499 #if defined(ADDRESS_SANITIZER) 500 const std::string sancov_file_name = 501 "zygote." + base::Uint64ToString(base::RandUint64()); 502 base::ScopedFD sancov_file_fd( 503 __sanitizer_maybe_open_cov_file(sancov_file_name.c_str())); 504 int sancov_socket_fds[2] = {-1, -1}; 505 CreateSanitizerCoverageSocketPair(sancov_socket_fds); 506 linux_sandbox->sanitizer_args()->coverage_sandboxed = 1; 507 linux_sandbox->sanitizer_args()->coverage_fd = sancov_socket_fds[1]; 508 linux_sandbox->sanitizer_args()->coverage_max_block_size = 509 kSanitizerMaxMessageLength; 510 // Zygote termination will block until the helper process exits, which will 511 // not happen until the write end of the socket is closed everywhere. Make 512 // sure the init process does not hold on to it. 513 base::Closure close_sancov_socket_fds = 514 base::Bind(&CloseFdPair, sancov_socket_fds); 515 post_fork_parent_callback = &close_sancov_socket_fds; 516 #endif 517 518 // This will pre-initialize the various sandboxes that need it. 519 linux_sandbox->PreinitializeSandbox(); 520 521 const bool must_enable_setuid_sandbox = 522 linux_sandbox->setuid_sandbox_client()->IsSuidSandboxChild(); 523 if (must_enable_setuid_sandbox) { 524 linux_sandbox->setuid_sandbox_client()->CloseDummyFile(); 525 526 // Let the ZygoteHost know we're booting up. 527 CHECK(UnixDomainSocket::SendMsg(kZygoteSocketPairFd, 528 kZygoteBootMessage, 529 sizeof(kZygoteBootMessage), 530 std::vector<int>())); 531 } 532 533 VLOG(1) << "ZygoteMain: initializing " << fork_delegates.size() 534 << " fork delegates"; 535 for (ScopedVector<ZygoteForkDelegate>::iterator i = fork_delegates.begin(); 536 i != fork_delegates.end(); 537 ++i) { 538 (*i)->Init(GetSandboxFD(), must_enable_setuid_sandbox); 539 } 540 541 // Turn on the first layer of the sandbox if the configuration warrants it. 542 EnterLayerOneSandbox(linux_sandbox, must_enable_setuid_sandbox, 543 post_fork_parent_callback); 544 545 std::vector<pid_t> extra_children; 546 std::vector<int> extra_fds; 547 548 #if defined(ADDRESS_SANITIZER) 549 pid_t sancov_helper_pid = ForkSanitizerCoverageHelper( 550 sancov_socket_fds[0], sancov_socket_fds[1], sancov_file_fd.Pass()); 551 // It's important that the zygote reaps the helper before dying. Otherwise, 552 // the destruction of the PID namespace could kill the helper before it 553 // completes its I/O tasks. |sancov_helper_pid| will exit once the last 554 // renderer holding the write end of |sancov_socket_fds| closes it. 555 extra_children.push_back(sancov_helper_pid); 556 // Sanitizer code in the renderers will inherit the write end of the socket 557 // from the zygote. We must keep it open until the very end of the zygote's 558 // lifetime, even though we don't explicitly use it. 559 extra_fds.push_back(sancov_socket_fds[1]); 560 #endif 561 562 int sandbox_flags = linux_sandbox->GetStatus(); 563 bool setuid_sandbox_engaged = sandbox_flags & kSandboxLinuxSUID; 564 CHECK_EQ(must_enable_setuid_sandbox, setuid_sandbox_engaged); 565 566 Zygote zygote(sandbox_flags, fork_delegates.Pass(), extra_children, 567 extra_fds); 568 // This function call can return multiple times, once per fork(). 569 return zygote.ProcessRequests(); 570 } 571 572 } // namespace content 573