blob: 9e6a5fa3d22e55f8fd559678aa7657221ad8ffe7 [file] [log] [blame]
// 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 "services/service_manager/zygote/host/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 "services/service_manager/embedder/result_codes.h"
#include "services/service_manager/embedder/switches.h"
#include "services/service_manager/sandbox/switches.h"
#include "services/service_manager/zygote/common/zygote_commands_linux.h"
#include "third_party/icu/source/i18n/unicode/timezone.h"
namespace service_manager {
ZygoteCommunication::ZygoteCommunication()
: control_fd_(),
control_lock_(),
pid_(),
list_of_running_zygote_children_(),
child_tracking_lock_(),
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(switches::kProcessType, switches::kZygoteProcess);
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 = 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 service_manager