diff options
author | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-08 14:30:41 +0200 |
---|---|---|
committer | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-12 13:49:54 +0200 |
commit | ab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch) | |
tree | 498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/content/zygote | |
parent | 4ce69f7403811819800e7c5ae1318b2647e778d1 (diff) | |
download | qtwebengine-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.cc | 345 | ||||
-rw-r--r-- | chromium/content/zygote/zygote_linux.h | 51 | ||||
-rw-r--r-- | chromium/content/zygote/zygote_main.h | 5 | ||||
-rw-r--r-- | chromium/content/zygote/zygote_main_linux.cc | 352 |
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(); } |