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_, ¶ms)) 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_, ¶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 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, ¶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 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, "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