summaryrefslogtreecommitdiff
path: root/chromium/content/zygote
diff options
context:
space:
mode:
authorJocelyn Turcotte <jocelyn.turcotte@digia.com>2014-08-08 14:30:41 +0200
committerJocelyn Turcotte <jocelyn.turcotte@digia.com>2014-08-12 13:49:54 +0200
commitab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch)
tree498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/content/zygote
parent4ce69f7403811819800e7c5ae1318b2647e778d1 (diff)
downloadqtwebengine-chromium-ab0a50979b9eb4dfa3320eff7e187e41efedf7a9.tar.gz
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/content/zygote')
-rw-r--r--chromium/content/zygote/zygote_linux.cc345
-rw-r--r--chromium/content/zygote/zygote_linux.h51
-rw-r--r--chromium/content/zygote/zygote_main.h5
-rw-r--r--chromium/content/zygote/zygote_main_linux.cc352
4 files changed, 470 insertions, 283 deletions
diff --git a/chromium/content/zygote/zygote_linux.cc b/chromium/content/zygote/zygote_linux.cc
index 2f0bd2033e1..e5e91296da7 100644
--- a/chromium/content/zygote/zygote_linux.cc
+++ b/chromium/content/zygote/zygote_linux.cc
@@ -15,6 +15,8 @@
#include "base/file_util.h"
#include "base/linux_util.h"
#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_vector.h"
#include "base/pickle.h"
#include "base/posix/eintr_wrapper.h"
#include "base/posix/global_descriptors.h"
@@ -31,6 +33,10 @@
#include "ipc/ipc_channel.h"
#include "ipc/ipc_switches.h"
+#if defined(ADDRESS_SANITIZER)
+#include <sanitizer/asan_interface.h>
+#endif
+
// See http://code.google.com/p/chromium/wiki/LinuxZygote
namespace content {
@@ -49,21 +55,41 @@ int LookUpFd(const base::GlobalDescriptors::Mapping& fd_mapping, uint32_t key) {
return -1;
}
-} // namespace
+void CreatePipe(base::ScopedFD* read_pipe, base::ScopedFD* write_pipe) {
+ int raw_pipe[2];
+ PCHECK(0 == pipe(raw_pipe));
+ read_pipe->reset(raw_pipe[0]);
+ write_pipe->reset(raw_pipe[1]);
+}
-Zygote::Zygote(int sandbox_flags,
- ZygoteForkDelegate* helper)
- : sandbox_flags_(sandbox_flags),
- helper_(helper),
- initial_uma_sample_(0),
- initial_uma_boundary_value_(0) {
- if (helper_) {
- helper_->InitialUMA(&initial_uma_name_,
- &initial_uma_sample_,
- &initial_uma_boundary_value_);
+void KillAndReap(pid_t pid, ZygoteForkDelegate* helper) {
+ if (helper) {
+ // Helper children may be forked in another PID namespace, so |pid| might
+ // be meaningless to us; or we just might not be able to directly send it
+ // signals. So we can't kill it.
+ // Additionally, we're not its parent, so we can't reap it anyway.
+ // TODO(mdempsky): Extend the ZygoteForkDelegate API to handle this.
+ LOG(WARNING) << "Unable to kill or reap helper children";
+ return;
}
+
+ // Kill the child process in case it's not already dead, so we can safely
+ // perform a blocking wait.
+ PCHECK(0 == kill(pid, SIGKILL));
+ PCHECK(pid == HANDLE_EINTR(waitpid(pid, NULL, 0)));
}
+} // namespace
+
+Zygote::Zygote(int sandbox_flags, ScopedVector<ZygoteForkDelegate> helpers,
+ const std::vector<base::ProcessHandle>& extra_children,
+ const std::vector<int>& extra_fds)
+ : sandbox_flags_(sandbox_flags),
+ helpers_(helpers.Pass()),
+ initial_uma_index_(0),
+ extra_children_(extra_children),
+ extra_fds_(extra_fds) {}
+
Zygote::~Zygote() {
}
@@ -83,10 +109,10 @@ bool Zygote::ProcessRequests() {
if (UsingSUIDSandbox()) {
// Let the ZygoteHost know we are ready to go.
// The receiving code is in content/browser/zygote_host_linux.cc.
- std::vector<int> empty;
bool r = UnixDomainSocket::SendMsg(kZygoteSocketPairFd,
kZygoteHelloMessage,
- sizeof(kZygoteHelloMessage), empty);
+ sizeof(kZygoteHelloMessage),
+ std::vector<int>());
#if defined(OS_CHROMEOS)
LOG_IF(WARNING, !r) << "Sending zygote magic failed";
// Exit normally on chromeos because session manager may send SIGTERM
@@ -122,12 +148,28 @@ bool Zygote::UsingSUIDSandbox() const {
}
bool Zygote::HandleRequestFromBrowser(int fd) {
- std::vector<int> fds;
+ ScopedVector<base::ScopedFD> fds;
char buf[kZygoteMaxMessageLength];
const ssize_t len = UnixDomainSocket::RecvMsg(fd, buf, sizeof(buf), &fds);
if (len == 0 || (len == -1 && errno == ECONNRESET)) {
// EOF from the browser. We should die.
+ // TODO(earthdok): call __sanititizer_cov_dump() here to obtain code
+ // coverage for the Zygote. Currently it's not possible because of
+ // confusion over who is responsible for closing the file descriptor.
+ for (std::vector<int>::iterator it = extra_fds_.begin();
+ it < extra_fds_.end(); ++it) {
+ PCHECK(0 == IGNORE_EINTR(close(*it)));
+ }
+#if !defined(ADDRESS_SANITIZER)
+ // TODO(earthdok): add watchdog thread before using this in non-ASAN builds.
+ CHECK(extra_children_.empty());
+#endif
+ for (std::vector<base::ProcessHandle>::iterator it =
+ extra_children_.begin();
+ it < extra_children_.end(); ++it) {
+ PCHECK(*it == HANDLE_EINTR(waitpid(*it, NULL, 0)));
+ }
_exit(0);
return false;
}
@@ -145,7 +187,7 @@ bool Zygote::HandleRequestFromBrowser(int fd) {
switch (kind) {
case kZygoteCommandFork:
// This function call can return multiple times, once per fork().
- return HandleForkRequest(fd, pickle, iter, fds);
+ return HandleForkRequest(fd, pickle, iter, fds.Pass());
case kZygoteCommandReap:
if (!fds.empty())
@@ -160,6 +202,13 @@ bool Zygote::HandleRequestFromBrowser(int fd) {
case kZygoteCommandGetSandboxStatus:
HandleGetSandboxStatus(fd, pickle, iter);
return false;
+ case kZygoteCommandForkRealPID:
+ // This shouldn't happen in practice, but some failure paths in
+ // HandleForkRequest (e.g., if ReadArgsAndFork fails during depickling)
+ // could leave this command pending on the socket.
+ LOG(ERROR) << "Unexpected real PID message from browser";
+ NOTREACHED();
+ return false;
default:
NOTREACHED();
break;
@@ -167,9 +216,6 @@ bool Zygote::HandleRequestFromBrowser(int fd) {
}
LOG(WARNING) << "Error parsing message from browser";
- for (std::vector<int>::const_iterator
- i = fds.begin(); i != fds.end(); ++i)
- close(*i);
return false;
}
@@ -192,8 +238,21 @@ void Zygote::HandleReapRequest(int fd,
}
if (!child_info.started_from_helper) {
+ // Do not call base::EnsureProcessTerminated() under ThreadSanitizer, as it
+ // spawns a separate thread which may live until the call to fork() in the
+ // zygote. As a result, ThreadSanitizer will report an error and almost
+ // disable race detection in the child process.
+ // Not calling EnsureProcessTerminated() may result in zombie processes
+ // sticking around. This will only happen during testing, so we can live
+ // with this for now.
+#if !defined(THREAD_SANITIZER)
// TODO(jln): this old code is completely broken. See crbug.com/274855.
base::EnsureProcessTerminated(child_info.internal_pid);
+#else
+ LOG(WARNING) << "Zygote process omitting a call to "
+ << "base::EnsureProcessTerminated() for child pid " << child
+ << " under ThreadSanitizer. See http://crbug.com/274855.";
+#endif
} else {
// For processes from the helper, send a GetTerminationStatus request
// with known_dead set to true.
@@ -223,9 +282,8 @@ bool Zygote::GetTerminationStatus(base::ProcessHandle real_pid,
// We know about |real_pid|.
const base::ProcessHandle child = child_info.internal_pid;
if (child_info.started_from_helper) {
- // Let the helper handle the request.
- DCHECK(helper_);
- if (!helper_->GetTerminationStatus(child, known_dead, status, exit_code)) {
+ if (!child_info.started_from_helper->GetTerminationStatus(
+ child, known_dead, status, exit_code)) {
return false;
}
} else {
@@ -283,58 +341,52 @@ void Zygote::HandleGetTerminationStatus(int fd,
int Zygote::ForkWithRealPid(const std::string& process_type,
const base::GlobalDescriptors::Mapping& fd_mapping,
- const std::string& channel_switch,
+ const std::string& channel_id,
+ base::ScopedFD pid_oracle,
std::string* uma_name,
int* uma_sample,
int* uma_boundary_value) {
- const bool use_helper = (helper_ && helper_->CanHelp(process_type,
- uma_name,
- uma_sample,
- uma_boundary_value));
- int dummy_fd;
- ino_t dummy_inode;
- int pipe_fds[2] = { -1, -1 };
- base::ProcessId pid = 0;
-
- dummy_fd = socket(PF_UNIX, SOCK_DGRAM, 0);
- if (dummy_fd < 0) {
- LOG(ERROR) << "Failed to create dummy FD";
- goto error;
- }
- if (!base::FileDescriptorGetInode(&dummy_inode, dummy_fd)) {
- LOG(ERROR) << "Failed to get inode for dummy FD";
- goto error;
- }
- if (pipe(pipe_fds) != 0) {
- LOG(ERROR) << "Failed to create pipe";
- goto error;
+ ZygoteForkDelegate* helper = NULL;
+ for (ScopedVector<ZygoteForkDelegate>::iterator i = helpers_.begin();
+ i != helpers_.end();
+ ++i) {
+ if ((*i)->CanHelp(process_type, uma_name, uma_sample, uma_boundary_value)) {
+ helper = *i;
+ break;
+ }
}
- if (use_helper) {
- std::vector<int> fds;
+ base::ScopedFD read_pipe, write_pipe;
+ base::ProcessId pid = 0;
+ if (helper) {
int ipc_channel_fd = LookUpFd(fd_mapping, kPrimaryIPCChannel);
if (ipc_channel_fd < 0) {
DLOG(ERROR) << "Failed to find kPrimaryIPCChannel in FD mapping";
- goto error;
+ return -1;
}
+ std::vector<int> fds;
fds.push_back(ipc_channel_fd); // kBrowserFDIndex
- fds.push_back(dummy_fd); // kDummyFDIndex
- fds.push_back(pipe_fds[0]); // kParentFDIndex
- pid = helper_->Fork(fds);
+ fds.push_back(pid_oracle.get()); // kPIDOracleFDIndex
+ pid = helper->Fork(process_type, fds, channel_id);
+
+ // Helpers should never return in the child process.
+ CHECK_NE(pid, 0);
} else {
+ CreatePipe(&read_pipe, &write_pipe);
pid = fork();
}
- if (pid < 0) {
- goto error;
- } else if (pid == 0) {
+
+ if (pid == 0) {
// In the child process.
- close(pipe_fds[1]);
+ write_pipe.reset();
+
+ // Ping the PID oracle socket so the browser can find our PID.
+ CHECK(SendZygoteChildPing(pid_oracle.get()));
+
+ // Now read back our real PID from the zygote.
base::ProcessId real_pid;
- // Wait until the parent process has discovered our PID. We
- // should not fork any child processes (which the seccomp
- // sandbox does) until then, because that can interfere with the
- // parent's discovery of our PID.
- if (!base::ReadFromFD(pipe_fds[0], reinterpret_cast<char*>(&real_pid),
+ if (!base::ReadFromFD(read_pipe.get(),
+ reinterpret_cast<char*>(&real_pid),
sizeof(real_pid))) {
LOG(FATAL) << "Failed to synchronise with parent zygote process";
}
@@ -350,87 +402,69 @@ int Zygote::ForkWithRealPid(const std::string& process_type,
base::debug::TraceLog::GetInstance()->SetProcessID(
static_cast<int>(real_pid));
#endif
- close(pipe_fds[0]);
- close(dummy_fd);
return 0;
- } else {
- // In the parent process.
- close(dummy_fd);
- dummy_fd = -1;
- close(pipe_fds[0]);
- pipe_fds[0] = -1;
- base::ProcessId real_pid;
- if (UsingSUIDSandbox()) {
- uint8_t reply_buf[512];
- Pickle request;
- request.WriteInt(LinuxSandbox::METHOD_GET_CHILD_WITH_INODE);
- request.WriteUInt64(dummy_inode);
-
- const ssize_t r = UnixDomainSocket::SendRecvMsg(
- GetSandboxFD(), reply_buf, sizeof(reply_buf), NULL,
- request);
- if (r == -1) {
- LOG(ERROR) << "Failed to get child process's real PID";
- goto error;
- }
-
- Pickle reply(reinterpret_cast<char*>(reply_buf), r);
- PickleIterator iter(reply);
- if (!reply.ReadInt(&iter, &real_pid))
- goto error;
- if (real_pid <= 0) {
- // METHOD_GET_CHILD_WITH_INODE failed. Did the child die already?
- LOG(ERROR) << "METHOD_GET_CHILD_WITH_INODE failed";
- goto error;
- }
- } else {
- // If no SUID sandbox is involved then no pid translation is
- // necessary.
- real_pid = pid;
- }
+ }
- // Now set-up this process to be tracked by the Zygote.
- if (process_info_map_.find(real_pid) != process_info_map_.end()) {
- LOG(ERROR) << "Already tracking PID " << real_pid;
- NOTREACHED();
- }
- process_info_map_[real_pid].internal_pid = pid;
- process_info_map_[real_pid].started_from_helper = use_helper;
-
- if (use_helper) {
- if (!helper_->AckChild(pipe_fds[1], channel_switch)) {
- LOG(ERROR) << "Failed to synchronise with zygote fork helper";
- goto error;
- }
- } else {
- int written =
- HANDLE_EINTR(write(pipe_fds[1], &real_pid, sizeof(real_pid)));
- if (written != sizeof(real_pid)) {
- LOG(ERROR) << "Failed to synchronise with child process";
- goto error;
- }
+ // In the parent process.
+ read_pipe.reset();
+ pid_oracle.reset();
+
+ // Always receive a real PID from the zygote host, though it might
+ // be invalid (see below).
+ base::ProcessId real_pid;
+ {
+ ScopedVector<base::ScopedFD> recv_fds;
+ char buf[kZygoteMaxMessageLength];
+ const ssize_t len = UnixDomainSocket::RecvMsg(
+ kZygoteSocketPairFd, buf, sizeof(buf), &recv_fds);
+ CHECK_GT(len, 0);
+ CHECK(recv_fds.empty());
+
+ Pickle pickle(buf, len);
+ PickleIterator iter(pickle);
+
+ int kind;
+ CHECK(pickle.ReadInt(&iter, &kind));
+ CHECK(kind == kZygoteCommandForkRealPID);
+ CHECK(pickle.ReadInt(&iter, &real_pid));
+ }
+
+ // Fork failed.
+ if (pid < 0) {
+ return -1;
+ }
+
+ // If we successfully forked a child, but it crashed without sending
+ // a message to the browser, the browser won't have found its PID.
+ if (real_pid < 0) {
+ KillAndReap(pid, helper);
+ return -1;
+ }
+
+ // If we're not using a helper, send the PID back to the child process.
+ if (!helper) {
+ ssize_t written =
+ HANDLE_EINTR(write(write_pipe.get(), &real_pid, sizeof(real_pid)));
+ if (written != sizeof(real_pid)) {
+ KillAndReap(pid, helper);
+ return -1;
}
- close(pipe_fds[1]);
- return real_pid;
}
- error:
- if (pid > 0) {
- if (waitpid(pid, NULL, WNOHANG) == -1)
- LOG(ERROR) << "Failed to wait for process";
+ // Now set-up this process to be tracked by the Zygote.
+ if (process_info_map_.find(real_pid) != process_info_map_.end()) {
+ LOG(ERROR) << "Already tracking PID " << real_pid;
+ NOTREACHED();
}
- if (dummy_fd >= 0)
- close(dummy_fd);
- if (pipe_fds[0] >= 0)
- close(pipe_fds[0]);
- if (pipe_fds[1] >= 0)
- close(pipe_fds[1]);
- return -1;
+ process_info_map_[real_pid].internal_pid = pid;
+ process_info_map_[real_pid].started_from_helper = helper;
+
+ return real_pid;
}
base::ProcessId Zygote::ReadArgsAndFork(const Pickle& pickle,
PickleIterator iter,
- std::vector<int>& fds,
+ ScopedVector<base::ScopedFD> fds,
std::string* uma_name,
int* uma_sample,
int* uma_boundary_value) {
@@ -454,7 +488,7 @@ base::ProcessId Zygote::ReadArgsAndFork(const Pickle& pickle,
return -1;
args.push_back(arg);
if (arg.compare(0, channel_id_prefix.length(), channel_id_prefix) == 0)
- channel_id = arg;
+ channel_id = arg.substr(channel_id_prefix.length());
}
if (!pickle.ReadInt(&iter, &numfds))
@@ -462,26 +496,40 @@ base::ProcessId Zygote::ReadArgsAndFork(const Pickle& pickle,
if (numfds != static_cast<int>(fds.size()))
return -1;
- for (int i = 0; i < numfds; ++i) {
+ // First FD is the PID oracle socket.
+ if (fds.size() < 1)
+ return -1;
+ base::ScopedFD pid_oracle(fds[0]->Pass());
+
+ // Remaining FDs are for the global descriptor mapping.
+ for (int i = 1; i < numfds; ++i) {
base::GlobalDescriptors::Key key;
if (!pickle.ReadUInt32(&iter, &key))
return -1;
- mapping.push_back(std::make_pair(key, fds[i]));
+ mapping.push_back(std::make_pair(key, fds[i]->get()));
}
mapping.push_back(std::make_pair(
static_cast<uint32_t>(kSandboxIPCChannel), GetSandboxFD()));
// Returns twice, once per process.
- base::ProcessId child_pid = ForkWithRealPid(process_type, mapping, channel_id,
- uma_name, uma_sample,
+ base::ProcessId child_pid = ForkWithRealPid(process_type,
+ mapping,
+ channel_id,
+ pid_oracle.Pass(),
+ uma_name,
+ uma_sample,
uma_boundary_value);
if (!child_pid) {
// This is the child process.
- close(kZygoteSocketPairFd); // Our socket from the browser.
- if (UsingSUIDSandbox())
- close(kZygoteIdFd); // Another socket from the browser.
+ // Our socket from the browser.
+ PCHECK(0 == IGNORE_EINTR(close(kZygoteSocketPairFd)));
+
+ // Pass ownership of file descriptors from fds to GlobalDescriptors.
+ for (ScopedVector<base::ScopedFD>::iterator i = fds.begin(); i != fds.end();
+ ++i)
+ ignore_result((*i)->release());
base::GlobalDescriptors::GetInstance()->Reset(mapping);
// Reset the process-wide command line to our new command line.
@@ -503,26 +551,19 @@ base::ProcessId Zygote::ReadArgsAndFork(const Pickle& pickle,
bool Zygote::HandleForkRequest(int fd,
const Pickle& pickle,
PickleIterator iter,
- std::vector<int>& fds) {
+ ScopedVector<base::ScopedFD> fds) {
std::string uma_name;
int uma_sample;
int uma_boundary_value;
- base::ProcessId child_pid = ReadArgsAndFork(pickle, iter, fds,
- &uma_name, &uma_sample,
- &uma_boundary_value);
+ base::ProcessId child_pid = ReadArgsAndFork(
+ pickle, iter, fds.Pass(), &uma_name, &uma_sample, &uma_boundary_value);
if (child_pid == 0)
return true;
- for (std::vector<int>::const_iterator
- i = fds.begin(); i != fds.end(); ++i)
- close(*i);
- if (uma_name.empty()) {
- // There is no UMA report from this particular fork.
- // Use the initial UMA report if any, and clear that record for next time.
- // Note the swap method here is the efficient way to do this, since
- // we know uma_name is empty.
- uma_name.swap(initial_uma_name_);
- uma_sample = initial_uma_sample_;
- uma_boundary_value = initial_uma_boundary_value_;
+ // If there's no UMA report for this particular fork, then check if any
+ // helpers have an initial UMA report for us to send instead.
+ while (uma_name.empty() && initial_uma_index_ < helpers_.size()) {
+ helpers_[initial_uma_index_++]->InitialUMA(
+ &uma_name, &uma_sample, &uma_boundary_value);
}
// Must always send reply, as ZygoteHost blocks while waiting for it.
Pickle reply_pickle;
diff --git a/chromium/content/zygote/zygote_linux.h b/chromium/content/zygote/zygote_linux.h
index 37e89b5f718..f93ea035564 100644
--- a/chromium/content/zygote/zygote_linux.h
+++ b/chromium/content/zygote/zygote_linux.h
@@ -5,10 +5,13 @@
#ifndef CONTENT_ZYGOTE_ZYGOTE_H_
#define CONTENT_ZYGOTE_ZYGOTE_H_
+#include <stddef.h>
+
#include <string>
-#include <vector>
#include "base/containers/small_map.h"
+#include "base/files/scoped_file.h"
+#include "base/memory/scoped_vector.h"
#include "base/posix/global_descriptors.h"
#include "base/process/kill.h"
#include "base/process/process.h"
@@ -25,8 +28,9 @@ class ZygoteForkDelegate;
// runs it.
class Zygote {
public:
- Zygote(int sandbox_flags,
- ZygoteForkDelegate* helper);
+ Zygote(int sandbox_flags, ScopedVector<ZygoteForkDelegate> helpers,
+ const std::vector<base::ProcessHandle>& extra_children,
+ const std::vector<int>& extra_fds);
~Zygote();
bool ProcessRequests();
@@ -35,9 +39,8 @@ class Zygote {
struct ZygoteProcessInfo {
// Pid from inside the Zygote's PID namespace.
base::ProcessHandle internal_pid;
- // Keeps track of whether or not a process was started from a fork
- // delegate helper.
- bool started_from_helper;
+ // Keeps track of which fork delegate helper the process was started from.
+ ZygoteForkDelegate* started_from_helper;
};
typedef base::SmallMap< std::map<base::ProcessHandle, ZygoteProcessInfo> >
ZygoteProcessMap;
@@ -74,12 +77,16 @@ class Zygote {
// This is equivalent to fork(), except that, when using the SUID sandbox, it
// returns the real PID of the child process as it appears outside the
- // sandbox, rather than returning the PID inside the sandbox. Optionally, it
- // fills in uma_name et al with a report the helper wants to make via
- // UMA_HISTOGRAM_ENUMERATION.
+ // sandbox, rather than returning the PID inside the sandbox. The child's
+ // real PID is determined by having it call content::SendZygoteChildPing(int)
+ // using the |pid_oracle| descriptor.
+ // Finally, when using a ZygoteForkDelegate helper, |uma_name|, |uma_sample|,
+ // and |uma_boundary_value| may be set if the helper wants to make a UMA
+ // report via UMA_HISTOGRAM_ENUMERATION.
int ForkWithRealPid(const std::string& process_type,
const base::GlobalDescriptors::Mapping& fd_mapping,
- const std::string& channel_switch,
+ const std::string& channel_id,
+ base::ScopedFD pid_oracle,
std::string* uma_name,
int* uma_sample,
int* uma_boundary_value);
@@ -89,7 +96,7 @@ class Zygote {
// process and the child process ID to the parent process, like fork().
base::ProcessId ReadArgsAndFork(const Pickle& pickle,
PickleIterator iter,
- std::vector<int>& fds,
+ ScopedVector<base::ScopedFD> fds,
std::string* uma_name,
int* uma_sample,
int* uma_boundary_value);
@@ -101,7 +108,7 @@ class Zygote {
bool HandleForkRequest(int fd,
const Pickle& pickle,
PickleIterator iter,
- std::vector<int>& fds);
+ ScopedVector<base::ScopedFD> fds);
bool HandleGetSandboxStatus(int fd,
const Pickle& pickle,
@@ -114,13 +121,21 @@ class Zygote {
ZygoteProcessMap process_info_map_;
const int sandbox_flags_;
- ZygoteForkDelegate* helper_;
+ ScopedVector<ZygoteForkDelegate> helpers_;
+
+ // Count of how many fork delegates for which we've invoked InitialUMA().
+ size_t initial_uma_index_;
+
+ // This vector contains the PIDs of any child processes which have been
+ // created prior to the construction of the Zygote object, and must be reaped
+ // before the Zygote exits. The Zygote will perform a blocking wait on these
+ // children, so they must be guaranteed to be exiting by the time the Zygote
+ // exits.
+ std::vector<base::ProcessHandle> extra_children_;
- // These might be set by helper_->InitialUMA. They supply a UMA enumeration
- // sample we should report on the first fork.
- std::string initial_uma_name_;
- int initial_uma_sample_;
- int initial_uma_boundary_value_;
+ // This vector contains the FDs that must be closed before reaping the extra
+ // children.
+ std::vector<int> extra_fds_;
};
} // namespace content
diff --git a/chromium/content/zygote/zygote_main.h b/chromium/content/zygote/zygote_main.h
index 64702323431..119210f14d4 100644
--- a/chromium/content/zygote/zygote_main.h
+++ b/chromium/content/zygote/zygote_main.h
@@ -5,13 +5,16 @@
#ifndef CONTENT_ZYGOTE_ZYGOTE_MAIN_H_
#define CONTENT_ZYGOTE_ZYGOTE_MAIN_H_
+template <typename>
+class ScopedVector;
+
namespace content {
struct MainFunctionParams;
class ZygoteForkDelegate;
bool ZygoteMain(const MainFunctionParams& params,
- ZygoteForkDelegate* forkdelegate);
+ ScopedVector<ZygoteForkDelegate> fork_delegates);
} // namespace content
diff --git a/chromium/content/zygote/zygote_main_linux.cc b/chromium/content/zygote/zygote_main_linux.cc
index 8e91c2f17e5..60f08b6ad63 100644
--- a/chromium/content/zygote/zygote_main_linux.cc
+++ b/chromium/content/zygote/zygote_main_linux.cc
@@ -5,25 +5,24 @@
#include "content/zygote/zygote_main.h"
#include <dlfcn.h>
-#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
-#include <stdio.h>
+#include <string.h>
#include <sys/socket.h>
-#include <sys/stat.h>
#include <sys/types.h>
-#include <sys/wait.h>
#include <unistd.h>
#include "base/basictypes.h"
#include "base/bind.h"
-#include "base/callback.h"
#include "base/command_line.h"
-#include "base/linux_util.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_vector.h"
#include "base/native_library.h"
#include "base/pickle.h"
+#include "base/posix/eintr_wrapper.h"
#include "base/posix/unix_domain_socket_linux.h"
#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
#include "base/sys_info.h"
#include "build/build_config.h"
#include "content/common/child_process_sandbox_support_impl_linux.h"
@@ -38,23 +37,24 @@
#include "content/public/common/zygote_fork_delegate_linux.h"
#include "content/zygote/zygote_linux.h"
#include "crypto/nss_util.h"
+#include "sandbox/linux/services/init_process_reaper.h"
#include "sandbox/linux/services/libc_urandom_override.h"
#include "sandbox/linux/suid/client/setuid_sandbox_client.h"
#include "third_party/icu/source/i18n/unicode/timezone.h"
#include "third_party/skia/include/ports/SkFontConfigInterface.h"
#if defined(OS_LINUX)
-#include <sys/epoll.h>
#include <sys/prctl.h>
-#include <sys/signal.h>
-#else
-#include <signal.h>
#endif
#if defined(ENABLE_WEBRTC)
#include "third_party/libjingle/overrides/init_webrtc.h"
#endif
+#if defined(ADDRESS_SANITIZER)
+#include <sanitizer/asan_interface.h>
+#endif
+
namespace content {
// See http://code.google.com/p/chromium/wiki/LinuxZygote
@@ -195,7 +195,12 @@ struct tm* localtime_override(const time_t* timep) {
} else {
CHECK_EQ(0, pthread_once(&g_libc_localtime_funcs_guard,
InitLibcLocaltimeFunctions));
- return g_libc_localtime(timep);
+ struct tm* res = g_libc_localtime(timep);
+#if defined(MEMORY_SANITIZER)
+ if (res) __msan_unpoison(res, sizeof(*res));
+ if (res->tm_zone) __msan_unpoison_string(res->tm_zone);
+#endif
+ return res;
}
}
@@ -214,7 +219,12 @@ struct tm* localtime64_override(const time_t* timep) {
} else {
CHECK_EQ(0, pthread_once(&g_libc_localtime_funcs_guard,
InitLibcLocaltimeFunctions));
- return g_libc_localtime64(timep);
+ struct tm* res = g_libc_localtime64(timep);
+#if defined(MEMORY_SANITIZER)
+ if (res) __msan_unpoison(res, sizeof(*res));
+ if (res->tm_zone) __msan_unpoison_string(res->tm_zone);
+#endif
+ return res;
}
}
@@ -230,7 +240,12 @@ struct tm* localtime_r_override(const time_t* timep, struct tm* result) {
} else {
CHECK_EQ(0, pthread_once(&g_libc_localtime_funcs_guard,
InitLibcLocaltimeFunctions));
- return g_libc_localtime_r(timep, result);
+ struct tm* res = g_libc_localtime_r(timep, result);
+#if defined(MEMORY_SANITIZER)
+ if (res) __msan_unpoison(res, sizeof(*res));
+ if (res->tm_zone) __msan_unpoison_string(res->tm_zone);
+#endif
+ return res;
}
}
@@ -246,7 +261,12 @@ struct tm* localtime64_r_override(const time_t* timep, struct tm* result) {
} else {
CHECK_EQ(0, pthread_once(&g_libc_localtime_funcs_guard,
InitLibcLocaltimeFunctions));
- return g_libc_localtime64_r(timep, result);
+ struct tm* res = g_libc_localtime64_r(timep, result);
+#if defined(MEMORY_SANITIZER)
+ if (res) __msan_unpoison(res, sizeof(*res));
+ if (res->tm_zone) __msan_unpoison_string(res->tm_zone);
+#endif
+ return res;
}
}
@@ -259,12 +279,12 @@ void PreloadPepperPlugins() {
ComputePepperPluginList(&plugins);
for (size_t i = 0; i < plugins.size(); ++i) {
if (!plugins[i].is_internal && plugins[i].is_sandboxed) {
- std::string error;
+ base::NativeLibraryLoadError error;
base::NativeLibrary library = base::LoadNativeLibrary(plugins[i].path,
&error);
VLOG_IF(1, !library) << "Unable to load plugin "
<< plugins[i].path.value() << " "
- << error;
+ << error.ToString();
(void)library; // Prevent release-mode warning.
}
@@ -274,7 +294,7 @@ void PreloadPepperPlugins() {
// This function triggers the static and lazy construction of objects that need
// to be created before imposing the sandbox.
-static void PreSandboxInit() {
+static void ZygotePreSandboxInit() {
base::RandUint64();
base::SysInfo::AmountOfPhysicalMemory();
@@ -311,132 +331,240 @@ static void PreSandboxInit() {
new FontConfigIPC(GetSandboxFD()))->unref();
}
-static void CloseFdAndHandleEintr(int fd) {
- close(fd);
+static bool CreateInitProcessReaper(base::Closure* post_fork_parent_callback) {
+ // The current process becomes init(1), this function returns from a
+ // newly created process.
+ const bool init_created =
+ sandbox::CreateInitProcessReaper(post_fork_parent_callback);
+ if (!init_created) {
+ LOG(ERROR) << "Error creating an init process to reap zombies";
+ return false;
+ }
+ return true;
}
-// This will set the *using_suid_sandbox variable to true if the SUID sandbox
-// is enabled. This does not necessarily exclude other types of sandboxing.
-static bool EnterSuidSandbox(LinuxSandbox* linux_sandbox,
- bool* using_suid_sandbox,
- bool* has_started_new_init) {
- *using_suid_sandbox = false;
- *has_started_new_init = false;
-
- sandbox::SetuidSandboxClient* setuid_sandbox =
- linux_sandbox->setuid_sandbox_client();
+// Enter the setuid sandbox. This requires the current process to have been
+// created through the setuid sandbox.
+static bool EnterSuidSandbox(sandbox::SetuidSandboxClient* setuid_sandbox,
+ base::Closure* post_fork_parent_callback) {
+ DCHECK(setuid_sandbox);
+ DCHECK(setuid_sandbox->IsSuidSandboxChild());
+
+ // Use the SUID sandbox. This still allows the seccomp sandbox to
+ // be enabled by the process later.
+
+ if (!setuid_sandbox->IsSuidSandboxUpToDate()) {
+ LOG(WARNING) <<
+ "You are using a wrong version of the setuid binary!\n"
+ "Please read "
+ "https://code.google.com/p/chromium/wiki/LinuxSUIDSandboxDevelopment."
+ "\n\n";
+ }
- if (!setuid_sandbox)
+ if (!setuid_sandbox->ChrootMe())
return false;
- PreSandboxInit();
-
- // Check that the pre-sandbox initialization didn't spawn threads.
- DCHECK(linux_sandbox->IsSingleThreaded());
+ if (setuid_sandbox->IsInNewPIDNamespace()) {
+ CHECK_EQ(1, getpid())
+ << "The SUID sandbox created a new PID namespace but Zygote "
+ "is not the init process. Please, make sure the SUID "
+ "binary is up to date.";
+ }
- if (setuid_sandbox->IsSuidSandboxChild()) {
- // Use the SUID sandbox. This still allows the seccomp sandbox to
- // be enabled by the process later.
- *using_suid_sandbox = true;
+ if (getpid() == 1) {
+ // The setuid sandbox has created a new PID namespace and we need
+ // to assume the role of init.
+ CHECK(CreateInitProcessReaper(post_fork_parent_callback));
+ }
- if (!setuid_sandbox->IsSuidSandboxUpToDate()) {
- LOG(WARNING) << "You are using a wrong version of the setuid binary!\n"
- "Please read "
- "https://code.google.com/p/chromium/wiki/LinuxSUIDSandboxDevelopment."
- "\n\n";
+#if !defined(OS_OPENBSD)
+ // Previously, we required that the binary be non-readable. This causes the
+ // kernel to mark the process as non-dumpable at startup. The thinking was
+ // that, although we were putting the renderers into a PID namespace (with
+ // the SUID sandbox), they would nonetheless be in the /same/ PID
+ // namespace. So they could ptrace each other unless they were non-dumpable.
+ //
+ // If the binary was readable, then there would be a window between process
+ // startup and the point where we set the non-dumpable flag in which a
+ // compromised renderer could ptrace attach.
+ //
+ // However, now that we have a zygote model, only the (trusted) zygote
+ // exists at this point and we can set the non-dumpable flag which is
+ // inherited by all our renderer children.
+ //
+ // Note: a non-dumpable process can't be debugged. To debug sandbox-related
+ // issues, one can specify --allow-sandbox-debugging to let the process be
+ // dumpable.
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ if (!command_line.HasSwitch(switches::kAllowSandboxDebugging)) {
+ prctl(PR_SET_DUMPABLE, 0, 0, 0, 0);
+ if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0)) {
+ LOG(ERROR) << "Failed to set non-dumpable flag";
+ return false;
}
+ }
+#endif
- if (!setuid_sandbox->ChrootMe())
- return false;
+ return true;
+}
- if (getpid() == 1) {
- // The setuid sandbox has created a new PID namespace and we need
- // to assume the role of init.
- // This "magic" socket must only appear in one process, so make sure
- // it gets closed in the parent after fork().
- base::Closure zygoteid_fd_closer =
- base::Bind(CloseFdAndHandleEintr, kZygoteIdFd);
- const bool init_created =
- setuid_sandbox->CreateInitProcessReaper(&zygoteid_fd_closer);
- if (!init_created) {
- LOG(ERROR) << "Error creating an init process to reap zombies";
- return false;
- }
- *has_started_new_init = true;
+#if defined(ADDRESS_SANITIZER)
+const size_t kSanitizerMaxMessageLength = 1 * 1024 * 1024;
+
+// A helper process which collects code coverage data from the renderers over a
+// socket and dumps it to a file. See http://crbug.com/336212 for discussion.
+static void SanitizerCoverageHelper(int socket_fd, int file_fd) {
+ scoped_ptr<char[]> buffer(new char[kSanitizerMaxMessageLength]);
+ while (true) {
+ ssize_t received_size = HANDLE_EINTR(
+ recv(socket_fd, buffer.get(), kSanitizerMaxMessageLength, 0));
+ PCHECK(received_size >= 0);
+ if (received_size == 0)
+ // All clients have closed the socket. We should die.
+ _exit(0);
+ PCHECK(file_fd >= 0);
+ ssize_t written_size = 0;
+ while (written_size < received_size) {
+ ssize_t write_res =
+ HANDLE_EINTR(write(file_fd, buffer.get() + written_size,
+ received_size - written_size));
+ PCHECK(write_res >= 0);
+ written_size += write_res;
}
+ PCHECK(0 == HANDLE_EINTR(fsync(file_fd)));
+ }
+}
-#if !defined(OS_OPENBSD)
- // Previously, we required that the binary be non-readable. This causes the
- // kernel to mark the process as non-dumpable at startup. The thinking was
- // that, although we were putting the renderers into a PID namespace (with
- // the SUID sandbox), they would nonetheless be in the /same/ PID
- // namespace. So they could ptrace each other unless they were non-dumpable.
- //
- // If the binary was readable, then there would be a window between process
- // startup and the point where we set the non-dumpable flag in which a
- // compromised renderer could ptrace attach.
- //
- // However, now that we have a zygote model, only the (trusted) zygote
- // exists at this point and we can set the non-dumpable flag which is
- // inherited by all our renderer children.
- //
- // Note: a non-dumpable process can't be debugged. To debug sandbox-related
- // issues, one can specify --allow-sandbox-debugging to let the process be
- // dumpable.
- const CommandLine& command_line = *CommandLine::ForCurrentProcess();
- if (!command_line.HasSwitch(switches::kAllowSandboxDebugging)) {
- prctl(PR_SET_DUMPABLE, 0, 0, 0, 0);
- if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0)) {
- LOG(ERROR) << "Failed to set non-dumpable flag";
- return false;
- }
- }
-#endif
+// fds[0] is the read end, fds[1] is the write end.
+static void CreateSanitizerCoverageSocketPair(int fds[2]) {
+ PCHECK(0 == socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds));
+ PCHECK(0 == shutdown(fds[0], SHUT_WR));
+ PCHECK(0 == shutdown(fds[1], SHUT_RD));
+}
+
+static pid_t ForkSanitizerCoverageHelper(int child_fd, int parent_fd,
+ base::ScopedFD file_fd) {
+ pid_t pid = fork();
+ PCHECK(pid >= 0);
+ if (pid == 0) {
+ // In the child.
+ PCHECK(0 == IGNORE_EINTR(close(parent_fd)));
+ SanitizerCoverageHelper(child_fd, file_fd.get());
+ _exit(0);
+ } else {
+ // In the parent.
+ PCHECK(0 == IGNORE_EINTR(close(child_fd)));
+ return pid;
}
+}
- return true;
+void CloseFdPair(const int fds[2]) {
+ PCHECK(0 == IGNORE_EINTR(close(fds[0])));
+ PCHECK(0 == IGNORE_EINTR(close(fds[1])));
+}
+#endif // defined(ADDRESS_SANITIZER)
+
+// If |is_suid_sandbox_child|, then make sure that the setuid sandbox is
+// engaged.
+static void EnterLayerOneSandbox(LinuxSandbox* linux_sandbox,
+ bool is_suid_sandbox_child,
+ base::Closure* post_fork_parent_callback) {
+ DCHECK(linux_sandbox);
+
+ ZygotePreSandboxInit();
+
+ // Check that the pre-sandbox initialization didn't spawn threads.
+#if !defined(THREAD_SANITIZER)
+ DCHECK(linux_sandbox->IsSingleThreaded());
+#endif
+
+ sandbox::SetuidSandboxClient* setuid_sandbox =
+ linux_sandbox->setuid_sandbox_client();
+
+ if (is_suid_sandbox_child) {
+ CHECK(EnterSuidSandbox(setuid_sandbox, post_fork_parent_callback))
+ << "Failed to enter setuid sandbox";
+ }
}
bool ZygoteMain(const MainFunctionParams& params,
- ZygoteForkDelegate* forkdelegate) {
+ ScopedVector<ZygoteForkDelegate> fork_delegates) {
g_am_zygote_or_renderer = true;
sandbox::InitLibcUrandomOverrides();
+ base::Closure *post_fork_parent_callback = NULL;
+
LinuxSandbox* linux_sandbox = LinuxSandbox::GetInstance();
+
+#if defined(ADDRESS_SANITIZER)
+ const std::string sancov_file_name =
+ "zygote." + base::Uint64ToString(base::RandUint64());
+ base::ScopedFD sancov_file_fd(
+ __sanitizer_maybe_open_cov_file(sancov_file_name.c_str()));
+ int sancov_socket_fds[2] = {-1, -1};
+ CreateSanitizerCoverageSocketPair(sancov_socket_fds);
+ linux_sandbox->sanitizer_args()->coverage_sandboxed = 1;
+ linux_sandbox->sanitizer_args()->coverage_fd = sancov_socket_fds[1];
+ linux_sandbox->sanitizer_args()->coverage_max_block_size =
+ kSanitizerMaxMessageLength;
+ // Zygote termination will block until the helper process exits, which will
+ // not happen until the write end of the socket is closed everywhere. Make
+ // sure the init process does not hold on to it.
+ base::Closure close_sancov_socket_fds =
+ base::Bind(&CloseFdPair, sancov_socket_fds);
+ post_fork_parent_callback = &close_sancov_socket_fds;
+#endif
+
// This will pre-initialize the various sandboxes that need it.
linux_sandbox->PreinitializeSandbox();
- if (forkdelegate != NULL) {
- VLOG(1) << "ZygoteMain: initializing fork delegate";
- forkdelegate->Init(GetSandboxFD());
- } else {
- VLOG(1) << "ZygoteMain: fork delegate is NULL";
- }
-
- // Turn on the sandbox.
- bool using_suid_sandbox = false;
- bool has_started_new_init = false;
+ const bool must_enable_setuid_sandbox =
+ linux_sandbox->setuid_sandbox_client()->IsSuidSandboxChild();
+ if (must_enable_setuid_sandbox) {
+ linux_sandbox->setuid_sandbox_client()->CloseDummyFile();
- if (!EnterSuidSandbox(linux_sandbox,
- &using_suid_sandbox,
- &has_started_new_init)) {
- LOG(FATAL) << "Failed to enter sandbox. Fail safe abort. (errno: "
- << errno << ")";
- return false;
+ // Let the ZygoteHost know we're booting up.
+ CHECK(UnixDomainSocket::SendMsg(kZygoteSocketPairFd,
+ kZygoteBootMessage,
+ sizeof(kZygoteBootMessage),
+ std::vector<int>()));
}
- sandbox::SetuidSandboxClient* setuid_sandbox =
- linux_sandbox->setuid_sandbox_client();
-
- if (setuid_sandbox->IsInNewPIDNamespace() && !has_started_new_init) {
- LOG(ERROR) << "The SUID sandbox created a new PID namespace but Zygote "
- "is not the init process. Please, make sure the SUID "
- "binary is up to date.";
+ VLOG(1) << "ZygoteMain: initializing " << fork_delegates.size()
+ << " fork delegates";
+ for (ScopedVector<ZygoteForkDelegate>::iterator i = fork_delegates.begin();
+ i != fork_delegates.end();
+ ++i) {
+ (*i)->Init(GetSandboxFD(), must_enable_setuid_sandbox);
}
+ // Turn on the first layer of the sandbox if the configuration warrants it.
+ EnterLayerOneSandbox(linux_sandbox, must_enable_setuid_sandbox,
+ post_fork_parent_callback);
+
+ std::vector<pid_t> extra_children;
+ std::vector<int> extra_fds;
+
+#if defined(ADDRESS_SANITIZER)
+ pid_t sancov_helper_pid = ForkSanitizerCoverageHelper(
+ sancov_socket_fds[0], sancov_socket_fds[1], sancov_file_fd.Pass());
+ // It's important that the zygote reaps the helper before dying. Otherwise,
+ // the destruction of the PID namespace could kill the helper before it
+ // completes its I/O tasks. |sancov_helper_pid| will exit once the last
+ // renderer holding the write end of |sancov_socket_fds| closes it.
+ extra_children.push_back(sancov_helper_pid);
+ // Sanitizer code in the renderers will inherit the write end of the socket
+ // from the zygote. We must keep it open until the very end of the zygote's
+ // lifetime, even though we don't explicitly use it.
+ extra_fds.push_back(sancov_socket_fds[1]);
+#endif
+
int sandbox_flags = linux_sandbox->GetStatus();
+ bool setuid_sandbox_engaged = sandbox_flags & kSandboxLinuxSUID;
+ CHECK_EQ(must_enable_setuid_sandbox, setuid_sandbox_engaged);
- Zygote zygote(sandbox_flags, forkdelegate);
+ Zygote zygote(sandbox_flags, fork_delegates.Pass(), extra_children,
+ extra_fds);
// This function call can return multiple times, once per fork().
return zygote.ProcessRequests();
}