| // Copyright 2012 The Chromium Authors |
| // 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 <errno.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <sys/types.h> |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/message_loop/message_pump_for_io.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/synchronization/lock.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/threading/simple_thread.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<char> data; |
| std::vector<int> 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<void(std::unique_ptr<MessageContents>)> |
| data_read_callback, |
| base::RepeatingCallback<void()> failure_callback, |
| scoped_refptr<base::SingleThreadTaskRunner> 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<void(std::unique_ptr<MessageContents>)> |
| data_read_callback_; |
| base::RepeatingCallback<void()> failure_callback_; |
| scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; |
| }; |
| |
| ChannelNacl::ReaderThreadRunner::ReaderThreadRunner( |
| int pipe, |
| base::RepeatingCallback<void(std::unique_ptr<MessageContents>)> |
| data_read_callback, |
| base::RepeatingCallback<void()> failure_callback, |
| scoped_refptr<base::SingleThreadTaskRunner> 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<MessageContents> 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<ReaderThreadRunner>( |
| pipe_, |
| base::BindRepeating(&ChannelNacl::DidRecvMsg, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindRepeating(&ChannelNacl::ReadDidFail, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::SingleThreadTaskRunner::GetCurrentDefault()); |
| reader_thread_ = std::make_unique<base::DelegateSimpleThread>( |
| 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::SingleThreadTaskRunner::GetCurrentDefault()->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> 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<MessageContents> 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<std::vector<char>>(); |
| 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<Message> 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<int> fds; |
| fds.reserve(num_fds); |
| for (size_t i = 0; i < num_fds; i++) { |
| scoped_refptr<MessageAttachment> attachment = |
| msg->attachment_set()->GetAttachmentAt(i); |
| DCHECK_EQ(MessageAttachment::Type::PLATFORM_FILE, attachment->GetType()); |
| fds.push_back(static_cast<internal::PlatformFileAttachment&>(*attachment) |
| .TakePlatformFile()); |
| } |
| |
| NaClAbiNaClImcMsgIoVec iov = { |
| const_cast<void*>(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<char>* 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. |
| base::ranges::copy(*vec, 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> Channel::Create( |
| const IPC::ChannelHandle& channel_handle, |
| Mode mode, |
| Listener* listener) { |
| return std::make_unique<ChannelNacl>(channel_handle, mode, listener); |
| } |
| |
| } // namespace IPC |