blob: 216528813519c8f2b9053340fe7c50144cb868e2 [file] [log] [blame]
// 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 "chrome/nacl/nacl_ipc_adapter.h"
#include <limits.h>
#include <string.h>
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/memory/scoped_ptr.h"
#include "base/shared_memory.h"
#include "build/build_config.h"
#include "ipc/ipc_message_macros.h"
#include "ipc/ipc_platform_file.h"
#include "native_client/src/trusted/desc/nacl_desc_custom.h"
#include "native_client/src/trusted/desc/nacl_desc_wrapper.h"
#include "ppapi/proxy/ppapi_messages.h"
namespace {
enum BufferSizeStatus {
// The buffer contains a full message with no extra bytes.
MESSAGE_IS_COMPLETE,
// The message doesn't fit and the buffer contains only some of it.
MESSAGE_IS_TRUNCATED,
// The buffer contains a full message + extra data.
MESSAGE_HAS_EXTRA_DATA
};
BufferSizeStatus GetBufferStatus(const char* data, size_t len) {
if (len < sizeof(NaClIPCAdapter::NaClMessageHeader))
return MESSAGE_IS_TRUNCATED;
const NaClIPCAdapter::NaClMessageHeader* header =
reinterpret_cast<const NaClIPCAdapter::NaClMessageHeader*>(data);
uint32 message_size =
sizeof(NaClIPCAdapter::NaClMessageHeader) + header->payload_size;
if (len == message_size)
return MESSAGE_IS_COMPLETE;
if (len > message_size)
return MESSAGE_HAS_EXTRA_DATA;
return MESSAGE_IS_TRUNCATED;
}
// This object allows the NaClDesc to hold a reference to a NaClIPCAdapter and
// forward calls to it.
struct DescThunker {
explicit DescThunker(NaClIPCAdapter* adapter_param)
: adapter(adapter_param) {
}
scoped_refptr<NaClIPCAdapter> adapter;
};
NaClIPCAdapter* ToAdapter(void* handle) {
return static_cast<DescThunker*>(handle)->adapter.get();
}
// NaClDescCustom implementation.
void NaClDescCustomDestroy(void* handle) {
delete static_cast<DescThunker*>(handle);
}
ssize_t NaClDescCustomSendMsg(void* handle, const NaClImcTypedMsgHdr* msg,
int /* flags */) {
return static_cast<ssize_t>(ToAdapter(handle)->Send(msg));
}
ssize_t NaClDescCustomRecvMsg(void* handle, NaClImcTypedMsgHdr* msg,
int /* flags */) {
return static_cast<ssize_t>(ToAdapter(handle)->BlockingReceive(msg));
}
NaClDesc* MakeNaClDescCustom(NaClIPCAdapter* adapter) {
NaClDescCustomFuncs funcs = NACL_DESC_CUSTOM_FUNCS_INITIALIZER;
funcs.Destroy = NaClDescCustomDestroy;
funcs.SendMsg = NaClDescCustomSendMsg;
funcs.RecvMsg = NaClDescCustomRecvMsg;
// NaClDescMakeCustomDesc gives us a reference on the returned NaClDesc.
return NaClDescMakeCustomDesc(new DescThunker(adapter), &funcs);
}
void DeleteChannel(IPC::Channel* channel) {
delete channel;
}
void WriteHandle(int handle_index,
const ppapi::proxy::SerializedHandle& handle,
IPC::Message* message) {
ppapi::proxy::SerializedHandle::WriteHeader(handle.header(), message);
// Now write the handle itself in POSIX style.
message->WriteBool(true); // valid == true
message->WriteInt(handle_index);
}
typedef std::vector<ppapi::proxy::SerializedHandle> Handles;
// We define one overload for catching SerializedHandles, so that we can share
// them correctly to the untrusted side, and another for handling all other
// parameters. See ConvertHandlesImpl for how these get used.
void ConvertHandle(const ppapi::proxy::SerializedHandle& handle,
Handles* handles, IPC::Message* msg, int* handle_index) {
handles->push_back(handle);
if (msg)
WriteHandle((*handle_index)++, handle, msg);
}
// This overload is to catch all types other than SerializedHandle. On Windows,
// |msg| will be a valid pointer, and we must write |param| to it
template <class T>
void ConvertHandle(const T& param, Handles* /* handles */, IPC::Message* msg,
int* /* handle_index */) {
// It's not a handle, so just write to the output message, if necessary.
if (msg)
IPC::WriteParam(msg, param);
}
// These just break apart the given tuple and run ConvertHandle over each param.
// The idea is to extract any handles in the tuple, while writing all data to
// msg (if msg is valid). The msg will only be valid on Windows, where we need
// to re-write all of the message parameters, writing the handles in POSIX style
// for NaCl.
template <class A>
void ConvertHandlesImpl(const Tuple1<A>& t1, Handles* handles,
IPC::Message* msg) {
int handle_index = 0;
ConvertHandle(t1.a, handles, msg, &handle_index);
}
template <class A, class B>
void ConvertHandlesImpl(const Tuple2<A, B>& t1, Handles* handles,
IPC::Message* msg) {
int handle_index = 0;
ConvertHandle(t1.a, handles, msg, &handle_index);
ConvertHandle(t1.b, handles, msg, &handle_index);
}
template <class A, class B, class C>
void ConvertHandlesImpl(const Tuple3<A, B, C>& t1, Handles* handles,
IPC::Message* msg) {
int handle_index = 0;
ConvertHandle(t1.a, handles, msg, &handle_index);
ConvertHandle(t1.b, handles, msg, &handle_index);
ConvertHandle(t1.c, handles, msg, &handle_index);
}
template <class A, class B, class C, class D>
void ConvertHandlesImpl(const Tuple4<A, B, C, D>& t1, Handles* handles,
IPC::Message* msg) {
int handle_index = 0;
ConvertHandle(t1.a, handles, msg, &handle_index);
ConvertHandle(t1.b, handles, msg, &handle_index);
ConvertHandle(t1.c, handles, msg, &handle_index);
ConvertHandle(t1.d, handles, msg, &handle_index);
}
template <class MessageType>
class HandleConverter {
public:
explicit HandleConverter(const IPC::Message* msg)
: msg_(static_cast<const MessageType*>(msg)) {
}
bool ConvertMessage(Handles* handles, IPC::Message* out_msg) {
typename TupleTypes<typename MessageType::Schema::Param>::ValueTuple params;
if (!MessageType::Read(msg_, &params))
return false;
ConvertHandlesImpl(params, handles, out_msg);
return true;
}
bool ConvertReply(Handles* handles, IPC::SyncMessage* out_msg) {
typename TupleTypes<typename MessageType::Schema::ReplyParam>::ValueTuple
params;
if (!MessageType::ReadReplyParam(msg_, &params))
return false;
// If we need to rewrite the message (i.e., on Windows), we need to make
// sure we write the message id first.
if (out_msg) {
out_msg->set_reply();
int id = IPC::SyncMessage::GetMessageId(*msg_);
out_msg->WriteInt(id);
}
ConvertHandlesImpl(params, handles, out_msg);
return true;
}
// TODO(dmichael): Add ConvertSyncMessage for outgoing sync messages, if we
// ever pass handles in one of those.
private:
const MessageType* msg_;
};
} // namespace
class NaClIPCAdapter::RewrittenMessage
: public base::RefCounted<RewrittenMessage> {
public:
RewrittenMessage();
bool is_consumed() const { return data_read_cursor_ == data_len_; }
void SetData(const NaClIPCAdapter::NaClMessageHeader& header,
const void* payload, size_t payload_length);
int Read(NaClImcTypedMsgHdr* msg);
void AddDescriptor(nacl::DescWrapper* desc) { descs_.push_back(desc); }
size_t desc_count() const { return descs_.size(); }
private:
friend class base::RefCounted<RewrittenMessage>;
~RewrittenMessage() {}
scoped_array<char> data_;
size_t data_len_;
// Offset into data where the next read will happen. This will be equal to
// data_len_ when all data has been consumed.
size_t data_read_cursor_;
// Wrapped descriptors for transfer to untrusted code.
ScopedVector<nacl::DescWrapper> descs_;
};
NaClIPCAdapter::RewrittenMessage::RewrittenMessage()
: data_len_(0),
data_read_cursor_(0) {
}
void NaClIPCAdapter::RewrittenMessage::SetData(
const NaClIPCAdapter::NaClMessageHeader& header,
const void* payload,
size_t payload_length) {
DCHECK(!data_.get() && data_len_ == 0);
size_t header_len = sizeof(NaClIPCAdapter::NaClMessageHeader);
data_len_ = header_len + payload_length;
data_.reset(new char[data_len_]);
memcpy(data_.get(), &header, sizeof(NaClIPCAdapter::NaClMessageHeader));
memcpy(&data_[header_len], payload, payload_length);
}
int NaClIPCAdapter::RewrittenMessage::Read(NaClImcTypedMsgHdr* msg) {
CHECK(data_len_ >= data_read_cursor_);
char* dest_buffer = static_cast<char*>(msg->iov[0].base);
size_t dest_buffer_size = msg->iov[0].length;
size_t bytes_to_write = std::min(dest_buffer_size,
data_len_ - data_read_cursor_);
if (bytes_to_write == 0)
return 0;
memcpy(dest_buffer, &data_[data_read_cursor_], bytes_to_write);
data_read_cursor_ += bytes_to_write;
// Once all data has been consumed, transfer any file descriptors.
if (is_consumed()) {
nacl_abi_size_t desc_count = static_cast<nacl_abi_size_t>(descs_.size());
CHECK(desc_count <= msg->ndesc_length);
msg->ndesc_length = desc_count;
for (nacl_abi_size_t i = 0; i < desc_count; i++) {
// Copy the NaClDesc to the buffer and add a ref so it won't be freed
// when we clear our ScopedVector.
msg->ndescv[i] = descs_[i]->desc();
NaClDescRef(descs_[i]->desc());
}
descs_.clear();
} else {
msg->ndesc_length = 0;
}
return static_cast<int>(bytes_to_write);
}
NaClIPCAdapter::LockedData::LockedData()
: channel_closed_(false) {
}
NaClIPCAdapter::LockedData::~LockedData() {
}
NaClIPCAdapter::IOThreadData::IOThreadData() {
}
NaClIPCAdapter::IOThreadData::~IOThreadData() {
}
NaClIPCAdapter::NaClIPCAdapter(const IPC::ChannelHandle& handle,
base::TaskRunner* runner)
: lock_(),
cond_var_(&lock_),
task_runner_(runner),
locked_data_() {
io_thread_data_.channel_.reset(
new IPC::Channel(handle, IPC::Channel::MODE_SERVER, this));
task_runner_->PostTask(FROM_HERE,
base::Bind(&NaClIPCAdapter::ConnectChannelOnIOThread, this));
}
NaClIPCAdapter::NaClIPCAdapter(scoped_ptr<IPC::Channel> channel,
base::TaskRunner* runner)
: lock_(),
cond_var_(&lock_),
task_runner_(runner),
locked_data_() {
io_thread_data_.channel_ = channel.Pass();
}
// Note that this message is controlled by the untrusted code. So we should be
// skeptical of anything it contains and quick to give up if anything is fishy.
int NaClIPCAdapter::Send(const NaClImcTypedMsgHdr* msg) {
if (msg->iov_length != 1)
return -1;
base::AutoLock lock(lock_);
const char* input_data = static_cast<char*>(msg->iov[0].base);
size_t input_data_len = msg->iov[0].length;
if (input_data_len > IPC::Channel::kMaximumMessageSize) {
ClearToBeSent();
return -1;
}
// current_message[_len] refers to the total input data received so far.
const char* current_message;
size_t current_message_len;
bool did_append_input_data;
if (locked_data_.to_be_sent_.empty()) {
// No accumulated data, we can avoid a copy by referring to the input
// buffer (the entire message fitting in one call is the common case).
current_message = input_data;
current_message_len = input_data_len;
did_append_input_data = false;
} else {
// We've already accumulated some data, accumulate this new data and
// point to the beginning of the buffer.
// Make sure our accumulated message size doesn't overflow our max. Since
// we know that data_len < max size (checked above) and our current
// accumulated value is also < max size, we just need to make sure that
// 2x max size can never overflow.
COMPILE_ASSERT(IPC::Channel::kMaximumMessageSize < (UINT_MAX / 2),
MaximumMessageSizeWillOverflow);
size_t new_size = locked_data_.to_be_sent_.size() + input_data_len;
if (new_size > IPC::Channel::kMaximumMessageSize) {
ClearToBeSent();
return -1;
}
locked_data_.to_be_sent_.append(input_data, input_data_len);
current_message = &locked_data_.to_be_sent_[0];
current_message_len = locked_data_.to_be_sent_.size();
did_append_input_data = true;
}
// Check the total data we've accumulated so far to see if it contains a full
// message.
switch (GetBufferStatus(current_message, current_message_len)) {
case MESSAGE_IS_COMPLETE: {
// Got a complete message, can send it out. This will be the common case.
bool success = SendCompleteMessage(current_message, current_message_len);
ClearToBeSent();
return success ? static_cast<int>(input_data_len) : -1;
}
case MESSAGE_IS_TRUNCATED:
// For truncated messages, just accumulate the new data (if we didn't
// already do so above) and go back to waiting for more.
if (!did_append_input_data)
locked_data_.to_be_sent_.append(input_data, input_data_len);
return static_cast<int>(input_data_len);
case MESSAGE_HAS_EXTRA_DATA:
default:
// When the plugin gives us too much data, it's an error.
ClearToBeSent();
return -1;
}
}
int NaClIPCAdapter::BlockingReceive(NaClImcTypedMsgHdr* msg) {
if (msg->iov_length != 1)
return -1;
int retval = 0;
{
base::AutoLock lock(lock_);
while (locked_data_.to_be_received_.empty() &&
!locked_data_.channel_closed_)
cond_var_.Wait();
if (locked_data_.channel_closed_) {
retval = -1;
} else {
retval = LockedReceive(msg);
DCHECK(retval > 0);
}
}
cond_var_.Signal();
return retval;
}
void NaClIPCAdapter::CloseChannel() {
{
base::AutoLock lock(lock_);
locked_data_.channel_closed_ = true;
}
cond_var_.Signal();
task_runner_->PostTask(FROM_HERE,
base::Bind(&NaClIPCAdapter::CloseChannelOnIOThread, this));
}
NaClDesc* NaClIPCAdapter::MakeNaClDesc() {
return MakeNaClDescCustom(this);
}
#if defined(OS_POSIX)
int NaClIPCAdapter::TakeClientFileDescriptor() {
return io_thread_data_.channel_->TakeClientFileDescriptor();
}
#endif
#define CASE_FOR_MESSAGE(MESSAGE_TYPE) \
case MESSAGE_TYPE::ID: { \
HandleConverter<MESSAGE_TYPE> extractor(&msg); \
if (!extractor.ConvertMessage(&handles, new_msg_ptr)) \
return false; \
break; \
}
#define CASE_FOR_REPLY(MESSAGE_TYPE) \
case MESSAGE_TYPE::ID: { \
HandleConverter<MESSAGE_TYPE> extractor(&msg); \
if (!extractor.ConvertReply( \
&handles, \
static_cast<IPC::SyncMessage*>(new_msg_ptr))) \
return false; \
break; \
}
bool NaClIPCAdapter::OnMessageReceived(const IPC::Message& msg) {
{
base::AutoLock lock(lock_);
scoped_refptr<RewrittenMessage> rewritten_msg(new RewrittenMessage);
// Pointer to the "new" message we will rewrite on Windows. On posix, this
// isn't necessary, so it will stay NULL.
IPC::Message* new_msg_ptr = NULL;
IPC::Message new_msg(msg.routing_id(), msg.type(), msg.priority());
#if defined(OS_WIN)
new_msg_ptr = &new_msg;
#else
// Even on POSIX, we have to rewrite messages to create channels, because
// these contain a handle with an invalid (place holder) descriptor. The
// message sending code sees this and doesn't pass the descriptor over
// correctly.
if (msg.type() == PpapiMsg_CreateNaClChannel::ID)
new_msg_ptr = &new_msg;
#endif
Handles handles;
switch (msg.type()) {
CASE_FOR_MESSAGE(PpapiMsg_CreateNaClChannel)
CASE_FOR_MESSAGE(PpapiMsg_PPBAudio_NotifyAudioStreamCreated)
CASE_FOR_MESSAGE(PpapiMsg_PPBAudioInput_OpenACK)
case IPC_REPLY_ID: {
int id = IPC::SyncMessage::GetMessageId(msg);
LockedData::PendingSyncMsgMap::iterator iter(
locked_data_.pending_sync_msgs_.find(id));
if (iter == locked_data_.pending_sync_msgs_.end()) {
NOTREACHED();
return false;
}
uint32_t type = iter->second;
locked_data_.pending_sync_msgs_.erase(iter);
switch (type) {
CASE_FOR_REPLY(PpapiHostMsg_PPBGraphics3D_GetTransferBuffer)
CASE_FOR_REPLY(PpapiHostMsg_PPBImageData_CreateNaCl)
default:
// Do nothing for messages we don't know.
break;
}
break;
}
default:
// Do nothing for messages we don't know.
break;
}
// Now add any descriptors we found to rewritten_msg. |handles| is usually
// empty, unless we read a message containing a FD or handle.
nacl::DescWrapperFactory factory;
for (Handles::const_iterator iter = handles.begin();
iter != handles.end();
++iter) {
scoped_ptr<nacl::DescWrapper> nacl_desc;
switch (iter->type()) {
case ppapi::proxy::SerializedHandle::SHARED_MEMORY: {
const base::SharedMemoryHandle& shm_handle = iter->shmem();
uint32_t size = iter->size();
nacl_desc.reset(factory.ImportShmHandle(
#if defined(OS_WIN)
reinterpret_cast<const NaClHandle>(shm_handle),
#else
shm_handle.fd,
#endif
static_cast<size_t>(size)));
break;
}
case ppapi::proxy::SerializedHandle::SOCKET: {
nacl_desc.reset(factory.ImportSyncSocketHandle(
#if defined(OS_WIN)
reinterpret_cast<const NaClHandle>(iter->descriptor())
#else
iter->descriptor().fd
#endif
));
break;
}
case ppapi::proxy::SerializedHandle::CHANNEL_HANDLE: {
// Check that this came from a PpapiMsg_CreateNaClChannel message.
// This code here is only appropriate for that message.
DCHECK(msg.type() == PpapiMsg_CreateNaClChannel::ID);
IPC::ChannelHandle channel_handle =
IPC::Channel::GenerateVerifiedChannelID("nacl");
scoped_refptr<NaClIPCAdapter> ipc_adapter(
new NaClIPCAdapter(channel_handle, task_runner_));
#if defined(OS_POSIX)
channel_handle.socket = base::FileDescriptor(
ipc_adapter->TakeClientFileDescriptor(), true);
#endif
nacl_desc.reset(factory.MakeGeneric(ipc_adapter->MakeNaClDesc()));
// Send back a message that the channel was created.
scoped_ptr<IPC::Message> response(
new PpapiHostMsg_ChannelCreated(channel_handle));
task_runner_->PostTask(FROM_HERE,
base::Bind(&NaClIPCAdapter::SendMessageOnIOThread, this,
base::Passed(&response)));
break;
}
case ppapi::proxy::SerializedHandle::INVALID: {
// Nothing to do. TODO(dmichael): Should we log this? Or is it
// sometimes okay to pass an INVALID handle?
break;
}
// No default, so the compiler will warn us if new types get added.
}
if (nacl_desc.get())
rewritten_msg->AddDescriptor(nacl_desc.release());
}
if (new_msg_ptr && !handles.empty())
SaveMessage(*new_msg_ptr, rewritten_msg.get());
else
SaveMessage(msg, rewritten_msg.get());
}
cond_var_.Signal();
return true;
}
void NaClIPCAdapter::OnChannelConnected(int32 peer_pid) {
}
void NaClIPCAdapter::OnChannelError() {
CloseChannel();
}
NaClIPCAdapter::~NaClIPCAdapter() {
// Make sure the channel is deleted on the IO thread.
task_runner_->PostTask(FROM_HERE,
base::Bind(&DeleteChannel, io_thread_data_.channel_.release()));
}
int NaClIPCAdapter::LockedReceive(NaClImcTypedMsgHdr* msg) {
lock_.AssertAcquired();
if (locked_data_.to_be_received_.empty())
return 0;
scoped_refptr<RewrittenMessage> current =
locked_data_.to_be_received_.front();
int retval = current->Read(msg);
// When a message is entirely consumed, remove if from the waiting queue.
if (current->is_consumed())
locked_data_.to_be_received_.pop();
return retval;
}
bool NaClIPCAdapter::SendCompleteMessage(const char* buffer,
size_t buffer_len) {
// The message will have already been validated, so we know it's large enough
// for our header.
const NaClMessageHeader* header =
reinterpret_cast<const NaClMessageHeader*>(buffer);
// Length of the message not including the body. The data passed to us by the
// plugin should match that in the message header. This should have already
// been validated by GetBufferStatus.
int body_len = static_cast<int>(buffer_len - sizeof(NaClMessageHeader));
DCHECK(body_len == static_cast<int>(header->payload_size));
// We actually discard the flags and only copy the ones we care about. This
// is just because message doesn't have a constructor that takes raw flags.
scoped_ptr<IPC::Message> msg(
new IPC::Message(header->routing, header->type,
IPC::Message::PRIORITY_NORMAL));
if (header->flags & IPC::Message::SYNC_BIT)
msg->set_sync();
if (header->flags & IPC::Message::REPLY_BIT)
msg->set_reply();
if (header->flags & IPC::Message::REPLY_ERROR_BIT)
msg->set_reply_error();
if (header->flags & IPC::Message::UNBLOCK_BIT)
msg->set_unblock(true);
msg->WriteBytes(&buffer[sizeof(NaClMessageHeader)], body_len);
// Technically we didn't have to do any of the previous work in the lock. But
// sometimes our buffer will point to the to_be_sent_ string which is
// protected by the lock, and it's messier to factor Send() such that it can
// unlock for us. Holding the lock for the message construction, which is
// just some memcpys, shouldn't be a big deal.
lock_.AssertAcquired();
if (locked_data_.channel_closed_)
return false; // TODO(brettw) clean up handles here when we add support!
// Store the type of all sync messages so that later we can translate the
// reply if necessary.
if (msg->is_sync()) {
int id = IPC::SyncMessage::GetMessageId(*msg);
locked_data_.pending_sync_msgs_[id] = msg->type();
}
// Actual send must be done on the I/O thread.
task_runner_->PostTask(FROM_HERE,
base::Bind(&NaClIPCAdapter::SendMessageOnIOThread, this,
base::Passed(&msg)));
return true;
}
void NaClIPCAdapter::ClearToBeSent() {
lock_.AssertAcquired();
// Don't let the string keep its buffer behind our back.
std::string empty;
locked_data_.to_be_sent_.swap(empty);
}
void NaClIPCAdapter::ConnectChannelOnIOThread() {
if (!io_thread_data_.channel_->Connect())
NOTREACHED();
}
void NaClIPCAdapter::CloseChannelOnIOThread() {
io_thread_data_.channel_->Close();
}
void NaClIPCAdapter::SendMessageOnIOThread(scoped_ptr<IPC::Message> message) {
io_thread_data_.channel_->Send(message.release());
}
void NaClIPCAdapter::SaveMessage(const IPC::Message& msg,
RewrittenMessage* rewritten_msg) {
lock_.AssertAcquired();
// There is some padding in this structure (the "padding" member is 16
// bits but this then gets padded to 32 bits). We want to be sure not to
// leak data to the untrusted plugin, so zero everything out first.
NaClMessageHeader header;
memset(&header, 0, sizeof(NaClMessageHeader));
header.payload_size = static_cast<uint32>(msg.payload_size());
header.routing = msg.routing_id();
header.type = msg.type();
header.flags = msg.flags();
header.num_fds = static_cast<int>(rewritten_msg->desc_count());
rewritten_msg->SetData(header, msg.payload(), msg.payload_size());
locked_data_.to_be_received_.push(rewritten_msg);
}