Home | History | Annotate | Download | only in proxy
      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 {
     31   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 
     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.
     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 
     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.
     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.
     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.
    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>
    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>
    152 void ScanTuple(const Tuple1<A>& t1, ScanningResults* results) {
    153   ScanParam(t1.a, results);
    154 }
    155 template <class A, class B>
    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>
    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>
    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:
    177   explicit MessageScannerImpl(const IPC::Message* msg)
    178       : msg_(static_cast<const MessageType*>(msg)) {
    179   }
    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 
    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 
    239 NaClMessageScanner::FileSystem::FileSystem()
    240     : reserved_quota_(0) {
    241 }
    242 
    243 NaClMessageScanner::FileSystem::~FileSystem() {
    244 }
    245 
    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 
    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 
    262 NaClMessageScanner::FileIO::~FileIO() {
    263 }
    264 
    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 
    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 
    280 NaClMessageScanner::NaClMessageScanner() {
    281 }
    282 
    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.
    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 
    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 
    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 
    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