// Copyright (c) 2006-2009 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 "base/zygote_manager.h" #if defined(OS_LINUX) #include #include #include #include #include // for flock() #include #include // for struct iovec #include #include // for ssize_t #include #include "base/eintr_wrapper.h" #include "base/file_descriptor_shuffle.h" #include "base/file_util.h" #include "base/logging.h" #include "base/process_util.h" #include "base/reserved_file_descriptors.h" #include "base/singleton.h" using file_util::Delete; // See comment below, where sigaction is called. static void SIGCHLDHandler(int signal) { } namespace base { const char ZygoteManager::kZMagic[] = "zygo"; ZygoteManager::~ZygoteManager() { if (server_fd_ != -1) { close(server_fd_); server_fd_ = -1; } if (client_fd_ != -1) { close(client_fd_); client_fd_ = -1; } if (lockfd_ != -1) { close(lockfd_); lockfd_ = -1; } if (canary_fd_ != -1) { // Wake up the poll() in ReadAndHandleMessage() close(canary_fd_); canary_fd_ = -1; } #ifndef OFFICIAL_BUILD // Closing the canary kills the server, // so after this it's ok for e.g. unit tests // to start a new zygote server. (void) unsetenv("ZYGOTE_MANAGER_STARTED"); #endif } // Runs in client process ZygoteManager* ZygoteManager::Get() { static bool checked = false; static bool enabled = false; if (!checked) { enabled = (getenv("ENABLE_ZYGOTE_MANAGER") != NULL); checked = true; } if (!enabled) return NULL; return Singleton::get(); } // Runs in zygote manager process int ZygoteManager::UnpickleHeader(const Pickle& reply, void** iter) { std::string magic; if (!reply.ReadString(iter, &magic) || magic != std::string(kZMagic)) { LOG(ERROR) << "reply didn't start with " << kZMagic; return ZMBAD; } pid_t clientpid = (pid_t) -1; if (!reply.ReadInt(iter, &clientpid)) { LOG(ERROR) << "Can't read client pid"; return ZMBAD; } if (clientpid != getpid()) { LOG(ERROR) << "got client pid " << clientpid << ", expected " << getpid(); DCHECK(clientpid == getpid()); return ZMBAD; } int kind; if (!reply.ReadInt(iter, &kind)) { LOG(ERROR) << "can't read kind"; return ZMBAD; } return kind; } // Runs in client process (only used in unit test) bool ZygoteManager::Ping(base::TimeDelta* delta) { if (client_fd_ == -1) return false; Pickle pickle; pickle.WriteString(kZMagic); pickle.WriteInt(getpid()); pickle.WriteInt(ZMPING); int bytes_sent; int bytes_read = -1; TimeTicks time_sent = TimeTicks::HighResNow(); // Lock fork server, send the pickle, wait for the reply, unlock if (flock(lockfd_, LOCK_EX)) LOG(ERROR) << "flock failed, errno " << errno; bytes_sent = HANDLE_EINTR(write(client_fd_, const_cast(pickle.data()), pickle.size())); if (bytes_sent > 0) { bytes_read = HANDLE_EINTR(read(client_fd_, msg_buf_, kMAX_MSG_LEN)); } if (flock(lockfd_, LOCK_UN)) LOG(ERROR) << "flock failed, errno " << errno; TimeTicks time_received = TimeTicks::HighResNow(); if (bytes_sent < 1) { LOG(ERROR) << "Can't send to zm, errno " << errno; return false; } if (bytes_read < 1) { LOG(ERROR) << "Can't get from zm, errno " << errno; return false; } // Unpickle the reply Pickle reply(msg_buf_, bytes_read); void* iter = NULL; int kind = UnpickleHeader(reply, &iter); if (kind != ZMPINGED) { LOG(ERROR) << "reply wrong kind " << kind; return false; } *delta = TimeTicks::HighResNow() - time_sent; LOG(INFO) << "Round trip time in microseconds: " << delta->InMicroseconds(); return true; } // Runs in zygote manager process void ZygoteManager::PingHandler(const Pickle& request, void* iter, Pickle* reply, std::vector** newargv) { reply->WriteInt(ZMPINGED); } // Runs in browser process, called only by base::ForkApp() pid_t ZygoteManager::LongFork(const std::vector& argv, const file_handle_mapping_vector& fds_to_remap) { if (client_fd_ == -1) return -1; Pickle pickle; // Encode the arguments and the desired remote fd numbers in the pickle, // and the fds in a separate buffer pickle.WriteString(kZMagic); pickle.WriteInt(getpid()); pickle.WriteInt(ZMFORK); pickle.WriteInt(argv.size()); std::vector::const_iterator argi; for (argi = argv.begin(); argi != argv.end(); ++argi) pickle.WriteString(*argi); pickle.WriteInt(fds_to_remap.size()); // Wrap the pickle and the fds together in a msghdr ::msghdr msg; memset(&msg, 0, sizeof(msg)); struct iovec iov; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cmsg_buf_; msg.msg_controllen = CMSG_LEN(sizeof(int) * fds_to_remap.size()); struct cmsghdr* cmsg; cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = msg.msg_controllen; int* wire_fds = reinterpret_cast(CMSG_DATA(cmsg)); for (size_t i = 0; i < fds_to_remap.size(); i++) { pickle.WriteInt(fds_to_remap[i].second); wire_fds[i] = fds_to_remap[i].first; } iov.iov_base = const_cast(pickle.data()); iov.iov_len = pickle.size(); int bytes_sent; int bytes_read = -1; // Lock fork server, send the pickle, wait for the reply, unlock if (flock(lockfd_, LOCK_EX)) LOG(ERROR) << "flock failed, errno " << errno; bytes_sent = HANDLE_EINTR(sendmsg(client_fd_, &msg, MSG_WAITALL)); if (bytes_sent > 0) { bytes_read = HANDLE_EINTR(read(client_fd_, msg_buf_, kMAX_MSG_LEN)); } if (flock(lockfd_, LOCK_UN)) LOG(ERROR) << "flock failed, errno " << errno; if (bytes_sent < 1) { LOG(ERROR) << "Can't send to zm, errno " << errno << ", fd " << client_fd_; return (pid_t) -1; } if (bytes_read < 1) { LOG(ERROR) << "Can't get from zm, errno " << errno; return (pid_t) -1; } // Unpickle the reply Pickle reply(msg_buf_, bytes_read); void* iter = NULL; int kind = UnpickleHeader(reply, &iter); if (kind != ZMFORKED) { LOG(ERROR) << "reply wrong kind " << kind; return (pid_t) -1; } pid_t newpid = (pid_t) -1; int pid_errno; if (!reply.ReadInt(&iter, &newpid) || !reply.ReadInt(&iter, &pid_errno)) { LOG(ERROR) << "fork failed, can't read pid/errno"; return (pid_t) -1; } if ((newpid == (pid_t) -1) || pid_errno != 0) { LOG(ERROR) << "fork failed, pid " << newpid << ", errno " << pid_errno; return (pid_t) -1; } return newpid; } // Runs in zygote manager process bool ZygoteManager::LongForkHandler(const Pickle& request, void* iter, Pickle* reply, std::vector** newargv, const int wire_fds[], int num_wire_fds) { file_handle_mapping_vector fds_to_remap; pid_t childpid; reply->WriteInt(ZMFORKED); // Unpickle commandline for new child std::vector* argv = new std::vector; int argc; request.ReadInt(&iter, &argc); for (int i = 0; i < argc; i++) { std::string arg; if (!request.ReadString(&iter, &arg)) { LOG(ERROR) << "can't read arg?"; goto error_reply; } argv->push_back(arg); } // Unpickle file descriptor map for new child int numfds; request.ReadInt(&iter, &numfds); DCHECK(numfds == num_wire_fds); if (numfds != num_wire_fds) { LOG(ERROR) << "numfds " << numfds << " != num_wire_fds " << num_wire_fds; goto error_reply; } for (int i = 0; i < numfds; i++) { int fd; if (!request.ReadInt(&iter, &fd)) { LOG(ERROR) << "can't read fd?"; goto error_reply; } fds_to_remap.push_back(std::pair(wire_fds[i], fd)); } // Mitosis! childpid = fork(); if (childpid != 0) { // parent // first off, close our copy of the child's file descriptors for (file_handle_mapping_vector::const_iterator it = fds_to_remap.begin(); it != fds_to_remap.end(); ++it) { close(it->first); } // Finish formatting the reply reply->WriteInt(childpid); if (childpid == (pid_t) -1) { reply->WriteInt(errno); return false; } else { reply->WriteInt(0); } } else { // child // Apply file descriptor map InjectiveMultimap fd_shuffle; for (file_handle_mapping_vector::const_iterator it = fds_to_remap.begin(); it != fds_to_remap.end(); ++it) { fd_shuffle.push_back(InjectionArc(it->first, it->second, false)); } // Avoid closing descriptor children will need to contact fork server. fd_shuffle.push_back(InjectionArc(client_fd_, client_fd_, false)); // Avoid closing log descriptor we're using int logfd = logging::GetLoggingFileDescriptor(); if (logfd != -1) fd_shuffle.push_back(InjectionArc(logfd, logfd, false)); // And of course avoid closing the cached fds. std::map::iterator i; for (i = cached_fds_.begin(); i != cached_fds_.end(); ++i) { fd_shuffle.push_back(InjectionArc(i->second, i->second, false)); } // If there is any clash in the mapping, this function will DCHECK. if (!ShuffleFileDescriptors(fd_shuffle)) exit(127); // Open this after shuffle to avoid using reserved slots. lockfd_ = open(lockfile_.c_str(), O_RDWR, 0); if (lockfd_ == -1) { // TODO(dkegel): real error handling exit(126); } // Mark it as not to be closed. fd_shuffle.push_back(InjectionArc(lockfd_, lockfd_, false)); // Also closes reserved fds. CloseSuperfluousFds(fd_shuffle); *newargv = argv; // Because *newargv is set, we will return to main instead of looping } return true; error_reply: reply->WriteInt(-1); reply->WriteInt(-1); for (int i=0; i(pickle.data()), pickle.size())); if (bytes_sent < 1) { LOG(ERROR) << "Can't send to zm, errno " << errno << ", fd " << client_fd_; } } // Runs in zygote manager process void ZygoteManager::EnsureProcessTerminatedHandler(const Pickle& request, void* iter) { pid_t childpid; request.ReadInt(&iter, &childpid); NOTIMPLEMENTED(); // TODO(dkegel): put childpid on a watch list, and terminate it // after a while as chrome/common/process_watcher does. } static bool ValidateFilename(const std::string& filename) { // We only have to open one kind of file, but we don't know // the directory it's in, so be as restrictive as we can within // those bounds. static const char* allowed_prefix = "/"; if (filename.compare(0, strlen(allowed_prefix), allowed_prefix) != 0) { LOG(ERROR) << "filename did not start with " << allowed_prefix; return false; } static const char* allowed_suffix = ".pak"; if ((filename.length() <= strlen(allowed_suffix)) || (filename.compare(filename.length() - strlen(allowed_suffix), strlen(allowed_suffix), allowed_suffix) != 0)) { LOG(ERROR) << "filename did not end in " << allowed_suffix; return false; } if (filename.find("../") != std::string::npos) { LOG(ERROR) << "filename contained relative component"; return false; } static const char* forbidden_prefixes[] = { "/var/", "/tmp/", "/etc/", "/dev/", "/proc/", 0 }; for (const char** p = forbidden_prefixes; *p; p++) { if (filename.compare(0, strlen(*p), *p) == 0) { LOG(ERROR) << "filename began with " << *p; return false; } } return true; } // Runs in browser process int ZygoteManager::OpenFile(const std::string& filename) { // For security reasons, we only support .pak files, // and only in certain locations. if (!ValidateFilename(filename)) { LOG(INFO) << "ZygoteManager: filename " << filename << " disallowed."; return -1; } std::map::iterator mapiter = cached_fds_.find(filename); if (mapiter != cached_fds_.end()) return mapiter->second; if (client_fd_ == -1) return -1; Pickle pickle; pickle.WriteString(kZMagic); pickle.WriteInt(getpid()); pickle.WriteInt(ZMOPEN); pickle.WriteString(filename); // Get ready to receive fds ::msghdr msg = {0}; struct iovec iov = {msg_buf_, kMAX_MSG_LEN}; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cmsg_buf_; msg.msg_controllen = kMAX_CMSG_LEN; ssize_t bytes_sent; ssize_t bytes_read = 0; if (flock(lockfd_, LOCK_EX)) LOG(ERROR) << "flock failed, errno " << errno; bytes_sent = HANDLE_EINTR( write(client_fd_, const_cast(pickle.data()), pickle.size())); if (bytes_sent > 0) { bytes_read = HANDLE_EINTR(recvmsg(client_fd_, &msg, MSG_WAITALL)); } if (flock(lockfd_, LOCK_UN)) LOG(ERROR) << "flock failed, errno " << errno; if (bytes_sent < 1) { LOG(ERROR) << "Can't send to zm, errno " << errno << ", fd " << client_fd_; return -1; } if (bytes_read < 1) { LOG(ERROR) << "Can't get from zm, errno " << errno; return -1; } // Locate the sole block of sent file descriptors within the list of // control messages const int* wire_fds = NULL; unsigned num_wire_fds = 0; if (msg.msg_controllen > 0) { struct cmsghdr* cmsg; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0); assert(payload_len % sizeof(int) == 0); wire_fds = reinterpret_cast(CMSG_DATA(cmsg)); num_wire_fds = payload_len / sizeof(int); break; } } } DCHECK(!(msg.msg_flags & MSG_CTRUNC)); // Unpickle the reply Pickle reply(msg_buf_, bytes_read); void* iter = NULL; int kind = UnpickleHeader(reply, &iter); if (kind != ZMOPENED) { LOG(ERROR) << "reply wrong kind " << kind; goto error_close; } int newfd_errno; if (!reply.ReadInt(&iter, &newfd_errno)) { LOG(ERROR) << "open failed, can't read errno"; goto error_close; } if (newfd_errno != 0) { LOG(ERROR) << "open failed, errno " << newfd_errno; goto error_close; } if (num_wire_fds != 1) { LOG(ERROR) << "open failed, reply wrong number fds " << num_wire_fds; goto error_close; } if (wire_fds[0] == -1) { LOG(ERROR) << "open failed, fd -1"; NOTREACHED(); return -1; } return wire_fds[0]; error_close: for (unsigned i=0; iWriteInt(ZMOPENED); std::string filename; if (!request.ReadString(&iter, &filename)) { LOG(ERROR) << "no filename?"; return false; } if (!ValidateFilename(filename)) { // Fake a unix error code reply->WriteInt(EPERM); return false; } std::map::iterator i; int newfd; std::map::iterator mapiter = cached_fds_.find(filename); if (mapiter == cached_fds_.end()) { // Verify that file is a plain file struct stat statbuf; if (lstat(filename.c_str(), &statbuf) != 0) { LOG(ERROR) << "can't stat " << filename << ", errno " << errno; return false; } if (!S_ISREG(statbuf.st_mode)) { LOG(ERROR) << "not regular file " << filename; // Fake a unix error code reply->WriteInt(EISDIR); return false; } newfd = open(filename.c_str(), O_RDONLY, 0); if (newfd != -1) { cached_fds_[filename] = newfd; } else { LOG(ERROR) << "can't open " << filename << ", errno " << errno; } } else { newfd = mapiter->second; } if (newfd == -1) { reply->WriteInt(errno); } else { reply->WriteInt(0); msg->msg_control = cmsg_buf_; msg->msg_controllen = CMSG_LEN(sizeof(int)); struct cmsghdr* cmsg; cmsg = CMSG_FIRSTHDR(msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = msg->msg_controllen; int* wire_fds = reinterpret_cast(CMSG_DATA(cmsg)); wire_fds[0] = newfd; } return true; } // Runs in zygote manager process bool ZygoteManager::ReadAndHandleMessage(std::vector** newargv) { // Wait for activity either on canary fd or main fd. struct pollfd watcher[2]; memset(watcher, 0, sizeof(watcher)); watcher[0].fd = canary_fd_; watcher[0].events = POLLIN|POLLHUP; watcher[1].fd = server_fd_; watcher[1].events = POLLIN; // Wait at most one minute. This lets us detect case where // canary socket is closed abruptly because the main client aborted. // Also lets us reap dead children once a minute even if we don't get SIGCHLD. // We'd like to wait less time, but that's hard on battery life. // Note: handle EINTR manually here, not with wrapper, as we need // to return when we're interrupted so caller can reap promptly. int nactive = poll(watcher, 2, 60*1000); if (nactive == -1) { if (errno == EINTR) { // Probably SIGCHLD. Return to main loop so it can reap. return true; } LOG(ERROR) << "poll failed, errno " << errno << ", aborting"; return false; } // If it was the canary, exit if (watcher[0].revents != 0) { LOG(INFO) << "notified of peer destruction, exiting"; return false; } if ((watcher[1].revents & POLLIN) != POLLIN) { // spurious wakeup? return true; } ssize_t bytes_read = 0; struct msghdr msg = {0}; struct iovec iov = {msg_buf_, kMAX_MSG_LEN}; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cmsg_buf_; msg.msg_controllen = kMAX_CMSG_LEN; bytes_read = HANDLE_EINTR(recvmsg(server_fd_, &msg, MSG_WAITALL)); if (bytes_read == 0) { LOG(ERROR) << "got EOF, aborting"; return false; } if (bytes_read == -1) { LOG(ERROR) << "got errno " << errno << ", aborting"; return false; } // Locate the sole block of sent file descriptors within the list of // control messages const int* wire_fds = NULL; unsigned num_wire_fds = 0; if (msg.msg_controllen > 0) { struct cmsghdr* cmsg; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0); assert(payload_len % sizeof(int) == 0); wire_fds = reinterpret_cast(CMSG_DATA(cmsg)); num_wire_fds = payload_len / sizeof(int); break; } } } DCHECK(!(msg.msg_flags & MSG_CTRUNC)); // Unpickle/parse message Pickle pickle(msg_buf_, bytes_read); void* iter = NULL; std::string magic; if (!pickle.ReadString(&iter, &magic) || magic != std::string(kZMagic)) { LOG(ERROR) << "msg didn't start with " << kZMagic << ", got " << magic; for (unsigned i=0; i(reply.data()), reply.size()}; replymsg.msg_iov = &riov; replymsg.msg_iovlen = 1; int bytes_sent; bytes_sent = HANDLE_EINTR(sendmsg(server_fd_, &replymsg, MSG_WAITALL)); if (bytes_sent != static_cast(riov.iov_len)) { // TODO(dkegel): real error handling LOG(ERROR) << "Can't send reply."; return false; } return true; } // Called only by ChromeMain(), forks the zygote manager process. std::vector* ZygoteManager::Start() { DCHECK(lockfd_ == -1); DCHECK(canary_fd_ == -1); DCHECK(server_fd_ == -1); DCHECK(client_fd_ == -1); #ifndef OFFICIAL_BUILD // Disallow nested ZygoteManager servers CHECK(getenv("ZYGOTE_MANAGER_STARTED") == NULL) << "already started?!"; (void) setenv("ZYGOTE_MANAGER_STARTED", "1", 1); #endif int pipe_fds[2]; // Avoid using the reserved fd slots. int reserved_fds[kReservedFds]; for (int i=0; i < kReservedFds; i++) reserved_fds[i] = open("/dev/null", O_RDONLY, 0); // Create the main communications pipe. int err = HANDLE_EINTR(socketpair(AF_UNIX, SOCK_DGRAM, 0, pipe_fds)); if (err != 0) { // TODO(dkegel): real error handling exit(99); } server_fd_ = pipe_fds[1]; client_fd_ = pipe_fds[0]; // Create the pipe used only to relay destruction event server. // Must be SOCK_STREAM so close() is sensed by poll(). err = HANDLE_EINTR(socketpair(AF_UNIX, SOCK_STREAM, 0, pipe_fds)); if (err != 0) { // TODO(dkegel): real error handling exit(99); } // Create lock file. // TODO(dkegel): get rid of this char lockfile[256]; strcpy(lockfile, "/tmp/zygote_manager_lock.XXXXXX"); lockfd_ = mkstemp(lockfile); if (lockfd_ == -1) { // TODO(dkegel): real error handling exit(99); } lockfile_.assign(lockfile); // Fork a fork server. pid_t childpid = fork(); if (!childpid) { for (int i=0; i < kReservedFds; i++) close(reserved_fds[i]); // Original child. Continues on with the main program // and becomes the first client. close(server_fd_); server_fd_ = -1; close(pipe_fds[1]); canary_fd_ = pipe_fds[0]; // Return now to indicate this is the original process. return NULL; } else { close(lockfd_); close(pipe_fds[0]); canary_fd_ = pipe_fds[1]; // We need to accept SIGCHLD, even though our handler is a no-op because // otherwise we cannot wait on children. (According to POSIX 2001.) // (And otherwise poll() might not wake up on SIGCHLD.) struct sigaction action; memset(&action, 0, sizeof(action)); action.sa_handler = SIGCHLDHandler; CHECK(sigaction(SIGCHLD, &action, NULL) == 0); // Original process. Acts as the server. while (true) { std::vector* newargv = NULL; if (!ReadAndHandleMessage(&newargv)) break; if (newargv) { // Return new commandline to show caller this is a new child process. return newargv; } // Server process continues around loop. // Reap children. while (true) { int status = -1; pid_t reaped = waitpid(-1, &status, WNOHANG); if (reaped != -1 && reaped != 0) { LOG(INFO) << "Reaped pid " << reaped; continue; } break; } } // Server cleanup after EOF or error reading from the socket. Delete(FilePath(lockfile_), false); // TODO(dkegel): real error handling LOG(INFO) << "exiting. " << cached_fds_.size() << " cached fds."; std::map::iterator i; for (i = cached_fds_.begin(); i != cached_fds_.end(); ++i) { LOG(INFO) << "Closing fd " << i->second << " filename " << i->first; close(i->second); } exit(0); } } } #endif // defined(OS_LINUX)