// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ipc/ipc_channel_nacl.h" #include #include #include #include #include #include #include "base/bind.h" #include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/message_loop/message_pump_for_io.h" #include "base/synchronization/lock.h" #include "base/task/single_thread_task_runner.h" #include "base/task/task_runner_util.h" #include "base/threading/simple_thread.h" #include "base/threading/thread_task_runner_handle.h" #include "base/trace_event/trace_event.h" #include "ipc/ipc_listener.h" #include "ipc/ipc_logging.h" #include "ipc/ipc_message_attachment_set.h" #include "ipc/ipc_platform_file_attachment_posix.h" #include "native_client/src/public/imc_syscalls.h" #include "native_client/src/public/imc_types.h" namespace IPC { struct MessageContents { std::vector data; std::vector fds; }; namespace { bool ReadDataOnReaderThread(int pipe, MessageContents* contents) { DCHECK(pipe >= 0); if (pipe < 0) return false; contents->data.resize(Channel::kReadBufferSize); contents->fds.resize(NACL_ABI_IMC_DESC_MAX); NaClAbiNaClImcMsgIoVec iov = { &contents->data[0], contents->data.size() }; NaClAbiNaClImcMsgHdr msg = { &iov, 1, &contents->fds[0], contents->fds.size() }; int bytes_read = imc_recvmsg(pipe, &msg, 0); if (bytes_read <= 0) { // NaClIPCAdapter::BlockingReceive returns -1 when the pipe closes (either // due to error or for regular shutdown). contents->data.clear(); contents->fds.clear(); return false; } DCHECK(bytes_read); // Resize the buffers down to the number of bytes and fds we actually read. contents->data.resize(bytes_read); contents->fds.resize(msg.desc_length); return true; } } // namespace // static constexpr size_t Channel::kMaximumMessageSize; class ChannelNacl::ReaderThreadRunner : public base::DelegateSimpleThread::Delegate { public: // |pipe|: A file descriptor from which we will read using imc_recvmsg. // |data_read_callback|: A callback we invoke (on the main thread) when we // have read data. // |failure_callback|: A callback we invoke when we have a failure reading // from |pipe|. // |main_message_loop|: A proxy for the main thread, where we will invoke the // above callbacks. ReaderThreadRunner( int pipe, base::RepeatingCallback)> data_read_callback, base::RepeatingCallback failure_callback, scoped_refptr main_task_runner); ReaderThreadRunner(const ReaderThreadRunner&) = delete; ReaderThreadRunner& operator=(const ReaderThreadRunner&) = delete; // DelegateSimpleThread implementation. Reads data from the pipe in a loop // until either we are told to quit or a read fails. void Run() override; private: int pipe_; base::RepeatingCallback)> data_read_callback_; base::RepeatingCallback failure_callback_; scoped_refptr main_task_runner_; }; ChannelNacl::ReaderThreadRunner::ReaderThreadRunner( int pipe, base::RepeatingCallback)> data_read_callback, base::RepeatingCallback failure_callback, scoped_refptr main_task_runner) : pipe_(pipe), data_read_callback_(data_read_callback), failure_callback_(failure_callback), main_task_runner_(main_task_runner) {} void ChannelNacl::ReaderThreadRunner::Run() { while (true) { std::unique_ptr msg_contents(new MessageContents); bool success = ReadDataOnReaderThread(pipe_, msg_contents.get()); if (success) { main_task_runner_->PostTask( FROM_HERE, base::BindOnce(data_read_callback_, std::move(msg_contents))); } else { main_task_runner_->PostTask(FROM_HERE, failure_callback_); // Because the read failed, we know we're going to quit. Don't bother // trying to read again. return; } } } ChannelNacl::ChannelNacl(const IPC::ChannelHandle& channel_handle, Mode mode, Listener* listener) : ChannelReader(listener), mode_(mode), waiting_connect_(true), pipe_(-1), weak_ptr_factory_(this) { if (!CreatePipe(channel_handle)) { // The pipe may have been closed already. const char *modestr = (mode_ & MODE_SERVER_FLAG) ? "server" : "client"; LOG(WARNING) << "Unable to create pipe in " << modestr << " mode"; } } ChannelNacl::~ChannelNacl() { CleanUp(); Close(); } bool ChannelNacl::Connect() { WillConnect(); if (pipe_ == -1) { DLOG(WARNING) << "Channel creation failed"; return false; } // Note that Connect is called on the "Channel" thread (i.e., the same thread // where Channel::Send will be called, and the same thread that should receive // messages). The constructor might be invoked on another thread (see // ChannelProxy for an example of that). Therefore, we must wait until Connect // is called to decide which SingleThreadTaskRunner to pass to // ReaderThreadRunner. reader_thread_runner_ = std::make_unique( pipe_, base::BindRepeating(&ChannelNacl::DidRecvMsg, weak_ptr_factory_.GetWeakPtr()), base::BindRepeating(&ChannelNacl::ReadDidFail, weak_ptr_factory_.GetWeakPtr()), base::ThreadTaskRunnerHandle::Get()); reader_thread_ = std::make_unique( reader_thread_runner_.get(), "ipc_channel_nacl reader thread"); reader_thread_->Start(); waiting_connect_ = false; // If there were any messages queued before connection, send them. ProcessOutgoingMessages(); base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(&ChannelNacl::CallOnChannelConnected, weak_ptr_factory_.GetWeakPtr())); return true; } void ChannelNacl::Close() { // For now, we assume that at shutdown, the reader thread will be woken with // a failure (see NaClIPCAdapter::BlockingRead and CloseChannel). Or... we // might simply be killed with no chance to clean up anyway :-). // If untrusted code tries to close the channel prior to shutdown, it's likely // to hang. // TODO(dmichael): Can we do anything smarter here to make sure the reader // thread wakes up and quits? reader_thread_->Join(); close(pipe_); pipe_ = -1; reader_thread_runner_.reset(); reader_thread_.reset(); read_queue_.clear(); output_queue_.clear(); } bool ChannelNacl::Send(Message* message) { DCHECK(!message->HasAttachments()); DVLOG(2) << "sending message @" << message << " on channel @" << this << " with type " << message->type(); std::unique_ptr message_ptr(message); #if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED) Logging::GetInstance()->OnSendMessage(message_ptr.get()); #endif // BUILDFLAG(IPC_MESSAGE_LOG_ENABLED) TRACE_EVENT_WITH_FLOW0("toplevel.flow", "ChannelNacl::Send", message->header()->flags, TRACE_EVENT_FLAG_FLOW_OUT); output_queue_.push_back(std::move(message_ptr)); if (!waiting_connect_) return ProcessOutgoingMessages(); return true; } void ChannelNacl::DidRecvMsg(std::unique_ptr contents) { // Close sets the pipe to -1. It's possible we'll get a buffer sent to us from // the reader thread after Close is called. If so, we ignore it. if (pipe_ == -1) return; auto data = std::make_unique>(); data->swap(contents->data); read_queue_.push_back(std::move(data)); input_attachments_.reserve(contents->fds.size()); for (int fd : contents->fds) { input_attachments_.push_back( new internal::PlatformFileAttachment(base::ScopedFD(fd))); } contents->fds.clear(); // In POSIX, we would be told when there are bytes to read by implementing // OnFileCanReadWithoutBlocking in MessagePumpForIO::FdWatcher. In NaCl, we // instead know at this point because the reader thread posted some data to // us. ProcessIncomingMessages(); } void ChannelNacl::ReadDidFail() { Close(); } bool ChannelNacl::CreatePipe( const IPC::ChannelHandle& channel_handle) { DCHECK(pipe_ == -1); // There's one possible case in NaCl: // 1) It's a channel wrapping a pipe that is given to us. // We don't support these: // 2) It's for a named channel. // 3) It's for a client that we implement ourself. // 4) It's the initial IPC channel. if (channel_handle.socket.fd == -1) { NOTIMPLEMENTED(); return false; } pipe_ = channel_handle.socket.fd; return true; } bool ChannelNacl::ProcessOutgoingMessages() { DCHECK(!waiting_connect_); // Why are we trying to send messages if there's // no connection? if (output_queue_.empty()) return true; if (pipe_ == -1) return false; // Write out all the messages. The trusted implementation is guaranteed to not // block. See NaClIPCAdapter::Send for the implementation of imc_sendmsg. while (!output_queue_.empty()) { std::unique_ptr msg = std::move(output_queue_.front()); output_queue_.pop_front(); const size_t num_fds = msg->attachment_set()->size(); DCHECK(num_fds <= MessageAttachmentSet::kMaxDescriptorsPerMessage); std::vector fds; fds.reserve(num_fds); for (size_t i = 0; i < num_fds; i++) { scoped_refptr attachment = msg->attachment_set()->GetAttachmentAt(i); DCHECK_EQ(MessageAttachment::Type::PLATFORM_FILE, attachment->GetType()); fds.push_back(static_cast(*attachment) .TakePlatformFile()); } NaClAbiNaClImcMsgIoVec iov = { const_cast(msg->data()), msg->size() }; NaClAbiNaClImcMsgHdr msgh = {&iov, 1, fds.data(), num_fds}; ssize_t bytes_written = imc_sendmsg(pipe_, &msgh, 0); DCHECK(bytes_written); // The trusted side shouldn't return 0. if (bytes_written < 0) { // The trusted side should only ever give us an error of EPIPE. We // should never be interrupted, nor should we get EAGAIN. DCHECK(errno == EPIPE); Close(); PLOG(ERROR) << "pipe_ error on " << pipe_ << " Currently writing message of size: " << msg->size(); return false; } else { msg->attachment_set()->CommitAllDescriptors(); } // Message sent OK! DVLOG(2) << "sent message @" << msg.get() << " with type " << msg->type() << " on fd " << pipe_; } return true; } void ChannelNacl::CallOnChannelConnected() { listener()->OnChannelConnected(-1); } ChannelNacl::ReadState ChannelNacl::ReadData( char* buffer, int buffer_len, int* bytes_read) { *bytes_read = 0; if (pipe_ == -1) return READ_FAILED; if (read_queue_.empty()) return READ_PENDING; while (!read_queue_.empty() && *bytes_read < buffer_len) { std::vector* vec = read_queue_.front().get(); size_t bytes_to_read = buffer_len - *bytes_read; if (vec->size() <= bytes_to_read) { // We can read and discard the entire vector. std::copy(vec->begin(), vec->end(), buffer + *bytes_read); *bytes_read += vec->size(); read_queue_.pop_front(); } else { // Read all the bytes we can and discard them from the front of the // vector. (This can be slowish, since erase has to move the back of the // vector to the front, but it's hopefully a temporary hack and it keeps // the code simple). std::copy(vec->begin(), vec->begin() + bytes_to_read, buffer + *bytes_read); vec->erase(vec->begin(), vec->begin() + bytes_to_read); *bytes_read += bytes_to_read; } } return READ_SUCCEEDED; } bool ChannelNacl::ShouldDispatchInputMessage(Message* msg) { return true; } bool ChannelNacl::GetAttachments(Message* msg) { uint16_t header_fds = msg->header()->num_fds; CHECK(header_fds == input_attachments_.size()); if (header_fds == 0) return true; // Nothing to do. for (auto& attachment : input_attachments_) { msg->attachment_set()->AddAttachment(std::move(attachment)); } input_attachments_.clear(); return true; } bool ChannelNacl::DidEmptyInputBuffers() { // When the input data buffer is empty, the attachments should be too. return input_attachments_.empty(); } void ChannelNacl::HandleInternalMessage(const Message& msg) { // The trusted side IPC::Channel should handle the "hello" handshake; we // should not receive the "Hello" message. NOTREACHED(); } // Channel's methods // static std::unique_ptr Channel::Create( const IPC::ChannelHandle& channel_handle, Mode mode, Listener* listener) { return std::make_unique(channel_handle, mode, listener); } } // namespace IPC