#include "ppapi/proxy/nacl_message_scanner.h"
#include <stddef.h>
#include <memory>
#include <tuple>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "build/build_config.h"
#include "ipc/ipc_message.h"
#include "ipc/ipc_message_macros.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/proxy/resource_message_params.h"
#include "ppapi/proxy/serialized_handle.h"
#include "ppapi/proxy/serialized_var.h"
class NaClDescImcShm;
namespace IPC {
class Message;
}
using ppapi::proxy::ResourceMessageReplyParams;
using ppapi::proxy::SerializedHandle;
using ppapi::proxy::SerializedVar;
namespace {
typedef std::vector<SerializedHandle> Handles;
struct ScanningResults {
ScanningResults() : handle_index(0), pp_resource(0) {}
Handles handles;
int handle_index;
std::unique_ptr<IPC::Message> new_msg;
PP_Resource pp_resource;
base::RepeatingCallback<
void(PP_Resource, const IPC::Message&, SerializedHandle*)>
nested_msg_callback;
};
void WriteHandle(int handle_index,
const SerializedHandle& handle,
base::Pickle* msg) {
SerializedHandle::WriteHeader(handle.header(), msg);
if (handle.type() == SerializedHandle::SHARED_MEMORY_REGION) {
const auto& region = handle.shmem_region();
if (region.IsValid()) {
IPC::WriteParam(msg, true);
IPC::WriteParam(msg, region.GetMode());
IPC::WriteParam(msg, static_cast<uint64_t>(region.GetSize()));
IPC::WriteParam(msg, region.GetGUID());
DCHECK_NE(region.GetMode(),
base::subtle::PlatformSharedMemoryRegion::Mode::kWritable);
IPC::WriteParam(msg, handle_index);
} else {
msg->WriteBool(false);
}
} else if (handle.type() != SerializedHandle::INVALID) {
msg->WriteBool(true);
msg->WriteInt(handle_index);
}
}
void ScanParam(SerializedHandle&& handle, ScanningResults* results) {
if (results->new_msg)
WriteHandle(results->handle_index++, handle, results->new_msg.get());
results->handles.push_back(std::move(handle));
}
void HandleWriter(int* handle_index,
base::Pickle* m,
const SerializedHandle& handle) {
WriteHandle((*handle_index)++, handle, m);
}
void ScanParam(SerializedVar&& var, ScanningResults* results) {
if (results->new_msg)
var.WriteDataToMessage(
results->new_msg.get(),
base::BindRepeating(&HandleWriter, &results->handle_index));
for (SerializedHandle* var_handle : var.GetHandles())
results->handles.push_back(std::move(*var_handle));
}
void ScanParam(ResourceMessageReplyParams&& params, ScanningResults* results) {
results->pp_resource = params.pp_resource();
if (params.handles().empty()) {
results->new_msg.reset(NULL);
return;
}
if (results->new_msg) {
params.WriteReplyHeader(results->new_msg.get());
results->new_msg->WriteInt(static_cast<int>(params.handles().size()));
}
std::vector<SerializedHandle> handles;
params.TakeAllHandles(&handles);
for (SerializedHandle& handle : handles) {
ScanParam(std::move(handle), results);
}
}
void ScanParam(IPC::Message&& param, ScanningResults* results) {
if (results->pp_resource && !results->nested_msg_callback.is_null()) {
SerializedHandle* handle = NULL;
if (results->handles.size() == 1)
handle = &results->handles[0];
results->nested_msg_callback.Run(results->pp_resource, param, handle);
}
if (results->new_msg)
IPC::WriteParam(results->new_msg.get(), param);
}
template <class T>
void ScanParam(std::vector<T>&& vec, ScanningResults* results) {
if (results->new_msg)
IPC::WriteParam(results->new_msg.get(), static_cast<int>(vec.size()));
for (T& element : vec)
ScanParam(std::move(element), results);
}
template <class T>
void ScanParam(T&& param, ScanningResults* results) {
if (results->new_msg)
IPC::WriteParam(results->new_msg.get(), param);
}
template <class A>
void ScanTuple(std::tuple<A>&& t1, ScanningResults* results) {
ScanParam(std::move(std::get<0>(t1)), results);
}
template <class A, class B>
void ScanTuple(std::tuple<A, B>&& t1, ScanningResults* results) {
ScanParam(std::move(std::get<0>(t1)), results);
ScanParam(std::move(std::get<1>(t1)), results);
}
template <class A, class B, class C>
void ScanTuple(std::tuple<A, B, C>&& t1, ScanningResults* results) {
ScanParam(std::move(std::get<0>(t1)), results);
ScanParam(std::move(std::get<1>(t1)), results);
ScanParam(std::move(std::get<2>(t1)), results);
}
template <class A, class B, class C, class D>
void ScanTuple(std::tuple<A, B, C, D>&& t1, ScanningResults* results) {
ScanParam(std::move(std::get<0>(t1)), results);
ScanParam(std::move(std::get<1>(t1)), results);
ScanParam(std::move(std::get<2>(t1)), results);
ScanParam(std::move(std::get<3>(t1)), results);
}
template <class MessageType>
class MessageScannerImpl {
public:
explicit MessageScannerImpl(const IPC::Message* msg) : msg_(msg) {}
bool ScanMessage(ScanningResults* results) {
typename MessageType::Param params;
if (!MessageType::Read(msg_, ¶ms))
return false;
ScanTuple(std::move(params), results);
return true;
}
bool ScanSyncMessage(ScanningResults* results) {
typename MessageType::SendParam params;
if (!MessageType::ReadSendParam(msg_, ¶ms))
return false;
if (results->new_msg) {
results->new_msg->set_sync();
int id = IPC::SyncMessage::GetMessageId(*msg_);
results->new_msg->WriteInt(id);
}
ScanTuple(std::move(params), results);
return true;
}
bool ScanReply(ScanningResults* results) {
typename MessageType::ReplyParam params;
if (!MessageType::ReadReplyParam(msg_, ¶ms))
return false;
if (results->new_msg) {
results->new_msg->set_reply();
int id = IPC::SyncMessage::GetMessageId(*msg_);
results->new_msg->WriteInt(id);
}
ScanTuple(std::move(params), results);
return true;
}
private:
const IPC::Message* msg_;
};
}
#define CASE_FOR_MESSAGE(MESSAGE_TYPE) \
case MESSAGE_TYPE::ID: { \
MessageScannerImpl<MESSAGE_TYPE> scanner(&msg); \
if (rewrite_msg) \
results.new_msg.reset( \
new IPC::Message(msg.routing_id(), msg.type(), \
IPC::Message::PRIORITY_NORMAL)); \
if (!scanner.ScanMessage(&results)) \
return false; \
break; \
}
#define CASE_FOR_SYNC_MESSAGE(MESSAGE_TYPE) \
case MESSAGE_TYPE::ID: { \
MessageScannerImpl<MESSAGE_TYPE> scanner(&msg); \
if (rewrite_msg) \
results.new_msg.reset( \
new IPC::Message(msg.routing_id(), msg.type(), \
IPC::Message::PRIORITY_NORMAL)); \
if (!scanner.ScanSyncMessage(&results)) \
return false; \
break; \
}
#define CASE_FOR_REPLY(MESSAGE_TYPE) \
case MESSAGE_TYPE::ID: { \
MessageScannerImpl<MESSAGE_TYPE> scanner(&msg); \
if (rewrite_msg) \
results.new_msg.reset( \
new IPC::Message(msg.routing_id(), msg.type(), \
IPC::Message::PRIORITY_NORMAL)); \
if (!scanner.ScanReply(&results)) \
return false; \
break; \
}
namespace ppapi {
namespace proxy {
class SerializedHandle;
NaClMessageScanner::FileSystem::FileSystem()
: reserved_quota_(0) {
}
NaClMessageScanner::FileSystem::~FileSystem() {
}
bool NaClMessageScanner::FileSystem::UpdateReservedQuota(int64_t delta) {
base::AutoLock lock(lock_);
if (std::numeric_limits<int64_t>::max() - reserved_quota_ < delta)
return false;
if (reserved_quota_ + delta < 0)
return false;
reserved_quota_ += delta;
return true;
}
NaClMessageScanner::FileIO::FileIO(FileSystem* file_system,
int64_t max_written_offset)
: file_system_(file_system),
max_written_offset_(max_written_offset) {
}
NaClMessageScanner::FileIO::~FileIO() {
}
void NaClMessageScanner::FileIO::SetMaxWrittenOffset(
int64_t max_written_offset) {
base::AutoLock lock(lock_);
max_written_offset_ = max_written_offset;
}
bool NaClMessageScanner::FileIO::Grow(int64_t amount) {
base::AutoLock lock(lock_);
DCHECK(amount > 0);
if (!file_system_->UpdateReservedQuota(-amount))
return false;
max_written_offset_ += amount;
return true;
}
NaClMessageScanner::NaClMessageScanner() {
}
NaClMessageScanner::~NaClMessageScanner() {
for (FileSystemMap::iterator it = file_systems_.begin();
it != file_systems_.end(); ++it)
delete it->second;
for (FileIOMap::iterator it = files_.begin(); it != files_.end(); ++it)
delete it->second;
}
bool NaClMessageScanner::ScanMessage(
const IPC::Message& msg,
uint32_t type,
std::vector<SerializedHandle>* handles,
std::unique_ptr<IPC::Message>* new_msg_ptr) {
DCHECK(handles);
DCHECK(handles->empty());
DCHECK(new_msg_ptr);
DCHECK(!new_msg_ptr->get());
bool rewrite_msg =
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
true;
#else
false;
#endif
ScanningResults results;
results.nested_msg_callback = base::BindRepeating(
&NaClMessageScanner::AuditNestedMessage, base::Unretained(this));
switch (type) {
CASE_FOR_MESSAGE(PpapiMsg_PPBAudio_NotifyAudioStreamCreated)
CASE_FOR_MESSAGE(PpapiMsg_PPPMessaging_HandleMessage)
CASE_FOR_MESSAGE(PpapiPluginMsg_ResourceReply)
CASE_FOR_SYNC_MESSAGE(PpapiMsg_PPPMessageHandler_HandleBlockingMessage)
CASE_FOR_SYNC_MESSAGE(PpapiMsg_PnaclTranslatorCompileInit)
CASE_FOR_SYNC_MESSAGE(PpapiMsg_PnaclTranslatorLink)
CASE_FOR_REPLY(PpapiHostMsg_OpenResource)
CASE_FOR_REPLY(PpapiHostMsg_PPBGraphics3D_Create)
CASE_FOR_REPLY(PpapiHostMsg_PPBGraphics3D_CreateTransferBuffer)
CASE_FOR_REPLY(PpapiHostMsg_PPBImageData_CreateSimple)
CASE_FOR_REPLY(PpapiHostMsg_ResourceSyncCall)
CASE_FOR_REPLY(PpapiHostMsg_SharedMemory_CreateSharedMemory)
default:
break;
}
if (!results.handles.empty()) {
handles->swap(results.handles);
*new_msg_ptr = std::move(results.new_msg);
}
return true;
}
void NaClMessageScanner::ScanUntrustedMessage(
const IPC::Message& untrusted_msg,
std::unique_ptr<IPC::Message>* new_msg_ptr) {
if (untrusted_msg.type() == PpapiHostMsg_ResourceCall::ID) {
ResourceMessageCallParams params;
IPC::Message nested_msg;
if (!UnpackMessage<PpapiHostMsg_ResourceCall>(
untrusted_msg, ¶ms, &nested_msg))
return;
switch (nested_msg.type()) {
case PpapiHostMsg_FileIO_Close::ID: {
FileIOMap::iterator it = files_.find(params.pp_resource());
if (it == files_.end())
return;
FileGrowth file_growth;
if (!UnpackMessage<PpapiHostMsg_FileIO_Close>(
nested_msg, &file_growth))
return;
int64_t trusted_max_written_offset = it->second->max_written_offset();
delete it->second;
files_.erase(it);
if (trusted_max_written_offset > file_growth.max_written_offset) {
*new_msg_ptr = std::make_unique<PpapiHostMsg_ResourceCall>(
params, PpapiHostMsg_FileIO_Close(
FileGrowth(trusted_max_written_offset, 0)));
}
break;
}
case PpapiHostMsg_FileIO_SetLength::ID: {
FileIOMap::iterator it = files_.find(params.pp_resource());
if (it == files_.end())
return;
int64_t length = 0;
if (!UnpackMessage<PpapiHostMsg_FileIO_SetLength>(
nested_msg, &length))
return;
if (length < 0)
return;
int64_t trusted_max_written_offset = it->second->max_written_offset();
int64_t increase = length - trusted_max_written_offset;
if (increase <= 0)
return;
if (!it->second->Grow(increase)) {
*new_msg_ptr = std::make_unique<PpapiHostMsg_ResourceCall>(
params, PpapiHostMsg_FileIO_SetLength(-1));
}
break;
}
case PpapiHostMsg_FileSystem_ReserveQuota::ID: {
int64_t amount = 0;
FileGrowthMap file_growths;
if (!UnpackMessage<PpapiHostMsg_FileSystem_ReserveQuota>(
nested_msg, &amount, &file_growths))
return;
bool audit_failed = false;
for (FileGrowthMap::iterator it = file_growths.begin();
it != file_growths.end(); ++it) {
FileIOMap::iterator file_it = files_.find(it->first);
if (file_it == files_.end())
continue;
int64_t trusted_max_written_offset =
file_it->second->max_written_offset();
if (trusted_max_written_offset > it->second.max_written_offset) {
audit_failed = true;
it->second.max_written_offset = trusted_max_written_offset;
}
if (it->second.append_mode_write_amount < 0) {
audit_failed = true;
it->second.append_mode_write_amount = 0;
}
}
if (audit_failed) {
*new_msg_ptr = std::make_unique<PpapiHostMsg_ResourceCall>(
params,
PpapiHostMsg_FileSystem_ReserveQuota(amount, file_growths));
}
break;
}
case PpapiHostMsg_ResourceDestroyed::ID: {
PP_Resource resource;
if (!UnpackMessage<PpapiHostMsg_ResourceDestroyed>(
nested_msg, &resource))
return;
FileSystemMap::iterator fs_it = file_systems_.find(resource);
if (fs_it != file_systems_.end()) {
delete fs_it->second;
file_systems_.erase(fs_it);
}
break;
}
}
}
}
NaClMessageScanner::FileIO* NaClMessageScanner::GetFile(
PP_Resource file_io) {
FileIOMap::iterator it = files_.find(file_io);
DCHECK(it != files_.end());
return it->second;
}
void NaClMessageScanner::AuditNestedMessage(PP_Resource resource,
const IPC::Message& msg,
SerializedHandle* handle) {
switch (msg.type()) {
case PpapiPluginMsg_FileIO_OpenReply::ID: {
PP_Resource quota_file_system;
int64_t max_written_offset = 0;
if (ppapi::UnpackMessage<PpapiPluginMsg_FileIO_OpenReply>(
msg, "a_file_system, &max_written_offset)) {
if (quota_file_system) {
FileSystem* file_system = NULL;
std::pair<FileSystemMap::iterator, bool> insert_result =
file_systems_.insert(std::make_pair(quota_file_system,
file_system));
if (insert_result.second)
insert_result.first->second = new FileSystem();
file_system = insert_result.first->second;
DCHECK(files_.find(resource) == files_.end());
files_.insert(std::make_pair(
resource,
new FileIO(file_system, max_written_offset)));
}
}
break;
}
case PpapiPluginMsg_FileSystem_ReserveQuotaReply::ID: {
int64_t amount = 0;
FileSizeMap file_sizes;
if (ppapi::UnpackMessage<PpapiPluginMsg_FileSystem_ReserveQuotaReply>(
msg, &amount, &file_sizes)) {
FileSystemMap::iterator it = file_systems_.find(resource);
DCHECK(it != file_systems_.end());
it->second->UpdateReservedQuota(amount);
FileSizeMap::const_iterator offset_it = file_sizes.begin();
for (; offset_it != file_sizes.end(); ++offset_it) {
FileIOMap::iterator fio_it = files_.find(offset_it->first);
DCHECK(fio_it != files_.end());
if (fio_it != files_.end())
fio_it->second->SetMaxWrittenOffset(offset_it->second);
}
}
break;
}
}
}
}
}