diff options
Diffstat (limited to 'chromium/content/common/zygote')
8 files changed, 623 insertions, 0 deletions
diff --git a/chromium/content/common/zygote/OWNERS b/chromium/content/common/zygote/OWNERS new file mode 100644 index 00000000000..eb4b322bdf5 --- /dev/null +++ b/chromium/content/common/zygote/OWNERS @@ -0,0 +1,4 @@ +file://content/zygote/OWNERS + +# TEAM: security-dev@chromium.org +# COMPONENT: Internals>Sandbox diff --git a/chromium/content/common/zygote/sandbox_support_linux.cc b/chromium/content/common/zygote/sandbox_support_linux.cc new file mode 100644 index 00000000000..dabc1009f72 --- /dev/null +++ b/chromium/content/common/zygote/sandbox_support_linux.cc @@ -0,0 +1,39 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/public/common/zygote/sandbox_support_linux.h" + +#include "base/pickle.h" +#include "base/posix/global_descriptors.h" +#include "base/posix/unix_domain_socket.h" +#include "build/build_config.h" +#include "services/service_manager/embedder/descriptors.h" +#include "services/service_manager/sandbox/linux/sandbox_linux.h" + +namespace content { + +#if !defined(OS_NACL_NONSFI) +int SharedMemoryIPCSupport::MakeSharedMemorySegment(size_t length, + bool executable) { + base::Pickle request; + request.WriteInt( + service_manager::SandboxLinux::METHOD_MAKE_SHARED_MEMORY_SEGMENT); + request.WriteUInt32(length); + request.WriteBool(executable); + uint8_t reply_buf[10]; + int result_fd; + ssize_t result = base::UnixDomainSocket::SendRecvMsg( + GetSandboxFD(), reply_buf, sizeof(reply_buf), &result_fd, request); + if (result == -1) + return -1; + return result_fd; +} +#endif + +int GetSandboxFD() { + return service_manager::kSandboxIPCChannel + + base::GlobalDescriptors::kBaseDescriptor; +} + +} // namespace content diff --git a/chromium/content/common/zygote/send_zygote_child_ping_linux.cc b/chromium/content/common/zygote/send_zygote_child_ping_linux.cc new file mode 100644 index 00000000000..ff07250fb72 --- /dev/null +++ b/chromium/content/common/zygote/send_zygote_child_ping_linux.cc @@ -0,0 +1,20 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/public/common/zygote/send_zygote_child_ping_linux.h" + +#include <vector> + +#include "base/posix/unix_domain_socket.h" +#include "content/common/zygote/zygote_commands_linux.h" + +namespace content { + +bool SendZygoteChildPing(int fd) { + return base::UnixDomainSocket::SendMsg(fd, kZygoteChildPingMessage, + sizeof(kZygoteChildPingMessage), + std::vector<int>()); +} + +} // namespace content diff --git a/chromium/content/common/zygote/zygote_commands_linux.h b/chromium/content/common/zygote/zygote_commands_linux.h new file mode 100644 index 00000000000..272b5b57871 --- /dev/null +++ b/chromium/content/common/zygote/zygote_commands_linux.h @@ -0,0 +1,54 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_COMMON_ZYGOTE_ZYGOTE_COMMANDS_LINUX_H_ +#define CONTENT_COMMON_ZYGOTE_ZYGOTE_COMMANDS_LINUX_H_ + +#include <stddef.h> + +#include "base/posix/global_descriptors.h" + +namespace content { + +// Contents of the initial message sent from the zygote to the browser right +// after it starts. +static const char kZygoteBootMessage[] = "ZYGOTE_BOOT"; + +// Contents of the initial message sent from the zygote to the browser when it +// is ready to go. +static const char kZygoteHelloMessage[] = "ZYGOTE_OK"; + +// Message sent by zygote children to the browser so the browser can discover +// the sending child's process ID. +static const char kZygoteChildPingMessage[] = "CHILD_PING"; + +// Maximum allowable length for messages sent to the zygote. +const size_t kZygoteMaxMessageLength = 12288; + +// File descriptors initialized by the Zygote Host +const int kZygoteSocketPairFd = base::GlobalDescriptors::kBaseDescriptor; + +// These are the command codes used on the wire between the browser and the +// zygote. +enum { + // Fork off a new renderer. + kZygoteCommandFork = 0, + + // Reap a renderer child. + kZygoteCommandReap = 1, + + // Check what happened to a child process. + kZygoteCommandGetTerminationStatus = 2, + + // Read a bitmask of kSandboxLinux* + kZygoteCommandGetSandboxStatus = 3, + + // Not a real zygote command, but a subcommand used during the zygote fork + // protocol. Sends the child's PID as seen from the browser process. + kZygoteCommandForkRealPID = 4 +}; + +} // namespace content + +#endif // CONTENT_COMMON_ZYGOTE_ZYGOTE_COMMANDS_LINUX_H_ diff --git a/chromium/content/common/zygote/zygote_communication_linux.cc b/chromium/content/common/zygote/zygote_communication_linux.cc new file mode 100644 index 00000000000..4ae0b9eebb3 --- /dev/null +++ b/chromium/content/common/zygote/zygote_communication_linux.cc @@ -0,0 +1,323 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/common/zygote/zygote_communication_linux.h" + +#include <string.h> +#include <sys/socket.h> + +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/i18n/unicodestring.h" +#include "base/logging.h" +#include "base/metrics/histogram_functions.h" +#include "base/path_service.h" +#include "base/pickle.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/unix_domain_socket.h" +#include "base/stl_util.h" +#include "content/common/zygote/zygote_commands_linux.h" +#include "content/public/common/content_switches.h" +#include "services/service_manager/embedder/result_codes.h" +#include "services/service_manager/embedder/switches.h" +#include "services/service_manager/sandbox/switches.h" +#include "third_party/icu/source/i18n/unicode/timezone.h" + +namespace content { + +ZygoteCommunication::ZygoteCommunication(ZygoteType type) + : type_(type), + pid_(), + sandbox_status_(0), + have_read_sandbox_status_word_(false), + init_(false) {} + +ZygoteCommunication::~ZygoteCommunication() {} + +bool ZygoteCommunication::SendMessage(const base::Pickle& data, + const std::vector<int>* fds) { + DCHECK(control_fd_.is_valid()); + CHECK(data.size() <= kZygoteMaxMessageLength) + << "Trying to send too-large message to zygote (sending " << data.size() + << " bytes, max is " << kZygoteMaxMessageLength << ")"; + CHECK(!fds || fds->size() <= base::UnixDomainSocket::kMaxFileDescriptors) + << "Trying to send message with too many file descriptors to zygote " + << "(sending " << fds->size() << ", max is " + << base::UnixDomainSocket::kMaxFileDescriptors << ")"; + + return base::UnixDomainSocket::SendMsg(control_fd_.get(), data.data(), + data.size(), + fds ? *fds : std::vector<int>()); +} + +ssize_t ZygoteCommunication::ReadSandboxStatus() { + DCHECK(control_fd_.is_valid()); + // At startup we send a kZygoteCommandGetSandboxStatus request to the zygote, + // but don't wait for the reply. Thus, the first time that we read from the + // zygote, we get the reply to that request. + ssize_t bytes_read = HANDLE_EINTR( + read(control_fd_.get(), &sandbox_status_, sizeof(sandbox_status_))); + if (bytes_read != sizeof(sandbox_status_)) { + return -1; + } + return bytes_read; +} + +ssize_t ZygoteCommunication::ReadReply(void* buf, size_t buf_len) { + DCHECK(control_fd_.is_valid()); + if (!have_read_sandbox_status_word_) { + if (ReadSandboxStatus() == -1) { + return -1; + } + have_read_sandbox_status_word_ = true; + base::UmaHistogramSparse("Linux.SandboxStatus", sandbox_status_); + } + + return HANDLE_EINTR(read(control_fd_.get(), buf, buf_len)); +} + +pid_t ZygoteCommunication::ForkRequest( + const std::vector<std::string>& argv, + const base::FileHandleMappingVector& mapping, + const std::string& process_type) { + DCHECK(init_); + + base::Pickle pickle; + int raw_socks[2]; + PCHECK(0 == socketpair(AF_UNIX, SOCK_SEQPACKET, 0, raw_socks)); + base::ScopedFD my_sock(raw_socks[0]); + base::ScopedFD peer_sock(raw_socks[1]); + CHECK(base::UnixDomainSocket::EnableReceiveProcessId(my_sock.get())); + + pickle.WriteInt(kZygoteCommandFork); + pickle.WriteString(process_type); + pickle.WriteInt(argv.size()); + for (std::vector<std::string>::const_iterator i = argv.begin(); + i != argv.end(); ++i) + pickle.WriteString(*i); + std::unique_ptr<icu::TimeZone> timezone(icu::TimeZone::createDefault()); + icu::UnicodeString timezone_id; + pickle.WriteString16( + base::i18n::UnicodeStringToString16(timezone->getID(timezone_id))); + + // Fork requests contain one file descriptor for the PID oracle, and one + // more for each file descriptor mapping for the child process. + const size_t num_fds_to_send = 1 + mapping.size(); + pickle.WriteInt(num_fds_to_send); + + std::vector<int> fds; + + // First FD to send is peer_sock. + // TODO(morrita): Ideally, this should be part of the mapping so that + // PosixFileDescriptorInfo can manages its lifetime. + fds.push_back(peer_sock.get()); + + // The rest come from mapping. + for (const auto& item : mapping) { + fds.push_back(item.first); + pickle.WriteUInt32(item.second); + } + + // Sanity check that we've populated |fds| correctly. + DCHECK_EQ(num_fds_to_send, fds.size()); + + pid_t pid; + { + base::AutoLock lock(control_lock_); + if (!SendMessage(pickle, &fds)) + return base::kNullProcessHandle; + peer_sock.reset(); + + { + char buf[sizeof(kZygoteChildPingMessage) + 1]; + std::vector<base::ScopedFD> recv_fds; + base::ProcessId real_pid; + + ssize_t n = base::UnixDomainSocket::RecvMsgWithPid( + my_sock.get(), buf, sizeof(buf), &recv_fds, &real_pid); + if (n != sizeof(kZygoteChildPingMessage) || + 0 != memcmp(buf, kZygoteChildPingMessage, + sizeof(kZygoteChildPingMessage))) { + // Zygote children should still be trustworthy when they're supposed to + // ping us, so something's broken if we don't receive a valid ping. + LOG(ERROR) << "Did not receive ping from zygote child"; + NOTREACHED(); + real_pid = -1; + } + my_sock.reset(); + + // Always send PID back to zygote. + base::Pickle pid_pickle; + pid_pickle.WriteInt(kZygoteCommandForkRealPID); + pid_pickle.WriteInt(real_pid); + if (!SendMessage(pid_pickle, nullptr)) + return base::kNullProcessHandle; + } + + // Read the reply, which pickles the PID and an optional UMA enumeration. + static const unsigned kMaxReplyLength = 2048; + char buf[kMaxReplyLength]; + const ssize_t len = ReadReply(buf, sizeof(buf)); + + base::Pickle reply_pickle(buf, len); + base::PickleIterator iter(reply_pickle); + if (len <= 0 || !iter.ReadInt(&pid)) + return base::kNullProcessHandle; + + // If there is a nonempty UMA name string, then there is a UMA + // enumeration to record. + std::string uma_name; + int uma_sample; + int uma_boundary_value; + if (iter.ReadString(&uma_name) && !uma_name.empty() && + iter.ReadInt(&uma_sample) && iter.ReadInt(&uma_boundary_value)) { + // We cannot use the UMA_HISTOGRAM_ENUMERATION macro here, + // because that's only for when the name is the same every time. + // Here we're using whatever name we got from the other side. + // But since it's likely that the same one will be used repeatedly + // (even though it's not guaranteed), we cache it here. + static base::HistogramBase* uma_histogram; + if (!uma_histogram || uma_histogram->histogram_name() != uma_name) { + uma_histogram = base::LinearHistogram::FactoryGet( + uma_name, 1, uma_boundary_value, uma_boundary_value + 1, + base::HistogramBase::kUmaTargetedHistogramFlag); + } + uma_histogram->Add(uma_sample); + } + + if (pid <= 0) + return base::kNullProcessHandle; + } + + ZygoteChildBorn(pid); + return pid; +} + +void ZygoteCommunication::EnsureProcessTerminated(pid_t process) { + DCHECK(init_); + base::Pickle pickle; + + pickle.WriteInt(kZygoteCommandReap); + pickle.WriteInt(process); + if (!SendMessage(pickle, nullptr)) + LOG(ERROR) << "Failed to send Reap message to zygote"; + ZygoteChildDied(process); +} + +void ZygoteCommunication::ZygoteChildBorn(pid_t process) { + base::AutoLock lock(child_tracking_lock_); + bool new_element_inserted = + list_of_running_zygote_children_.insert(process).second; + DCHECK(new_element_inserted); +} + +void ZygoteCommunication::ZygoteChildDied(pid_t process) { + base::AutoLock lock(child_tracking_lock_); + size_t num_erased = list_of_running_zygote_children_.erase(process); + DCHECK_EQ(1U, num_erased); +} + +void ZygoteCommunication::Init( + base::OnceCallback<pid_t(base::CommandLine*, base::ScopedFD*)> launcher) { + CHECK(!init_); + + base::FilePath chrome_path; + CHECK(base::PathService::Get(base::FILE_EXE, &chrome_path)); + + base::CommandLine cmd_line(chrome_path); + cmd_line.AppendSwitchASCII(service_manager::switches::kProcessType, + service_manager::switches::kZygoteProcess); + + if (type_ == ZygoteType::kUnsandboxed) + cmd_line.AppendSwitch(service_manager::switches::kNoZygoteSandbox); + + const base::CommandLine& browser_command_line = + *base::CommandLine::ForCurrentProcess(); + if (browser_command_line.HasSwitch(switches::kZygoteCmdPrefix)) { + cmd_line.PrependWrapper( + browser_command_line.GetSwitchValueNative(switches::kZygoteCmdPrefix)); + } + // Append any switches from the service manager that need to be forwarded on + // to the zygote/renderers. + static const char* const kForwardSwitches[] = { + service_manager::switches::kAllowSandboxDebugging, + service_manager::switches::kDisableInProcessStackTraces, + service_manager::switches::kDisableSeccompFilterSandbox, + service_manager::switches::kNoSandbox, + }; + cmd_line.CopySwitchesFrom(browser_command_line, kForwardSwitches, + base::size(kForwardSwitches)); + + pid_ = std::move(launcher).Run(&cmd_line, &control_fd_); + + base::Pickle pickle; + pickle.WriteInt(kZygoteCommandGetSandboxStatus); + if (!SendMessage(pickle, nullptr)) + LOG(FATAL) << "Cannot communicate with zygote"; + + init_ = true; +} + +base::TerminationStatus ZygoteCommunication::GetTerminationStatus( + base::ProcessHandle handle, + bool known_dead, + int* exit_code) { + DCHECK(init_); + base::Pickle pickle; + pickle.WriteInt(kZygoteCommandGetTerminationStatus); + pickle.WriteBool(known_dead); + pickle.WriteInt(handle); + + static const unsigned kMaxMessageLength = 128; + char buf[kMaxMessageLength]; + ssize_t len; + { + base::AutoLock lock(control_lock_); + if (!SendMessage(pickle, nullptr)) + LOG(ERROR) << "Failed to send GetTerminationStatus message to zygote"; + len = ReadReply(buf, sizeof(buf)); + } + + // Set this now to handle the error cases. + if (exit_code) + *exit_code = service_manager::RESULT_CODE_NORMAL_EXIT; + int status = base::TERMINATION_STATUS_NORMAL_TERMINATION; + + if (len == -1) { + PLOG(WARNING) << "Error reading message from zygote"; + } else if (len == 0) { + LOG(WARNING) << "Socket closed prematurely."; + } else { + base::Pickle read_pickle(buf, len); + int tmp_status, tmp_exit_code; + base::PickleIterator iter(read_pickle); + if (!iter.ReadInt(&tmp_status) || !iter.ReadInt(&tmp_exit_code)) { + LOG(WARNING) + << "Error parsing GetTerminationStatus response from zygote."; + } else { + if (exit_code) + *exit_code = tmp_exit_code; + status = tmp_status; + } + } + + if (status != base::TERMINATION_STATUS_STILL_RUNNING) { + ZygoteChildDied(handle); + } + return static_cast<base::TerminationStatus>(status); +} + +int ZygoteCommunication::GetSandboxStatus() { + if (have_read_sandbox_status_word_) { + return sandbox_status_; + } + if (ReadSandboxStatus() == -1) { + return 0; + } + have_read_sandbox_status_word_ = true; + base::UmaHistogramSparse("Linux.SandboxStatus", sandbox_status_); + return sandbox_status_; +} + +} // namespace content diff --git a/chromium/content/common/zygote/zygote_communication_linux.h b/chromium/content/common/zygote/zygote_communication_linux.h new file mode 100644 index 00000000000..37d8ec3e212 --- /dev/null +++ b/chromium/content/common/zygote/zygote_communication_linux.h @@ -0,0 +1,107 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_COMMON_ZYGOTE_ZYGOTE_COMMUNICATION_LINUX_H_ +#define CONTENT_COMMON_ZYGOTE_ZYGOTE_COMMUNICATION_LINUX_H_ + +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include <sys/types.h> + +#include "base/callback.h" +#include "base/files/scoped_file.h" +#include "base/process/kill.h" +#include "base/process/launch.h" +#include "base/process/process_handle.h" +#include "base/synchronization/lock.h" +#include "content/common/content_export.h" + +namespace base { +class Pickle; +} // namespace base + +namespace content { + +// Handles interprocess communication with the Linux zygote process. The zygote +// does not use standard Chrome IPC or mojo, see: +// https://chromium.googlesource.com/chromium/src/+/master/docs/linux/sandbox_ipc.md +class CONTENT_EXPORT ZygoteCommunication { + public: + enum class ZygoteType { kSandboxed, kUnsandboxed }; + explicit ZygoteCommunication(ZygoteType type); + ~ZygoteCommunication(); + + void Init( + base::OnceCallback<pid_t(base::CommandLine*, base::ScopedFD*)> launcher); + + // Tries to start a process of type indicated by process_type. + // Returns its pid on success, otherwise base::kNullProcessHandle; + pid_t ForkRequest(const std::vector<std::string>& command_line, + const base::FileHandleMappingVector& mapping, + const std::string& process_type); + + void EnsureProcessTerminated(pid_t process); + + // Should be called every time a Zygote child died. + void ZygoteChildDied(pid_t process); + + // Get the termination status (and, optionally, the exit code) of + // the process. |exit_code| is set to the exit code of the child + // process. (|exit_code| may be NULL.) + // Unfortunately the Zygote can not accurately figure out if a process + // is already dead without waiting synchronously for it. + // |known_dead| should be set to true when we already know that the process + // is dead. When |known_dead| is false, processes could be seen as + // still running, even when they're not. When |known_dead| is true, the + // process will be SIGKILL-ed first (which should have no effect if it was + // really dead). This is to prevent a waiting waitpid() from blocking in + // a single-threaded Zygote. See https://crbug.com/157458. + base::TerminationStatus GetTerminationStatus(base::ProcessHandle handle, + bool known_dead, + int* exit_code); + + // Returns the sandbox status of this zygote. + int GetSandboxStatus(); + + private: + // Should be called every time a Zygote child is born. + void ZygoteChildBorn(pid_t process); + + // Read the reply from the zygote. + ssize_t ReadReply(void* buf, size_t buf_len); + + // Sends |data| to the zygote via |control_fd_|. If |fds| is non-NULL, the + // included file descriptors will also be passed. The caller is responsible + // for acquiring |control_lock_|. + bool SendMessage(const base::Pickle& data, const std::vector<int>* fds); + + // Get the sandbox status from the zygote. + ssize_t ReadSandboxStatus(); + + // Indicates whether the Zygote starts unsandboxed or not. + const ZygoteType type_; + + base::ScopedFD control_fd_; // the socket to the zygote. + // A lock protecting all communication with the zygote. This lock must be + // acquired before sending a command and released after the result has been + // received. + base::Lock control_lock_; + // The pid of the zygote. + pid_t pid_; + // The list of running zygote children. + std::set<pid_t> list_of_running_zygote_children_; + // The lock to guard the list of running zygote children. + base::Lock child_tracking_lock_; + int sandbox_status_; + bool have_read_sandbox_status_word_; + // Set to true when the zygote is initialized successfully. + bool init_; +}; + +} // namespace content + +#endif // CONTENT_COMMON_ZYGOTE_ZYGOTE_COMMUNICATION_LINUX_H_ diff --git a/chromium/content/common/zygote/zygote_handle_impl_linux.h b/chromium/content/common/zygote/zygote_handle_impl_linux.h new file mode 100644 index 00000000000..6a530828514 --- /dev/null +++ b/chromium/content/common/zygote/zygote_handle_impl_linux.h @@ -0,0 +1,31 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_COMMON_ZYGOTE_ZYGOTE_HANDLE_IMPL_LINUX_H_ +#define CONTENT_COMMON_ZYGOTE_ZYGOTE_HANDLE_IMPL_LINUX_H_ + +#include "content/public/common/zygote/zygote_handle.h" + +namespace content { + +using ZygoteLaunchCallback = + base::OnceCallback<pid_t(base::CommandLine*, base::ScopedFD*)>; + +// Allocates and initializes the global generic zygote process, and returns the +// ZygoteHandle used to communicate with it. |launch_cb| is a callback that +// should actually launch the process, after adding additional command line +// switches to the ones composed by this function. It returns the pid created, +// and provides a control fd for it. +CONTENT_EXPORT +ZygoteHandle CreateGenericZygote(ZygoteLaunchCallback launch_cb); + +// Similar to the above but for creating an unsandboxed zygote from which +// processes which need non-generic sandboxes can be derived. +CONTENT_EXPORT +ZygoteHandle CreateUnsandboxedZygote(ZygoteLaunchCallback launch_cb); +CONTENT_EXPORT ZygoteHandle GetUnsandboxedZygote(); + +} // namespace content + +#endif // CONTENT_COMMON_ZYGOTE_ZYGOTE_HANDLE_IMPL_LINUX_H_ diff --git a/chromium/content/common/zygote/zygote_handle_linux.cc b/chromium/content/common/zygote/zygote_handle_linux.cc new file mode 100644 index 00000000000..dd0a0acaa65 --- /dev/null +++ b/chromium/content/common/zygote/zygote_handle_linux.cc @@ -0,0 +1,45 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/public/common/zygote/zygote_handle.h" + +#include "content/common/zygote/zygote_communication_linux.h" +#include "content/common/zygote/zygote_handle_impl_linux.h" + +namespace content { +namespace { + +// Intentionally leaked. +ZygoteHandle g_generic_zygote = nullptr; +ZygoteHandle g_unsandboxed_zygote = nullptr; + +} // namespace + +ZygoteHandle CreateGenericZygote(ZygoteLaunchCallback launch_cb) { + CHECK(!g_generic_zygote); + g_generic_zygote = + new ZygoteCommunication(ZygoteCommunication::ZygoteType::kSandboxed); + g_generic_zygote->Init(std::move(launch_cb)); + return g_generic_zygote; +} + +ZygoteHandle GetGenericZygote() { + CHECK(g_generic_zygote); + return g_generic_zygote; +} + +ZygoteHandle CreateUnsandboxedZygote(ZygoteLaunchCallback launch_cb) { + CHECK(!g_unsandboxed_zygote); + g_unsandboxed_zygote = + new ZygoteCommunication(ZygoteCommunication::ZygoteType::kUnsandboxed); + g_unsandboxed_zygote->Init(std::move(launch_cb)); + return g_unsandboxed_zygote; +} + +ZygoteHandle GetUnsandboxedZygote() { + CHECK(g_unsandboxed_zygote); + return g_unsandboxed_zygote; +} + +} // namespace content |