• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_, &params))
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_, &params))
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, &params, &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, &quota_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