1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ppapi/proxy/nacl_message_scanner.h"
9 #include "ipc/ipc_message.h"
10 #include "ipc/ipc_message_macros.h"
11 #include "ppapi/proxy/ppapi_messages.h"
12 #include "ppapi/proxy/resource_message_params.h"
13 #include "ppapi/proxy/serialized_handle.h"
14 #include "ppapi/proxy/serialized_var.h"
22 using ppapi::proxy::ResourceMessageReplyParams
;
23 using ppapi::proxy::SerializedHandle
;
24 using ppapi::proxy::SerializedVar
;
28 typedef std::vector
<SerializedHandle
> Handles
;
30 struct ScanningResults
{
31 ScanningResults() : handle_index(0), pp_resource(0) {}
33 // Vector to hold handles found in the message.
35 // Current handle index in the rewritten message. During the scan, it will be
36 // be less than or equal to handles.size(). After the scan it should be equal.
38 // The rewritten message. This may be NULL, so all ScanParam overloads should
39 // check for NULL before writing to it. In some cases, a ScanParam overload
40 // may set this to NULL when it can determine that there are no parameters
41 // that need conversion. (See the ResourceMessageReplyParams overload.)
42 scoped_ptr
<IPC::Message
> new_msg
;
43 // Resource id for resource messages. Save this when scanning resource replies
44 // so when we audit the nested message, we know which resource it is for.
45 PP_Resource pp_resource
;
46 // Callback to receive the nested message in a resource message or reply.
47 base::Callback
<void(PP_Resource
, const IPC::Message
&, SerializedHandle
*)>
51 void WriteHandle(int handle_index
,
52 const SerializedHandle
& handle
,
54 SerializedHandle::WriteHeader(handle
.header(), msg
);
56 // Now write the handle itself in POSIX style.
57 msg
->WriteBool(true); // valid == true
58 msg
->WriteInt(handle_index
);
61 // Define overloads for each kind of message parameter that requires special
62 // handling. See ScanTuple for how these get used.
64 // Overload to match SerializedHandle.
65 void ScanParam(const SerializedHandle
& handle
, ScanningResults
* results
) {
66 results
->handles
.push_back(handle
);
68 WriteHandle(results
->handle_index
++, handle
, results
->new_msg
.get());
71 void HandleWriter(int* handle_index
,
73 const SerializedHandle
& handle
) {
74 WriteHandle((*handle_index
)++, handle
, m
);
77 // Overload to match SerializedVar, which can contain handles.
78 void ScanParam(const SerializedVar
& var
, ScanningResults
* results
) {
79 std::vector
<SerializedHandle
*> var_handles
= var
.GetHandles();
80 // Copy any handles and then rewrite the message.
81 for (size_t i
= 0; i
< var_handles
.size(); ++i
)
82 results
->handles
.push_back(*var_handles
[i
]);
84 var
.WriteDataToMessage(results
->new_msg
.get(),
85 base::Bind(&HandleWriter
, &results
->handle_index
));
88 // For PpapiMsg_ResourceReply and the reply to PpapiHostMsg_ResourceSyncCall,
89 // the handles are carried inside the ResourceMessageReplyParams.
90 // NOTE: We only intercept handles from host->NaCl. The only kind of
91 // ResourceMessageParams that travels this direction is
92 // ResourceMessageReplyParams, so that's the only one we need to handle.
93 void ScanParam(const ResourceMessageReplyParams
& params
,
94 ScanningResults
* results
) {
95 results
->pp_resource
= params
.pp_resource();
96 // If the resource reply params don't contain handles, NULL the new message
97 // pointer to cancel further rewriting.
98 // NOTE: This works because only handles currently need rewriting, and we
99 // know at this point that this message has none.
100 if (params
.handles().empty()) {
101 results
->new_msg
.reset(NULL
);
105 // If we need to rewrite the message, write everything before the handles
106 // (there's nothing after the handles).
107 if (results
->new_msg
) {
108 params
.WriteReplyHeader(results
->new_msg
.get());
109 // IPC writes the vector length as an int before the contents of the
111 results
->new_msg
->WriteInt(static_cast<int>(params
.handles().size()));
113 for (Handles::const_iterator iter
= params
.handles().begin();
114 iter
!= params
.handles().end();
116 // ScanParam will write each handle to the new message, if necessary.
117 ScanParam(*iter
, results
);
119 // Tell ResourceMessageReplyParams that we have taken the handles, so it
120 // shouldn't close them. The NaCl runtime will take ownership of them.
121 params
.ConsumeHandles();
124 // Overload to match nested messages. If we need to rewrite the message, write
126 void ScanParam(const IPC::Message
& param
, ScanningResults
* results
) {
127 if (results
->pp_resource
&& !results
->nested_msg_callback
.is_null()) {
128 SerializedHandle
* handle
= NULL
;
129 if (results
->handles
.size() == 1)
130 handle
= &results
->handles
[0];
131 results
->nested_msg_callback
.Run(results
->pp_resource
, param
, handle
);
133 if (results
->new_msg
)
134 IPC::WriteParam(results
->new_msg
.get(), param
);
137 // Overload to match all other types. If we need to rewrite the message, write
140 void ScanParam(const T
& param
, ScanningResults
* results
) {
141 if (results
->new_msg
)
142 IPC::WriteParam(results
->new_msg
.get(), param
);
145 // These just break apart the given tuple and run ScanParam over each param.
146 // The idea is to scan elements in the tuple which require special handling,
147 // and write them into the |results| struct.
149 void ScanTuple(const Tuple1
<A
>& t1
, ScanningResults
* results
) {
150 ScanParam(t1
.a
, results
);
152 template <class A
, class B
>
153 void ScanTuple(const Tuple2
<A
, B
>& t1
, ScanningResults
* results
) {
154 ScanParam(t1
.a
, results
);
155 ScanParam(t1
.b
, results
);
157 template <class A
, class B
, class C
>
158 void ScanTuple(const Tuple3
<A
, B
, C
>& t1
, ScanningResults
* results
) {
159 ScanParam(t1
.a
, results
);
160 ScanParam(t1
.b
, results
);
161 ScanParam(t1
.c
, results
);
163 template <class A
, class B
, class C
, class D
>
164 void ScanTuple(const Tuple4
<A
, B
, C
, D
>& t1
, ScanningResults
* results
) {
165 ScanParam(t1
.a
, results
);
166 ScanParam(t1
.b
, results
);
167 ScanParam(t1
.c
, results
);
168 ScanParam(t1
.d
, results
);
171 template <class MessageType
>
172 class MessageScannerImpl
{
174 explicit MessageScannerImpl(const IPC::Message
* msg
)
175 : msg_(static_cast<const MessageType
*>(msg
)) {
177 bool ScanMessage(ScanningResults
* results
) {
178 typename TupleTypes
<typename
MessageType::Schema::Param
>::ValueTuple params
;
179 if (!MessageType::Read(msg_
, ¶ms
))
181 ScanTuple(params
, results
);
185 bool ScanReply(ScanningResults
* results
) {
186 typename TupleTypes
<typename
MessageType::Schema::ReplyParam
>::ValueTuple
188 if (!MessageType::ReadReplyParam(msg_
, ¶ms
))
190 // If we need to rewrite the message, write the message id first.
191 if (results
->new_msg
) {
192 results
->new_msg
->set_reply();
193 int id
= IPC::SyncMessage::GetMessageId(*msg_
);
194 results
->new_msg
->WriteInt(id
);
196 ScanTuple(params
, results
);
199 // TODO(dmichael): Add ScanSyncMessage for outgoing sync messages, if we ever
200 // need to scan those.
203 const MessageType
* msg_
;
208 #define CASE_FOR_MESSAGE(MESSAGE_TYPE) \
209 case MESSAGE_TYPE::ID: { \
210 MessageScannerImpl<MESSAGE_TYPE> scanner(&msg); \
212 results.new_msg.reset( \
213 new IPC::Message(msg.routing_id(), msg.type(), \
214 IPC::Message::PRIORITY_NORMAL)); \
215 if (!scanner.ScanMessage(&results)) \
219 #define CASE_FOR_REPLY(MESSAGE_TYPE) \
220 case MESSAGE_TYPE::ID: { \
221 MessageScannerImpl<MESSAGE_TYPE> scanner(&msg); \
223 results.new_msg.reset( \
224 new IPC::Message(msg.routing_id(), msg.type(), \
225 IPC::Message::PRIORITY_NORMAL)); \
226 if (!scanner.ScanReply(&results)) \
234 class SerializedHandle
;
236 NaClMessageScanner::FileSystem::FileSystem()
237 : reserved_quota_(0) {
240 NaClMessageScanner::FileSystem::~FileSystem() {
243 bool NaClMessageScanner::FileSystem::UpdateReservedQuota(int64_t delta
) {
244 base::AutoLock
lock(lock_
);
245 if (std::numeric_limits
<int64_t>::max() - reserved_quota_
< delta
)
246 return false; // reserved_quota_ + delta would overflow.
247 if (reserved_quota_
+ delta
< 0)
249 reserved_quota_
+= delta
;
253 NaClMessageScanner::FileIO::FileIO(FileSystem
* file_system
,
254 int64_t max_written_offset
)
255 : file_system_(file_system
),
256 max_written_offset_(max_written_offset
) {
259 NaClMessageScanner::FileIO::~FileIO() {
262 void NaClMessageScanner::FileIO::SetMaxWrittenOffset(
263 int64_t max_written_offset
) {
264 base::AutoLock
lock(lock_
);
265 max_written_offset_
= max_written_offset
;
268 bool NaClMessageScanner::FileIO::Grow(int64_t amount
) {
269 base::AutoLock
lock(lock_
);
271 if (!file_system_
->UpdateReservedQuota(-amount
))
273 max_written_offset_
+= amount
;
277 NaClMessageScanner::NaClMessageScanner() {
280 NaClMessageScanner::~NaClMessageScanner() {
281 for (FileSystemMap::iterator it
= file_systems_
.begin();
282 it
!= file_systems_
.end(); ++it
)
284 for (FileIOMap::iterator it
= files_
.begin(); it
!= files_
.end(); ++it
)
288 // Windows IPC differs from POSIX in that native handles are serialized in the
289 // message body, rather than passed in a separate FileDescriptorSet. Therefore,
290 // on Windows, any message containing handles must be rewritten in the POSIX
291 // format before we can send it to the NaCl plugin.
292 bool NaClMessageScanner::ScanMessage(
293 const IPC::Message
& msg
,
294 std::vector
<SerializedHandle
>* handles
,
295 scoped_ptr
<IPC::Message
>* new_msg_ptr
) {
297 DCHECK(handles
->empty());
299 DCHECK(!new_msg_ptr
->get());
308 // We can't always tell from the message ID if rewriting is needed. Therefore,
309 // scan any message types that might contain a handle. If we later determine
310 // that there are no handles, we can cancel the rewriting by clearing the
311 // results.new_msg pointer.
312 ScanningResults results
;
313 results
.nested_msg_callback
=
314 base::Bind(&NaClMessageScanner::AuditNestedMessage
,
315 base::Unretained(this));
316 switch (msg
.type()) {
317 CASE_FOR_MESSAGE(PpapiMsg_PPBAudio_NotifyAudioStreamCreated
)
318 CASE_FOR_MESSAGE(PpapiMsg_PPPMessaging_HandleMessage
)
319 CASE_FOR_MESSAGE(PpapiPluginMsg_ResourceReply
)
321 int id
= IPC::SyncMessage::GetMessageId(msg
);
322 PendingSyncMsgMap::iterator
iter(pending_sync_msgs_
.find(id
));
323 if (iter
== pending_sync_msgs_
.end()) {
327 uint32_t type
= iter
->second
;
328 pending_sync_msgs_
.erase(iter
);
330 CASE_FOR_REPLY(PpapiHostMsg_PPBGraphics3D_CreateTransferBuffer
)
331 CASE_FOR_REPLY(PpapiHostMsg_PPBImageData_CreateSimple
)
332 CASE_FOR_REPLY(PpapiHostMsg_ResourceSyncCall
)
333 CASE_FOR_REPLY(PpapiHostMsg_SharedMemory_CreateSharedMemory
)
335 // Do nothing for messages we don't know.
341 // Do nothing for messages we don't know.
345 // Only messages containing handles need to be rewritten. If no handles are
346 // found, don't return the rewritten message either. This must be changed if
347 // we ever add new param types that also require rewriting.
348 if (!results
.handles
.empty()) {
349 handles
->swap(results
.handles
);
350 *new_msg_ptr
= results
.new_msg
.Pass();
355 void NaClMessageScanner::ScanUntrustedMessage(
356 const IPC::Message
& untrusted_msg
,
357 scoped_ptr
<IPC::Message
>* new_msg_ptr
) {
358 if (untrusted_msg
.is_sync())
359 RegisterSyncMessageForReply(untrusted_msg
);
361 // Audit FileIO and FileSystem messages to ensure that the plugin doesn't
362 // exceed its file quota. If we find the message is malformed, just pass it
363 // through - we only care about well formed messages to the host.
364 if (untrusted_msg
.type() == PpapiHostMsg_ResourceCall::ID
) {
365 ResourceMessageCallParams params
;
366 IPC::Message nested_msg
;
367 if (!UnpackMessage
<PpapiHostMsg_ResourceCall
>(
368 untrusted_msg
, ¶ms
, &nested_msg
))
371 switch (nested_msg
.type()) {
372 case PpapiHostMsg_FileIO_Close::ID
: {
373 FileIOMap::iterator it
= files_
.find(params
.pp_resource());
374 if (it
== files_
.end())
376 // Audit FileIO Close messages to make sure the plugin reports an
377 // accurate file size.
378 FileGrowth file_growth
;
379 if (!UnpackMessage
<PpapiHostMsg_FileIO_Close
>(
380 nested_msg
, &file_growth
))
383 int64_t trusted_max_written_offset
= it
->second
->max_written_offset();
386 // If the plugin is under-reporting, rewrite the message with the
388 if (trusted_max_written_offset
> file_growth
.max_written_offset
) {
390 new PpapiHostMsg_ResourceCall(
392 PpapiHostMsg_FileIO_Close(
393 FileGrowth(trusted_max_written_offset
, 0))));
396 case PpapiHostMsg_FileIO_SetLength::ID
: {
397 FileIOMap::iterator it
= files_
.find(params
.pp_resource());
398 if (it
== files_
.end())
400 // Audit FileIO SetLength messages to make sure the plugin is within
401 // the current quota reservation. In addition, deduct the file size
402 // increase from the quota reservation.
404 if (!UnpackMessage
<PpapiHostMsg_FileIO_SetLength
>(
405 nested_msg
, &length
))
408 // Calculate file size increase, taking care to avoid overflows.
411 int64_t trusted_max_written_offset
= it
->second
->max_written_offset();
412 int64_t increase
= length
- trusted_max_written_offset
;
415 if (!it
->second
->Grow(increase
)) {
417 new PpapiHostMsg_ResourceCall(
419 PpapiHostMsg_FileIO_SetLength(-1)));
423 case PpapiHostMsg_FileSystem_ReserveQuota::ID
: {
424 // Audit FileSystem ReserveQuota messages to make sure the plugin
425 // reports accurate file sizes.
427 FileGrowthMap file_growths
;
428 if (!UnpackMessage
<PpapiHostMsg_FileSystem_ReserveQuota
>(
429 nested_msg
, &amount
, &file_growths
))
432 bool audit_failed
= false;
433 for (FileGrowthMap::iterator it
= file_growths
.begin();
434 it
!= file_growths
.end(); ++it
) {
435 FileIOMap::iterator file_it
= files_
.find(it
->first
);
436 if (file_it
== files_
.end())
438 int64_t trusted_max_written_offset
=
439 file_it
->second
->max_written_offset();
440 if (trusted_max_written_offset
> it
->second
.max_written_offset
) {
442 it
->second
.max_written_offset
= trusted_max_written_offset
;
444 if (it
->second
.append_mode_write_amount
< 0) {
446 it
->second
.append_mode_write_amount
= 0;
451 new PpapiHostMsg_ResourceCall(
453 PpapiHostMsg_FileSystem_ReserveQuota(
454 amount
, file_growths
)));
458 case PpapiHostMsg_ResourceDestroyed::ID
: {
459 // Audit resource destroyed messages to release FileSystems.
460 PP_Resource resource
;
461 if (!UnpackMessage
<PpapiHostMsg_ResourceDestroyed
>(
462 nested_msg
, &resource
))
464 FileSystemMap::iterator fs_it
= file_systems_
.find(resource
);
465 if (fs_it
!= file_systems_
.end()) {
466 delete fs_it
->second
;
467 file_systems_
.erase(fs_it
);
474 void NaClMessageScanner::RegisterSyncMessageForReply(const IPC::Message
& msg
) {
475 int msg_id
= IPC::SyncMessage::GetMessageId(msg
);
476 DCHECK(pending_sync_msgs_
.find(msg_id
) == pending_sync_msgs_
.end());
478 pending_sync_msgs_
[msg_id
] = msg
.type();
481 NaClMessageScanner::FileIO
* NaClMessageScanner::GetFile(
482 PP_Resource file_io
) {
483 FileIOMap::iterator it
= files_
.find(file_io
);
484 DCHECK(it
!= files_
.end());
488 void NaClMessageScanner::AuditNestedMessage(PP_Resource resource
,
489 const IPC::Message
& msg
,
490 SerializedHandle
* handle
) {
491 switch (msg
.type()) {
492 case PpapiPluginMsg_FileIO_OpenReply::ID
: {
493 // A file that requires quota checking was opened.
494 PP_Resource quota_file_system
;
495 int64_t max_written_offset
= 0;
496 if (ppapi::UnpackMessage
<PpapiPluginMsg_FileIO_OpenReply
>(
497 msg
, "a_file_system
, &max_written_offset
)) {
498 if (quota_file_system
) {
499 // Look up the FileSystem by inserting a new one. If it was already
500 // present, get the existing one, otherwise construct it.
501 FileSystem
* file_system
= NULL
;
502 std::pair
<FileSystemMap::iterator
, bool> insert_result
=
503 file_systems_
.insert(std::make_pair(quota_file_system
,
505 if (insert_result
.second
)
506 insert_result
.first
->second
= new FileSystem();
507 file_system
= insert_result
.first
->second
;
508 // Create the FileIO.
509 DCHECK(files_
.find(resource
) == files_
.end());
510 files_
.insert(std::make_pair(
512 new FileIO(file_system
, max_written_offset
)));
517 case PpapiPluginMsg_FileSystem_ReserveQuotaReply::ID
: {
518 // The amount of reserved quota for a FileSystem was refreshed.
520 FileSizeMap file_sizes
;
521 if (ppapi::UnpackMessage
<PpapiPluginMsg_FileSystem_ReserveQuotaReply
>(
522 msg
, &amount
, &file_sizes
)) {
523 FileSystemMap::iterator it
= file_systems_
.find(resource
);
524 DCHECK(it
!= file_systems_
.end());
525 it
->second
->UpdateReservedQuota(amount
);
527 FileSizeMap::const_iterator offset_it
= file_sizes
.begin();
528 for (; offset_it
!= file_sizes
.end(); ++offset_it
) {
529 FileIOMap::iterator fio_it
= files_
.find(offset_it
->first
);
530 DCHECK(fio_it
!= files_
.end());
531 if (fio_it
!= files_
.end())
532 fio_it
->second
->SetMaxWrittenOffset(offset_it
->second
);