• 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__anon5cb220fb0111::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   if (handle.type() != SerializedHandle::INVALID) {
57     // Now write the handle itself in POSIX style.
58     // See ParamTraits<FileDescriptor>::Read for where these values are read.
59     msg->WriteBool(true);  // valid == true
60     msg->WriteInt(handle_index);
61   }
62 }
63 
64 // Define overloads for each kind of message parameter that requires special
65 // handling. See ScanTuple for how these get used.
66 
67 // Overload to match SerializedHandle.
ScanParam(const SerializedHandle & handle,ScanningResults * results)68 void ScanParam(const SerializedHandle& handle, ScanningResults* results) {
69   results->handles.push_back(handle);
70   if (results->new_msg)
71     WriteHandle(results->handle_index++, handle, results->new_msg.get());
72 }
73 
HandleWriter(int * handle_index,IPC::Message * m,const SerializedHandle & handle)74 void HandleWriter(int* handle_index,
75                   IPC::Message* m,
76                   const SerializedHandle& handle) {
77   WriteHandle((*handle_index)++, handle, m);
78 }
79 
80 // Overload to match SerializedVar, which can contain handles.
ScanParam(const SerializedVar & var,ScanningResults * results)81 void ScanParam(const SerializedVar& var, ScanningResults* results) {
82   std::vector<SerializedHandle*> var_handles = var.GetHandles();
83   // Copy any handles and then rewrite the message.
84   for (size_t i = 0; i < var_handles.size(); ++i)
85     results->handles.push_back(*var_handles[i]);
86   if (results->new_msg)
87     var.WriteDataToMessage(results->new_msg.get(),
88                            base::Bind(&HandleWriter, &results->handle_index));
89 }
90 
91 // For PpapiMsg_ResourceReply and the reply to PpapiHostMsg_ResourceSyncCall,
92 // the handles are carried inside the ResourceMessageReplyParams.
93 // NOTE: We only intercept handles from host->NaCl. The only kind of
94 //       ResourceMessageParams that travels this direction is
95 //       ResourceMessageReplyParams, so that's the only one we need to handle.
ScanParam(const ResourceMessageReplyParams & params,ScanningResults * results)96 void ScanParam(const ResourceMessageReplyParams& params,
97                ScanningResults* results) {
98   results->pp_resource = params.pp_resource();
99   // If the resource reply params don't contain handles, NULL the new message
100   // pointer to cancel further rewriting.
101   // NOTE: This works because only handles currently need rewriting, and we
102   //       know at this point that this message has none.
103   if (params.handles().empty()) {
104     results->new_msg.reset(NULL);
105     return;
106   }
107 
108   // If we need to rewrite the message, write everything before the handles
109   // (there's nothing after the handles).
110   if (results->new_msg) {
111     params.WriteReplyHeader(results->new_msg.get());
112     // IPC writes the vector length as an int before the contents of the
113     // vector.
114     results->new_msg->WriteInt(static_cast<int>(params.handles().size()));
115   }
116   for (Handles::const_iterator iter = params.handles().begin();
117        iter != params.handles().end();
118        ++iter) {
119     // ScanParam will write each handle to the new message, if necessary.
120     ScanParam(*iter, results);
121   }
122   // Tell ResourceMessageReplyParams that we have taken the handles, so it
123   // shouldn't close them. The NaCl runtime will take ownership of them.
124   params.ConsumeHandles();
125 }
126 
127 // Overload to match nested messages. If we need to rewrite the message, write
128 // the parameter.
ScanParam(const IPC::Message & param,ScanningResults * results)129 void ScanParam(const IPC::Message& param, ScanningResults* results) {
130   if (results->pp_resource && !results->nested_msg_callback.is_null()) {
131     SerializedHandle* handle = NULL;
132     if (results->handles.size() == 1)
133       handle = &results->handles[0];
134     results->nested_msg_callback.Run(results->pp_resource, param, handle);
135   }
136   if (results->new_msg)
137     IPC::WriteParam(results->new_msg.get(), param);
138 }
139 
140 // Overload to match all other types. If we need to rewrite the message, write
141 // the parameter.
142 template <class T>
ScanParam(const T & param,ScanningResults * results)143 void ScanParam(const T& param, ScanningResults* results) {
144   if (results->new_msg)
145     IPC::WriteParam(results->new_msg.get(), param);
146 }
147 
148 // These just break apart the given tuple and run ScanParam over each param.
149 // The idea is to scan elements in the tuple which require special handling,
150 // and write them into the |results| struct.
151 template <class A>
ScanTuple(const Tuple1<A> & t1,ScanningResults * results)152 void ScanTuple(const Tuple1<A>& t1, ScanningResults* results) {
153   ScanParam(t1.a, results);
154 }
155 template <class A, class B>
ScanTuple(const Tuple2<A,B> & t1,ScanningResults * results)156 void ScanTuple(const Tuple2<A, B>& t1, ScanningResults* results) {
157   ScanParam(t1.a, results);
158   ScanParam(t1.b, results);
159 }
160 template <class A, class B, class C>
ScanTuple(const Tuple3<A,B,C> & t1,ScanningResults * results)161 void ScanTuple(const Tuple3<A, B, C>& t1, ScanningResults* results) {
162   ScanParam(t1.a, results);
163   ScanParam(t1.b, results);
164   ScanParam(t1.c, results);
165 }
166 template <class A, class B, class C, class D>
ScanTuple(const Tuple4<A,B,C,D> & t1,ScanningResults * results)167 void ScanTuple(const Tuple4<A, B, C, D>& t1, ScanningResults* results) {
168   ScanParam(t1.a, results);
169   ScanParam(t1.b, results);
170   ScanParam(t1.c, results);
171   ScanParam(t1.d, results);
172 }
173 
174 template <class MessageType>
175 class MessageScannerImpl {
176  public:
MessageScannerImpl(const IPC::Message * msg)177   explicit MessageScannerImpl(const IPC::Message* msg)
178       : msg_(static_cast<const MessageType*>(msg)) {
179   }
ScanMessage(ScanningResults * results)180   bool ScanMessage(ScanningResults* results) {
181     typename TupleTypes<typename MessageType::Schema::Param>::ValueTuple params;
182     if (!MessageType::Read(msg_, &params))
183       return false;
184     ScanTuple(params, results);
185     return true;
186   }
187 
ScanReply(ScanningResults * results)188   bool ScanReply(ScanningResults* results) {
189     typename TupleTypes<typename MessageType::Schema::ReplyParam>::ValueTuple
190         params;
191     if (!MessageType::ReadReplyParam(msg_, &params))
192       return false;
193     // If we need to rewrite the message, write the message id first.
194     if (results->new_msg) {
195       results->new_msg->set_reply();
196       int id = IPC::SyncMessage::GetMessageId(*msg_);
197       results->new_msg->WriteInt(id);
198     }
199     ScanTuple(params, results);
200     return true;
201   }
202   // TODO(dmichael): Add ScanSyncMessage for outgoing sync messages, if we ever
203   //                 need to scan those.
204 
205  private:
206   const MessageType* msg_;
207 };
208 
209 }  // namespace
210 
211 #define CASE_FOR_MESSAGE(MESSAGE_TYPE) \
212       case MESSAGE_TYPE::ID: { \
213         MessageScannerImpl<MESSAGE_TYPE> scanner(&msg); \
214         if (rewrite_msg) \
215           results.new_msg.reset( \
216               new IPC::Message(msg.routing_id(), msg.type(), \
217                                IPC::Message::PRIORITY_NORMAL)); \
218         if (!scanner.ScanMessage(&results)) \
219           return false; \
220         break; \
221       }
222 #define CASE_FOR_REPLY(MESSAGE_TYPE) \
223       case MESSAGE_TYPE::ID: { \
224         MessageScannerImpl<MESSAGE_TYPE> scanner(&msg); \
225         if (rewrite_msg) \
226           results.new_msg.reset( \
227               new IPC::Message(msg.routing_id(), msg.type(), \
228                                IPC::Message::PRIORITY_NORMAL)); \
229         if (!scanner.ScanReply(&results)) \
230           return false; \
231         break; \
232       }
233 
234 namespace ppapi {
235 namespace proxy {
236 
237 class SerializedHandle;
238 
FileSystem()239 NaClMessageScanner::FileSystem::FileSystem()
240     : reserved_quota_(0) {
241 }
242 
~FileSystem()243 NaClMessageScanner::FileSystem::~FileSystem() {
244 }
245 
UpdateReservedQuota(int64_t delta)246 bool NaClMessageScanner::FileSystem::UpdateReservedQuota(int64_t delta) {
247   base::AutoLock lock(lock_);
248   if (std::numeric_limits<int64_t>::max() - reserved_quota_ < delta)
249     return false;  // reserved_quota_ + delta would overflow.
250   if (reserved_quota_ + delta < 0)
251     return false;
252   reserved_quota_ += delta;
253   return true;
254 }
255 
FileIO(FileSystem * file_system,int64_t max_written_offset)256 NaClMessageScanner::FileIO::FileIO(FileSystem* file_system,
257                                    int64_t max_written_offset)
258     : file_system_(file_system),
259       max_written_offset_(max_written_offset) {
260 }
261 
~FileIO()262 NaClMessageScanner::FileIO::~FileIO() {
263 }
264 
SetMaxWrittenOffset(int64_t max_written_offset)265 void NaClMessageScanner::FileIO::SetMaxWrittenOffset(
266     int64_t max_written_offset) {
267   base::AutoLock lock(lock_);
268   max_written_offset_ = max_written_offset;
269 }
270 
Grow(int64_t amount)271 bool NaClMessageScanner::FileIO::Grow(int64_t amount) {
272   base::AutoLock lock(lock_);
273   DCHECK(amount > 0);
274   if (!file_system_->UpdateReservedQuota(-amount))
275     return false;
276   max_written_offset_ += amount;
277   return true;
278 }
279 
NaClMessageScanner()280 NaClMessageScanner::NaClMessageScanner() {
281 }
282 
~NaClMessageScanner()283 NaClMessageScanner::~NaClMessageScanner() {
284   for (FileSystemMap::iterator it = file_systems_.begin();
285       it != file_systems_.end(); ++it)
286     delete it->second;
287   for (FileIOMap::iterator it = files_.begin(); it != files_.end(); ++it)
288     delete it->second;
289 }
290 
291 // Windows IPC differs from POSIX in that native handles are serialized in the
292 // message body, rather than passed in a separate FileDescriptorSet. Therefore,
293 // on Windows, any message containing handles must be rewritten in the POSIX
294 // format before we can send it to the NaCl plugin.
ScanMessage(const IPC::Message & msg,uint32_t type,std::vector<SerializedHandle> * handles,scoped_ptr<IPC::Message> * new_msg_ptr)295 bool NaClMessageScanner::ScanMessage(
296     const IPC::Message& msg,
297     uint32_t type,
298     std::vector<SerializedHandle>* handles,
299     scoped_ptr<IPC::Message>* new_msg_ptr) {
300   DCHECK(handles);
301   DCHECK(handles->empty());
302   DCHECK(new_msg_ptr);
303   DCHECK(!new_msg_ptr->get());
304 
305   bool rewrite_msg =
306 #if defined(OS_WIN)
307       true;
308 #else
309       false;
310 #endif
311 
312   // We can't always tell from the message ID if rewriting is needed. Therefore,
313   // scan any message types that might contain a handle. If we later determine
314   // that there are no handles, we can cancel the rewriting by clearing the
315   // results.new_msg pointer.
316   ScanningResults results;
317   results.nested_msg_callback =
318       base::Bind(&NaClMessageScanner::AuditNestedMessage,
319                  base::Unretained(this));
320   switch (type) {
321     CASE_FOR_MESSAGE(PpapiMsg_PPBAudio_NotifyAudioStreamCreated)
322     CASE_FOR_MESSAGE(PpapiMsg_PPPMessaging_HandleMessage)
323     CASE_FOR_MESSAGE(PpapiPluginMsg_ResourceReply)
324     CASE_FOR_REPLY(PpapiHostMsg_OpenResource)
325     CASE_FOR_REPLY(PpapiHostMsg_PPBGraphics3D_Create)
326     CASE_FOR_REPLY(PpapiHostMsg_PPBGraphics3D_CreateTransferBuffer)
327     CASE_FOR_REPLY(PpapiHostMsg_PPBImageData_CreateSimple)
328     CASE_FOR_REPLY(PpapiHostMsg_ResourceSyncCall)
329     CASE_FOR_REPLY(PpapiHostMsg_SharedMemory_CreateSharedMemory)
330     default:
331       // Do nothing for messages we don't know.
332       break;
333   }
334 
335   // Only messages containing handles need to be rewritten. If no handles are
336   // found, don't return the rewritten message either. This must be changed if
337   // we ever add new param types that also require rewriting.
338   if (!results.handles.empty()) {
339     handles->swap(results.handles);
340     *new_msg_ptr = results.new_msg.Pass();
341   }
342   return true;
343 }
344 
ScanUntrustedMessage(const IPC::Message & untrusted_msg,scoped_ptr<IPC::Message> * new_msg_ptr)345 void NaClMessageScanner::ScanUntrustedMessage(
346     const IPC::Message& untrusted_msg,
347     scoped_ptr<IPC::Message>* new_msg_ptr) {
348   // Audit FileIO and FileSystem messages to ensure that the plugin doesn't
349   // exceed its file quota. If we find the message is malformed, just pass it
350   // through - we only care about well formed messages to the host.
351   if (untrusted_msg.type() == PpapiHostMsg_ResourceCall::ID) {
352     ResourceMessageCallParams params;
353     IPC::Message nested_msg;
354     if (!UnpackMessage<PpapiHostMsg_ResourceCall>(
355             untrusted_msg, &params, &nested_msg))
356       return;
357 
358     switch (nested_msg.type()) {
359       case PpapiHostMsg_FileIO_Close::ID: {
360         FileIOMap::iterator it = files_.find(params.pp_resource());
361         if (it == files_.end())
362           return;
363         // Audit FileIO Close messages to make sure the plugin reports an
364         // accurate file size.
365         FileGrowth file_growth;
366         if (!UnpackMessage<PpapiHostMsg_FileIO_Close>(
367                 nested_msg, &file_growth))
368           return;
369 
370         int64_t trusted_max_written_offset = it->second->max_written_offset();
371         delete it->second;
372         files_.erase(it);
373         // If the plugin is under-reporting, rewrite the message with the
374         // trusted value.
375         if (trusted_max_written_offset > file_growth.max_written_offset) {
376           new_msg_ptr->reset(
377               new PpapiHostMsg_ResourceCall(
378                   params,
379                   PpapiHostMsg_FileIO_Close(
380                       FileGrowth(trusted_max_written_offset, 0))));
381         }
382         break;
383       }
384       case PpapiHostMsg_FileIO_SetLength::ID: {
385         FileIOMap::iterator it = files_.find(params.pp_resource());
386         if (it == files_.end())
387           return;
388         // Audit FileIO SetLength messages to make sure the plugin is within
389         // the current quota reservation. In addition, deduct the file size
390         // increase from the quota reservation.
391         int64_t length = 0;
392         if (!UnpackMessage<PpapiHostMsg_FileIO_SetLength>(
393                 nested_msg, &length))
394           return;
395 
396         // Calculate file size increase, taking care to avoid overflows.
397         if (length < 0)
398           return;
399         int64_t trusted_max_written_offset = it->second->max_written_offset();
400         int64_t increase = length - trusted_max_written_offset;
401         if (increase <= 0)
402           return;
403         if (!it->second->Grow(increase)) {
404           new_msg_ptr->reset(
405               new PpapiHostMsg_ResourceCall(
406                   params,
407                   PpapiHostMsg_FileIO_SetLength(-1)));
408         }
409         break;
410       }
411       case PpapiHostMsg_FileSystem_ReserveQuota::ID: {
412         // Audit FileSystem ReserveQuota messages to make sure the plugin
413         // reports accurate file sizes.
414         int64_t amount = 0;
415         FileGrowthMap file_growths;
416         if (!UnpackMessage<PpapiHostMsg_FileSystem_ReserveQuota>(
417                 nested_msg, &amount, &file_growths))
418           return;
419 
420         bool audit_failed = false;
421         for (FileGrowthMap::iterator it = file_growths.begin();
422             it != file_growths.end(); ++it) {
423           FileIOMap::iterator file_it = files_.find(it->first);
424           if (file_it == files_.end())
425             continue;
426           int64_t trusted_max_written_offset =
427               file_it->second->max_written_offset();
428           if (trusted_max_written_offset > it->second.max_written_offset) {
429             audit_failed = true;
430             it->second.max_written_offset = trusted_max_written_offset;
431           }
432           if (it->second.append_mode_write_amount < 0) {
433             audit_failed = true;
434             it->second.append_mode_write_amount = 0;
435           }
436         }
437         if (audit_failed) {
438           new_msg_ptr->reset(
439               new PpapiHostMsg_ResourceCall(
440                   params,
441                   PpapiHostMsg_FileSystem_ReserveQuota(
442                       amount, file_growths)));
443         }
444         break;
445       }
446       case PpapiHostMsg_ResourceDestroyed::ID: {
447         // Audit resource destroyed messages to release FileSystems.
448         PP_Resource resource;
449         if (!UnpackMessage<PpapiHostMsg_ResourceDestroyed>(
450                 nested_msg, &resource))
451           return;
452         FileSystemMap::iterator fs_it = file_systems_.find(resource);
453         if (fs_it != file_systems_.end()) {
454           delete fs_it->second;
455           file_systems_.erase(fs_it);
456         }
457         break;
458       }
459     }
460   }
461 }
462 
GetFile(PP_Resource file_io)463 NaClMessageScanner::FileIO* NaClMessageScanner::GetFile(
464     PP_Resource file_io) {
465   FileIOMap::iterator it = files_.find(file_io);
466   DCHECK(it != files_.end());
467   return it->second;
468 }
469 
AuditNestedMessage(PP_Resource resource,const IPC::Message & msg,SerializedHandle * handle)470 void NaClMessageScanner::AuditNestedMessage(PP_Resource resource,
471                                             const IPC::Message& msg,
472                                             SerializedHandle* handle) {
473   switch (msg.type()) {
474     case PpapiPluginMsg_FileIO_OpenReply::ID: {
475       // A file that requires quota checking was opened.
476       PP_Resource quota_file_system;
477       int64_t max_written_offset = 0;
478       if (ppapi::UnpackMessage<PpapiPluginMsg_FileIO_OpenReply>(
479               msg, &quota_file_system, &max_written_offset)) {
480         if (quota_file_system) {
481           // Look up the FileSystem by inserting a new one. If it was already
482           // present, get the existing one, otherwise construct it.
483           FileSystem* file_system = NULL;
484           std::pair<FileSystemMap::iterator, bool> insert_result =
485               file_systems_.insert(std::make_pair(quota_file_system,
486                                                   file_system));
487           if (insert_result.second)
488             insert_result.first->second = new FileSystem();
489           file_system = insert_result.first->second;
490           // Create the FileIO.
491           DCHECK(files_.find(resource) == files_.end());
492           files_.insert(std::make_pair(
493               resource,
494               new FileIO(file_system, max_written_offset)));
495         }
496       }
497       break;
498     }
499     case PpapiPluginMsg_FileSystem_ReserveQuotaReply::ID: {
500       // The amount of reserved quota for a FileSystem was refreshed.
501       int64_t amount = 0;
502       FileSizeMap file_sizes;
503       if (ppapi::UnpackMessage<PpapiPluginMsg_FileSystem_ReserveQuotaReply>(
504           msg, &amount, &file_sizes)) {
505         FileSystemMap::iterator it = file_systems_.find(resource);
506         DCHECK(it != file_systems_.end());
507         it->second->UpdateReservedQuota(amount);
508 
509         FileSizeMap::const_iterator offset_it = file_sizes.begin();
510         for (; offset_it != file_sizes.end(); ++offset_it) {
511           FileIOMap::iterator fio_it = files_.find(offset_it->first);
512           DCHECK(fio_it != files_.end());
513           if (fio_it != files_.end())
514             fio_it->second->SetMaxWrittenOffset(offset_it->second);
515         }
516       }
517       break;
518     }
519   }
520 }
521 
522 }  // namespace proxy
523 }  // namespace ppapi
524