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