nacl_message_scanner.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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
16class NaClDescImcShm;
17
18namespace IPC {
19class Message;
20}
21
22using ppapi::proxy::ResourceMessageReplyParams;
23using ppapi::proxy::SerializedHandle;
24using ppapi::proxy::SerializedVar;
25
26namespace {
27
28typedef std::vector<SerializedHandle> Handles;
29
30struct 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
51void 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.
65void 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
71void 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.
78void 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.
93void 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.
126void 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.
139template <class T>
140void 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.
148template <class A>
149void ScanTuple(const Tuple1<A>& t1, ScanningResults* results) {
150  ScanParam(t1.a, results);
151}
152template <class A, class B>
153void ScanTuple(const Tuple2<A, B>& t1, ScanningResults* results) {
154  ScanParam(t1.a, results);
155  ScanParam(t1.b, results);
156}
157template <class A, class B, class C>
158void 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}
163template <class A, class B, class C, class D>
164void 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
171template <class MessageType>
172class MessageScannerImpl {
173 public:
174  explicit MessageScannerImpl(const IPC::Message* msg)
175      : msg_(static_cast<const MessageType*>(msg)) {
176  }
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
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
231namespace ppapi {
232namespace proxy {
233
234class SerializedHandle;
235
236NaClMessageScanner::FileSystem::FileSystem()
237    : reserved_quota_(0) {
238}
239
240NaClMessageScanner::FileSystem::~FileSystem() {
241}
242
243bool 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
253NaClMessageScanner::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
259NaClMessageScanner::FileIO::~FileIO() {
260}
261
262void NaClMessageScanner::FileIO::SetMaxWrittenOffset(
263    int64_t max_written_offset) {
264  base::AutoLock lock(lock_);
265  max_written_offset_ = max_written_offset;
266}
267
268bool 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
277NaClMessageScanner::NaClMessageScanner() {
278}
279
280NaClMessageScanner::~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.
292bool 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
355void 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
476void 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
483NaClMessageScanner::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
490void 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