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.
4
5 #include "ppapi/proxy/nacl_message_scanner.h"
6
7 #include <vector>
8 #include "base/bind.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"
15
16 class NaClDescImcShm;
17
18 namespace IPC {
19 class Message;
20 }
21
22 using ppapi::proxy::ResourceMessageReplyParams;
23 using ppapi::proxy::SerializedHandle;
24 using ppapi::proxy::SerializedVar;
25
26 namespace {
27
28 typedef std::vector<SerializedHandle> Handles;
29
30 struct ScanningResults {
ScanningResults__anon63802b880111::ScanningResults31 ScanningResults() : handle_index(0), pp_resource(0) {}
32
33 // Vector to hold handles found in the message.
34 Handles handles;
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.
37 int handle_index;
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*)>
48 nested_msg_callback;
49 };
50
WriteHandle(int handle_index,const SerializedHandle & handle,IPC::Message * msg)51 void WriteHandle(int handle_index,
52 const SerializedHandle& handle,
53 IPC::Message* msg) {
54 SerializedHandle::WriteHeader(handle.header(), msg);
55
56 // Now write the handle itself in POSIX style.
57 msg->WriteBool(true); // valid == true
58 msg->WriteInt(handle_index);
59 }
60
61 // Define overloads for each kind of message parameter that requires special
62 // handling. See ScanTuple for how these get used.
63
64 // Overload to match SerializedHandle.
ScanParam(const SerializedHandle & handle,ScanningResults * results)65 void ScanParam(const SerializedHandle& handle, ScanningResults* results) {
66 results->handles.push_back(handle);
67 if (results->new_msg)
68 WriteHandle(results->handle_index++, handle, results->new_msg.get());
69 }
70
HandleWriter(int * handle_index,IPC::Message * m,const SerializedHandle & handle)71 void HandleWriter(int* handle_index,
72 IPC::Message* m,
73 const SerializedHandle& handle) {
74 WriteHandle((*handle_index)++, handle, m);
75 }
76
77 // Overload to match SerializedVar, which can contain handles.
ScanParam(const SerializedVar & var,ScanningResults * results)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]);
83 if (results->new_msg)
84 var.WriteDataToMessage(results->new_msg.get(),
85 base::Bind(&HandleWriter, &results->handle_index));
86 }
87
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.
ScanParam(const ResourceMessageReplyParams & params,ScanningResults * results)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);
102 return;
103 }
104
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
110 // vector.
111 results->new_msg->WriteInt(static_cast<int>(params.handles().size()));
112 }
113 for (Handles::const_iterator iter = params.handles().begin();
114 iter != params.handles().end();
115 ++iter) {
116 // ScanParam will write each handle to the new message, if necessary.
117 ScanParam(*iter, results);
118 }
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();
122 }
123
124 // Overload to match nested messages. If we need to rewrite the message, write
125 // the parameter.
ScanParam(const IPC::Message & param,ScanningResults * results)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);
132 }
133 if (results->new_msg)
134 IPC::WriteParam(results->new_msg.get(), param);
135 }
136
137 // Overload to match all other types. If we need to rewrite the message, write
138 // the parameter.
139 template <class T>
ScanParam(const T & param,ScanningResults * results)140 void ScanParam(const T& param, ScanningResults* results) {
141 if (results->new_msg)
142 IPC::WriteParam(results->new_msg.get(), param);
143 }
144
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.
148 template <class A>
ScanTuple(const Tuple1<A> & t1,ScanningResults * results)149 void ScanTuple(const Tuple1<A>& t1, ScanningResults* results) {
150 ScanParam(t1.a, results);
151 }
152 template <class A, class B>
ScanTuple(const Tuple2<A,B> & t1,ScanningResults * results)153 void ScanTuple(const Tuple2<A, B>& t1, ScanningResults* results) {
154 ScanParam(t1.a, results);
155 ScanParam(t1.b, results);
156 }
157 template <class A, class B, class C>
ScanTuple(const Tuple3<A,B,C> & t1,ScanningResults * results)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);
162 }
163 template <class A, class B, class C, class D>
ScanTuple(const Tuple4<A,B,C,D> & t1,ScanningResults * results)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);
169 }
170
171 template <class MessageType>
172 class MessageScannerImpl {
173 public:
MessageScannerImpl(const IPC::Message * msg)174 explicit MessageScannerImpl(const IPC::Message* msg)
175 : msg_(static_cast<const MessageType*>(msg)) {
176 }
ScanMessage(ScanningResults * results)177 bool ScanMessage(ScanningResults* results) {
178 typename TupleTypes<typename MessageType::Schema::Param>::ValueTuple params;
179 if (!MessageType::Read(msg_, ¶ms))
180 return false;
181 ScanTuple(params, results);
182 return true;
183 }
184
ScanReply(ScanningResults * results)185 bool ScanReply(ScanningResults* results) {
186 typename TupleTypes<typename MessageType::Schema::ReplyParam>::ValueTuple
187 params;
188 if (!MessageType::ReadReplyParam(msg_, ¶ms))
189 return false;
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);
195 }
196 ScanTuple(params, results);
197 return true;
198 }
199 // TODO(dmichael): Add ScanSyncMessage for outgoing sync messages, if we ever
200 // need to scan those.
201
202 private:
203 const MessageType* msg_;
204 };
205
206 } // namespace
207
208 #define CASE_FOR_MESSAGE(MESSAGE_TYPE) \
209 case MESSAGE_TYPE::ID: { \
210 MessageScannerImpl<MESSAGE_TYPE> scanner(&msg); \
211 if (rewrite_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)) \
216 return false; \
217 break; \
218 }
219 #define CASE_FOR_REPLY(MESSAGE_TYPE) \
220 case MESSAGE_TYPE::ID: { \
221 MessageScannerImpl<MESSAGE_TYPE> scanner(&msg); \
222 if (rewrite_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)) \
227 return false; \
228 break; \
229 }
230
231 namespace ppapi {
232 namespace proxy {
233
234 class SerializedHandle;
235
FileSystem()236 NaClMessageScanner::FileSystem::FileSystem()
237 : reserved_quota_(0) {
238 }
239
~FileSystem()240 NaClMessageScanner::FileSystem::~FileSystem() {
241 }
242
UpdateReservedQuota(int64_t delta)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)
248 return false;
249 reserved_quota_ += delta;
250 return true;
251 }
252
FileIO(FileSystem * file_system,int64_t max_written_offset)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) {
257 }
258
~FileIO()259 NaClMessageScanner::FileIO::~FileIO() {
260 }
261
SetMaxWrittenOffset(int64_t max_written_offset)262 void NaClMessageScanner::FileIO::SetMaxWrittenOffset(
263 int64_t max_written_offset) {
264 base::AutoLock lock(lock_);
265 max_written_offset_ = max_written_offset;
266 }
267
Grow(int64_t amount)268 bool NaClMessageScanner::FileIO::Grow(int64_t amount) {
269 base::AutoLock lock(lock_);
270 DCHECK(amount > 0);
271 if (!file_system_->UpdateReservedQuota(-amount))
272 return false;
273 max_written_offset_ += amount;
274 return true;
275 }
276
NaClMessageScanner()277 NaClMessageScanner::NaClMessageScanner() {
278 }
279
~NaClMessageScanner()280 NaClMessageScanner::~NaClMessageScanner() {
281 for (FileSystemMap::iterator it = file_systems_.begin();
282 it != file_systems_.end(); ++it)
283 delete it->second;
284 for (FileIOMap::iterator it = files_.begin(); it != files_.end(); ++it)
285 delete it->second;
286 }
287
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.
ScanMessage(const IPC::Message & msg,std::vector<SerializedHandle> * handles,scoped_ptr<IPC::Message> * new_msg_ptr)292 bool NaClMessageScanner::ScanMessage(
293 const IPC::Message& msg,
294 std::vector<SerializedHandle>* handles,
295 scoped_ptr<IPC::Message>* new_msg_ptr) {
296 DCHECK(handles);
297 DCHECK(handles->empty());
298 DCHECK(new_msg_ptr);
299 DCHECK(!new_msg_ptr->get());
300
301 bool rewrite_msg =
302 #if defined(OS_WIN)
303 true;
304 #else
305 false;
306 #endif
307
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)
320 case IPC_REPLY_ID: {
321 int id = IPC::SyncMessage::GetMessageId(msg);
322 PendingSyncMsgMap::iterator iter(pending_sync_msgs_.find(id));
323 if (iter == pending_sync_msgs_.end()) {
324 NOTREACHED();
325 return false;
326 }
327 uint32_t type = iter->second;
328 pending_sync_msgs_.erase(iter);
329 switch (type) {
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)
334 default:
335 // Do nothing for messages we don't know.
336 break;
337 }
338 break;
339 }
340 default:
341 // Do nothing for messages we don't know.
342 break;
343 }
344
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();
351 }
352 return true;
353 }
354
ScanUntrustedMessage(const IPC::Message & untrusted_msg,scoped_ptr<IPC::Message> * new_msg_ptr)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);
360
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))
369 return;
370
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())
375 return;
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))
381 return;
382
383 int64_t trusted_max_written_offset = it->second->max_written_offset();
384 delete it->second;
385 files_.erase(it);
386 // If the plugin is under-reporting, rewrite the message with the
387 // trusted value.
388 if (trusted_max_written_offset > file_growth.max_written_offset) {
389 new_msg_ptr->reset(
390 new PpapiHostMsg_ResourceCall(
391 params,
392 PpapiHostMsg_FileIO_Close(
393 FileGrowth(trusted_max_written_offset, 0))));
394 }
395 break;
396 }
397 case PpapiHostMsg_FileIO_SetLength::ID: {
398 FileIOMap::iterator it = files_.find(params.pp_resource());
399 if (it == files_.end())
400 return;
401 // Audit FileIO SetLength messages to make sure the plugin is within
402 // the current quota reservation. In addition, deduct the file size
403 // increase from the quota reservation.
404 int64_t length = 0;
405 if (!UnpackMessage<PpapiHostMsg_FileIO_SetLength>(
406 nested_msg, &length))
407 return;
408
409 // Calculate file size increase, taking care to avoid overflows.
410 if (length < 0)
411 return;
412 int64_t trusted_max_written_offset = it->second->max_written_offset();
413 int64_t increase = length - trusted_max_written_offset;
414 if (increase <= 0)
415 return;
416 if (!it->second->Grow(increase)) {
417 new_msg_ptr->reset(
418 new PpapiHostMsg_ResourceCall(
419 params,
420 PpapiHostMsg_FileIO_SetLength(-1)));
421 }
422 break;
423 }
424 case PpapiHostMsg_FileSystem_ReserveQuota::ID: {
425 // Audit FileSystem ReserveQuota messages to make sure the plugin
426 // reports accurate file sizes.
427 int64_t amount = 0;
428 FileGrowthMap file_growths;
429 if (!UnpackMessage<PpapiHostMsg_FileSystem_ReserveQuota>(
430 nested_msg, &amount, &file_growths))
431 return;
432
433 bool audit_failed = false;
434 for (FileGrowthMap::iterator it = file_growths.begin();
435 it != file_growths.end(); ++it) {
436 FileIOMap::iterator file_it = files_.find(it->first);
437 if (file_it == files_.end())
438 continue;
439 int64_t trusted_max_written_offset =
440 file_it->second->max_written_offset();
441 if (trusted_max_written_offset > it->second.max_written_offset) {
442 audit_failed = true;
443 it->second.max_written_offset = trusted_max_written_offset;
444 }
445 if (it->second.append_mode_write_amount < 0) {
446 audit_failed = true;
447 it->second.append_mode_write_amount = 0;
448 }
449 }
450 if (audit_failed) {
451 new_msg_ptr->reset(
452 new PpapiHostMsg_ResourceCall(
453 params,
454 PpapiHostMsg_FileSystem_ReserveQuota(
455 amount, file_growths)));
456 }
457 break;
458 }
459 case PpapiHostMsg_ResourceDestroyed::ID: {
460 // Audit resource destroyed messages to release FileSystems.
461 PP_Resource resource;
462 if (!UnpackMessage<PpapiHostMsg_ResourceDestroyed>(
463 nested_msg, &resource))
464 return;
465 FileSystemMap::iterator fs_it = file_systems_.find(resource);
466 if (fs_it != file_systems_.end()) {
467 delete fs_it->second;
468 file_systems_.erase(fs_it);
469 }
470 break;
471 }
472 }
473 }
474 }
475
RegisterSyncMessageForReply(const IPC::Message & msg)476 void NaClMessageScanner::RegisterSyncMessageForReply(const IPC::Message& msg) {
477 int msg_id = IPC::SyncMessage::GetMessageId(msg);
478 DCHECK(pending_sync_msgs_.find(msg_id) == pending_sync_msgs_.end());
479
480 pending_sync_msgs_[msg_id] = msg.type();
481 }
482
GetFile(PP_Resource file_io)483 NaClMessageScanner::FileIO* NaClMessageScanner::GetFile(
484 PP_Resource file_io) {
485 FileIOMap::iterator it = files_.find(file_io);
486 DCHECK(it != files_.end());
487 return it->second;
488 }
489
AuditNestedMessage(PP_Resource resource,const IPC::Message & msg,SerializedHandle * handle)490 void NaClMessageScanner::AuditNestedMessage(PP_Resource resource,
491 const IPC::Message& msg,
492 SerializedHandle* handle) {
493 switch (msg.type()) {
494 case PpapiPluginMsg_FileIO_OpenReply::ID: {
495 // A file that requires quota checking was opened.
496 PP_Resource quota_file_system;
497 int64_t max_written_offset = 0;
498 if (ppapi::UnpackMessage<PpapiPluginMsg_FileIO_OpenReply>(
499 msg, "a_file_system, &max_written_offset)) {
500 if (quota_file_system) {
501 // Look up the FileSystem by inserting a new one. If it was already
502 // present, get the existing one, otherwise construct it.
503 FileSystem* file_system = NULL;
504 std::pair<FileSystemMap::iterator, bool> insert_result =
505 file_systems_.insert(std::make_pair(quota_file_system,
506 file_system));
507 if (insert_result.second)
508 insert_result.first->second = new FileSystem();
509 file_system = insert_result.first->second;
510 // Create the FileIO.
511 DCHECK(files_.find(resource) == files_.end());
512 files_.insert(std::make_pair(
513 resource,
514 new FileIO(file_system, max_written_offset)));
515 }
516 }
517 break;
518 }
519 case PpapiPluginMsg_FileSystem_ReserveQuotaReply::ID: {
520 // The amount of reserved quota for a FileSystem was refreshed.
521 int64_t amount = 0;
522 FileSizeMap file_sizes;
523 if (ppapi::UnpackMessage<PpapiPluginMsg_FileSystem_ReserveQuotaReply>(
524 msg, &amount, &file_sizes)) {
525 FileSystemMap::iterator it = file_systems_.find(resource);
526 DCHECK(it != file_systems_.end());
527 it->second->UpdateReservedQuota(amount);
528
529 FileSizeMap::const_iterator offset_it = file_sizes.begin();
530 for (; offset_it != file_sizes.end(); ++offset_it) {
531 FileIOMap::iterator fio_it = files_.find(offset_it->first);
532 DCHECK(fio_it != files_.end());
533 if (fio_it != files_.end())
534 fio_it->second->SetMaxWrittenOffset(offset_it->second);
535 }
536 }
537 break;
538 }
539 }
540 }
541
542 } // namespace proxy
543 } // namespace ppapi
544