diff options
-rw-r--r-- | components/nacl/loader/nacl_ipc_adapter.cc | 116 | ||||
-rw-r--r-- | content/browser/renderer_host/pepper/pepper_file_io_host.cc | 227 | ||||
-rw-r--r-- | content/browser/renderer_host/pepper/pepper_file_io_host.h | 28 | ||||
-rw-r--r-- | content/browser/renderer_host/pepper/pepper_file_system_browser_host.cc | 125 | ||||
-rw-r--r-- | content/browser/renderer_host/pepper/pepper_file_system_browser_host.h | 41 | ||||
-rw-r--r-- | content/browser/renderer_host/pepper/quota_reservation.cc | 2 | ||||
-rw-r--r-- | ppapi/ppapi_tests.gypi | 3 | ||||
-rw-r--r-- | ppapi/proxy/file_io_resource.cc | 231 | ||||
-rw-r--r-- | ppapi/proxy/file_io_resource.h | 53 | ||||
-rw-r--r-- | ppapi/proxy/file_system_resource.cc | 109 | ||||
-rw-r--r-- | ppapi/proxy/file_system_resource.h | 28 | ||||
-rw-r--r-- | ppapi/proxy/nacl_message_scanner.cc | 269 | ||||
-rw-r--r-- | ppapi/proxy/nacl_message_scanner.h | 66 | ||||
-rw-r--r-- | ppapi/proxy/nacl_message_scanner_unittest.cc | 293 | ||||
-rw-r--r-- | ppapi/proxy/ppapi_messages.h | 20 | ||||
-rw-r--r-- | ppapi/thunk/ppb_file_io_api.h | 2 | ||||
-rw-r--r-- | ppapi/thunk/ppb_file_system_api.h | 6 |
17 files changed, 1244 insertions, 375 deletions
diff --git a/components/nacl/loader/nacl_ipc_adapter.cc b/components/nacl/loader/nacl_ipc_adapter.cc index c6aaf61..f591827 100644 --- a/components/nacl/loader/nacl_ipc_adapter.cc +++ b/components/nacl/loader/nacl_ipc_adapter.cc @@ -19,6 +19,8 @@ #include "native_client/src/trusted/desc/nacl_desc_custom.h" #include "native_client/src/trusted/desc/nacl_desc_imc_shm.h" #include "native_client/src/trusted/desc/nacl_desc_io.h" +#include "native_client/src/trusted/desc/nacl_desc_quota.h" +#include "native_client/src/trusted/desc/nacl_desc_quota_interface.h" #include "native_client/src/trusted/desc/nacl_desc_sync_socket.h" #include "native_client/src/trusted/desc/nacl_desc_wrapper.h" #include "native_client/src/trusted/service_runtime/include/sys/fcntl.h" @@ -26,6 +28,8 @@ #include "ppapi/proxy/ppapi_messages.h" #include "ppapi/proxy/serialized_handle.h" +using ppapi::proxy::NaClMessageScanner; + namespace { enum BufferSizeStatus { @@ -55,11 +59,12 @@ BufferSizeStatus GetBufferStatus(const char* data, size_t len) { return MESSAGE_IS_TRUNCATED; } +//------------------------------------------------------------------------------ // This object allows the NaClDesc to hold a reference to a NaClIPCAdapter and // forward calls to it. struct DescThunker { - explicit DescThunker(NaClIPCAdapter* adapter_param) - : adapter(adapter_param) { + explicit DescThunker(NaClIPCAdapter* adapter_arg) + : adapter(adapter_arg) { } scoped_refptr<NaClIPCAdapter> adapter; }; @@ -92,6 +97,90 @@ NaClDesc* MakeNaClDescCustom(NaClIPCAdapter* adapter) { return NaClDescMakeCustomDesc(new DescThunker(adapter), &funcs); } +//------------------------------------------------------------------------------ +// This object is passed to a NaClDescQuota to intercept writes and forward them +// to the NaClIPCAdapter, which checks quota. This is a NaCl-style struct. Don't +// add non-trivial fields or virtual methods. Construction should use malloc, +// because this is owned by the NaClDesc, and the NaCl Dtor code will call free. +struct QuotaInterface { + // The "base" struct must be first. NaCl code expects a NaCl style ref-counted + // object, so the "vtable" and other base class fields must be first. + struct NaClDescQuotaInterface base NACL_IS_REFCOUNT_SUBCLASS; + + NaClMessageScanner::FileIO* file_io; +}; + +static int64_t QuotaInterfaceWriteRequest( + NaClDescQuotaInterface* ndqi, + const uint8_t* /* unused_id */, + int64_t offset, + int64_t length) { + if (offset < 0 || length < 0) + return 0; + if (std::numeric_limits<int64_t>::max() - length < offset) + return 0; // offset + length would overflow. + int64_t max_offset = offset + length; + if (max_offset < 0) + return 0; + + QuotaInterface* quota_interface = reinterpret_cast<QuotaInterface*>(ndqi); + NaClMessageScanner::FileIO* file_io = quota_interface->file_io; + int64_t increase = max_offset - file_io->max_written_offset(); + if (increase <= 0 || file_io->Grow(increase)) + return length; + + return 0; +} + +static int64_t QuotaInterfaceFtruncateRequest( + NaClDescQuotaInterface* ndqi, + const uint8_t* /* unused_id */, + int64_t length) { + // We can't implement SetLength on the plugin side due to sandbox limitations. + // See crbug.com/156077. + NOTREACHED(); + return 0; +} + +struct NaClDescQuotaInterfaceVtbl const kQuotaInterfaceVtbl = { + kNaClDescQuotaInterfaceVtbl.vbase, // NaClRefCountVtbl, containing Dtor. + QuotaInterfaceWriteRequest, + QuotaInterfaceFtruncateRequest +}; + +uint8_t unused_id[NACL_DESC_QUOTA_FILE_ID_LEN]; + +NaClDesc* MakeNaClDescQuota( + NaClMessageScanner::FileIO* file_io, + NaClDesc* wrapped_desc) { + // Create the QuotaInterface. + QuotaInterface* quota_interface = + static_cast<QuotaInterface*>(malloc(sizeof *quota_interface)); + if (quota_interface && NaClDescQuotaInterfaceCtor("a_interface->base)) { + quota_interface->base.base.vtbl = + (struct NaClRefCountVtbl *)(&kQuotaInterfaceVtbl); + // QuotaInterface is a trivial class, so skip the ctor. + quota_interface->file_io = file_io; + // Create the NaClDescQuota. + NaClDescQuota* desc = static_cast<NaClDescQuota*>(malloc(sizeof *desc)); + if (desc && NaClDescQuotaCtor(desc, + wrapped_desc, + unused_id, + "a_interface->base)) { + return &desc->base; + } + if (desc) + NaClDescUnref(reinterpret_cast<NaClDesc*>(desc)); + } + + if (quota_interface) + NaClDescQuotaInterfaceUnref("a_interface->base); + + return NULL; +} + +//------------------------------------------------------------------------------ + void DeleteChannel(IPC::Channel* channel) { delete channel; } @@ -432,21 +521,26 @@ bool NaClIPCAdapter::OnMessageReceived(const IPC::Message& msg) { base::Passed(&response))); break; } - case ppapi::proxy::SerializedHandle::FILE: - // IMPORTANT: The NaClDescIoDescFromHandleAllocCtor function creates - // a NaClDesc that checks file flags before reading and writing. This - // is essential since PPB_FileIO now sends a file descriptor to the - // plugin which may have write capabilities. We can't allow the plugin - // to write with it since it could bypass quota checks, which still - // happen in the host. - nacl_desc.reset(new NaClDescWrapper(NaClDescIoDescFromHandleAllocCtor( + case ppapi::proxy::SerializedHandle::FILE: { + // Create the NaClDesc for the file descriptor. If quota checking is + // required, wrap it in a NaClDescQuota. + NaClDesc* desc = NaClDescIoDescFromHandleAllocCtor( #if defined(OS_WIN) iter->descriptor(), #else iter->descriptor().fd, #endif - TranslatePepperFileReadWriteOpenFlags(iter->open_flags())))); + TranslatePepperFileReadWriteOpenFlags(iter->open_flags())); + if (desc && iter->file_io()) { + desc = MakeNaClDescQuota( + locked_data_.nacl_msg_scanner_.GetFile(iter->file_io()), + desc); + } + if (desc) + nacl_desc.reset(new NaClDescWrapper(desc)); break; + } + case ppapi::proxy::SerializedHandle::INVALID: { // Nothing to do. TODO(dmichael): Should we log this? Or is it // sometimes okay to pass an INVALID handle? diff --git a/content/browser/renderer_host/pepper/pepper_file_io_host.cc b/content/browser/renderer_host/pepper/pepper_file_io_host.cc index 1118c58..6fe68de 100644 --- a/content/browser/renderer_host/pepper/pepper_file_io_host.cc +++ b/content/browser/renderer_host/pepper/pepper_file_io_host.cc @@ -41,13 +41,6 @@ using ppapi::PPTimeToTime; namespace { -int32_t ErrorOrByteNumber(int32_t pp_error, int32_t byte_number) { - // On the plugin side, some callbacks expect a parameter that means different - // things depending on whether it is negative or not. We translate for those - // callbacks here. - return pp_error == PP_OK ? byte_number : pp_error; -} - PepperFileIOHost::UIThreadStuff GetUIThreadStuffForInternalFileSystems(int render_process_id) { PepperFileIOHost::UIThreadStuff stuff; @@ -79,6 +72,10 @@ bool GetPluginAllowedToCallRequestOSFileHandle(int render_process_id, host->GetBrowserContext(), document_url); } +bool FileOpenForWrite(int32_t open_flags) { + return (open_flags & (PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_APPEND)) != 0; +} + } // namespace PepperFileIOHost::PepperFileIOHost(BrowserPpapiHostImpl* host, @@ -104,7 +101,9 @@ PepperFileIOHost::PepperFileIOHost(BrowserPpapiHostImpl* host, } PepperFileIOHost::~PepperFileIOHost() { - OnHostMsgClose(NULL); + // FileIOResource will normally send a close message, but the plugin may have + // crashed. + OnHostMsgClose(NULL, max_written_offset_); } int32_t PepperFileIOHost::OnResourceMessageReceived( @@ -115,14 +114,12 @@ int32_t PepperFileIOHost::OnResourceMessageReceived( OnHostMsgOpen) PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileIO_Touch, OnHostMsgTouch) - PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileIO_Write, - OnHostMsgWrite) PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileIO_SetLength, OnHostMsgSetLength) PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_FileIO_Flush, OnHostMsgFlush) - PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_FileIO_Close, - OnHostMsgClose) + PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileIO_Close, + OnHostMsgClose) PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_FileIO_RequestOSFileHandle, OnHostMsgRequestOSFileHandle) IPC_END_MESSAGE_MAP() @@ -209,13 +206,13 @@ void PepperFileIOHost::GotUIThreadStuffForInternalFileSystems( if (resolved_render_process_id_ == base::kNullProcessId || !file_system_context_.get()) { reply_context.params.set_result(PP_ERROR_FAILED); - host()->SendReply(reply_context, PpapiPluginMsg_FileIO_OpenReply()); + SendOpenErrorReply(reply_context); return; } if (!file_system_context_->GetFileSystemBackend(file_system_url_.type())) { reply_context.params.set_result(PP_ERROR_FAILED); - host()->SendReply(reply_context, PpapiPluginMsg_FileIO_OpenReply()); + SendOpenErrorReply(reply_context); return; } @@ -237,8 +234,8 @@ void PepperFileIOHost::DidOpenInternalFile( if (result == base::PLATFORM_FILE_OK) { on_close_callback_ = on_close_callback; - check_quota_ = file_system_host_ && file_system_host_->ChecksQuota(); - if (check_quota_) { + if (FileOpenForWrite(open_flags_) && file_system_host_->ChecksQuota()) { + check_quota_ = true; file_system_host_->OpenQuotaFile( this, file_system_url_.path(), @@ -293,55 +290,6 @@ int32_t PepperFileIOHost::OnHostMsgTouch( return PP_OK_COMPLETIONPENDING; } -int32_t PepperFileIOHost::OnHostMsgWrite( - ppapi::host::HostMessageContext* context, - int64_t offset, - const std::string& buffer) { - int32_t rv = state_manager_.CheckOperationState( - FileIOStateManager::OPERATION_WRITE, true); - if (rv != PP_OK) - return rv; - if (offset < 0) - return PP_ERROR_BADARGUMENT; - - if (check_quota_) { - int64_t actual_offset = - (open_flags_ & PP_FILEOPENFLAG_APPEND) ? max_written_offset_ : offset; - - uint64_t max_offset = actual_offset + buffer.size(); - if (max_offset > static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) - return PP_ERROR_FAILED; // max_offset overflows. - int64_t amount = static_cast<int64_t>(max_offset) - max_written_offset_; - - // Quota request amounts are restricted to 32 bits so we can use atomics - // when we move this code to the plugin side of the proxy. - if (amount > std::numeric_limits<int32_t>::max()) - return PP_ERROR_NOQUOTA; - - if (amount > 0) { - int32_t result = file_system_host_->RequestQuota( - static_cast<int32_t>(amount), - base::Bind(&PepperFileIOHost::GotWriteQuota, - weak_factory_.GetWeakPtr(), - context->MakeReplyMessageContext(), - offset, buffer)); - if (result == PP_OK_COMPLETIONPENDING) { - state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_WRITE); - return result; - } - // RequestQuota returns either PP_OK_COMPLETIONPENDING or the requested - // quota amount. - DCHECK(result > 0); - } - } - - if (!CallWrite(context->MakeReplyMessageContext(), offset, buffer)) - return PP_ERROR_FAILED; - - state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_WRITE); - return PP_OK_COMPLETIONPENDING; -} - int32_t PepperFileIOHost::OnHostMsgSetLength( ppapi::host::HostMessageContext* context, int64_t length) { @@ -352,32 +300,16 @@ int32_t PepperFileIOHost::OnHostMsgSetLength( if (length < 0) return PP_ERROR_BADARGUMENT; - if (check_quota_) { - int64_t amount = length - max_written_offset_; - // Quota request amounts are restricted to 32 bits so we can use atomics - // when we move this code to the plugin side of the proxy. - if (amount > std::numeric_limits<int32_t>::max()) - return PP_ERROR_NOQUOTA; - - if (amount > 0) { - int32_t result = file_system_host_->RequestQuota( - static_cast<int32_t>(amount), - base::Bind(&PepperFileIOHost::GotSetLengthQuota, - weak_factory_.GetWeakPtr(), - context->MakeReplyMessageContext(), - length)); - if (result == PP_OK_COMPLETIONPENDING) { - state_manager_.SetPendingOperation( - FileIOStateManager::OPERATION_EXCLUSIVE); - return result; - } - // RequestQuota returns either PP_OK_COMPLETIONPENDING or the requested - // quota amount. - DCHECK(result > 0); - } - } + // Quota checks are performed on the plugin side, in order to use the same + // quota reservation and request system as Write. - if (!CallSetLength(context->MakeReplyMessageContext(), length)) + if (!base::FileUtilProxy::Truncate( + file_message_loop_, + file_, + length, + base::Bind(&PepperFileIOHost::ExecutePlatformGeneralCallback, + weak_factory_.GetWeakPtr(), + context->MakeReplyMessageContext()))) return PP_ERROR_FAILED; state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE); @@ -404,9 +336,10 @@ int32_t PepperFileIOHost::OnHostMsgFlush( } int32_t PepperFileIOHost::OnHostMsgClose( - ppapi::host::HostMessageContext* context) { + ppapi::host::HostMessageContext* context, + int64_t max_written_offset) { if (check_quota_) { - file_system_host_->CloseQuotaFile(this); + file_system_host_->CloseQuotaFile(this, max_written_offset); check_quota_ = false; } @@ -426,75 +359,12 @@ void PepperFileIOHost::DidOpenQuotaFile( base::PlatformFile file, int64_t max_written_offset) { max_written_offset_ = max_written_offset; - DCHECK_LE(0, max_written_offset_); ExecutePlatformOpenFileCallback( reply_context, base::PLATFORM_FILE_OK, base::PassPlatformFile(&file), true); } -void PepperFileIOHost::GotWriteQuota( - ppapi::host::ReplyMessageContext reply_context, - int64_t offset, - const std::string& buffer, - int32_t granted) { - if (granted == 0) { - reply_context.params.set_result(PP_ERROR_NOQUOTA); - } else if (!CallWrite(reply_context, offset, buffer)) { - reply_context.params.set_result(PP_ERROR_FAILED); - } else { - max_written_offset_ += granted; - return; - } - // Return the error result set above. - host()->SendReply(reply_context, PpapiPluginMsg_FileIO_GeneralReply()); - state_manager_.SetOperationFinished(); -} - -void PepperFileIOHost::GotSetLengthQuota( - ppapi::host::ReplyMessageContext reply_context, - int64_t length, - int32_t granted) { - if (granted == 0) { - reply_context.params.set_result(PP_ERROR_NOQUOTA); - } else if (!CallSetLength(reply_context, length)) { - reply_context.params.set_result(PP_ERROR_FAILED); - } else { - max_written_offset_ += granted; - return; - } - // Return the error result set above. - host()->SendReply(reply_context, PpapiPluginMsg_FileIO_GeneralReply()); - state_manager_.SetOperationFinished(); -} - -bool PepperFileIOHost::CallWrite( - ppapi::host::ReplyMessageContext reply_context, - int64_t offset, - const std::string& buffer) { - return base::FileUtilProxy::Write( - file_message_loop_, - file_, - offset, - buffer.c_str(), - buffer.size(), - base::Bind(&PepperFileIOHost::ExecutePlatformWriteCallback, - weak_factory_.GetWeakPtr(), - reply_context)); -} - -bool PepperFileIOHost::CallSetLength( - ppapi::host::ReplyMessageContext reply_context, - int64_t length) { - return base::FileUtilProxy::Truncate( - file_message_loop_, - file_, - length, - base::Bind(&PepperFileIOHost::ExecutePlatformGeneralCallback, - weak_factory_.GetWeakPtr(), - reply_context)); -} - void PepperFileIOHost::DidCloseFile(base::PlatformFileError error) { // Silently ignore if we fail to close the file. if (!on_close_callback_.is_null()) { @@ -553,39 +423,31 @@ void PepperFileIOHost::ExecutePlatformOpenFileCallback( base::PassPlatformFile file, bool unused_created) { int32_t pp_error = ppapi::PlatformFileErrorToPepperError(error_code); - if (pp_error == PP_OK) - state_manager_.SetOpenSucceed(); - DCHECK(file_ == base::kInvalidPlatformFileValue); file_ = file.ReleaseValue(); - if (file_ != base::kInvalidPlatformFileValue) { - int32_t flags_to_send = open_flags_; - if (!host()->permissions().HasPermission(ppapi::PERMISSION_DEV)) { - // IMPORTANT: Clear PP_FILEOPENFLAG_WRITE and PP_FILEOPENFLAG_APPEND so - // the plugin can't write and so bypass our quota checks. - flags_to_send = - open_flags_ & ~(PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_APPEND); - } - if (!AddFileToReplyContext(flags_to_send, &reply_context)) - pp_error = PP_ERROR_FAILED; + if (file_ != base::kInvalidPlatformFileValue && + !AddFileToReplyContext(open_flags_, &reply_context)) + pp_error = PP_ERROR_FAILED; + + PP_Resource quota_file_system = 0; + if (pp_error == PP_OK) { + state_manager_.SetOpenSucceed(); + // A non-zero resource id signals the plugin side to check quota. + if (check_quota_) + quota_file_system = file_system_host_->pp_resource(); } + reply_context.params.set_result(pp_error); - host()->SendReply(reply_context, PpapiPluginMsg_FileIO_OpenReply()); + host()->SendReply(reply_context, + PpapiPluginMsg_FileIO_OpenReply(quota_file_system, + max_written_offset_)); state_manager_.SetOperationFinished(); } -void PepperFileIOHost::ExecutePlatformWriteCallback( - ppapi::host::ReplyMessageContext reply_context, - base::PlatformFileError error_code, - int bytes_written) { - // On the plugin side, the callback expects a parameter with different meaning - // depends on whether is negative or not. It is the result here. We translate - // for the callback. - int32_t pp_error = ppapi::PlatformFileErrorToPepperError(error_code); - reply_context.params.set_result(ErrorOrByteNumber(pp_error, bytes_written)); - host()->SendReply(reply_context, PpapiPluginMsg_FileIO_GeneralReply()); - state_manager_.SetOperationFinished(); +void PepperFileIOHost::SendOpenErrorReply( + ppapi::host::ReplyMessageContext reply_context) { + host()->SendReply(reply_context, PpapiPluginMsg_FileIO_OpenReply(0, 0)); } bool PepperFileIOHost::AddFileToReplyContext( @@ -602,8 +464,11 @@ bool PepperFileIOHost::AddFileToReplyContext( file_, plugin_process_id, false); if (transit_file == IPC::InvalidPlatformFileForTransit()) return false; + ppapi::proxy::SerializedHandle file_handle; - file_handle.set_file_handle(transit_file, open_flags, 0 /* file_io */); + // A non-zero resource id signals NaClIPCAdapter to create a NaClQuotaDesc. + PP_Resource quota_file_io = check_quota_ ? pp_resource() : 0; + file_handle.set_file_handle(transit_file, open_flags, quota_file_io); reply_context->params.AppendHandle(file_handle); return true; } diff --git a/content/browser/renderer_host/pepper/pepper_file_io_host.h b/content/browser/renderer_host/pepper/pepper_file_io_host.h index 9394e6c..4fa4bc2 100644 --- a/content/browser/renderer_host/pepper/pepper_file_io_host.h +++ b/content/browser/renderer_host/pepper/pepper_file_io_host.h @@ -41,12 +41,6 @@ class PepperFileIOHost : public ppapi::host::ResourceHost, const IPC::Message& msg, ppapi::host::HostMessageContext* context) OVERRIDE; - // Direct access for PepperFileSystemBrowserHost. - int64_t max_written_offset() const { return max_written_offset_; } - void set_max_written_offset(int64_t max_written_offset) { - max_written_offset_ = max_written_offset; - } - struct UIThreadStuff { UIThreadStuff(); ~UIThreadStuff(); @@ -60,12 +54,10 @@ class PepperFileIOHost : public ppapi::host::ResourceHost, int32_t OnHostMsgTouch(ppapi::host::HostMessageContext* context, PP_Time last_access_time, PP_Time last_modified_time); - int32_t OnHostMsgWrite(ppapi::host::HostMessageContext* context, - int64_t offset, - const std::string& buffer); int32_t OnHostMsgSetLength(ppapi::host::HostMessageContext* context, int64_t length); - int32_t OnHostMsgClose(ppapi::host::HostMessageContext* context); + int32_t OnHostMsgClose(ppapi::host::HostMessageContext* context, + int64_t max_written_offset); int32_t OnHostMsgFlush(ppapi::host::HostMessageContext* context); int32_t OnHostMsgRequestOSFileHandle( ppapi::host::HostMessageContext* context); @@ -86,10 +78,6 @@ class PepperFileIOHost : public ppapi::host::ResourceHost, base::PlatformFileError error_code, base::PassPlatformFile file, bool unused_created); - void ExecutePlatformWriteCallback( - ppapi::host::ReplyMessageContext reply_context, - base::PlatformFileError error_code, - int bytes_written); void GotUIThreadStuffForInternalFileSystems( ppapi::host::ReplyMessageContext reply_context, @@ -109,21 +97,13 @@ class PepperFileIOHost : public ppapi::host::ResourceHost, void DidOpenQuotaFile(ppapi::host::ReplyMessageContext reply_context, base::PlatformFile file, int64_t max_written_offset); - void GotWriteQuota(ppapi::host::ReplyMessageContext reply_context, - int64_t offset, - const std::string& buffer, - int32_t granted); - void GotSetLengthQuota(ppapi::host::ReplyMessageContext reply_context, - int64_t length, - int32_t granted); - bool CallWrite(ppapi::host::ReplyMessageContext reply_context, - int64_t offset, - const std::string& buffer); bool CallSetLength(ppapi::host::ReplyMessageContext reply_context, int64_t length); void DidCloseFile(base::PlatformFileError error); + void SendOpenErrorReply(ppapi::host::ReplyMessageContext reply_context); + // Adds file_ to |reply_context| with the specified |open_flags|. bool AddFileToReplyContext( int32_t open_flags, diff --git a/content/browser/renderer_host/pepper/pepper_file_system_browser_host.cc b/content/browser/renderer_host/pepper/pepper_file_system_browser_host.cc index 9161ad7..2bc0042 100644 --- a/content/browser/renderer_host/pepper/pepper_file_system_browser_host.cc +++ b/content/browser/renderer_host/pepper/pepper_file_system_browser_host.cc @@ -48,16 +48,6 @@ GetFileSystemContextFromRenderId(int render_process_id) { } // namespace -PepperFileSystemBrowserHost::QuotaRequest::QuotaRequest( - int32_t amount_arg, - const RequestQuotaCallback& callback_arg) - : amount(amount_arg), - callback(callback_arg) { -} - -PepperFileSystemBrowserHost::QuotaRequest::~QuotaRequest() { -} - PepperFileSystemBrowserHost::PepperFileSystemBrowserHost(BrowserPpapiHost* host, PP_Instance instance, PP_Resource resource, @@ -110,6 +100,9 @@ int32_t PepperFileSystemBrowserHost::OnResourceMessageReceived( PPAPI_DISPATCH_HOST_RESOURCE_CALL( PpapiHostMsg_FileSystem_InitIsolatedFileSystem, OnHostMsgInitIsolatedFileSystem) + PPAPI_DISPATCH_HOST_RESOURCE_CALL( + PpapiHostMsg_FileSystem_ReserveQuota, + OnHostMsgReserveQuota) IPC_END_MESSAGE_MAP() return PP_ERROR_FAILED; } @@ -140,12 +133,11 @@ void PepperFileSystemBrowserHost::OpenQuotaFile( } void PepperFileSystemBrowserHost::CloseQuotaFile( - PepperFileIOHost* file_io_host) { + PepperFileIOHost* file_io_host, + int64_t max_written_offset) { int32_t id = file_io_host->pp_resource(); - int64_t max_written_offset = 0; FileMap::iterator it = files_.find(id); if (it != files_.end()) { - max_written_offset = file_io_host->max_written_offset(); files_.erase(it); } else { NOTREACHED(); @@ -160,25 +152,6 @@ void PepperFileSystemBrowserHost::CloseQuotaFile( max_written_offset)); } -int32_t PepperFileSystemBrowserHost::RequestQuota( - int32_t amount, - const RequestQuotaCallback& callback) { - DCHECK(amount >= 0); - if (!reserving_quota_ && reserved_quota_ >= amount) { - reserved_quota_ -= amount; - return amount; - } - - // Queue up a pending quota request. - pending_quota_requests_.push(QuotaRequest(amount, callback)); - - // Reserve more quota if we haven't already. - if (!reserving_quota_) - ReserveQuota(amount); - - return PP_OK_COMPLETIONPENDING; -} - int32_t PepperFileSystemBrowserHost::OnHostMsgOpen( ppapi::host::HostMessageContext* context, int64_t /* unused */) { @@ -381,6 +354,33 @@ int32_t PepperFileSystemBrowserHost::OnHostMsgInitIsolatedFileSystem( return PP_OK_COMPLETIONPENDING; } +int32_t PepperFileSystemBrowserHost::OnHostMsgReserveQuota( + ppapi::host::HostMessageContext* context, + int64_t amount, + const std::map<int32_t, int64_t>& max_written_offsets) { + DCHECK(ChecksQuota()); + DCHECK(amount > 0); + + if (reserving_quota_) + return PP_ERROR_INPROGRESS; + reserving_quota_ = true; + + int64_t reservation_amount = std::max<int64_t>(kMinimumQuotaReservationSize, + amount); + file_system_context_->default_file_task_runner()->PostTask( + FROM_HERE, + base::Bind(&QuotaReservation::ReserveQuota, + quota_reservation_, + reservation_amount, + max_written_offsets, + base::Bind(&PepperFileSystemBrowserHost::GotReservedQuota, + weak_factory_.GetWeakPtr(), + context->MakeReplyMessageContext()))); + + + return PP_OK_COMPLETIONPENDING; +} + void PepperFileSystemBrowserHost::SendReplyForFileSystem( ppapi::host::ReplyMessageContext reply_context, int32_t pp_error) { @@ -413,7 +413,7 @@ bool PepperFileSystemBrowserHost::ShouldCreateQuotaReservation() const { if (!ppapi::FileSystemTypeHasQuota(type_)) return false; - // For file system types with quota, ome origins have unlimited storage. + // For file system types with quota, some origins have unlimited storage. quota::QuotaManagerProxy* quota_manager_proxy = file_system_context_->quota_manager_proxy(); CHECK(quota_manager_proxy); @@ -447,67 +447,18 @@ void PepperFileSystemBrowserHost::GotQuotaReservation( callback.Run(); } -void PepperFileSystemBrowserHost::ReserveQuota(int32_t amount) { - DCHECK(!reserving_quota_); - reserving_quota_ = true; - - // Get the max_written_offset for each open file. - QuotaReservation::OffsetMap max_written_offsets; - for (FileMap::iterator it = files_.begin(); it != files_.end(); ++ it) { - max_written_offsets.insert( - std::make_pair(it->first, it->second->max_written_offset())); - } - - int64_t reservation_amount = std::max<int64_t>(kMinimumQuotaReservationSize, - amount); - file_system_context_->default_file_task_runner()->PostTask( - FROM_HERE, - base::Bind(&QuotaReservation::ReserveQuota, - quota_reservation_, - reservation_amount, - max_written_offsets, - base::Bind(&PepperFileSystemBrowserHost::GotReservedQuota, - weak_factory_.GetWeakPtr()))); -} - void PepperFileSystemBrowserHost::GotReservedQuota( + ppapi::host::ReplyMessageContext reply_context, int64_t amount, const QuotaReservation::OffsetMap& max_written_offsets) { DCHECK(reserving_quota_); reserving_quota_ = false; reserved_quota_ = amount; - // Update open files with their new base sizes. This won't write over any - // updates since the files are waiting for quota and can't write. - for (FileMap::iterator it = files_.begin(); it != files_.end(); ++ it) { - QuotaReservation::OffsetMap::const_iterator offset_it = - max_written_offsets.find(it->first); - if (offset_it != max_written_offsets.end()) - it->second->set_max_written_offset(offset_it->second); - else - NOTREACHED(); - } - - DCHECK(!pending_quota_requests_.empty()); - // If we can't grant the first request after refreshing reserved_quota_, then - // fail all pending quota requests to avoid an infinite refresh/fail loop. - bool fail_all = reserved_quota_ < pending_quota_requests_.front().amount; - while (!pending_quota_requests_.empty()) { - QuotaRequest& request = pending_quota_requests_.front(); - if (fail_all) { - request.callback.Run(0); - pending_quota_requests_.pop(); - } else if (reserved_quota_ >= request.amount) { - reserved_quota_ -= request.amount; - request.callback.Run(request.amount); - pending_quota_requests_.pop(); - } else { - // Refresh the quota reservation for the first pending request that we - // can't satisfy. - ReserveQuota(request.amount); - break; - } - } + reply_context.params.set_result(PP_OK); + host()->SendReply( + reply_context, + PpapiPluginMsg_FileSystem_ReserveQuotaReply(amount, max_written_offsets)); } std::string PepperFileSystemBrowserHost::GetPluginMimeType() const { diff --git a/content/browser/renderer_host/pepper/pepper_file_system_browser_host.h b/content/browser/renderer_host/pepper/pepper_file_system_browser_host.h index bd47a43..6a90fae 100644 --- a/content/browser/renderer_host/pepper/pepper_file_system_browser_host.h +++ b/content/browser/renderer_host/pepper/pepper_file_system_browser_host.h @@ -72,26 +72,12 @@ class CONTENT_EXPORT PepperFileSystemBrowserHost const OpenQuotaFileCallback& callback); // Closes the file. This must be called after OpenQuotaFile and before the // PepperFileIOHost is destroyed. - void CloseQuotaFile(PepperFileIOHost* file_io_host); - // Requests the given amount of quota. Returns the amount requested or - // PP_OK_COMPLETIONPENDING, in which case the amount granted is returned in - // the callback. Requests can't partially succeed so the amount granted is - // either 0 or the amount of the request. Requesting an amount of 0 will - // return immediately with a 0 result. - typedef base::Callback<void(int32_t)> RequestQuotaCallback; - int32_t RequestQuota(int32_t amount, - const RequestQuotaCallback& callback); + void CloseQuotaFile(PepperFileIOHost* file_io_host, + int64_t max_written_offset); + private: friend class PepperFileSystemBrowserHostTest; - struct QuotaRequest { - QuotaRequest(int32_t amount, const RequestQuotaCallback& callback); - ~QuotaRequest(); - - int32_t amount; - RequestQuotaCallback callback; - }; - void OpenExistingFileSystem( const base::Closure& callback, scoped_refptr<fileapi::FileSystemContext> file_system_context); @@ -118,12 +104,17 @@ class CONTENT_EXPORT PepperFileSystemBrowserHost const std::string& fsid, base::PlatformFileError error); - int32_t OnHostMsgOpen(ppapi::host::HostMessageContext* context, - int64_t expected_size); + int32_t OnHostMsgOpen( + ppapi::host::HostMessageContext* context, + int64_t expected_size); int32_t OnHostMsgInitIsolatedFileSystem( ppapi::host::HostMessageContext* context, const std::string& fsid, PP_IsolatedFileSystemType_Private type); + int32_t OnHostMsgReserveQuota( + ppapi::host::HostMessageContext* context, + int64_t amount, + const std::map<int32_t, int64_t>& max_written_offsets); void SendReplyForFileSystem( ppapi::host::ReplyMessageContext reply_context, @@ -142,9 +133,14 @@ class CONTENT_EXPORT PepperFileSystemBrowserHost const base::Closure& callback, scoped_refptr<QuotaReservation> quota_reservation); - void ReserveQuota(int32_t amount); - void GotReservedQuota(int64_t amount, - const QuotaReservation::OffsetMap& max_written_offsets); + void GotReservedQuota( + ppapi::host::ReplyMessageContext reply_context, + int64_t amount, + const std::map<int32_t, int64_t>& max_written_offsets); + void DidOpenQuotaFile( + PP_Resource file_io_resource, + const OpenQuotaFileCallback& callback, + int64_t max_written_offset); std::string GetPluginMimeType() const; @@ -167,7 +163,6 @@ class CONTENT_EXPORT PepperFileSystemBrowserHost // destroyed. typedef std::map<int32_t, PepperFileIOHost*> FileMap; FileMap files_; - std::queue<QuotaRequest> pending_quota_requests_; int64_t reserved_quota_; bool reserving_quota_; // Access only on the FileSystemContext's default_file_task_runner(). diff --git a/content/browser/renderer_host/pepper/quota_reservation.cc b/content/browser/renderer_host/pepper/quota_reservation.cc index a512f10..d928f23 100644 --- a/content/browser/renderer_host/pepper/quota_reservation.cc +++ b/content/browser/renderer_host/pepper/quota_reservation.cc @@ -78,7 +78,7 @@ void QuotaReservation::ReserveQuota( int64_t amount, const OffsetMap& max_written_offsets, const ReserveQuotaCallback& callback) { - for (FileMap::iterator it = files_.begin(); it != files_.end(); ++ it) { + for (FileMap::iterator it = files_.begin(); it != files_.end(); ++it) { OffsetMap::const_iterator offset_it = max_written_offsets.find(it->first); if (offset_it != max_written_offsets.end()) it->second->UpdateMaxWrittenOffset(offset_it->second); diff --git a/ppapi/ppapi_tests.gypi b/ppapi/ppapi_tests.gypi index 0b4570a..9a4dd52 100644 --- a/ppapi/ppapi_tests.gypi +++ b/ppapi/ppapi_tests.gypi @@ -174,6 +174,7 @@ 'proxy/interface_list_unittest.cc', 'proxy/mock_resource.cc', 'proxy/mock_resource.h', + 'proxy/nacl_message_scanner_unittest.cc', 'proxy/pdf_resource_unittest.cc', 'proxy/plugin_dispatcher_unittest.cc', 'proxy/plugin_resource_tracker_unittest.cc', @@ -207,7 +208,7 @@ }], ], # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. - 'msvs_disabled_warnings': [ 4267, ], + 'msvs_disabled_warnings': [ 4267, ], }, { 'target_name': 'ppapi_example_skeleton', diff --git a/ppapi/proxy/file_io_resource.cc b/ppapi/proxy/file_io_resource.cc index ad07187..e86c321 100644 --- a/ppapi/proxy/file_io_resource.cc +++ b/ppapi/proxy/file_io_resource.cc @@ -80,9 +80,39 @@ int32_t FileIOResource::ReadOp::DoWork() { file_handle_->raw_handle(), offset_, buffer_.get(), bytes_to_read_); } +FileIOResource::WriteOp::WriteOp(scoped_refptr<FileHandleHolder> file_handle, + int64_t offset, + const char* buffer, + int32_t bytes_to_write, + bool append) + : file_handle_(file_handle), + offset_(offset), + buffer_(buffer), + bytes_to_write_(bytes_to_write), + append_(append) { +} + +FileIOResource::WriteOp::~WriteOp() { +} + +int32_t FileIOResource::WriteOp::DoWork() { + // We can't just call WritePlatformFile in append mode, since NaCl doesn't + // implement fcntl, causing the function to call pwrite, which is incorrect. + if (append_) { + return base::WritePlatformFileAtCurrentPos( + file_handle_->raw_handle(), buffer_, bytes_to_write_); + } else { + return base::WritePlatformFile( + file_handle_->raw_handle(), offset_, buffer_, bytes_to_write_); + } +} + FileIOResource::FileIOResource(Connection connection, PP_Instance instance) : PluginResource(connection, instance), file_system_type_(PP_FILESYSTEMTYPE_INVALID), + open_flags_(0), + max_written_offset_(0), + check_quota_(false), called_close_(false) { SendCreate(BROWSER, PpapiHostMsg_FileIO_Create()); } @@ -113,6 +143,7 @@ int32_t FileIOResource::Open(PP_Resource file_ref, if (rv != PP_OK) return rv; + open_flags_ = open_flags; file_system_type_ = create_info.file_system_type; if (create_info.file_system_plugin_resource) { @@ -239,22 +270,42 @@ int32_t FileIOResource::Write(int64_t offset, const char* buffer, int32_t bytes_to_write, scoped_refptr<TrackedCallback> callback) { + if (!buffer) + return PP_ERROR_FAILED; + if (bytes_to_write < 0) + return PP_ERROR_FAILED; + if (!FileHandleHolder::IsValid(file_handle_)) + return PP_ERROR_FAILED; + int32_t rv = state_manager_.CheckOperationState( FileIOStateManager::OPERATION_WRITE, true); if (rv != PP_OK) return rv; - // TODO(brettw) it would be nice to use a shared memory buffer for large - // writes rather than having to copy to a string (which will involve a number - // of extra copies to serialize over IPC). - bytes_to_write = std::min(bytes_to_write, kMaxReadWriteSize); - Call<PpapiPluginMsg_FileIO_GeneralReply>(BROWSER, - PpapiHostMsg_FileIO_Write(offset, std::string(buffer, bytes_to_write)), - base::Bind(&FileIOResource::OnPluginMsgGeneralComplete, this, - callback)); - state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_WRITE); - return PP_OK_COMPLETIONPENDING; + + if (check_quota_) { + int64_t actual_offset = + (open_flags_ & PP_FILEOPENFLAG_APPEND) ? max_written_offset_ : offset; + + uint64_t max_offset = actual_offset + bytes_to_write; + if (max_offset > static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) + return PP_ERROR_FAILED; // amount calculation would overflow. + int64_t increase = static_cast<int64_t>(max_offset) - max_written_offset_; + if (increase > 0) { + int64_t result = + file_system_resource_->AsPPB_FileSystem_API()->RequestQuota( + increase, + base::Bind(&FileIOResource::OnRequestWriteQuotaComplete, + this, + offset, buffer, bytes_to_write, callback)); + if (result == PP_OK_COMPLETIONPENDING) + return PP_OK_COMPLETIONPENDING; + DCHECK(result == increase); + max_written_offset_ = max_offset; + } + } + return WriteValidated(offset, buffer, bytes_to_write, callback); } int32_t FileIOResource::SetLength(int64_t length, @@ -263,13 +314,30 @@ int32_t FileIOResource::SetLength(int64_t length, FileIOStateManager::OPERATION_EXCLUSIVE, true); if (rv != PP_OK) return rv; + if (length < 0) + return PP_ERROR_FAILED; - Call<PpapiPluginMsg_FileIO_GeneralReply>(BROWSER, - PpapiHostMsg_FileIO_SetLength(length), - base::Bind(&FileIOResource::OnPluginMsgGeneralComplete, this, - callback)); + if (check_quota_) { + int64_t increase = length - max_written_offset_; + if (increase > 0) { + int32_t result = + file_system_resource_->AsPPB_FileSystem_API()->RequestQuota( + increase, + base::Bind(&FileIOResource::OnRequestSetLengthQuotaComplete, + this, + length, callback)); + if (result == PP_OK_COMPLETIONPENDING) { + state_manager_.SetPendingOperation( + FileIOStateManager::OPERATION_EXCLUSIVE); + return PP_OK_COMPLETIONPENDING; + } + DCHECK(result == increase); + max_written_offset_ = length; + } + } state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE); + SetLengthValidated(length, callback); return PP_OK_COMPLETIONPENDING; } @@ -288,15 +356,29 @@ int32_t FileIOResource::Flush(scoped_refptr<TrackedCallback> callback) { return PP_OK_COMPLETIONPENDING; } +int64_t FileIOResource::GetMaxWrittenOffset() const { + return max_written_offset_; +} + +void FileIOResource::SetMaxWrittenOffset(int64_t max_written_offset) { + max_written_offset_ = max_written_offset; +} + void FileIOResource::Close() { if (called_close_) return; called_close_ = true; + if (check_quota_) { + check_quota_ = false; + file_system_resource_->AsPPB_FileSystem_API()->CloseQuotaFile( + pp_resource()); + } + if (file_handle_) file_handle_ = NULL; - Post(BROWSER, PpapiHostMsg_FileIO_Close()); + Post(BROWSER, PpapiHostMsg_FileIO_Close(max_written_offset_)); } int32_t FileIOResource::RequestOSFileHandle( @@ -380,6 +462,57 @@ int32_t FileIOResource::ReadValidated(int64_t offset, return PP_OK_COMPLETIONPENDING; } +int32_t FileIOResource::WriteValidated( + int64_t offset, + const char* buffer, + int32_t bytes_to_write, + scoped_refptr<TrackedCallback> callback) { + bool append = (open_flags_ & PP_FILEOPENFLAG_APPEND) != 0; + if (callback->is_blocking()) { + int32_t result; + { + // Release the proxy lock while making a potentially slow file call. + ProxyAutoUnlock unlock; + if (append) { + result = base::WritePlatformFileAtCurrentPos( + file_handle_->raw_handle(), buffer, bytes_to_write); + } else { + result = base::WritePlatformFile( + file_handle_->raw_handle(), offset, buffer, bytes_to_write); + } + } + if (result < 0) + result = PP_ERROR_FAILED; + + state_manager_.SetOperationFinished(); + return result; + } + + // For the non-blocking case, post a task to the file thread. + scoped_refptr<WriteOp> write_op( + new WriteOp(file_handle_, offset, buffer, bytes_to_write, append)); + base::PostTaskAndReplyWithResult( + PpapiGlobals::Get()->GetFileTaskRunner(), + FROM_HERE, + Bind(&FileIOResource::WriteOp::DoWork, write_op), + RunWhileLocked(Bind(&TrackedCallback::Run, callback))); + callback->set_completion_task( + Bind(&FileIOResource::OnWriteComplete, this, write_op)); + + return PP_OK_COMPLETIONPENDING; +} + +void FileIOResource::SetLengthValidated( + int64_t length, + scoped_refptr<TrackedCallback> callback) { + Call<PpapiPluginMsg_FileIO_GeneralReply>(BROWSER, + PpapiHostMsg_FileIO_SetLength(length), + base::Bind(&FileIOResource::OnPluginMsgGeneralComplete, this, + callback)); + + max_written_offset_ = length; +} + int32_t FileIOResource::OnQueryComplete(scoped_refptr<QueryOp> query_op, PP_FileInfo* info, int32_t result) { @@ -416,6 +549,49 @@ int32_t FileIOResource::OnReadComplete(scoped_refptr<ReadOp> read_op, return result; } +void FileIOResource::OnRequestWriteQuotaComplete( + int64_t offset, + const char* buffer, + int32_t bytes_to_write, + scoped_refptr<TrackedCallback> callback, + int64_t granted) { + DCHECK(granted >= 0); + if (granted == 0) { + callback->Run(PP_ERROR_NOQUOTA); + return; + } + max_written_offset_ += granted; + int32_t result = WriteValidated(offset, buffer, bytes_to_write, callback); + if (result != PP_OK_COMPLETIONPENDING) + callback->Run(result); +} + +void FileIOResource::OnRequestSetLengthQuotaComplete( + int64_t length, + scoped_refptr<TrackedCallback> callback, + int64_t granted) { + DCHECK(granted >= 0); + if (granted == 0) { + callback->Run(PP_ERROR_NOQUOTA); + return; + } + + max_written_offset_ = length; + SetLengthValidated(length, callback); +} + +int32_t FileIOResource::OnWriteComplete(scoped_refptr<WriteOp> write_op, + int32_t result) { + DCHECK(state_manager_.get_pending_operation() == + FileIOStateManager::OPERATION_WRITE); + // |result| is the return value of WritePlatformFile; -1 indicates failure. + if (result < 0) + result = PP_ERROR_FAILED; + + state_manager_.SetOperationFinished(); + return result; +} + void FileIOResource::OnPluginMsgGeneralComplete( scoped_refptr<TrackedCallback> callback, const ResourceMessageReplyParams& params) { @@ -431,20 +607,31 @@ void FileIOResource::OnPluginMsgGeneralComplete( void FileIOResource::OnPluginMsgOpenFileComplete( scoped_refptr<TrackedCallback> callback, - const ResourceMessageReplyParams& params) { + const ResourceMessageReplyParams& params, + PP_Resource quota_file_system, + int64_t max_written_offset) { DCHECK(state_manager_.get_pending_operation() == FileIOStateManager::OPERATION_EXCLUSIVE); // Release the FileRef resource. file_ref_ = NULL; - if (params.result() == PP_OK) + int32_t result = params.result(); + if (result == PP_OK) { state_manager_.SetOpenSucceed(); - int32_t result = params.result(); - IPC::PlatformFileForTransit transit_file; - if ((result == PP_OK) && params.TakeFileHandleAtIndex(0, &transit_file)) { - file_handle_ = new FileHandleHolder( - IPC::PlatformFileForTransitToPlatformFile(transit_file)); + if (quota_file_system) { + DCHECK(quota_file_system == file_system_resource_->pp_resource()); + check_quota_ = true; + max_written_offset_ = max_written_offset; + file_system_resource_->AsPPB_FileSystem_API()->OpenQuotaFile( + pp_resource()); + } + + IPC::PlatformFileForTransit transit_file; + if (params.TakeFileHandleAtIndex(0, &transit_file)) { + file_handle_ = new FileHandleHolder( + IPC::PlatformFileForTransitToPlatformFile(transit_file)); + } } // End this operation now, so the user's callback can execute another FileIO // operation, assuming there are no other pending operations. diff --git a/ppapi/proxy/file_io_resource.h b/ppapi/proxy/file_io_resource.h index bfdf24f..23df0e6 100644 --- a/ppapi/proxy/file_io_resource.h +++ b/ppapi/proxy/file_io_resource.h @@ -57,6 +57,8 @@ class PPAPI_PROXY_EXPORT FileIOResource scoped_refptr<TrackedCallback> callback) OVERRIDE; virtual int32_t SetLength(int64_t length, scoped_refptr<TrackedCallback> callback) OVERRIDE; + virtual int64_t GetMaxWrittenOffset() const OVERRIDE; + virtual void SetMaxWrittenOffset(int64_t max_written_offset) OVERRIDE; virtual int32_t Flush(scoped_refptr<TrackedCallback> callback) OVERRIDE; virtual void Close() OVERRIDE; virtual int32_t RequestOSFileHandle( @@ -139,10 +141,49 @@ class PPAPI_PROXY_EXPORT FileIOResource scoped_ptr<char[]> buffer_; }; + // Class to perform file write operations across multiple threads. + class WriteOp : public base::RefCountedThreadSafe<WriteOp> { + public: + WriteOp(scoped_refptr<FileHandleHolder> file_handle, + int64_t offset, + const char* buffer, + int32_t bytes_to_write, + bool append); + + // Writes the file. Called on the file thread (non-blocking) or the plugin + // thread (blocking). This should not be called when we hold the proxy lock. + int32_t DoWork(); + + private: + friend class base::RefCountedThreadSafe<WriteOp>; + ~WriteOp(); + + scoped_refptr<FileHandleHolder> file_handle_; + int64_t offset_; + const char* buffer_; + int32_t bytes_to_write_; + bool append_; + }; + + void OnRequestWriteQuotaComplete(int64_t offset, + const char* buffer, + int32_t bytes_to_write, + scoped_refptr<TrackedCallback> callback, + int64_t granted); + void OnRequestSetLengthQuotaComplete(int64_t length, + scoped_refptr<TrackedCallback> callback, + int64_t granted); + int32_t ReadValidated(int64_t offset, int32_t bytes_to_read, const PP_ArrayOutput& array_output, scoped_refptr<TrackedCallback> callback); + int32_t WriteValidated(int64_t offset, + const char* buffer, + int32_t bytes_to_write, + scoped_refptr<TrackedCallback> callback); + void SetLengthValidated(int64_t length, + scoped_refptr<TrackedCallback> callback); // Completion tasks for file operations that are done in the plugin. int32_t OnQueryComplete(scoped_refptr<QueryOp> query_op, @@ -151,12 +192,16 @@ class PPAPI_PROXY_EXPORT FileIOResource int32_t OnReadComplete(scoped_refptr<ReadOp> read_op, PP_ArrayOutput array_output, int32_t result); + int32_t OnWriteComplete(scoped_refptr<WriteOp> write_op, + int32_t result); // Reply message handlers for operations that are done in the host. void OnPluginMsgGeneralComplete(scoped_refptr<TrackedCallback> callback, const ResourceMessageReplyParams& params); void OnPluginMsgOpenFileComplete(scoped_refptr<TrackedCallback> callback, - const ResourceMessageReplyParams& params); + const ResourceMessageReplyParams& params, + PP_Resource quota_file_system, + int64_t max_written_offset); void OnPluginMsgRequestOSFileHandleComplete( scoped_refptr<TrackedCallback> callback, PP_FileHandle* output_handle, @@ -165,11 +210,15 @@ class PPAPI_PROXY_EXPORT FileIOResource scoped_refptr<FileHandleHolder> file_handle_; PP_FileSystemType file_system_type_; scoped_refptr<Resource> file_system_resource_; - bool called_close_; FileIOStateManager state_manager_; scoped_refptr<Resource> file_ref_; + int32_t open_flags_; + int64_t max_written_offset_; + bool check_quota_; + bool called_close_; + DISALLOW_COPY_AND_ASSIGN(FileIOResource); }; diff --git a/ppapi/proxy/file_system_resource.cc b/ppapi/proxy/file_system_resource.cc index df8c390..8061189 100644 --- a/ppapi/proxy/file_system_resource.cc +++ b/ppapi/proxy/file_system_resource.cc @@ -9,12 +9,26 @@ #include "ppapi/c/pp_errors.h" #include "ppapi/proxy/ppapi_messages.h" #include "ppapi/shared_impl/tracked_callback.h" +#include "ppapi/thunk/enter.h" +#include "ppapi/thunk/ppb_file_io_api.h" +using ppapi::thunk::EnterResourceNoLock; +using ppapi::thunk::PPB_FileIO_API; using ppapi::thunk::PPB_FileSystem_API; namespace ppapi { namespace proxy { +FileSystemResource::QuotaRequest::QuotaRequest( + int64_t amount_arg, + const RequestQuotaCallback& callback_arg) + : amount(amount_arg), + callback(callback_arg) { +} + +FileSystemResource::QuotaRequest::~QuotaRequest() { +} + FileSystemResource::FileSystemResource(Connection connection, PP_Instance instance, PP_FileSystemType type) @@ -22,7 +36,9 @@ FileSystemResource::FileSystemResource(Connection connection, type_(type), called_open_(false), callback_count_(0), - callback_result_(PP_OK) { + callback_result_(PP_OK), + reserved_quota_(0), + reserving_quota_(false) { DCHECK(type_ != PP_FILESYSTEMTYPE_INVALID); SendCreate(RENDERER, PpapiHostMsg_FileSystem_Create(type_)); SendCreate(BROWSER, PpapiHostMsg_FileSystem_Create(type_)); @@ -37,7 +53,9 @@ FileSystemResource::FileSystemResource(Connection connection, type_(type), called_open_(true), callback_count_(0), - callback_result_(PP_OK) { + callback_result_(PP_OK), + reserved_quota_(0), + reserving_quota_(false) { DCHECK(type_ != PP_FILESYSTEMTYPE_INVALID); AttachToPendingHost(RENDERER, pending_renderer_id); AttachToPendingHost(BROWSER, pending_browser_id); @@ -74,6 +92,39 @@ PP_FileSystemType FileSystemResource::GetType() { return type_; } +void FileSystemResource::OpenQuotaFile(PP_Resource file_io) { + DCHECK(max_written_offsets_.find(file_io) == max_written_offsets_.end()); + EnterResourceNoLock<PPB_FileIO_API> enter(file_io, true); + DCHECK(!enter.failed()); + PPB_FileIO_API* file_io_api = enter.object(); + max_written_offsets_[file_io] = file_io_api->GetMaxWrittenOffset(); +} + +void FileSystemResource::CloseQuotaFile(PP_Resource file_io) { + OffsetMap::iterator it = max_written_offsets_.find(file_io); + DCHECK(it != max_written_offsets_.end()); + max_written_offsets_.erase(it); +} + +int64_t FileSystemResource::RequestQuota( + int64_t amount, + const RequestQuotaCallback& callback) { + DCHECK(amount >= 0); + if (!reserving_quota_ && reserved_quota_ >= amount) { + reserved_quota_ -= amount; + return amount; + } + + // Queue up a pending quota request. + pending_quota_requests_.push(QuotaRequest(amount, callback)); + + // Reserve more quota if we haven't already. + if (!reserving_quota_) + ReserveQuota(amount); + + return PP_OK_COMPLETIONPENDING; +} + int32_t FileSystemResource::InitIsolatedFileSystem( const std::string& fsid, PP_IsolatedFileSystemType_Private type, @@ -122,5 +173,59 @@ void FileSystemResource::InitIsolatedFileSystemComplete( callback.Run(callback_result_); } +void FileSystemResource::ReserveQuota(int64_t amount) { + DCHECK(!reserving_quota_); + reserving_quota_ = true; + for (OffsetMap::iterator it = max_written_offsets_.begin(); + it != max_written_offsets_.end(); ++it) { + EnterResourceNoLock<PPB_FileIO_API> enter(it->first, true); + DCHECK(!enter.failed()); + PPB_FileIO_API* file_io_api = enter.object(); + it->second = file_io_api->GetMaxWrittenOffset(); + } + Call<PpapiPluginMsg_FileSystem_ReserveQuotaReply>(BROWSER, + PpapiHostMsg_FileSystem_ReserveQuota(amount, max_written_offsets_), + base::Bind(&FileSystemResource::ReserveQuotaComplete, + this)); +} + +void FileSystemResource::ReserveQuotaComplete( + const ResourceMessageReplyParams& params, + int64_t amount, + const OffsetMap& max_written_offsets) { + DCHECK(reserving_quota_); + reserving_quota_ = false; + reserved_quota_ = amount; + + for (OffsetMap::const_iterator it = max_written_offsets.begin(); + it != max_written_offsets.end(); ++it) { + EnterResourceNoLock<PPB_FileIO_API> enter(it->first, true); + DCHECK(!enter.failed()); + PPB_FileIO_API* file_io_api = enter.object(); + file_io_api->SetMaxWrittenOffset(it->second); + } + + DCHECK(!pending_quota_requests_.empty()); + // If we can't grant the first request after refreshing reserved_quota_, then + // fail all pending quota requests to avoid an infinite refresh/fail loop. + bool fail_all = reserved_quota_ < pending_quota_requests_.front().amount; + while (!pending_quota_requests_.empty()) { + QuotaRequest& request = pending_quota_requests_.front(); + if (fail_all) { + request.callback.Run(0); + pending_quota_requests_.pop(); + } else if (reserved_quota_ >= request.amount) { + reserved_quota_ -= request.amount; + request.callback.Run(request.amount); + pending_quota_requests_.pop(); + } else { + // Refresh the quota reservation for the first pending request that we + // can't satisfy. + ReserveQuota(request.amount); + break; + } + } +} + } // namespace proxy } // namespace ppapi diff --git a/ppapi/proxy/file_system_resource.h b/ppapi/proxy/file_system_resource.h index 9029583..e3e22b1 100644 --- a/ppapi/proxy/file_system_resource.h +++ b/ppapi/proxy/file_system_resource.h @@ -5,10 +5,13 @@ #ifndef PPAPI_PROXY_FILE_SYSTEM_RESOURCE_H_ #define PPAPI_PROXY_FILE_SYSTEM_RESOURCE_H_ +#include <map> +#include <queue> #include <string> #include "base/memory/ref_counted.h" #include "ppapi/c/pp_file_info.h" +#include "ppapi/c/pp_resource.h" #include "ppapi/c/private/ppb_isolated_file_system_private.h" #include "ppapi/proxy/connection.h" #include "ppapi/proxy/plugin_resource.h" @@ -48,11 +51,25 @@ class PPAPI_PROXY_EXPORT FileSystemResource virtual int32_t Open(int64_t expected_size, scoped_refptr<TrackedCallback> callback) OVERRIDE; virtual PP_FileSystemType GetType() OVERRIDE; + virtual void OpenQuotaFile(PP_Resource file_io) OVERRIDE; + virtual void CloseQuotaFile(PP_Resource file_io) OVERRIDE; + typedef base::Callback<void(int64_t)> RequestQuotaCallback; + virtual int64_t RequestQuota(int64_t amount, + const RequestQuotaCallback& callback) OVERRIDE; int32_t InitIsolatedFileSystem(const std::string& fsid, PP_IsolatedFileSystemType_Private type, const base::Callback<void(int32_t)>& callback); private: + struct QuotaRequest { + QuotaRequest(int64_t amount, + const RequestQuotaCallback& callback); + ~QuotaRequest(); + + int64_t amount; + RequestQuotaCallback callback; + }; + // Called when the host has responded to our open request. void OpenComplete(scoped_refptr<TrackedCallback> callback, const ResourceMessageReplyParams& params); @@ -62,11 +79,22 @@ class PPAPI_PROXY_EXPORT FileSystemResource const base::Callback<void(int32_t)>& callback, const ResourceMessageReplyParams& params); + void ReserveQuota(int64_t amount); + typedef std::map<int32_t, int64_t> OffsetMap; + void ReserveQuotaComplete(const ResourceMessageReplyParams& params, + int64_t amount, + const OffsetMap& max_written_offsets); + PP_FileSystemType type_; bool called_open_; uint32_t callback_count_; int32_t callback_result_; + OffsetMap max_written_offsets_; + std::queue<QuotaRequest> pending_quota_requests_; + int64_t reserved_quota_; + bool reserving_quota_; + DISALLOW_COPY_AND_ASSIGN(FileSystemResource); }; diff --git a/ppapi/proxy/nacl_message_scanner.cc b/ppapi/proxy/nacl_message_scanner.cc index 602b18e..3c2b45f8 100644 --- a/ppapi/proxy/nacl_message_scanner.cc +++ b/ppapi/proxy/nacl_message_scanner.cc @@ -19,12 +19,16 @@ namespace IPC { class Message; } +using ppapi::proxy::ResourceMessageReplyParams; +using ppapi::proxy::SerializedHandle; +using ppapi::proxy::SerializedVar; + namespace { -typedef std::vector<ppapi::proxy::SerializedHandle> Handles; +typedef std::vector<SerializedHandle> Handles; struct ScanningResults { - ScanningResults() : handle_index(0) {} + ScanningResults() : handle_index(0), pp_resource(0) {} // Vector to hold handles found in the message. Handles handles; @@ -36,12 +40,18 @@ struct ScanningResults { // may set this to NULL when it can determine that there are no parameters // that need conversion. (See the ResourceMessageReplyParams overload.) scoped_ptr<IPC::Message> new_msg; + // Resource id for resource messages. Save this when scanning resource replies + // so when we audit the nested message, we know which resource it is for. + PP_Resource pp_resource; + // Callback to receive the nested message in a resource message or reply. + base::Callback<void(PP_Resource, const IPC::Message&, SerializedHandle*)> + nested_msg_callback; }; void WriteHandle(int handle_index, - const ppapi::proxy::SerializedHandle& handle, + const SerializedHandle& handle, IPC::Message* msg) { - ppapi::proxy::SerializedHandle::WriteHeader(handle.header(), msg); + SerializedHandle::WriteHeader(handle.header(), msg); // Now write the handle itself in POSIX style. msg->WriteBool(true); // valid == true @@ -52,8 +62,7 @@ void WriteHandle(int handle_index, // handling. See ScanTuple for how these get used. // Overload to match SerializedHandle. -void ScanParam(const ppapi::proxy::SerializedHandle& handle, - ScanningResults* results) { +void ScanParam(const SerializedHandle& handle, ScanningResults* results) { results->handles.push_back(handle); if (results->new_msg) WriteHandle(results->handle_index++, handle, results->new_msg.get()); @@ -61,14 +70,13 @@ void ScanParam(const ppapi::proxy::SerializedHandle& handle, void HandleWriter(int* handle_index, IPC::Message* m, - const ppapi::proxy::SerializedHandle& handle) { + const SerializedHandle& handle) { WriteHandle((*handle_index)++, handle, m); } // Overload to match SerializedVar, which can contain handles. -void ScanParam(const ppapi::proxy::SerializedVar& var, - ScanningResults* results) { - std::vector<ppapi::proxy::SerializedHandle*> var_handles = var.GetHandles(); +void ScanParam(const SerializedVar& var, ScanningResults* results) { + std::vector<SerializedHandle*> var_handles = var.GetHandles(); // Copy any handles and then rewrite the message. for (size_t i = 0; i < var_handles.size(); ++i) results->handles.push_back(*var_handles[i]); @@ -82,8 +90,9 @@ void ScanParam(const ppapi::proxy::SerializedVar& var, // NOTE: We only intercept handles from host->NaCl. The only kind of // ResourceMessageParams that travels this direction is // ResourceMessageReplyParams, so that's the only one we need to handle. -void ScanParam(const ppapi::proxy::ResourceMessageReplyParams& params, +void ScanParam(const ResourceMessageReplyParams& params, ScanningResults* results) { + results->pp_resource = params.pp_resource(); // If the resource reply params don't contain handles, NULL the new message // pointer to cancel further rewriting. // NOTE: This works because only handles currently need rewriting, and we @@ -112,8 +121,21 @@ void ScanParam(const ppapi::proxy::ResourceMessageReplyParams& params, params.ConsumeHandles(); } -// Overload to match all other types. If we need to rewrite the message, -// write the parameter. +// Overload to match nested messages. If we need to rewrite the message, write +// the parameter. +void ScanParam(const IPC::Message& param, ScanningResults* results) { + if (results->pp_resource && !results->nested_msg_callback.is_null()) { + SerializedHandle* handle = NULL; + if (results->handles.size() == 1) + handle = &results->handles[0]; + results->nested_msg_callback.Run(results->pp_resource, param, handle); + } + if (results->new_msg) + IPC::WriteParam(results->new_msg.get(), param); +} + +// Overload to match all other types. If we need to rewrite the message, write +// the parameter. template <class T> void ScanParam(const T& param, ScanningResults* results) { if (results->new_msg) @@ -211,9 +233,58 @@ namespace proxy { class SerializedHandle; +NaClMessageScanner::FileSystem::FileSystem() + : reserved_quota_(0) { +} + +NaClMessageScanner::FileSystem::~FileSystem() { +} + +bool NaClMessageScanner::FileSystem::UpdateReservedQuota(int64_t delta) { + base::AutoLock lock(lock_); + if (std::numeric_limits<int64_t>::max() - reserved_quota_ < delta) + return false; // reserved_quota_ + delta would overflow. + if (reserved_quota_ + delta < 0) + return false; + reserved_quota_ += delta; + return true; +} + +NaClMessageScanner::FileIO::FileIO(FileSystem* file_system, + int64_t max_written_offset) + : file_system_(file_system), + max_written_offset_(max_written_offset) { +} + +NaClMessageScanner::FileIO::~FileIO() { +} + +void NaClMessageScanner::FileIO::SetMaxWrittenOffset( + int64_t max_written_offset) { + base::AutoLock lock(lock_); + max_written_offset_ = max_written_offset; +} + +bool NaClMessageScanner::FileIO::Grow(int64_t amount) { + base::AutoLock lock(lock_); + DCHECK(amount > 0); + if (!file_system_->UpdateReservedQuota(-amount)) + return false; + max_written_offset_ += amount; + return true; +} + NaClMessageScanner::NaClMessageScanner() { } +NaClMessageScanner::~NaClMessageScanner() { + for (FileSystemMap::iterator it = file_systems_.begin(); + it != file_systems_.end(); ++it) + delete it->second; + for (FileIOMap::iterator it = files_.begin(); it != files_.end(); ++it) + delete it->second; +} + // Windows IPC differs from POSIX in that native handles are serialized in the // message body, rather than passed in a separate FileDescriptorSet. Therefore, // on Windows, any message containing handles must be rewritten in the POSIX @@ -239,12 +310,14 @@ bool NaClMessageScanner::ScanMessage( (msg.type() == PpapiMsg_CreateNaClChannel::ID); #endif - // We can't always tell from the message ID if rewriting is needed. Therefore, // scan any message types that might contain a handle. If we later determine // that there are no handles, we can cancel the rewriting by clearing the // results.new_msg pointer. ScanningResults results; + results.nested_msg_callback = + base::Bind(&NaClMessageScanner::AuditNestedMessage, + base::Unretained(this)); switch (msg.type()) { CASE_FOR_MESSAGE(PpapiMsg_CreateNaClChannel) CASE_FOR_MESSAGE(PpapiMsg_PPBAudio_NotifyAudioStreamCreated) @@ -290,8 +363,113 @@ void NaClMessageScanner::ScanUntrustedMessage( scoped_ptr<IPC::Message>* new_msg_ptr) { if (untrusted_msg.is_sync()) RegisterSyncMessageForReply(untrusted_msg); - // TODO(bbudge) Add message auditing for FileSystem and FileIO resources when - // we implement Write on the plugin side of the proxy. See crbug.com/194304. + + // Audit FileIO and FileSystem messages to ensure that the plugin doesn't + // exceed its file quota. If we find the message is malformed, just pass it + // through - we only care about well formed messages to the host. + if (untrusted_msg.type() == PpapiHostMsg_ResourceCall::ID) { + ResourceMessageCallParams params; + IPC::Message nested_msg; + if (!UnpackMessage<PpapiHostMsg_ResourceCall>( + untrusted_msg, ¶ms, &nested_msg)) + return; + + switch (nested_msg.type()) { + case PpapiHostMsg_FileIO_Close::ID: { + FileIOMap::iterator it = files_.find(params.pp_resource()); + if (it == files_.end()) + return; + // Audit FileIO Close messages to make sure the plugin reports an + // accurate file size. + int64_t max_written_offset = 0; + if (!UnpackMessage<PpapiHostMsg_FileIO_Close>( + nested_msg, &max_written_offset)) + return; + + int64_t trusted_max_written_offset = it->second->max_written_offset(); + delete it->second; + files_.erase(it); + // If the plugin is under-reporting, rewrite the message with the + // trusted value. + if (trusted_max_written_offset > max_written_offset) { + new_msg_ptr->reset( + new PpapiHostMsg_ResourceCall( + params, + PpapiHostMsg_FileIO_Close(trusted_max_written_offset))); + } + } + case PpapiHostMsg_FileIO_SetLength::ID: { + FileIOMap::iterator it = files_.find(params.pp_resource()); + if (it == files_.end()) + return; + // Audit FileIO SetLength messages to make sure the plugin is within + // the current quota reservation. In addition, deduct the file size + // increase from the quota reservation. + int64_t length = 0; + if (!UnpackMessage<PpapiHostMsg_FileIO_SetLength>( + nested_msg, &length)) + return; + + // Calculate file size increase, taking care to avoid overflows. + if (length < 0) + return; + int64_t trusted_max_written_offset = it->second->max_written_offset(); + int64_t increase = length - trusted_max_written_offset; + if (increase <= 0) + return; + if (!it->second->Grow(increase)) { + new_msg_ptr->reset( + new PpapiHostMsg_ResourceCall( + params, + PpapiHostMsg_FileIO_SetLength(-1))); + } + break; + } + case PpapiHostMsg_FileSystem_ReserveQuota::ID: { + // Audit FileSystem ReserveQuota messages to make sure the plugin + // reports accurate file sizes. + int64_t amount = 0; + FileOffsetMap max_written_offsets; + if (!UnpackMessage<PpapiHostMsg_FileSystem_ReserveQuota>( + nested_msg, &amount, &max_written_offsets)) + return; + + bool audit_failed = false; + for (FileOffsetMap::iterator it = max_written_offsets.begin(); + it != max_written_offsets.end(); ++it) { + FileIOMap::iterator file_it = files_.find(it->first); + if (file_it == files_.end()) + continue; + int64_t trusted_max_written_offset = + file_it->second->max_written_offset(); + if (trusted_max_written_offset > it->second) { + audit_failed = true; + it->second = trusted_max_written_offset; + } + } + if (audit_failed) { + new_msg_ptr->reset( + new PpapiHostMsg_ResourceCall( + params, + PpapiHostMsg_FileSystem_ReserveQuota( + amount, max_written_offsets))); + } + break; + } + case PpapiHostMsg_ResourceDestroyed::ID: { + // Audit resource destroyed messages to release FileSystems. + PP_Resource resource; + if (!UnpackMessage<PpapiHostMsg_ResourceDestroyed>( + nested_msg, &resource)) + return; + FileSystemMap::iterator fs_it = file_systems_.find(resource); + if (fs_it != file_systems_.end()) { + delete fs_it->second; + file_systems_.erase(fs_it); + } + } + } + } } void NaClMessageScanner::RegisterSyncMessageForReply(const IPC::Message& msg) { @@ -301,5 +479,64 @@ void NaClMessageScanner::RegisterSyncMessageForReply(const IPC::Message& msg) { pending_sync_msgs_[msg_id] = msg.type(); } +NaClMessageScanner::FileIO* NaClMessageScanner::GetFile( + PP_Resource file_io) { + FileIOMap::iterator it = files_.find(file_io); + DCHECK(it != files_.end()); + return it->second; +} + +void NaClMessageScanner::AuditNestedMessage(PP_Resource resource, + const IPC::Message& msg, + SerializedHandle* handle) { + switch (msg.type()) { + case PpapiPluginMsg_FileIO_OpenReply::ID: { + // A file that requires quota checking was opened. + PP_Resource quota_file_system; + int64_t max_written_offset = 0; + if (ppapi::UnpackMessage<PpapiPluginMsg_FileIO_OpenReply>( + msg, "a_file_system, &max_written_offset)) { + if (quota_file_system) { + // Look up the FileSystem by inserting a new one. If it was already + // present, get the existing one, otherwise construct it. + FileSystem* file_system = NULL; + std::pair<FileSystemMap::iterator, bool> insert_result = + file_systems_.insert(std::make_pair(quota_file_system, + file_system)); + if (insert_result.second) + insert_result.first->second = new FileSystem(); + file_system = insert_result.first->second; + // Create the FileIO. + DCHECK(files_.find(resource) == files_.end()); + files_.insert(std::make_pair( + resource, + new FileIO(file_system, max_written_offset))); + } + } + break; + } + case PpapiPluginMsg_FileSystem_ReserveQuotaReply::ID: { + // The amount of reserved quota for a FileSystem was refreshed. + int64_t amount = 0; + FileOffsetMap max_written_offsets; + if (ppapi::UnpackMessage<PpapiPluginMsg_FileSystem_ReserveQuotaReply>( + msg, &amount, &max_written_offsets)) { + FileSystemMap::iterator it = file_systems_.find(resource); + DCHECK(it != file_systems_.end()); + it->second->UpdateReservedQuota(amount); + + FileOffsetMap::const_iterator offset_it = max_written_offsets.begin(); + for (; offset_it != max_written_offsets.end(); ++offset_it) { + FileIOMap::iterator fio_it = files_.find(offset_it->first); + DCHECK(fio_it != files_.end()); + if (fio_it != files_.end()) + fio_it->second->SetMaxWrittenOffset(offset_it->second); + } + } + break; + } + } +} + } // namespace proxy } // namespace ppapi diff --git a/ppapi/proxy/nacl_message_scanner.h b/ppapi/proxy/nacl_message_scanner.h index 8704160..d1360b7 100644 --- a/ppapi/proxy/nacl_message_scanner.h +++ b/ppapi/proxy/nacl_message_scanner.h @@ -10,6 +10,8 @@ #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" +#include "ppapi/c/pp_resource.h" #include "ppapi/proxy/ppapi_proxy_export.h" namespace IPC { @@ -24,6 +26,7 @@ class SerializedHandle; class PPAPI_PROXY_EXPORT NaClMessageScanner { public: NaClMessageScanner(); + ~NaClMessageScanner(); // Scans the message for items that require special handling. Copies any // SerializedHandles in the message into |handles| and if the message must be @@ -44,8 +47,63 @@ class PPAPI_PROXY_EXPORT NaClMessageScanner { void ScanUntrustedMessage(const IPC::Message& untrusted_msg, scoped_ptr<IPC::Message>* new_msg_ptr); + // FileSystem information for quota auditing. + class PPAPI_PROXY_EXPORT FileSystem { + public: + FileSystem(); + ~FileSystem(); + + int64_t reserved_quota() const { return reserved_quota_; } + + // Adds amount to reserved quota. Returns true if reserved quota >= 0. + bool UpdateReservedQuota(int64_t delta); + + private: + base::Lock lock_; + // This is the remaining amount of quota reserved for the file system. + // Acquire the lock to modify this field, since it may be used on multiple + // threads. + int64_t reserved_quota_; + + DISALLOW_COPY_AND_ASSIGN(FileSystem); + }; + + // FileIO information for quota auditing. + class PPAPI_PROXY_EXPORT FileIO { + public: + FileIO(FileSystem* file_system, int64_t max_written_offset); + ~FileIO(); + + int64_t max_written_offset() { return max_written_offset_; } + + void SetMaxWrittenOffset(int64_t max_written_offset); + + // Grows file by the given amount. Returns true on success. + bool Grow(int64_t amount); + + private: + base::Lock lock_; + + // The file system that contains this file. + FileSystem* file_system_; + + // The maximum written offset. This is initialized by NaClMessageScanner + // when the file is opened and modified by a NaClDescQuotaInterface when the + // plugin writes to greater maximum offsets. + int64_t max_written_offset_; + + DISALLOW_COPY_AND_ASSIGN(FileIO); + }; + + FileIO* GetFile(PP_Resource file_io); + private: + friend class NaClMessageScannerTest; + void RegisterSyncMessageForReply(const IPC::Message& msg); + void AuditNestedMessage(PP_Resource resource, + const IPC::Message& msg, + SerializedHandle* handle); // When we send a synchronous message (from untrusted to trusted), we store // its type here, so that later we can associate the reply with its type @@ -53,6 +111,14 @@ class PPAPI_PROXY_EXPORT NaClMessageScanner { typedef std::map<int, uint32> PendingSyncMsgMap; PendingSyncMsgMap pending_sync_msgs_; + // We intercept FileSystem and FileIO messages to maintain information about + // file systems and open files. This is used by NaClQuotaDescs to calculate + // quota consumption and check it against the reserved amount. + typedef std::map<int32_t, FileSystem*> FileSystemMap; + FileSystemMap file_systems_; + typedef std::map<int32_t, FileIO*> FileIOMap; + FileIOMap files_; + DISALLOW_COPY_AND_ASSIGN(NaClMessageScanner); }; diff --git a/ppapi/proxy/nacl_message_scanner_unittest.cc b/ppapi/proxy/nacl_message_scanner_unittest.cc new file mode 100644 index 0000000..e0516ff --- /dev/null +++ b/ppapi/proxy/nacl_message_scanner_unittest.cc @@ -0,0 +1,293 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ipc/ipc_message.h" +#include "ppapi/proxy/nacl_message_scanner.h" +#include "ppapi/proxy/ppapi_messages.h" +#include "ppapi/proxy/ppapi_proxy_test.h" +#include "ppapi/proxy/serialized_handle.h" +#include "ppapi/shared_impl/host_resource.h" + +namespace ppapi { +namespace proxy { + +namespace { +const PP_Resource kInvalidResource = 0; +const PP_Resource kFileSystem = 1; +const PP_Resource kFileIO = 2; +const int64_t kQuotaReservationAmount = 100; +} + +class NaClMessageScannerTest : public PluginProxyTest { + public: + NaClMessageScannerTest() {} + + uint32 FindPendingSyncMessage( + const NaClMessageScanner& scanner, + const IPC::Message& msg) { + int msg_id = IPC::SyncMessage::GetMessageId(msg); + std::map<int, uint32>::const_iterator it = + scanner.pending_sync_msgs_.find(msg_id); + // O can signal that no message was found. + return (it != scanner.pending_sync_msgs_.end()) ? it->second : 0; + } + + NaClMessageScanner::FileSystem* FindFileSystem( + const NaClMessageScanner& scanner, + PP_Resource file_system) { + NaClMessageScanner::FileSystemMap::const_iterator it = + scanner.file_systems_.find(file_system); + return (it != scanner.file_systems_.end()) ? it->second : NULL; + } + + NaClMessageScanner::FileIO* FindFileIO( + const NaClMessageScanner& scanner, + PP_Resource file_io) { + NaClMessageScanner::FileIOMap::const_iterator it = + scanner.files_.find(file_io); + return (it != scanner.files_.end()) ? it->second : NULL; + } + + void OpenQuotaFile(NaClMessageScanner* scanner, + PP_Resource file_io, + PP_Resource file_system) { + std::vector<SerializedHandle> unused_handles; + ResourceMessageReplyParams fio_reply_params(file_io, 0); + scoped_ptr<IPC::Message> new_msg_ptr; + scanner->ScanMessage( + PpapiPluginMsg_ResourceReply( + fio_reply_params, + PpapiPluginMsg_FileIO_OpenReply(file_system, 0)), + &unused_handles, + &new_msg_ptr); + EXPECT_FALSE(new_msg_ptr); + } +}; + +TEST_F(NaClMessageScannerTest, SyncMessageAndReply) { + NaClMessageScanner test; + ppapi::proxy::SerializedHandle handle( + ppapi::proxy::SerializedHandle::SHARED_MEMORY); + IPC::Message msg = + PpapiHostMsg_PPBGraphics3D_GetTransferBuffer( + ppapi::API_ID_PPB_GRAPHICS_3D, + HostResource(), + 0, // id + &handle); + scoped_ptr<IPC::Message> new_msg_ptr; + EXPECT_NE(msg.type(), FindPendingSyncMessage(test, msg)); + test.ScanUntrustedMessage(msg, &new_msg_ptr); + EXPECT_FALSE(new_msg_ptr); + EXPECT_EQ(msg.type(), FindPendingSyncMessage(test, msg)); + + // TODO(bbudge) Figure out how to put together a sync reply message. +} + +TEST_F(NaClMessageScannerTest, FileOpenClose) { + NaClMessageScanner test; + std::vector<SerializedHandle> unused_handles; + ResourceMessageCallParams fio_call_params(kFileIO, 0); + ResourceMessageCallParams fs_call_params(kFileSystem, 0); + ResourceMessageReplyParams fio_reply_params(kFileIO, 0); + ResourceMessageReplyParams fs_reply_params(kFileSystem, 0); + scoped_ptr<IPC::Message> new_msg_ptr; + + EXPECT_EQ(NULL, FindFileSystem(test, kFileSystem)); + EXPECT_EQ(NULL, FindFileIO(test, kFileIO)); + + // Open a file, not in a quota file system. + test.ScanMessage( + PpapiPluginMsg_ResourceReply( + fio_reply_params, + PpapiPluginMsg_FileIO_OpenReply(kInvalidResource, 0)), + &unused_handles, + &new_msg_ptr); + EXPECT_FALSE(new_msg_ptr); + EXPECT_FALSE(FindFileSystem(test, kFileSystem)); + EXPECT_FALSE(FindFileIO(test, kFileIO)); + + // Open a file in a quota file system; info objects for it and its file system + // should be created. + OpenQuotaFile(&test, kFileIO, kFileSystem); + NaClMessageScanner::FileSystem* fs = FindFileSystem(test, kFileSystem); + NaClMessageScanner::FileIO* fio = FindFileIO(test, kFileIO); + EXPECT_TRUE(fs); + EXPECT_EQ(0, fs->reserved_quota()); + EXPECT_TRUE(fio); + EXPECT_EQ(0, fio->max_written_offset()); + + const int64_t kNewFileSize = 10; + fio->SetMaxWrittenOffset(kNewFileSize); + + // We should not be able to under-report max_written_offset when closing. + test.ScanUntrustedMessage( + PpapiHostMsg_ResourceCall( + fio_call_params, + PpapiHostMsg_FileIO_Close(0)), + &new_msg_ptr); + EXPECT_TRUE(new_msg_ptr); + ResourceMessageCallParams call_params; + IPC::Message nested_msg; + int64_t max_written_offset = 0; + EXPECT_TRUE(UnpackMessage<PpapiHostMsg_ResourceCall>( + *new_msg_ptr, &call_params, &nested_msg) && + UnpackMessage<PpapiHostMsg_FileIO_Close>( + nested_msg, &max_written_offset)); + new_msg_ptr.reset(); + EXPECT_EQ(kNewFileSize, max_written_offset); + EXPECT_FALSE(FindFileIO(test, kFileIO)); + + // Reopen the file. + OpenQuotaFile(&test, kFileIO, kFileSystem); + fio = FindFileIO(test, kFileIO); + fio->SetMaxWrittenOffset(kNewFileSize); + + // Close with correct max_written_offset. + test.ScanUntrustedMessage( + PpapiHostMsg_ResourceCall( + fio_call_params, + PpapiHostMsg_FileIO_Close(kNewFileSize)), + &new_msg_ptr); + EXPECT_FALSE(new_msg_ptr); + EXPECT_FALSE(FindFileIO(test, kFileIO)); + + // Destroy file system. + test.ScanUntrustedMessage( + PpapiHostMsg_ResourceCall( + fs_call_params, + PpapiHostMsg_ResourceDestroyed(kFileSystem)), + &new_msg_ptr); + EXPECT_FALSE(FindFileSystem(test, kFileSystem)); +} + +TEST_F(NaClMessageScannerTest, QuotaAuditing) { + NaClMessageScanner test; + std::vector<SerializedHandle> unused_handles; + ResourceMessageCallParams fio_call_params(kFileIO, 0); + ResourceMessageCallParams fs_call_params(kFileSystem, 0); + ResourceMessageReplyParams fio_reply_params(kFileIO, 0); + ResourceMessageReplyParams fs_reply_params(kFileSystem, 0); + scoped_ptr<IPC::Message> new_msg_ptr; + + OpenQuotaFile(&test, kFileIO, kFileSystem); + NaClMessageScanner::FileSystem* fs = FindFileSystem(test, kFileSystem); + NaClMessageScanner::FileIO* fio = FindFileIO(test, kFileIO); + EXPECT_TRUE(fs); + EXPECT_EQ(0, fs->reserved_quota()); + EXPECT_TRUE(fio); + EXPECT_EQ(0, fio->max_written_offset()); + + // Without reserving quota, we should not be able to grow the file. + EXPECT_FALSE(fio->Grow(1)); + EXPECT_EQ(0, fs->reserved_quota()); + EXPECT_EQ(0, fio->max_written_offset()); + + // Receive reserved quota, and updated file sizes. + const int64_t kNewFileSize = 10; + FileOffsetMap offset_map; + offset_map.insert(std::make_pair(kFileIO, kNewFileSize)); + test.ScanMessage( + PpapiPluginMsg_ResourceReply( + fs_reply_params, + PpapiPluginMsg_FileSystem_ReserveQuotaReply( + kQuotaReservationAmount, + offset_map)), + &unused_handles, + &new_msg_ptr); + EXPECT_FALSE(new_msg_ptr); + EXPECT_EQ(kQuotaReservationAmount, fs->reserved_quota()); + EXPECT_EQ(kNewFileSize, fio->max_written_offset()); + + // We should be able to grow the file within quota. + EXPECT_TRUE(fio->Grow(1)); + EXPECT_EQ(kQuotaReservationAmount - 1, fs->reserved_quota()); + EXPECT_EQ(kNewFileSize + 1, fio->max_written_offset()); + + // We should not be able to grow the file over quota. + EXPECT_FALSE(fio->Grow(kQuotaReservationAmount)); + EXPECT_EQ(kQuotaReservationAmount - 1, fs->reserved_quota()); + EXPECT_EQ(kNewFileSize + 1, fio->max_written_offset()); + + // Plugin should not under-report max written offsets when reserving quota. + offset_map[kFileIO] = 0; // should be kNewFileSize + 1. + test.ScanUntrustedMessage( + PpapiHostMsg_ResourceCall( + fio_call_params, + PpapiHostMsg_FileSystem_ReserveQuota( + kQuotaReservationAmount, + offset_map)), + &new_msg_ptr); + EXPECT_TRUE(new_msg_ptr); + ResourceMessageCallParams call_params; + IPC::Message nested_msg; + int64_t amount = 0; + FileOffsetMap new_offset_map; + EXPECT_TRUE(UnpackMessage<PpapiHostMsg_ResourceCall>( + *new_msg_ptr, &call_params, &nested_msg) && + UnpackMessage<PpapiHostMsg_FileSystem_ReserveQuota>( + nested_msg, &amount, &new_offset_map)); + new_msg_ptr.reset(); + EXPECT_EQ(kQuotaReservationAmount, amount); + EXPECT_EQ(kNewFileSize + 1, new_offset_map[kFileIO]); +} + +TEST_F(NaClMessageScannerTest, SetLength) { + NaClMessageScanner test; + std::vector<SerializedHandle> unused_handles; + ResourceMessageCallParams fio_call_params(kFileIO, 0); + ResourceMessageCallParams fs_call_params(kFileSystem, 0); + ResourceMessageReplyParams fio_reply_params(kFileIO, 0); + ResourceMessageReplyParams fs_reply_params(kFileSystem, 0); + scoped_ptr<IPC::Message> new_msg_ptr; + + OpenQuotaFile(&test, kFileIO, kFileSystem); + NaClMessageScanner::FileSystem* fs = FindFileSystem(test, kFileSystem); + NaClMessageScanner::FileIO* fio = FindFileIO(test, kFileIO); + + // Receive reserved quota, and updated file sizes. + const int64_t kNewFileSize = 10; + FileOffsetMap offset_map; + offset_map.insert(std::make_pair(kFileIO, 0)); + test.ScanMessage( + PpapiPluginMsg_ResourceReply( + fs_reply_params, + PpapiPluginMsg_FileSystem_ReserveQuotaReply( + kQuotaReservationAmount, + offset_map)), + &unused_handles, + &new_msg_ptr); + + // We should be able to SetLength within quota. + test.ScanUntrustedMessage( + PpapiHostMsg_ResourceCall( + fio_call_params, + PpapiHostMsg_FileIO_SetLength(kNewFileSize)), + &new_msg_ptr); + EXPECT_FALSE(new_msg_ptr); + EXPECT_EQ(kQuotaReservationAmount - kNewFileSize, fs->reserved_quota()); + EXPECT_EQ(kNewFileSize, fio->max_written_offset()); + + // We shouldn't be able to SetLength beyond quota. The message should be + // rewritten to fail with length == -1. + test.ScanUntrustedMessage( + PpapiHostMsg_ResourceCall( + fio_call_params, + PpapiHostMsg_FileIO_SetLength(kQuotaReservationAmount + 1)), + &new_msg_ptr); + EXPECT_TRUE(new_msg_ptr); + ResourceMessageCallParams call_params; + IPC::Message nested_msg; + int64_t length = 0; + EXPECT_TRUE(UnpackMessage<PpapiHostMsg_ResourceCall>( + *new_msg_ptr, &call_params, &nested_msg) && + UnpackMessage<PpapiHostMsg_FileIO_SetLength>( + nested_msg, &length)); + new_msg_ptr.reset(); + EXPECT_EQ(-1, length); + EXPECT_EQ(kQuotaReservationAmount - kNewFileSize, fs->reserved_quota()); + EXPECT_EQ(kNewFileSize, fio->max_written_offset()); +} + +} // namespace proxy +} // namespace ppapi diff --git a/ppapi/proxy/ppapi_messages.h b/ppapi/proxy/ppapi_messages.h index ca4ea36..b2fa2af 100644 --- a/ppapi/proxy/ppapi_messages.h +++ b/ppapi/proxy/ppapi_messages.h @@ -3,6 +3,7 @@ // found in the LICENSE file. // Multiply-included message header, no traditional include guard. +#include <map> #include <string> #include <vector> @@ -1224,14 +1225,14 @@ IPC_MESSAGE_CONTROL0(PpapiHostMsg_FileIO_Create) IPC_MESSAGE_CONTROL2(PpapiHostMsg_FileIO_Open, PP_Resource /* file_ref_resource */, int32_t /* open_flags */) -IPC_MESSAGE_CONTROL0(PpapiPluginMsg_FileIO_OpenReply) -IPC_MESSAGE_CONTROL0(PpapiHostMsg_FileIO_Close) +IPC_MESSAGE_CONTROL2(PpapiPluginMsg_FileIO_OpenReply, + PP_Resource /* quota_file_system */, + int64_t /* max_written_offset */) +IPC_MESSAGE_CONTROL1(PpapiHostMsg_FileIO_Close, + int64_t /* max_written_offset */) IPC_MESSAGE_CONTROL2(PpapiHostMsg_FileIO_Touch, PP_Time /* last_access_time */, PP_Time /* last_modified_time */) -IPC_MESSAGE_CONTROL2(PpapiHostMsg_FileIO_Write, - int64_t /* offset */, - std::string /* data */) IPC_MESSAGE_CONTROL1(PpapiHostMsg_FileIO_SetLength, int64_t /* length */) IPC_MESSAGE_CONTROL0(PpapiHostMsg_FileIO_Flush) @@ -1317,6 +1318,15 @@ IPC_MESSAGE_CONTROL2(PpapiHostMsg_FileSystem_CreateFromRenderer, // linked to the existing resource host given in the ResourceVar. IPC_MESSAGE_CONTROL1(PpapiPluginMsg_FileSystem_CreateFromPendingHost, PP_FileSystemType /* file_system_type */) +// IPC_MESSAGE macros choke on extra , in the std::map, when expanding. We need +// to typedef it to avoid that. +typedef std::map<int32_t, int64_t> FileOffsetMap; +IPC_MESSAGE_CONTROL2(PpapiHostMsg_FileSystem_ReserveQuota, + int64_t /* amount */, + FileOffsetMap /* max_written_offsets */) +IPC_MESSAGE_CONTROL2(PpapiPluginMsg_FileSystem_ReserveQuotaReply, + int64_t /* amount */, + FileOffsetMap /* max_written_offsets */) // Flash DRM ------------------------------------------------------------------ IPC_MESSAGE_CONTROL0(PpapiHostMsg_FlashDRM_Create) diff --git a/ppapi/thunk/ppb_file_io_api.h b/ppapi/thunk/ppb_file_io_api.h index b794f1c..830b140 100644 --- a/ppapi/thunk/ppb_file_io_api.h +++ b/ppapi/thunk/ppb_file_io_api.h @@ -42,6 +42,8 @@ class PPAPI_THUNK_EXPORT PPB_FileIO_API { scoped_refptr<TrackedCallback> callback) = 0; virtual int32_t SetLength(int64_t length, scoped_refptr<TrackedCallback> callback) = 0; + virtual int64_t GetMaxWrittenOffset() const = 0; + virtual void SetMaxWrittenOffset(int64_t max_written_offset) = 0; virtual int32_t Flush(scoped_refptr<TrackedCallback> callback) = 0; virtual void Close() = 0; diff --git a/ppapi/thunk/ppb_file_system_api.h b/ppapi/thunk/ppb_file_system_api.h index f9524dc..0351bde 100644 --- a/ppapi/thunk/ppb_file_system_api.h +++ b/ppapi/thunk/ppb_file_system_api.h @@ -5,6 +5,7 @@ #ifndef PPAPI_THUNK_PPB_FILE_SYSTEM_API_H_ #define PPAPI_THUNK_PPB_FILE_SYSTEM_API_H_ +#include "base/callback_forward.h" #include "base/memory/ref_counted.h" #include "ppapi/c/ppb_file_system.h" @@ -21,6 +22,11 @@ class PPB_FileSystem_API { virtual int32_t Open(int64_t expected_size, scoped_refptr<TrackedCallback> callback) = 0; virtual PP_FileSystemType GetType() = 0; + virtual void OpenQuotaFile(PP_Resource file_io) = 0; + virtual void CloseQuotaFile(PP_Resource file_io) = 0; + typedef base::Callback<void(int64_t)> RequestQuotaCallback; + virtual int64_t RequestQuota(int64_t amount, + const RequestQuotaCallback& callback) = 0; }; } // namespace thunk |