diff options
author | alexis.menard@intel.com <alexis.menard@intel.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-22 20:44:02 +0000 |
---|---|---|
committer | alexis.menard@intel.com <alexis.menard@intel.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-22 20:44:02 +0000 |
commit | 7c766e9434441f945508f2bbdbe416fd557b7d02 (patch) | |
tree | 985dc3422d9f359ede2d26e893696391632a5728 /components | |
parent | 879517bd47bd27255ae5d29a1b6cfe3b2bb793fe (diff) | |
download | chromium_src-7c766e9434441f945508f2bbdbe416fd557b7d02.zip chromium_src-7c766e9434441f945508f2bbdbe416fd557b7d02.tar.gz chromium_src-7c766e9434441f945508f2bbdbe416fd557b7d02.tar.bz2 |
Move more files from chrome/browser/nacl_host/ to components/nacl/browser/
These files have no dependencies on chrome/ so they can be safely moved
to the components/ directory.
This is part of an effort to componentize NaCl code.
BUG=244791
R=brettw@chromium.org, cpu@chromium.org, jochen@chromium.org, mseaborn@chromium.org
Review URL: https://codereview.chromium.org/75463005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@236821 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components')
22 files changed, 3643 insertions, 1 deletions
diff --git a/components/components_tests.gyp b/components/components_tests.gyp index 2e00f4a..9847b90 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -134,8 +134,12 @@ }], ['disable_nacl==0', { 'sources': [ + 'nacl/browser/nacl_file_host_unittest.cc', + 'nacl/browser/nacl_process_host_unittest.cc', 'nacl/browser/nacl_validation_cache_unittest.cc', + 'nacl/browser/pnacl_host_unittest.cc', 'nacl/browser/pnacl_translation_cache_unittest.cc', + 'nacl/browser/test_nacl_browser_delegate.cc', ], 'dependencies': [ 'nacl.gyp:nacl_browser', diff --git a/components/nacl.gyp b/components/nacl.gyp index 21fce79..cb9b554 100644 --- a/components/nacl.gyp +++ b/components/nacl.gyp @@ -40,7 +40,6 @@ 'nacl/loader/nacl_validation_db.h', 'nacl/loader/nacl_validation_query.cc', 'nacl/loader/nacl_validation_query.h', - 'nacl/browser/test_nacl_browser_delegate.cc', ], # TODO(gregoryd): consider switching NaCl to use Chrome OS defines 'conditions': [ @@ -103,16 +102,39 @@ 'target_name': 'nacl_browser', 'type': 'static_library', 'sources': [ + 'nacl/browser/nacl_broker_host_win.cc', + 'nacl/browser/nacl_broker_host_win.h', + 'nacl/browser/nacl_broker_service_win.cc', + 'nacl/browser/nacl_broker_service_win.h', 'nacl/browser/nacl_browser.cc', 'nacl/browser/nacl_browser.h', + 'nacl/browser/nacl_file_host.cc', + 'nacl/browser/nacl_file_host.h', + 'nacl/browser/nacl_host_message_filter.cc', + 'nacl/browser/nacl_host_message_filter.h', + 'nacl/browser/nacl_process_host.cc', + 'nacl/browser/nacl_process_host.h', 'nacl/browser/nacl_validation_cache.cc', 'nacl/browser/nacl_validation_cache.h', + 'nacl/browser/pnacl_host.cc', + 'nacl/browser/pnacl_host.h', 'nacl/browser/pnacl_translation_cache.cc', 'nacl/browser/pnacl_translation_cache.h', + 'nacl/common/nacl_debug_exception_handler_win.cc', + 'nacl/common/nacl_debug_exception_handler_win.h', ], 'include_dirs': [ '..', ], + 'dependencies': [ + 'nacl_common.gyp:nacl_common', + 'nacl_common.gyp:nacl_switches', + '../native_client/src/trusted/service_runtime/service_runtime.gyp:sel', + '../content/content.gyp:content_browser', + ], + 'defines': [ + '<@(nacl_defines)', + ], # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. 'msvs_disabled_warnings': [4267, ], }, diff --git a/components/nacl/browser/DEPS b/components/nacl/browser/DEPS index 67b5b47..ed24f51 100644 --- a/components/nacl/browser/DEPS +++ b/components/nacl/browser/DEPS @@ -1,5 +1,9 @@ include_rules = [ "+content/public/browser", "+content/public/test", + "+native_client", "+net", + "+ppapi/host", + "+ppapi/proxy", + "+ppapi/shared_impl", ] diff --git a/components/nacl/browser/nacl_broker_host_win.cc b/components/nacl/browser/nacl_broker_host_win.cc new file mode 100644 index 0000000..878ca6e --- /dev/null +++ b/components/nacl/browser/nacl_broker_host_win.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2012 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 "components/nacl/browser/nacl_broker_host_win.h" + +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/path_service.h" +#include "components/nacl/browser/nacl_broker_service_win.h" +#include "components/nacl/browser/nacl_browser.h" +#include "components/nacl/common/nacl_cmd_line.h" +#include "components/nacl/common/nacl_messages.h" +#include "components/nacl/common/nacl_process_type.h" +#include "components/nacl/common/nacl_switches.h" +#include "content/public/browser/browser_child_process_host.h" +#include "content/public/browser/child_process_data.h" +#include "content/public/common/child_process_host.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/sandboxed_process_launcher_delegate.h" +#include "ipc/ipc_switches.h" + +namespace { +// NOTE: changes to this class need to be reviewed by the security team. +class NaClBrokerSandboxedProcessLauncherDelegate + : public content::SandboxedProcessLauncherDelegate { + public: + NaClBrokerSandboxedProcessLauncherDelegate() {} + virtual ~NaClBrokerSandboxedProcessLauncherDelegate() {} + + virtual void ShouldSandbox(bool* in_sandbox) OVERRIDE { + *in_sandbox = false; + } + + private: + DISALLOW_COPY_AND_ASSIGN(NaClBrokerSandboxedProcessLauncherDelegate); +}; +} // namespace + +namespace nacl { + +NaClBrokerHost::NaClBrokerHost() : is_terminating_(false) { + process_.reset(content::BrowserChildProcessHost::Create( + PROCESS_TYPE_NACL_BROKER, this)); +} + +NaClBrokerHost::~NaClBrokerHost() { +} + +bool NaClBrokerHost::Init() { + // Create the channel that will be used for communicating with the broker. + std::string channel_id = process_->GetHost()->CreateChannel(); + if (channel_id.empty()) + return false; + + // Create the path to the nacl broker/loader executable. + base::FilePath nacl_path; + if (!NaClBrowser::GetInstance()->GetNaCl64ExePath(&nacl_path)) + return false; + + CommandLine* cmd_line = new CommandLine(nacl_path); + CopyNaClCommandLineArguments(cmd_line); + + cmd_line->AppendSwitchASCII(switches::kProcessType, + switches::kNaClBrokerProcess); + cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id); + if (NaClBrowser::GetDelegate()->DialogsAreSuppressed()) + cmd_line->AppendSwitch(switches::kNoErrorDialogs); + + process_->Launch(new NaClBrokerSandboxedProcessLauncherDelegate, cmd_line); + return true; +} + +bool NaClBrokerHost::OnMessageReceived(const IPC::Message& msg) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(NaClBrokerHost, msg) + IPC_MESSAGE_HANDLER(NaClProcessMsg_LoaderLaunched, OnLoaderLaunched) + IPC_MESSAGE_HANDLER(NaClProcessMsg_DebugExceptionHandlerLaunched, + OnDebugExceptionHandlerLaunched) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +bool NaClBrokerHost::LaunchLoader(const std::string& loader_channel_id) { + return process_->Send( + new NaClProcessMsg_LaunchLoaderThroughBroker(loader_channel_id)); +} + +void NaClBrokerHost::OnLoaderLaunched(const std::string& loader_channel_id, + base::ProcessHandle handle) { + NaClBrokerService::GetInstance()->OnLoaderLaunched(loader_channel_id, handle); +} + +bool NaClBrokerHost::LaunchDebugExceptionHandler( + int32 pid, base::ProcessHandle process_handle, + const std::string& startup_info) { + base::ProcessHandle broker_process = process_->GetData().handle; + base::ProcessHandle handle_in_broker_process; + if (!DuplicateHandle(::GetCurrentProcess(), process_handle, + broker_process, &handle_in_broker_process, + 0, /* bInheritHandle= */ FALSE, DUPLICATE_SAME_ACCESS)) + return false; + return process_->Send(new NaClProcessMsg_LaunchDebugExceptionHandler( + pid, handle_in_broker_process, startup_info)); +} + +void NaClBrokerHost::OnDebugExceptionHandlerLaunched(int32 pid, bool success) { + NaClBrokerService::GetInstance()->OnDebugExceptionHandlerLaunched(pid, + success); +} + +void NaClBrokerHost::StopBroker() { + is_terminating_ = true; + process_->Send(new NaClProcessMsg_StopBroker()); +} + +} // namespace nacl diff --git a/components/nacl/browser/nacl_broker_host_win.h b/components/nacl/browser/nacl_broker_host_win.h new file mode 100644 index 0000000..bbafdff --- /dev/null +++ b/components/nacl/browser/nacl_broker_host_win.h @@ -0,0 +1,64 @@ +// Copyright (c) 2012 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. + +#ifndef COMPONENTS_NACL_BROWSER_NACL_BROKER_HOST_WIN_H_ +#define COMPONENTS_NACL_BROWSER_NACL_BROKER_HOST_WIN_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/process/process.h" +#include "content/public/browser/browser_child_process_host_delegate.h" + +namespace content { +class BrowserChildProcessHost; +} + +namespace nacl { + +class NaClBrokerHost : public content::BrowserChildProcessHostDelegate { + public: + NaClBrokerHost(); + ~NaClBrokerHost(); + + // This function starts the broker process. It needs to be called + // before loaders can be launched. + bool Init(); + + // Send a message to the broker process, causing it to launch + // a Native Client loader process. + bool LaunchLoader(const std::string& loader_channel_id); + + bool LaunchDebugExceptionHandler(int32 pid, + base::ProcessHandle process_handle, + const std::string& startup_info); + + // Stop the broker process. + void StopBroker(); + + // Returns true if the process has been asked to terminate. If true, this + // object should no longer be used; it will eventually be destroyed by + // BrowserChildProcessHostImpl::OnChildDisconnected() + bool IsTerminating() { return is_terminating_; } + + private: + // Handler for NaClProcessMsg_LoaderLaunched message + void OnLoaderLaunched(const std::string& loader_channel_id, + base::ProcessHandle handle); + // Handler for NaClProcessMsg_DebugExceptionHandlerLaunched message + void OnDebugExceptionHandlerLaunched(int32 pid, bool success); + + // BrowserChildProcessHostDelegate implementation: + virtual bool OnMessageReceived(const IPC::Message& msg); + + scoped_ptr<content::BrowserChildProcessHost> process_; + bool is_terminating_; + + DISALLOW_COPY_AND_ASSIGN(NaClBrokerHost); +}; + +} // namespace nacl + +#endif // COMPONENTS_NACL_BROWSER_NACL_BROKER_HOST_WIN_H_ diff --git a/components/nacl/browser/nacl_broker_service_win.cc b/components/nacl/browser/nacl_broker_service_win.cc new file mode 100644 index 0000000..af23be1 --- /dev/null +++ b/components/nacl/browser/nacl_broker_service_win.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2012 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 "components/nacl/browser/nacl_broker_service_win.h" + +#include "components/nacl/browser/nacl_process_host.h" +#include "components/nacl/common/nacl_process_type.h" +#include "content/public/browser/browser_child_process_host_iterator.h" + +using content::BrowserChildProcessHostIterator; + +namespace nacl { + +NaClBrokerService* NaClBrokerService::GetInstance() { + return Singleton<NaClBrokerService>::get(); +} + +NaClBrokerService::NaClBrokerService() + : loaders_running_(0) { +} + +bool NaClBrokerService::StartBroker() { + NaClBrokerHost* broker_host = new NaClBrokerHost; + if (!broker_host->Init()) { + delete broker_host; + return false; + } + return true; +} + +bool NaClBrokerService::LaunchLoader( + base::WeakPtr<nacl::NaClProcessHost> nacl_process_host, + const std::string& loader_channel_id) { + // Add task to the list + pending_launches_[loader_channel_id] = nacl_process_host; + NaClBrokerHost* broker_host = GetBrokerHost(); + + if (!broker_host) { + if (!StartBroker()) + return false; + broker_host = GetBrokerHost(); + } + broker_host->LaunchLoader(loader_channel_id); + + return true; +} + +void NaClBrokerService::OnLoaderLaunched(const std::string& channel_id, + base::ProcessHandle handle) { + PendingLaunchesMap::iterator it = pending_launches_.find(channel_id); + if (pending_launches_.end() == it) + NOTREACHED(); + + NaClProcessHost* client = it->second.get(); + if (client) + client->OnProcessLaunchedByBroker(handle); + pending_launches_.erase(it); + ++loaders_running_; +} + +void NaClBrokerService::OnLoaderDied() { + DCHECK(loaders_running_ > 0); + --loaders_running_; + // Stop the broker only if there are no loaders running or being launched. + NaClBrokerHost* broker_host = GetBrokerHost(); + if (loaders_running_ + pending_launches_.size() == 0 && broker_host != NULL) { + broker_host->StopBroker(); + } +} + +bool NaClBrokerService::LaunchDebugExceptionHandler( + base::WeakPtr<NaClProcessHost> nacl_process_host, int32 pid, + base::ProcessHandle process_handle, const std::string& startup_info) { + pending_debuggers_[pid] = nacl_process_host; + NaClBrokerHost* broker_host = GetBrokerHost(); + if (!broker_host) + return false; + return broker_host->LaunchDebugExceptionHandler(pid, process_handle, + startup_info); +} + +void NaClBrokerService::OnDebugExceptionHandlerLaunched(int32 pid, + bool success) { + PendingDebugExceptionHandlersMap::iterator it = pending_debuggers_.find(pid); + if (pending_debuggers_.end() == it) + NOTREACHED(); + + NaClProcessHost* client = it->second.get(); + if (client) + client->OnDebugExceptionHandlerLaunchedByBroker(success); + pending_debuggers_.erase(it); +} + +NaClBrokerHost* NaClBrokerService::GetBrokerHost() { + BrowserChildProcessHostIterator iter(PROCESS_TYPE_NACL_BROKER); + while (!iter.Done()) { + NaClBrokerHost* host = static_cast<NaClBrokerHost*>( + iter.GetDelegate()); + if (!host->IsTerminating()) + return host; + ++iter; + } + return NULL; +} + +} // namespace nacl diff --git a/components/nacl/browser/nacl_broker_service_win.h b/components/nacl/browser/nacl_broker_service_win.h new file mode 100644 index 0000000..77d69dd --- /dev/null +++ b/components/nacl/browser/nacl_broker_service_win.h @@ -0,0 +1,70 @@ +// Copyright (c) 2012 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. + +#ifndef COMPONENTS_NACL_BROWSER_NACL_BROKER_SERVICE_WIN_H_ +#define COMPONENTS_NACL_BROWSER_NACL_BROKER_SERVICE_WIN_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/memory/singleton.h" +#include "base/memory/weak_ptr.h" +#include "components/nacl/browser/nacl_broker_host_win.h" + +namespace nacl { + +class NaClProcessHost; + +class NaClBrokerService { + public: + // Returns the NaClBrokerService singleton. + static NaClBrokerService* GetInstance(); + + // Can be called several times, must be called before LaunchLoader. + bool StartBroker(); + + // Send a message to the broker process, causing it to launch + // a Native Client loader process. + bool LaunchLoader(base::WeakPtr<NaClProcessHost> client, + const std::string& loader_channel_id); + + // Called by NaClBrokerHost to notify the service that a loader was launched. + void OnLoaderLaunched(const std::string& channel_id, + base::ProcessHandle handle); + + // Called by NaClProcessHost when a loader process is terminated + void OnLoaderDied(); + + bool LaunchDebugExceptionHandler(base::WeakPtr<NaClProcessHost> client, + int32 pid, + base::ProcessHandle process_handle, + const std::string& startup_info); + + // Called by NaClBrokerHost to notify the service that a debug + // exception handler was started. + void OnDebugExceptionHandlerLaunched(int32 pid, bool success); + + private: + typedef std::map<std::string, base::WeakPtr<NaClProcessHost> > + PendingLaunchesMap; + typedef std::map<int, base::WeakPtr<NaClProcessHost> > + PendingDebugExceptionHandlersMap; + + friend struct DefaultSingletonTraits<NaClBrokerService>; + + NaClBrokerService(); + ~NaClBrokerService() {} + + NaClBrokerHost* GetBrokerHost(); + + int loaders_running_; + PendingLaunchesMap pending_launches_; + PendingDebugExceptionHandlersMap pending_debuggers_; + + DISALLOW_COPY_AND_ASSIGN(NaClBrokerService); +}; + +} // namespace nacl + +#endif // COMPONENTS_NACL_BROWSER_NACL_BROKER_SERVICE_WIN_H_ diff --git a/components/nacl/browser/nacl_file_host.cc b/components/nacl/browser/nacl_file_host.cc new file mode 100644 index 0000000..44a1c83 --- /dev/null +++ b/components/nacl/browser/nacl_file_host.cc @@ -0,0 +1,249 @@ +// Copyright (c) 2012 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 "components/nacl/browser/nacl_file_host.h" + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "base/platform_file.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/sequenced_worker_pool.h" +#include "components/nacl/browser/nacl_browser.h" +#include "components/nacl/browser/nacl_browser_delegate.h" +#include "components/nacl/browser/nacl_host_message_filter.h" +#include "components/nacl/common/nacl_host_messages.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/site_instance.h" +#include "ipc/ipc_platform_file.h" + +using content::BrowserThread; + +namespace { + +// Force a prefix to prevent user from opening "magic" files. +const char* kExpectedFilePrefix = "pnacl_public_"; + +// Restrict PNaCl file lengths to reduce likelyhood of hitting bugs +// in file name limit error-handling-code-paths, etc. +const size_t kMaxFileLength = 40; + +void NotifyRendererOfError( + nacl::NaClHostMessageFilter* nacl_host_message_filter, + IPC::Message* reply_msg) { + reply_msg->set_reply_error(); + nacl_host_message_filter->Send(reply_msg); +} + +bool PnaclDoOpenFile(const base::FilePath& file_to_open, + base::PlatformFile* out_file) { + base::PlatformFileError error_code; + *out_file = base::CreatePlatformFile(file_to_open, + base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_READ, + NULL, + &error_code); + if (error_code != base::PLATFORM_FILE_OK) { + return false; + } + return true; +} + +void DoOpenPnaclFile( + scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter, + const std::string& filename, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); + base::FilePath full_filepath; + + // PNaCl must be installed. + base::FilePath pnacl_dir; + if (!nacl::NaClBrowser::GetDelegate()->GetPnaclDirectory(&pnacl_dir) || + !base::PathExists(pnacl_dir)) { + NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); + return; + } + + // Do some validation. + if (!nacl_file_host::PnaclCanOpenFile(filename, &full_filepath)) { + NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); + return; + } + + base::PlatformFile file_to_open; + if (!PnaclDoOpenFile(full_filepath, &file_to_open)) { + NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); + return; + } + + // Send the reply! + // Do any DuplicateHandle magic that is necessary first. + IPC::PlatformFileForTransit target_desc = + IPC::GetFileHandleForProcess(file_to_open, + nacl_host_message_filter->PeerHandle(), + true /* Close source */); + if (target_desc == IPC::InvalidPlatformFileForTransit()) { + NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); + return; + } + NaClHostMsg_GetReadonlyPnaclFD::WriteReplyParams( + reply_msg, target_desc); + nacl_host_message_filter->Send(reply_msg); +} + +void DoRegisterOpenedNaClExecutableFile( + scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter, + base::PlatformFile file, + base::FilePath file_path, + IPC::Message* reply_msg) { + // IO thread owns the NaClBrowser singleton. + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + nacl::NaClBrowser* nacl_browser = nacl::NaClBrowser::GetInstance(); + uint64 file_token_lo = 0; + uint64 file_token_hi = 0; + nacl_browser->PutFilePath(file_path, &file_token_lo, &file_token_hi); + + IPC::PlatformFileForTransit file_desc = IPC::GetFileHandleForProcess( + file, + nacl_host_message_filter->PeerHandle(), + true /* close_source */); + + NaClHostMsg_OpenNaClExecutable::WriteReplyParams( + reply_msg, file_desc, file_token_lo, file_token_hi); + nacl_host_message_filter->Send(reply_msg); +} + +// Convert the file URL into a file descriptor. +// This function is security sensitive. Be sure to check with a security +// person before you modify it. +void DoOpenNaClExecutableOnThreadPool( + scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter, + const GURL& file_url, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); + + base::FilePath file_path; + if (!nacl::NaClBrowser::GetDelegate()->MapUrlToLocalFilePath( + file_url, true /* use_blocking_api */, &file_path)) { + NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); + return; + } + + base::PlatformFile file; + nacl::OpenNaClExecutableImpl(file_path, &file); + if (file != base::kInvalidPlatformFileValue) { + // This function is running on the blocking pool, but the path needs to be + // registered in a structure owned by the IO thread. + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind( + &DoRegisterOpenedNaClExecutableFile, + nacl_host_message_filter, + file, file_path, reply_msg)); + } else { + NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); + return; + } +} + +} // namespace + +namespace nacl_file_host { + +void GetReadonlyPnaclFd( + scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter, + const std::string& filename, + IPC::Message* reply_msg) { + if (!BrowserThread::PostBlockingPoolTask( + FROM_HERE, + base::Bind(&DoOpenPnaclFile, + nacl_host_message_filter, + filename, + reply_msg))) { + NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); + } +} + +// This function is security sensitive. Be sure to check with a security +// person before you modify it. +bool PnaclCanOpenFile(const std::string& filename, + base::FilePath* file_to_open) { + if (filename.length() > kMaxFileLength) + return false; + + if (filename.empty()) + return false; + + // Restrict character set of the file name to something really simple + // (a-z, 0-9, and underscores). + for (size_t i = 0; i < filename.length(); ++i) { + char charAt = filename[i]; + if (charAt < 'a' || charAt > 'z') + if (charAt < '0' || charAt > '9') + if (charAt != '_') + return false; + } + + // PNaCl must be installed. + base::FilePath pnacl_dir; + if (!nacl::NaClBrowser::GetDelegate()->GetPnaclDirectory(&pnacl_dir) || + pnacl_dir.empty()) + return false; + + // Prepend the prefix to restrict files to a whitelisted set. + base::FilePath full_path = pnacl_dir.AppendASCII( + std::string(kExpectedFilePrefix) + filename); + *file_to_open = full_path; + return true; +} + +void OpenNaClExecutable( + scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter, + int render_view_id, + const GURL& file_url, + IPC::Message* reply_msg) { + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind( + &OpenNaClExecutable, + nacl_host_message_filter, + render_view_id, file_url, reply_msg)); + return; + } + + // Make sure render_view_id is valid and that the URL is a part of the + // render view's site. Without these checks, apps could probe the extension + // directory or run NaCl code from other extensions. + content::RenderViewHost* rvh = content::RenderViewHost::FromID( + nacl_host_message_filter->render_process_id(), render_view_id); + if (!rvh) { + nacl_host_message_filter->BadMessageReceived(); // Kill the renderer. + return; + } + content::SiteInstance* site_instance = rvh->GetSiteInstance(); + if (!content::SiteInstance::IsSameWebSite(site_instance->GetBrowserContext(), + site_instance->GetSiteURL(), + file_url)) { + NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); + return; + } + + // The URL is part of the current app. Now query the extension system for the + // file path and convert that to a file descriptor. This should be done on a + // blocking pool thread. + if (!BrowserThread::PostBlockingPoolTask( + FROM_HERE, + base::Bind( + &DoOpenNaClExecutableOnThreadPool, + nacl_host_message_filter, + file_url, reply_msg))) { + NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg); + } +} + +} // namespace nacl_file_host diff --git a/components/nacl/browser/nacl_file_host.h b/components/nacl/browser/nacl_file_host.h new file mode 100644 index 0000000..abe75b1 --- /dev/null +++ b/components/nacl/browser/nacl_file_host.h @@ -0,0 +1,50 @@ +// Copyright (c) 2012 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. + +#ifndef COMPONENTS_NACL_BROWSER_NACL_FILE_HOST_H_ +#define COMPONENTS_NACL_BROWSER_NACL_FILE_HOST_H_ + +#include <string> + +#include "base/memory/ref_counted.h" + +class GURL; + +namespace base { +class FilePath; +} + +namespace IPC { +class Message; +} + +namespace nacl { +class NaClHostMessageFilter; +} + +// Opens NaCl Files in the Browser process, on behalf of the NaCl plugin. + +namespace nacl_file_host { + +// Open a PNaCl file (readonly) on behalf of the NaCl plugin. +void GetReadonlyPnaclFd( + scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter, + const std::string& filename, + IPC::Message* reply_msg); + +// Return true if the filename requested is valid for opening. +// Sets file_to_open to the base::FilePath which we will attempt to open. +bool PnaclCanOpenFile(const std::string& filename, + base::FilePath* file_to_open); + +// Opens a NaCl executable file for reading and executing. +void OpenNaClExecutable( + scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter, + int render_view_id, + const GURL& file_url, + IPC::Message* reply_msg); + +} // namespace nacl_file_host + +#endif // COMPONENTS_NACL_BROWSER_NACL_FILE_HOST_H_ diff --git a/components/nacl/browser/nacl_file_host_unittest.cc b/components/nacl/browser/nacl_file_host_unittest.cc new file mode 100644 index 0000000..0a83b7f --- /dev/null +++ b/components/nacl/browser/nacl_file_host_unittest.cc @@ -0,0 +1,119 @@ +// Copyright (c) 2012 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 "components/nacl/browser/nacl_file_host.h" + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/test/scoped_path_override.h" +#include "components/nacl/browser/nacl_browser.h" +#include "components/nacl/browser/nacl_browser_delegate.h" +#include "components/nacl/browser/test_nacl_browser_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" + +using nacl_file_host::PnaclCanOpenFile; + +class FileHostTestNaClBrowserDelegate : public TestNaClBrowserDelegate { + public: + FileHostTestNaClBrowserDelegate() {} + + virtual bool GetPnaclDirectory(base::FilePath* pnacl_dir) OVERRIDE { + *pnacl_dir = pnacl_path_; + return true; + } + + void SetPnaclDirectory(const base::FilePath& pnacl_dir) { + pnacl_path_ = pnacl_dir; + } + + private: + base::FilePath pnacl_path_; +}; + +class NaClFileHostTest : public testing::Test { + protected: + NaClFileHostTest(); + virtual ~NaClFileHostTest(); + + virtual void SetUp() OVERRIDE { + nacl_browser_delegate_ = new FileHostTestNaClBrowserDelegate; + nacl::NaClBrowser::SetDelegate(nacl_browser_delegate_); + } + + virtual void TearDown() OVERRIDE { + // This deletes nacl_browser_delegate_. + nacl::NaClBrowser::SetDelegate(NULL); + } + + FileHostTestNaClBrowserDelegate* nacl_browser_delegate() { + return nacl_browser_delegate_; + } + + private: + FileHostTestNaClBrowserDelegate* nacl_browser_delegate_; + DISALLOW_COPY_AND_ASSIGN(NaClFileHostTest); +}; + +NaClFileHostTest::NaClFileHostTest() : nacl_browser_delegate_(NULL) {} + +NaClFileHostTest::~NaClFileHostTest() { +} + +// Try to pass a few funny filenames with a dummy PNaCl directory set. +TEST_F(NaClFileHostTest, TestFilenamesWithPnaclPath) { + base::ScopedTempDir scoped_tmp_dir; + ASSERT_TRUE(scoped_tmp_dir.CreateUniqueTempDir()); + + base::FilePath kTestPnaclPath = scoped_tmp_dir.path(); + + nacl_browser_delegate()->SetPnaclDirectory(kTestPnaclPath); + ASSERT_TRUE(nacl::NaClBrowser::GetDelegate()->GetPnaclDirectory( + &kTestPnaclPath)); + + // Check allowed strings, and check that the expected prefix is added. + base::FilePath out_path; + EXPECT_TRUE(PnaclCanOpenFile("pnacl_json", &out_path)); + base::FilePath expected_path = kTestPnaclPath.Append( + FILE_PATH_LITERAL("pnacl_public_pnacl_json")); + EXPECT_EQ(expected_path, out_path); + + EXPECT_TRUE(PnaclCanOpenFile("x86_32_llc", &out_path)); + expected_path = kTestPnaclPath.Append( + FILE_PATH_LITERAL("pnacl_public_x86_32_llc")); + EXPECT_EQ(expected_path, out_path); + + // Check character ranges. + EXPECT_FALSE(PnaclCanOpenFile(".xchars", &out_path)); + EXPECT_FALSE(PnaclCanOpenFile("/xchars", &out_path)); + EXPECT_FALSE(PnaclCanOpenFile("x/chars", &out_path)); + EXPECT_FALSE(PnaclCanOpenFile("\\xchars", &out_path)); + EXPECT_FALSE(PnaclCanOpenFile("x\\chars", &out_path)); + EXPECT_FALSE(PnaclCanOpenFile("$xchars", &out_path)); + EXPECT_FALSE(PnaclCanOpenFile("%xchars", &out_path)); + EXPECT_FALSE(PnaclCanOpenFile("CAPS", &out_path)); + const char non_ascii[] = "\xff\xfe"; + EXPECT_FALSE(PnaclCanOpenFile(non_ascii, &out_path)); + + // Check file length restriction. + EXPECT_FALSE(PnaclCanOpenFile("thisstringisactuallywaaaaaaaaaaaaaaaaaaaaaaaa" + "toolongwaytoolongwaaaaayyyyytoooooooooooooooo" + "looooooooong", + &out_path)); + + // Other bad files. + EXPECT_FALSE(PnaclCanOpenFile(std::string(), &out_path)); + EXPECT_FALSE(PnaclCanOpenFile(".", &out_path)); + EXPECT_FALSE(PnaclCanOpenFile("..", &out_path)); +#if defined(OS_WIN) + EXPECT_FALSE(PnaclCanOpenFile("..\\llc", &out_path)); + EXPECT_FALSE(PnaclCanOpenFile("%SystemRoot%", &out_path)); + EXPECT_FALSE(PnaclCanOpenFile("%SystemRoot%\\explorer.exe", &out_path)); +#else + EXPECT_FALSE(PnaclCanOpenFile("../llc", &out_path)); + EXPECT_FALSE(PnaclCanOpenFile("/bin/sh", &out_path)); + EXPECT_FALSE(PnaclCanOpenFile("$HOME", &out_path)); + EXPECT_FALSE(PnaclCanOpenFile("$HOME/.bashrc", &out_path)); +#endif +} diff --git a/components/nacl/browser/nacl_host_message_filter.cc b/components/nacl/browser/nacl_host_message_filter.cc new file mode 100644 index 0000000..1334ffa --- /dev/null +++ b/components/nacl/browser/nacl_host_message_filter.cc @@ -0,0 +1,177 @@ +// 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 "components/nacl/browser/nacl_host_message_filter.h" + +#include "components/nacl/browser/nacl_browser.h" +#include "components/nacl/browser/nacl_file_host.h" +#include "components/nacl/browser/nacl_process_host.h" +#include "components/nacl/browser/pnacl_host.h" +#include "components/nacl/common/nacl_host_messages.h" +#include "ipc/ipc_platform_file.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" +#include "url/gurl.h" + +namespace nacl { + +NaClHostMessageFilter::NaClHostMessageFilter( + int render_process_id, + bool is_off_the_record, + const base::FilePath& profile_directory, + net::URLRequestContextGetter* request_context) + : render_process_id_(render_process_id), + off_the_record_(is_off_the_record), + profile_directory_(profile_directory), + request_context_(request_context), + weak_ptr_factory_(this) { +} + +NaClHostMessageFilter::~NaClHostMessageFilter() { +} + +void NaClHostMessageFilter::OnChannelClosing() { + pnacl::PnaclHost::GetInstance()->RendererClosing(render_process_id_); +} + +bool NaClHostMessageFilter::OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(NaClHostMessageFilter, message, *message_was_ok) +#if !defined(DISABLE_NACL) + IPC_MESSAGE_HANDLER_DELAY_REPLY(NaClHostMsg_LaunchNaCl, OnLaunchNaCl) + IPC_MESSAGE_HANDLER_DELAY_REPLY(NaClHostMsg_GetReadonlyPnaclFD, + OnGetReadonlyPnaclFd) + IPC_MESSAGE_HANDLER_DELAY_REPLY(NaClHostMsg_NaClCreateTemporaryFile, + OnNaClCreateTemporaryFile) + IPC_MESSAGE_HANDLER(NaClHostMsg_NexeTempFileRequest, + OnGetNexeFd) + IPC_MESSAGE_HANDLER(NaClHostMsg_ReportTranslationFinished, + OnTranslationFinished) + IPC_MESSAGE_HANDLER(NaClHostMsg_NaClErrorStatus, OnNaClErrorStatus) + IPC_MESSAGE_HANDLER_DELAY_REPLY(NaClHostMsg_OpenNaClExecutable, + OnOpenNaClExecutable) +#endif + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + + return handled; +} + +net::HostResolver* NaClHostMessageFilter::GetHostResolver() { + return request_context_->GetURLRequestContext()->host_resolver(); +} + +#if !defined(DISABLE_NACL) +void NaClHostMessageFilter::OnLaunchNaCl( + const nacl::NaClLaunchParams& launch_params, + IPC::Message* reply_msg) { + NaClProcessHost* host = new NaClProcessHost( + GURL(launch_params.manifest_url), + launch_params.render_view_id, + launch_params.permission_bits, + launch_params.uses_irt, + launch_params.enable_dyncode_syscalls, + launch_params.enable_exception_handling, + launch_params.enable_crash_throttling, + off_the_record_, + profile_directory_); + GURL manifest_url(launch_params.manifest_url); + base::FilePath manifest_path; + // We're calling MapUrlToLocalFilePath with the non-blocking API + // because we're running in the I/O thread. Ideally we'd use the other path, + // which would cover more cases. + nacl::NaClBrowser::GetDelegate()->MapUrlToLocalFilePath( + manifest_url, false /* use_blocking_api */, &manifest_path); + host->Launch(this, reply_msg, manifest_path); +} + +void NaClHostMessageFilter::OnGetReadonlyPnaclFd( + const std::string& filename, IPC::Message* reply_msg) { + // This posts a task to another thread, but the renderer will + // block until the reply is sent. + nacl_file_host::GetReadonlyPnaclFd(this, filename, reply_msg); + + // This is the first message we receive from the renderer once it knows we + // want to use PNaCl, so start the translation cache initialization here. + pnacl::PnaclHost::GetInstance()->Init(); +} + +// Return the temporary file via a reply to the +// NaClHostMsg_NaClCreateTemporaryFile sync message. +void NaClHostMessageFilter::SyncReturnTemporaryFile( + IPC::Message* reply_msg, + base::PlatformFile fd) { + if (fd == base::kInvalidPlatformFileValue) { + reply_msg->set_reply_error(); + } else { + NaClHostMsg_NaClCreateTemporaryFile::WriteReplyParams( + reply_msg, + IPC::GetFileHandleForProcess(fd, PeerHandle(), true)); + } + Send(reply_msg); +} + +void NaClHostMessageFilter::OnNaClCreateTemporaryFile( + IPC::Message* reply_msg) { + pnacl::PnaclHost::GetInstance()->CreateTemporaryFile( + base::Bind(&NaClHostMessageFilter::SyncReturnTemporaryFile, + this, + reply_msg)); +} + +void NaClHostMessageFilter::AsyncReturnTemporaryFile( + int pp_instance, + base::PlatformFile fd, + bool is_hit) { + Send(new NaClViewMsg_NexeTempFileReply( + pp_instance, + is_hit, + // Don't close our copy of the handle, because PnaclHost will use it + // when the translation finishes. + IPC::GetFileHandleForProcess(fd, PeerHandle(), false))); +} + +void NaClHostMessageFilter::OnGetNexeFd( + int render_view_id, + int pp_instance, + const nacl::PnaclCacheInfo& cache_info) { + if (!cache_info.pexe_url.is_valid()) { + LOG(ERROR) << "Bad URL received from GetNexeFd: " << + cache_info.pexe_url.possibly_invalid_spec(); + BadMessageReceived(); + return; + } + + pnacl::PnaclHost::GetInstance()->GetNexeFd( + render_process_id_, + render_view_id, + pp_instance, + off_the_record_, + cache_info, + base::Bind(&NaClHostMessageFilter::AsyncReturnTemporaryFile, + this, + pp_instance)); +} + +void NaClHostMessageFilter::OnTranslationFinished(int instance, bool success) { + pnacl::PnaclHost::GetInstance()->TranslationFinished( + render_process_id_, instance, success); +} + +void NaClHostMessageFilter::OnNaClErrorStatus(int render_view_id, + int error_id) { + nacl::NaClBrowser::GetDelegate()->ShowNaClInfobar(render_process_id_, + render_view_id, error_id); +} + +void NaClHostMessageFilter::OnOpenNaClExecutable(int render_view_id, + const GURL& file_url, + IPC::Message* reply_msg) { + nacl_file_host::OpenNaClExecutable(this, render_view_id, file_url, + reply_msg); +} +#endif + +} // namespace nacl diff --git a/components/nacl/browser/nacl_host_message_filter.h b/components/nacl/browser/nacl_host_message_filter.h new file mode 100644 index 0000000..01d610f --- /dev/null +++ b/components/nacl/browser/nacl_host_message_filter.h @@ -0,0 +1,86 @@ +// 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. + +#ifndef COMPONENTS_NACL_BROWSER_NACL_HOST_MESSAGE_FILTER_H_ +#define COMPONENTS_NACL_BROWSER_NACL_HOST_MESSAGE_FILTER_H_ + +#include "base/files/file_path.h" +#include "base/memory/weak_ptr.h" +#include "base/platform_file.h" +#include "content/public/browser/browser_message_filter.h" + +class GURL; + +namespace nacl { +struct NaClLaunchParams; +struct PnaclCacheInfo; +} + +namespace net { +class HostResolver; +class URLRequestContextGetter; +} + +namespace nacl { + +// This class filters out incoming Chrome-specific IPC messages for the renderer +// process on the IPC thread. +class NaClHostMessageFilter : public content::BrowserMessageFilter { + public: + NaClHostMessageFilter(int render_process_id, + bool is_off_the_record, + const base::FilePath& profile_directory, + net::URLRequestContextGetter* request_context); + + // content::BrowserMessageFilter methods: + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) OVERRIDE; + virtual void OnChannelClosing() OVERRIDE; + + int render_process_id() { return render_process_id_; } + bool off_the_record() { return off_the_record_; } + net::HostResolver* GetHostResolver(); + + private: + friend class content::BrowserThread; + friend class base::DeleteHelper<NaClHostMessageFilter>; + + virtual ~NaClHostMessageFilter(); + +#if !defined(DISABLE_NACL) + void OnLaunchNaCl(const NaClLaunchParams& launch_params, + IPC::Message* reply_msg); + void OnGetReadonlyPnaclFd(const std::string& filename, + IPC::Message* reply_msg); + void OnNaClCreateTemporaryFile(IPC::Message* reply_msg); + void OnGetNexeFd(int render_view_id, + int pp_instance, + const PnaclCacheInfo& cache_info); + void OnTranslationFinished(int instance, bool success); + void OnNaClErrorStatus(int render_view_id, int error_id); + void OnOpenNaClExecutable(int render_view_id, + const GURL& file_url, + IPC::Message* reply_msg); + void SyncReturnTemporaryFile(IPC::Message* reply_msg, + base::PlatformFile fd); + void AsyncReturnTemporaryFile(int pp_instance, + base::PlatformFile fd, + bool is_hit); +#endif + int render_process_id_; + + // off_the_record_ is copied from the profile partly so that it can be + // read on the IO thread. + bool off_the_record_; + base::FilePath profile_directory_; + scoped_refptr<net::URLRequestContextGetter> request_context_; + + base::WeakPtrFactory<NaClHostMessageFilter> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(NaClHostMessageFilter); +}; + +} // namespace nacl + +#endif // COMPONENTS_NACL_BROWSER_NACL_HOST_MESSAGE_FILTER_H_ diff --git a/components/nacl/browser/nacl_process_host.cc b/components/nacl/browser/nacl_process_host.cc new file mode 100644 index 0000000..565a0d5 --- /dev/null +++ b/components/nacl/browser/nacl_process_host.cc @@ -0,0 +1,1026 @@ +// Copyright (c) 2012 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 "components/nacl/browser/nacl_process_host.h" + +#include <algorithm> +#include <string> +#include <vector> + +#include "base/base_switches.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/path_service.h" +#include "base/process/launch.h" +#include "base/process/process_iterator.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/windows_version.h" +#include "build/build_config.h" +#include "components/nacl/browser/nacl_browser.h" +#include "components/nacl/browser/nacl_host_message_filter.h" +#include "components/nacl/common/nacl_cmd_line.h" +#include "components/nacl/common/nacl_host_messages.h" +#include "components/nacl/common/nacl_messages.h" +#include "components/nacl/common/nacl_process_type.h" +#include "components/nacl/common/nacl_switches.h" +#include "content/public/browser/browser_child_process_host.h" +#include "content/public/browser/browser_ppapi_host.h" +#include "content/public/browser/child_process_data.h" +#include "content/public/common/child_process_host.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/process_type.h" +#include "ipc/ipc_channel.h" +#include "ipc/ipc_switches.h" +#include "native_client/src/shared/imc/nacl_imc_c.h" +#include "net/base/net_util.h" +#include "net/socket/tcp_listen_socket.h" +#include "ppapi/host/host_factory.h" +#include "ppapi/host/ppapi_host.h" +#include "ppapi/proxy/ppapi_messages.h" +#include "ppapi/shared_impl/ppapi_nacl_channel_args.h" + +#if defined(OS_POSIX) +#include <fcntl.h> + +#include "ipc/ipc_channel_posix.h" +#elif defined(OS_WIN) +#include <windows.h> + +#include "base/threading/thread.h" +#include "base/win/scoped_handle.h" +#include "components/nacl/browser/nacl_broker_service_win.h" +#include "components/nacl/common/nacl_debug_exception_handler_win.h" +#include "content/public/common/sandbox_init.h" +#include "content/public/common/sandboxed_process_launcher_delegate.h" +#endif + +using content::BrowserThread; +using content::ChildProcessData; +using content::ChildProcessHost; +using ppapi::proxy::SerializedHandle; + +#if defined(OS_WIN) + +namespace { + +// Looks for the largest contiguous unallocated region of address +// space and returns it via |*out_addr| and |*out_size|. +void FindAddressSpace(base::ProcessHandle process, + char** out_addr, size_t* out_size) { + *out_addr = NULL; + *out_size = 0; + char* addr = 0; + while (true) { + MEMORY_BASIC_INFORMATION info; + size_t result = VirtualQueryEx(process, static_cast<void*>(addr), + &info, sizeof(info)); + if (result < sizeof(info)) + break; + if (info.State == MEM_FREE && info.RegionSize > *out_size) { + *out_addr = addr; + *out_size = info.RegionSize; + } + addr += info.RegionSize; + } +} + +} // namespace + +namespace nacl { + +// Allocates |size| bytes of address space in the given process at a +// randomised address. +void* AllocateAddressSpaceASLR(base::ProcessHandle process, size_t size) { + char* addr; + size_t avail_size; + FindAddressSpace(process, &addr, &avail_size); + if (avail_size < size) + return NULL; + size_t offset = base::RandGenerator(avail_size - size); + const int kPageSize = 0x10000; + void* request_addr = + reinterpret_cast<void*>(reinterpret_cast<uint64>(addr + offset) + & ~(kPageSize - 1)); + return VirtualAllocEx(process, request_addr, size, + MEM_RESERVE, PAGE_NOACCESS); +} + +} // namespace nacl + +#endif // defined(OS_WIN) + +namespace { + +#if defined(OS_WIN) +bool RunningOnWOW64() { + return (base::win::OSInfo::GetInstance()->wow64_status() == + base::win::OSInfo::WOW64_ENABLED); +} + +// NOTE: changes to this class need to be reviewed by the security team. +class NaClSandboxedProcessLauncherDelegate + : public content::SandboxedProcessLauncherDelegate { + public: + NaClSandboxedProcessLauncherDelegate() {} + virtual ~NaClSandboxedProcessLauncherDelegate() {} + + virtual void PostSpawnTarget(base::ProcessHandle process) { + // For Native Client sel_ldr processes on 32-bit Windows, reserve 1 GB of + // address space to prevent later failure due to address space fragmentation + // from .dll loading. The NaCl process will attempt to locate this space by + // scanning the address space using VirtualQuery. + // TODO(bbudge) Handle the --no-sandbox case. + // http://code.google.com/p/nativeclient/issues/detail?id=2131 + const SIZE_T kNaClSandboxSize = 1 << 30; + if (!nacl::AllocateAddressSpaceASLR(process, kNaClSandboxSize)) { + DLOG(WARNING) << "Failed to reserve address space for Native Client"; + } + } +}; + +#endif // OS_WIN + +void SetCloseOnExec(NaClHandle fd) { +#if defined(OS_POSIX) + int flags = fcntl(fd, F_GETFD); + CHECK_NE(flags, -1); + int rc = fcntl(fd, F_SETFD, flags | FD_CLOEXEC); + CHECK_EQ(rc, 0); +#endif +} + +bool ShareHandleToSelLdr( + base::ProcessHandle processh, + NaClHandle sourceh, + bool close_source, + std::vector<nacl::FileDescriptor> *handles_for_sel_ldr) { +#if defined(OS_WIN) + HANDLE channel; + int flags = DUPLICATE_SAME_ACCESS; + if (close_source) + flags |= DUPLICATE_CLOSE_SOURCE; + if (!DuplicateHandle(GetCurrentProcess(), + reinterpret_cast<HANDLE>(sourceh), + processh, + &channel, + 0, // Unused given DUPLICATE_SAME_ACCESS. + FALSE, + flags)) { + LOG(ERROR) << "DuplicateHandle() failed"; + return false; + } + handles_for_sel_ldr->push_back( + reinterpret_cast<nacl::FileDescriptor>(channel)); +#else + nacl::FileDescriptor channel; + channel.fd = sourceh; + channel.auto_close = close_source; + handles_for_sel_ldr->push_back(channel); +#endif + return true; +} + +ppapi::PpapiPermissions GetNaClPermissions(uint32 permission_bits) { + // Only allow NaCl plugins to request certain permissions. We don't want + // a compromised renderer to be able to start a nacl plugin with e.g. Flash + // permissions which may expand the surface area of the sandbox. + uint32 masked_bits = permission_bits & ppapi::PERMISSION_DEV; + return ppapi::PpapiPermissions::GetForCommandLine(masked_bits); +} + +} // namespace + +namespace nacl { + +struct NaClProcessHost::NaClInternal { + NaClHandle socket_for_renderer; + NaClHandle socket_for_sel_ldr; + + NaClInternal() + : socket_for_renderer(NACL_INVALID_HANDLE), + socket_for_sel_ldr(NACL_INVALID_HANDLE) { } +}; + +// ----------------------------------------------------------------------------- + +NaClProcessHost::PluginListener::PluginListener(NaClProcessHost* host) + : host_(host) { +} + +bool NaClProcessHost::PluginListener::OnMessageReceived( + const IPC::Message& msg) { + return host_->OnUntrustedMessageForwarded(msg); +} + +NaClProcessHost::NaClProcessHost(const GURL& manifest_url, + int render_view_id, + uint32 permission_bits, + bool uses_irt, + bool enable_dyncode_syscalls, + bool enable_exception_handling, + bool enable_crash_throttling, + bool off_the_record, + const base::FilePath& profile_directory) + : manifest_url_(manifest_url), + permissions_(GetNaClPermissions(permission_bits)), +#if defined(OS_WIN) + process_launched_by_broker_(false), +#endif + reply_msg_(NULL), +#if defined(OS_WIN) + debug_exception_handler_requested_(false), +#endif + internal_(new NaClInternal()), + weak_factory_(this), + uses_irt_(uses_irt), + enable_debug_stub_(false), + enable_dyncode_syscalls_(enable_dyncode_syscalls), + enable_exception_handling_(enable_exception_handling), + enable_crash_throttling_(enable_crash_throttling), + off_the_record_(off_the_record), + profile_directory_(profile_directory), + ipc_plugin_listener_(this), + render_view_id_(render_view_id) { + process_.reset(content::BrowserChildProcessHost::Create( + PROCESS_TYPE_NACL_LOADER, this)); + + // Set the display name so the user knows what plugin the process is running. + // We aren't on the UI thread so getting the pref locale for language + // formatting isn't possible, so IDN will be lost, but this is probably OK + // for this use case. + process_->SetName(net::FormatUrl(manifest_url_, std::string())); + + enable_debug_stub_ = CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableNaClDebug); +} + +NaClProcessHost::~NaClProcessHost() { + // Report exit status only if the process was successfully started. + if (process_->GetData().handle != base::kNullProcessHandle) { + int exit_code = 0; + process_->GetTerminationStatus(false /* known_dead */, &exit_code); + std::string message = + base::StringPrintf("NaCl process exited with status %i (0x%x)", + exit_code, exit_code); + if (exit_code == 0) { + VLOG(1) << message; + } else { + LOG(ERROR) << message; + } + } + + if (internal_->socket_for_renderer != NACL_INVALID_HANDLE) { + if (NaClClose(internal_->socket_for_renderer) != 0) { + NOTREACHED() << "NaClClose() failed"; + } + } + + if (internal_->socket_for_sel_ldr != NACL_INVALID_HANDLE) { + if (NaClClose(internal_->socket_for_sel_ldr) != 0) { + NOTREACHED() << "NaClClose() failed"; + } + } + + if (reply_msg_) { + // The process failed to launch for some reason. + // Don't keep the renderer hanging. + reply_msg_->set_reply_error(); + nacl_host_message_filter_->Send(reply_msg_); + } +#if defined(OS_WIN) + if (process_launched_by_broker_) { + NaClBrokerService::GetInstance()->OnLoaderDied(); + } +#endif +} + +void NaClProcessHost::OnProcessCrashed(int exit_status) { + if (enable_crash_throttling_ && + !CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisablePnaclCrashThrottling)) { + NaClBrowser::GetInstance()->OnProcessCrashed(); + } +} + +// This is called at browser startup. +// static +void NaClProcessHost::EarlyStartup() { + NaClBrowser::GetInstance()->EarlyStartup(); +#if defined(OS_LINUX) && !defined(OS_CHROMEOS) + // Open the IRT file early to make sure that it isn't replaced out from + // under us by autoupdate. + NaClBrowser::GetInstance()->EnsureIrtAvailable(); +#endif + CommandLine* cmd = CommandLine::ForCurrentProcess(); + UMA_HISTOGRAM_BOOLEAN( + "NaCl.nacl-gdb", + !cmd->GetSwitchValuePath(switches::kNaClGdb).empty()); + UMA_HISTOGRAM_BOOLEAN( + "NaCl.nacl-gdb-script", + !cmd->GetSwitchValuePath(switches::kNaClGdbScript).empty()); + UMA_HISTOGRAM_BOOLEAN( + "NaCl.enable-nacl-debug", + cmd->HasSwitch(switches::kEnableNaClDebug)); + NaClBrowser::GetDelegate()->SetDebugPatterns( + cmd->GetSwitchValueASCII(switches::kNaClDebugMask)); +} + +void NaClProcessHost::Launch( + NaClHostMessageFilter* nacl_host_message_filter, + IPC::Message* reply_msg, + const base::FilePath& manifest_path) { + nacl_host_message_filter_ = nacl_host_message_filter; + reply_msg_ = reply_msg; + manifest_path_ = manifest_path; + + // Do not launch the requested NaCl module if NaCl is marked "unstable" due + // to too many crashes within a given time period. + if (enable_crash_throttling_ && + !CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisablePnaclCrashThrottling) && + NaClBrowser::GetInstance()->IsThrottled()) { + SendErrorToRenderer("Process creation was throttled due to excessive" + " crashes"); + delete this; + return; + } + + const CommandLine* cmd = CommandLine::ForCurrentProcess(); +#if defined(OS_WIN) + if (cmd->HasSwitch(switches::kEnableNaClDebug) && + !cmd->HasSwitch(switches::kNoSandbox)) { + // We don't switch off sandbox automatically for security reasons. + SendErrorToRenderer("NaCl's GDB debug stub requires --no-sandbox flag" + " on Windows. See crbug.com/265624."); + delete this; + return; + } +#endif + if (cmd->HasSwitch(switches::kNaClGdb) && + !cmd->HasSwitch(switches::kEnableNaClDebug)) { + LOG(WARNING) << "--nacl-gdb flag requires --enable-nacl-debug flag"; + } + + // Start getting the IRT open asynchronously while we launch the NaCl process. + // We'll make sure this actually finished in StartWithLaunchedProcess, below. + NaClBrowser* nacl_browser = NaClBrowser::GetInstance(); + nacl_browser->EnsureAllResourcesAvailable(); + if (!nacl_browser->IsOk()) { + SendErrorToRenderer("could not find all the resources needed" + " to launch the process"); + delete this; + return; + } + + // Rather than creating a socket pair in the renderer, and passing + // one side through the browser to sel_ldr, socket pairs are created + // in the browser and then passed to the renderer and sel_ldr. + // + // This is mainly for the benefit of Windows, where sockets cannot + // be passed in messages, but are copied via DuplicateHandle(). + // This means the sandboxed renderer cannot send handles to the + // browser process. + + NaClHandle pair[2]; + // Create a connected socket + if (NaClSocketPair(pair) == -1) { + SendErrorToRenderer("NaClSocketPair() failed"); + delete this; + return; + } + internal_->socket_for_renderer = pair[0]; + internal_->socket_for_sel_ldr = pair[1]; + SetCloseOnExec(pair[0]); + SetCloseOnExec(pair[1]); + + // Launch the process + if (!LaunchSelLdr()) { + delete this; + } +} + +void NaClProcessHost::OnChannelConnected(int32 peer_pid) { + if (!CommandLine::ForCurrentProcess()->GetSwitchValuePath( + switches::kNaClGdb).empty()) { + LaunchNaClGdb(); + } +} + +#if defined(OS_WIN) +void NaClProcessHost::OnProcessLaunchedByBroker(base::ProcessHandle handle) { + process_launched_by_broker_ = true; + process_->SetHandle(handle); + if (!StartWithLaunchedProcess()) + delete this; +} + +void NaClProcessHost::OnDebugExceptionHandlerLaunchedByBroker(bool success) { + IPC::Message* reply = attach_debug_exception_handler_reply_msg_.release(); + NaClProcessMsg_AttachDebugExceptionHandler::WriteReplyParams(reply, success); + Send(reply); +} +#endif + +// Needed to handle sync messages in OnMessageRecieved. +bool NaClProcessHost::Send(IPC::Message* msg) { + return process_->Send(msg); +} + +bool NaClProcessHost::LaunchNaClGdb() { +#if defined(OS_WIN) + base::FilePath nacl_gdb = + CommandLine::ForCurrentProcess()->GetSwitchValuePath(switches::kNaClGdb); + CommandLine cmd_line(nacl_gdb); +#else + CommandLine::StringType nacl_gdb = + CommandLine::ForCurrentProcess()->GetSwitchValueNative( + switches::kNaClGdb); + CommandLine::StringVector argv; + // We don't support spaces inside arguments in --nacl-gdb switch. + base::SplitString(nacl_gdb, static_cast<CommandLine::CharType>(' '), &argv); + CommandLine cmd_line(argv); +#endif + cmd_line.AppendArg("--eval-command"); + base::FilePath::StringType irt_path( + NaClBrowser::GetInstance()->GetIrtFilePath().value()); + // Avoid back slashes because nacl-gdb uses posix escaping rules on Windows. + // See issue https://code.google.com/p/nativeclient/issues/detail?id=3482. + std::replace(irt_path.begin(), irt_path.end(), '\\', '/'); + cmd_line.AppendArgNative(FILE_PATH_LITERAL("nacl-irt \"") + irt_path + + FILE_PATH_LITERAL("\"")); + if (!manifest_path_.empty()) { + cmd_line.AppendArg("--eval-command"); + base::FilePath::StringType manifest_path_value(manifest_path_.value()); + std::replace(manifest_path_value.begin(), manifest_path_value.end(), + '\\', '/'); + cmd_line.AppendArgNative(FILE_PATH_LITERAL("nacl-manifest \"") + + manifest_path_value + FILE_PATH_LITERAL("\"")); + } + cmd_line.AppendArg("--eval-command"); + cmd_line.AppendArg("target remote :4014"); + base::FilePath script = CommandLine::ForCurrentProcess()->GetSwitchValuePath( + switches::kNaClGdbScript); + if (!script.empty()) { + cmd_line.AppendArg("--command"); + cmd_line.AppendArgNative(script.value()); + } + return base::LaunchProcess(cmd_line, base::LaunchOptions(), NULL); +} + +bool NaClProcessHost::LaunchSelLdr() { + std::string channel_id = process_->GetHost()->CreateChannel(); + if (channel_id.empty()) { + SendErrorToRenderer("CreateChannel() failed"); + return false; + } + + CommandLine::StringType nacl_loader_prefix; +#if defined(OS_POSIX) + nacl_loader_prefix = CommandLine::ForCurrentProcess()->GetSwitchValueNative( + switches::kNaClLoaderCmdPrefix); +#endif // defined(OS_POSIX) + + // Build command line for nacl. + +#if defined(OS_MACOSX) + // The Native Client process needs to be able to allocate a 1GB contiguous + // region to use as the client environment's virtual address space. ASLR + // (PIE) interferes with this by making it possible that no gap large enough + // to accomodate this request will exist in the child process' address + // space. Disable PIE for NaCl processes. See http://crbug.com/90221 and + // http://code.google.com/p/nativeclient/issues/detail?id=2043. + int flags = ChildProcessHost::CHILD_NO_PIE; +#elif defined(OS_LINUX) + int flags = nacl_loader_prefix.empty() ? ChildProcessHost::CHILD_ALLOW_SELF : + ChildProcessHost::CHILD_NORMAL; +#else + int flags = ChildProcessHost::CHILD_NORMAL; +#endif + + base::FilePath exe_path = ChildProcessHost::GetChildPath(flags); + if (exe_path.empty()) + return false; + +#if defined(OS_WIN) + // On Windows 64-bit NaCl loader is called nacl64.exe instead of chrome.exe + if (RunningOnWOW64()) { + if (!NaClBrowser::GetInstance()->GetNaCl64ExePath(&exe_path)) { + SendErrorToRenderer("could not get path to nacl64.exe"); + return false; + } + } +#endif + + scoped_ptr<CommandLine> cmd_line(new CommandLine(exe_path)); + CopyNaClCommandLineArguments(cmd_line.get()); + + cmd_line->AppendSwitchASCII(switches::kProcessType, + switches::kNaClLoaderProcess); + cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id); + if (NaClBrowser::GetDelegate()->DialogsAreSuppressed()) + cmd_line->AppendSwitch(switches::kNoErrorDialogs); + + if (!nacl_loader_prefix.empty()) + cmd_line->PrependWrapper(nacl_loader_prefix); + + // On Windows we might need to start the broker process to launch a new loader +#if defined(OS_WIN) + if (RunningOnWOW64()) { + if (!NaClBrokerService::GetInstance()->LaunchLoader( + weak_factory_.GetWeakPtr(), channel_id)) { + SendErrorToRenderer("broker service did not launch process"); + return false; + } + } else { + process_->Launch(new NaClSandboxedProcessLauncherDelegate, + cmd_line.release()); + } +#elif defined(OS_POSIX) + process_->Launch(nacl_loader_prefix.empty(), // use_zygote + base::EnvironmentMap(), + cmd_line.release()); +#endif + + return true; +} + +bool NaClProcessHost::OnMessageReceived(const IPC::Message& msg) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(NaClProcessHost, msg) + IPC_MESSAGE_HANDLER(NaClProcessMsg_QueryKnownToValidate, + OnQueryKnownToValidate) + IPC_MESSAGE_HANDLER(NaClProcessMsg_SetKnownToValidate, + OnSetKnownToValidate) + IPC_MESSAGE_HANDLER_DELAY_REPLY(NaClProcessMsg_ResolveFileToken, + OnResolveFileToken) +#if defined(OS_WIN) + IPC_MESSAGE_HANDLER_DELAY_REPLY(NaClProcessMsg_AttachDebugExceptionHandler, + OnAttachDebugExceptionHandler) +#endif + IPC_MESSAGE_HANDLER(NaClProcessHostMsg_PpapiChannelCreated, + OnPpapiChannelCreated) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void NaClProcessHost::OnProcessLaunched() { + if (!StartWithLaunchedProcess()) + delete this; +} + +// Called when the NaClBrowser singleton has been fully initialized. +void NaClProcessHost::OnResourcesReady() { + NaClBrowser* nacl_browser = NaClBrowser::GetInstance(); + if (!nacl_browser->IsReady()) { + SendErrorToRenderer("could not acquire shared resources needed by NaCl"); + delete this; + } else if (!SendStart()) { + delete this; + } +} + +bool NaClProcessHost::ReplyToRenderer( + const IPC::ChannelHandle& channel_handle) { +#if defined(OS_WIN) + // If we are on 64-bit Windows, the NaCl process's sandbox is + // managed by a different process from the renderer's sandbox. We + // need to inform the renderer's sandbox about the NaCl process so + // that the renderer can send handles to the NaCl process using + // BrokerDuplicateHandle(). + if (RunningOnWOW64()) { + if (!content::BrokerAddTargetPeer(process_->GetData().handle)) { + SendErrorToRenderer("BrokerAddTargetPeer() failed"); + return false; + } + } +#endif + + FileDescriptor handle_for_renderer; +#if defined(OS_WIN) + // Copy the handle into the renderer process. + HANDLE handle_in_renderer; + if (!DuplicateHandle(base::GetCurrentProcessHandle(), + reinterpret_cast<HANDLE>( + internal_->socket_for_renderer), + nacl_host_message_filter_->PeerHandle(), + &handle_in_renderer, + 0, // Unused given DUPLICATE_SAME_ACCESS. + FALSE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + SendErrorToRenderer("DuplicateHandle() failed"); + return false; + } + handle_for_renderer = reinterpret_cast<FileDescriptor>( + handle_in_renderer); +#else + // No need to dup the imc_handle - we don't pass it anywhere else so + // it cannot be closed. + FileDescriptor imc_handle; + imc_handle.fd = internal_->socket_for_renderer; + imc_handle.auto_close = true; + handle_for_renderer = imc_handle; +#endif + + const ChildProcessData& data = process_->GetData(); + SendMessageToRenderer( + NaClLaunchResult(handle_for_renderer, + channel_handle, + base::GetProcId(data.handle), + data.id), + std::string() /* error_message */); + internal_->socket_for_renderer = NACL_INVALID_HANDLE; + return true; +} + +void NaClProcessHost::SendErrorToRenderer(const std::string& error_message) { + LOG(ERROR) << "NaCl process launch failed: " << error_message; + SendMessageToRenderer(NaClLaunchResult(), error_message); +} + +void NaClProcessHost::SendMessageToRenderer( + const NaClLaunchResult& result, + const std::string& error_message) { + DCHECK(nacl_host_message_filter_); + DCHECK(reply_msg_); + if (nacl_host_message_filter_ != NULL && reply_msg_ != NULL) { + NaClHostMsg_LaunchNaCl::WriteReplyParams( + reply_msg_, result, error_message); + nacl_host_message_filter_->Send(reply_msg_); + nacl_host_message_filter_ = NULL; + reply_msg_ = NULL; + } +} + +// TCP port we chose for NaCl debug stub. It can be any other number. +static const int kDebugStubPort = 4014; + +#if defined(OS_POSIX) +net::SocketDescriptor NaClProcessHost::GetDebugStubSocketHandle() { + NaClBrowser* nacl_browser = NaClBrowser::GetInstance(); + net::SocketDescriptor s = net::kInvalidSocket; + // We allocate currently unused TCP port for debug stub tests. The port + // number is passed to the test via debug stub port listener. + if (nacl_browser->HasGdbDebugStubPortListener()) { + int port; + s = net::TCPListenSocket::CreateAndBindAnyPort("127.0.0.1", &port); + if (s != net::kInvalidSocket) { + nacl_browser->FireGdbDebugStubPortOpened(port); + } + } else { + s = net::TCPListenSocket::CreateAndBind("127.0.0.1", kDebugStubPort); + } + if (s == net::kInvalidSocket) { + LOG(ERROR) << "failed to open socket for debug stub"; + return net::kInvalidSocket; + } + if (listen(s, 1)) { + LOG(ERROR) << "listen() failed on debug stub socket"; + if (HANDLE_EINTR(close(s)) < 0) + PLOG(ERROR) << "failed to close debug stub socket"; + return net::kInvalidSocket; + } + return s; +} +#endif + +bool NaClProcessHost::StartNaClExecution() { + NaClBrowser* nacl_browser = NaClBrowser::GetInstance(); + + NaClStartParams params; + params.validation_cache_enabled = nacl_browser->ValidationCacheIsEnabled(); + params.validation_cache_key = nacl_browser->GetValidationCacheKey(); + params.version = NaClBrowser::GetDelegate()->GetVersionString(); + params.enable_exception_handling = enable_exception_handling_; + params.enable_debug_stub = enable_debug_stub_ && + NaClBrowser::GetDelegate()->URLMatchesDebugPatterns(manifest_url_); + // Enable PPAPI proxy channel creation only for renderer processes. + params.enable_ipc_proxy = enable_ppapi_proxy(); + params.uses_irt = uses_irt_; + params.enable_dyncode_syscalls = enable_dyncode_syscalls_; + + const ChildProcessData& data = process_->GetData(); + if (!ShareHandleToSelLdr(data.handle, + internal_->socket_for_sel_ldr, true, + ¶ms.handles)) { + return false; + } + + if (params.uses_irt) { + base::PlatformFile irt_file = nacl_browser->IrtFile(); + CHECK_NE(irt_file, base::kInvalidPlatformFileValue); + // Send over the IRT file handle. We don't close our own copy! + if (!ShareHandleToSelLdr(data.handle, irt_file, false, ¶ms.handles)) + return false; + } + +#if defined(OS_MACOSX) + // For dynamic loading support, NaCl requires a file descriptor that + // was created in /tmp, since those created with shm_open() are not + // mappable with PROT_EXEC. Rather than requiring an extra IPC + // round trip out of the sandbox, we create an FD here. + base::SharedMemory memory_buffer; + base::SharedMemoryCreateOptions options; + options.size = 1; + options.executable = true; + if (!memory_buffer.Create(options)) { + DLOG(ERROR) << "Failed to allocate memory buffer"; + return false; + } + FileDescriptor memory_fd; + memory_fd.fd = dup(memory_buffer.handle().fd); + if (memory_fd.fd < 0) { + DLOG(ERROR) << "Failed to dup() a file descriptor"; + return false; + } + memory_fd.auto_close = true; + params.handles.push_back(memory_fd); +#endif + +#if defined(OS_POSIX) + if (params.enable_debug_stub) { + net::SocketDescriptor server_bound_socket = GetDebugStubSocketHandle(); + if (server_bound_socket != net::kInvalidSocket) { + params.debug_stub_server_bound_socket = + FileDescriptor(server_bound_socket, true); + } + } +#endif + + process_->Send(new NaClProcessMsg_Start(params)); + + internal_->socket_for_sel_ldr = NACL_INVALID_HANDLE; + return true; +} + +bool NaClProcessHost::SendStart() { + if (!enable_ppapi_proxy()) { + if (!ReplyToRenderer(IPC::ChannelHandle())) + return false; + } + return StartNaClExecution(); +} + +// This method is called when NaClProcessHostMsg_PpapiChannelCreated is +// received or PpapiHostMsg_ChannelCreated is forwarded by our plugin +// listener. +void NaClProcessHost::OnPpapiChannelCreated( + const IPC::ChannelHandle& channel_handle) { + // Only renderer processes should create a channel. + DCHECK(enable_ppapi_proxy()); + // If the proxy channel is null, this must be the initial NaCl-Browser IPC + // channel. + if (!ipc_proxy_channel_.get()) { + DCHECK_EQ(PROCESS_TYPE_NACL_LOADER, process_->GetData().process_type); + + ipc_proxy_channel_.reset( + new IPC::ChannelProxy(channel_handle, + IPC::Channel::MODE_CLIENT, + &ipc_plugin_listener_, + base::MessageLoopProxy::current().get())); + // Create the browser ppapi host and enable PPAPI message dispatching to the + // browser process. + ppapi_host_.reset(content::BrowserPpapiHost::CreateExternalPluginProcess( + ipc_proxy_channel_.get(), // sender + permissions_, + process_->GetData().handle, + ipc_proxy_channel_.get(), + nacl_host_message_filter_->render_process_id(), + render_view_id_, + profile_directory_)); + + ppapi::PpapiNaClChannelArgs args; + args.off_the_record = nacl_host_message_filter_->off_the_record(); + args.permissions = permissions_; + CommandLine* cmdline = CommandLine::ForCurrentProcess(); + DCHECK(cmdline); + std::string flag_whitelist[] = {switches::kV, switches::kVModule}; + for (size_t i = 0; i < arraysize(flag_whitelist); ++i) { + std::string value = cmdline->GetSwitchValueASCII(flag_whitelist[i]); + if (!value.empty()) { + args.switch_names.push_back(flag_whitelist[i]); + args.switch_values.push_back(value); + } + } + + ppapi_host_->GetPpapiHost()->AddHostFactoryFilter( + scoped_ptr<ppapi::host::HostFactory>( + NaClBrowser::GetDelegate()->CreatePpapiHostFactory( + ppapi_host_.get()))); + + // Send a message to create the NaCl-Renderer channel. The handle is just + // a place holder. + ipc_proxy_channel_->Send( + new PpapiMsg_CreateNaClChannel( + nacl_host_message_filter_->render_process_id(), + args, + SerializedHandle(SerializedHandle::CHANNEL_HANDLE, + IPC::InvalidPlatformFileForTransit()))); + } else if (reply_msg_) { + // Otherwise, this must be a renderer channel. + ReplyToRenderer(channel_handle); + } else { + // Attempt to open more than 1 renderer channel is not supported. + // Shut down the NaCl process. + process_->GetHost()->ForceShutdown(); + } +} + +bool NaClProcessHost::OnUntrustedMessageForwarded(const IPC::Message& msg) { + // Handle messages that have been forwarded from our PluginListener. + // These messages come from untrusted code so should be handled with care. + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(NaClProcessHost, msg) + IPC_MESSAGE_HANDLER(PpapiHostMsg_ChannelCreated, + OnPpapiChannelCreated) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +bool NaClProcessHost::StartWithLaunchedProcess() { + NaClBrowser* nacl_browser = NaClBrowser::GetInstance(); + + if (nacl_browser->IsReady()) { + return SendStart(); + } else if (nacl_browser->IsOk()) { + nacl_browser->WaitForResources( + base::Bind(&NaClProcessHost::OnResourcesReady, + weak_factory_.GetWeakPtr())); + return true; + } else { + SendErrorToRenderer("previously failed to acquire shared resources"); + return false; + } +} + +void NaClProcessHost::OnQueryKnownToValidate(const std::string& signature, + bool* result) { + NaClBrowser* nacl_browser = NaClBrowser::GetInstance(); + *result = nacl_browser->QueryKnownToValidate(signature, off_the_record_); +} + +void NaClProcessHost::OnSetKnownToValidate(const std::string& signature) { + NaClBrowser::GetInstance()->SetKnownToValidate( + signature, off_the_record_); +} + +void NaClProcessHost::FileResolved( + base::PlatformFile* file, + const base::FilePath& file_path, + IPC::Message* reply_msg) { + if (*file != base::kInvalidPlatformFileValue) { + IPC::PlatformFileForTransit handle = IPC::GetFileHandleForProcess( + *file, + process_->GetData().handle, + true /* close_source */); + NaClProcessMsg_ResolveFileToken::WriteReplyParams( + reply_msg, + handle, + file_path); + } else { + NaClProcessMsg_ResolveFileToken::WriteReplyParams( + reply_msg, + IPC::InvalidPlatformFileForTransit(), + base::FilePath(FILE_PATH_LITERAL(""))); + } + Send(reply_msg); +} + +void NaClProcessHost::OnResolveFileToken(uint64 file_token_lo, + uint64 file_token_hi, + IPC::Message* reply_msg) { + // Was the file registered? + // + // Note that the file path cache is of bounded size, and old entries can get + // evicted. If a large number of NaCl modules are being launched at once, + // resolving the file_token may fail because the path cache was thrashed + // while the file_token was in flight. In this case the query fails, and we + // need to fall back to the slower path. + // + // However: each NaCl process will consume 2-3 entries as it starts up, this + // means that eviction will not happen unless you start up 33+ NaCl processes + // at the same time, and this still requires worst-case timing. As a + // practical matter, no entries should be evicted prematurely. + // The cache itself should take ~ (150 characters * 2 bytes/char + ~60 bytes + // data structure overhead) * 100 = 35k when full, so making it bigger should + // not be a problem, if needed. + // + // Each NaCl process will consume 2-3 entries because the manifest and main + // nexe are currently not resolved. Shared libraries will be resolved. They + // will be loaded sequentially, so they will only consume a single entry + // while the load is in flight. + // + // TODO(ncbray): track behavior with UMA. If entries are getting evicted or + // bogus keys are getting queried, this would be good to know. + base::FilePath file_path; + if (!NaClBrowser::GetInstance()->GetFilePath( + file_token_lo, file_token_hi, &file_path)) { + NaClProcessMsg_ResolveFileToken::WriteReplyParams( + reply_msg, + IPC::InvalidPlatformFileForTransit(), + base::FilePath(FILE_PATH_LITERAL(""))); + Send(reply_msg); + return; + } + + // Scratch space to share between the callbacks. + base::PlatformFile* data = new base::PlatformFile(); + + // Open the file. + if (!content::BrowserThread::PostBlockingPoolTaskAndReply( + FROM_HERE, + base::Bind(OpenNaClExecutableImpl, + file_path, data), + base::Bind(&NaClProcessHost::FileResolved, + weak_factory_.GetWeakPtr(), + base::Owned(data), + file_path, + reply_msg))) { + NaClProcessMsg_ResolveFileToken::WriteReplyParams( + reply_msg, + IPC::InvalidPlatformFileForTransit(), + base::FilePath(FILE_PATH_LITERAL(""))); + Send(reply_msg); + } +} + +#if defined(OS_WIN) +void NaClProcessHost::OnAttachDebugExceptionHandler(const std::string& info, + IPC::Message* reply_msg) { + if (!AttachDebugExceptionHandler(info, reply_msg)) { + // Send failure message. + NaClProcessMsg_AttachDebugExceptionHandler::WriteReplyParams(reply_msg, + false); + Send(reply_msg); + } +} + +bool NaClProcessHost::AttachDebugExceptionHandler(const std::string& info, + IPC::Message* reply_msg) { + if (!enable_exception_handling_ && !enable_debug_stub_) { + DLOG(ERROR) << + "Debug exception handler requested by NaCl process when not enabled"; + return false; + } + if (debug_exception_handler_requested_) { + // The NaCl process should not request this multiple times. + DLOG(ERROR) << "Multiple AttachDebugExceptionHandler requests received"; + return false; + } + debug_exception_handler_requested_ = true; + + base::ProcessId nacl_pid = base::GetProcId(process_->GetData().handle); + base::win::ScopedHandle process_handle; + // We cannot use process_->GetData().handle because it does not have + // the necessary access rights. We open the new handle here rather + // than in the NaCl broker process in case the NaCl loader process + // dies before the NaCl broker process receives the message we send. + // The debug exception handler uses DebugActiveProcess() to attach, + // but this takes a PID. We need to prevent the NaCl loader's PID + // from being reused before DebugActiveProcess() is called, and + // holding a process handle open achieves this. + if (!base::OpenProcessHandleWithAccess( + nacl_pid, + base::kProcessAccessQueryInformation | + base::kProcessAccessSuspendResume | + base::kProcessAccessTerminate | + base::kProcessAccessVMOperation | + base::kProcessAccessVMRead | + base::kProcessAccessVMWrite | + base::kProcessAccessDuplicateHandle | + base::kProcessAccessWaitForTermination, + process_handle.Receive())) { + LOG(ERROR) << "Failed to get process handle"; + return false; + } + + attach_debug_exception_handler_reply_msg_.reset(reply_msg); + // If the NaCl loader is 64-bit, the process running its debug + // exception handler must be 64-bit too, so we use the 64-bit NaCl + // broker process for this. Otherwise, on a 32-bit system, we use + // the 32-bit browser process to run the debug exception handler. + if (RunningOnWOW64()) { + return NaClBrokerService::GetInstance()->LaunchDebugExceptionHandler( + weak_factory_.GetWeakPtr(), nacl_pid, process_handle, info); + } else { + NaClStartDebugExceptionHandlerThread( + process_handle.Take(), info, + base::MessageLoopProxy::current(), + base::Bind(&NaClProcessHost::OnDebugExceptionHandlerLaunchedByBroker, + weak_factory_.GetWeakPtr())); + return true; + } +} +#endif + +} // namespace nacl diff --git a/components/nacl/browser/nacl_process_host.h b/components/nacl/browser/nacl_process_host.h new file mode 100644 index 0000000..135fb63 --- /dev/null +++ b/components/nacl/browser/nacl_process_host.h @@ -0,0 +1,241 @@ +// Copyright (c) 2012 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. + +#ifndef COMPONENTS_NACL_BROWSER_NACL_PROCESS_HOST_H_ +#define COMPONENTS_NACL_BROWSER_NACL_PROCESS_HOST_H_ + +#include "build/build_config.h" + +#include "base/files/file_path.h" +#include "base/files/file_util_proxy.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/process/process.h" +#include "components/nacl/common/nacl_types.h" +#include "content/public/browser/browser_child_process_host_delegate.h" +#include "content/public/browser/browser_child_process_host_iterator.h" +#include "ipc/ipc_channel_handle.h" +#include "net/socket/socket_descriptor.h" +#include "ppapi/shared_impl/ppapi_permissions.h" +#include "url/gurl.h" + +class CommandLine; + +namespace content { +class BrowserChildProcessHost; +class BrowserPpapiHost; +} + +namespace IPC { +class ChannelProxy; +} + +namespace nacl { + +class NaClHostMessageFilter; +void* AllocateAddressSpaceASLR(base::ProcessHandle process, size_t size); + +// Represents the browser side of the browser <--> NaCl communication +// channel. There will be one NaClProcessHost per NaCl process +// The browser is responsible for starting the NaCl process +// when requested by the renderer. +// After that, most of the communication is directly between NaCl plugin +// running in the renderer and NaCl processes. +class NaClProcessHost : public content::BrowserChildProcessHostDelegate { + public: + // manifest_url: the URL of the manifest of the Native Client plugin being + // executed. + // render_view_id: RenderView routing id, to control access to private APIs. + // permission_bits: controls which interfaces the NaCl plugin can use. + // uses_irt: whether the launched process should use the IRT. + // enable_dyncode_syscalls: whether the launched process should allow dyncode + // and mmap with PROT_EXEC. + // enable_exception_handling: whether the launched process should allow + // hardware exception handling. + // enable_crash_throttling: whether a crash of this process contributes + // to the crash throttling statistics, and also + // whether this process should not start when too + // many crashes have been observed. + // off_the_record: was the process launched from an incognito renderer? + // profile_directory: is the path of current profile directory. + NaClProcessHost(const GURL& manifest_url, + int render_view_id, + uint32 permission_bits, + bool uses_irt, + bool enable_dyncode_syscalls, + bool enable_exception_handling, + bool enable_crash_throttling, + bool off_the_record, + const base::FilePath& profile_directory); + virtual ~NaClProcessHost(); + + virtual void OnProcessCrashed(int exit_status) OVERRIDE; + + // Do any minimal work that must be done at browser startup. + static void EarlyStartup(); + + // Initialize the new NaCl process. Result is returned by sending ipc + // message reply_msg. + void Launch(NaClHostMessageFilter* nacl_host_message_filter, + IPC::Message* reply_msg, + const base::FilePath& manifest_path); + + virtual void OnChannelConnected(int32 peer_pid) OVERRIDE; + +#if defined(OS_WIN) + void OnProcessLaunchedByBroker(base::ProcessHandle handle); + void OnDebugExceptionHandlerLaunchedByBroker(bool success); +#endif + + bool Send(IPC::Message* msg); + + content::BrowserChildProcessHost* process() { return process_.get(); } + content::BrowserPpapiHost* browser_ppapi_host() { return ppapi_host_.get(); } + + private: + friend class PluginListener; + + // Internal class that holds the NaClHandle objecs so that + // nacl_process_host.h doesn't include NaCl headers. Needed since it's + // included by src\content, which can't depend on the NaCl gyp file because it + // depends on chrome.gyp (circular dependency). + struct NaClInternal; + + // PluginListener that forwards any messages from untrusted code that aren't + // handled by the PepperMessageFilter to us. + class PluginListener : public IPC::Listener { + public: + explicit PluginListener(NaClProcessHost* host); + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + private: + // Non-owning pointer so we can forward messages to the host. + NaClProcessHost* host_; + }; + + bool LaunchNaClGdb(); + +#if defined(OS_POSIX) + // Create bound TCP socket in the browser process so that the NaCl GDB debug + // stub can use it to accept incoming connections even when the Chrome sandbox + // is enabled. + net::SocketDescriptor GetDebugStubSocketHandle(); +#endif + bool LaunchSelLdr(); + + // BrowserChildProcessHostDelegate implementation: + virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE; + virtual void OnProcessLaunched() OVERRIDE; + + void OnResourcesReady(); + + // Enable the PPAPI proxy only for NaCl processes corresponding to a renderer. + bool enable_ppapi_proxy() { return render_view_id_ != 0; } + + // Sends the reply message to the renderer who is waiting for the plugin + // to load. Returns true on success. + bool ReplyToRenderer(const IPC::ChannelHandle& channel_handle); + + // Sends the reply with error message to the renderer. + void SendErrorToRenderer(const std::string& error_message); + + // Sends the reply message to the renderer. Either result or + // error message must be empty. + void SendMessageToRenderer(const NaClLaunchResult& result, + const std::string& error_message); + + // Sends the message to the NaCl process to load the plugin. Returns true + // on success. + bool StartNaClExecution(); + + // Called once all initialization is complete and the NaCl process is + // ready to go. Returns true on success. + bool SendStart(); + + // Does post-process-launching tasks for starting the NaCl process once + // we have a connection. + // + // Returns false on failure. + bool StartWithLaunchedProcess(); + + // Message handlers for validation caching. + void OnQueryKnownToValidate(const std::string& signature, bool* result); + void OnSetKnownToValidate(const std::string& signature); + void OnResolveFileToken(uint64 file_token_lo, uint64 file_token_hi, + IPC::Message* reply_msg); + void FileResolved(base::PlatformFile* file, const base::FilePath& file_path, + IPC::Message* reply_msg); + +#if defined(OS_WIN) + // Message handler for Windows hardware exception handling. + void OnAttachDebugExceptionHandler(const std::string& info, + IPC::Message* reply_msg); + bool AttachDebugExceptionHandler(const std::string& info, + IPC::Message* reply_msg); +#endif + + // Called when a PPAPI IPC channel has been created. + void OnPpapiChannelCreated(const IPC::ChannelHandle& channel_handle); + // Called by PluginListener, so messages from the untrusted side of + // the IPC proxy can be handled. + bool OnUntrustedMessageForwarded(const IPC::Message& msg); + + GURL manifest_url_; + ppapi::PpapiPermissions permissions_; + +#if defined(OS_WIN) + // This field becomes true when the broker successfully launched + // the NaCl loader. + bool process_launched_by_broker_; +#endif + // The NaClHostMessageFilter that requested this NaCl process. We use + // this for sending the reply once the process has started. + scoped_refptr<NaClHostMessageFilter> nacl_host_message_filter_; + + // The reply message to send. We must always send this message when the + // sub-process either succeeds or fails to unblock the renderer waiting for + // the reply. NULL when there is no reply to send. + IPC::Message* reply_msg_; +#if defined(OS_WIN) + bool debug_exception_handler_requested_; + scoped_ptr<IPC::Message> attach_debug_exception_handler_reply_msg_; +#endif + + // The file path to the manifest is passed to nacl-gdb when it is used to + // debug the NaCl loader. + base::FilePath manifest_path_; + + // Socket pairs for the NaCl process and renderer. + scoped_ptr<NaClInternal> internal_; + + base::WeakPtrFactory<NaClProcessHost> weak_factory_; + + scoped_ptr<content::BrowserChildProcessHost> process_; + + bool uses_irt_; + + bool enable_debug_stub_; + bool enable_dyncode_syscalls_; + bool enable_exception_handling_; + bool enable_crash_throttling_; + + bool off_the_record_; + + const base::FilePath profile_directory_; + + // Channel proxy to terminate the NaCl-Browser PPAPI channel. + scoped_ptr<IPC::ChannelProxy> ipc_proxy_channel_; + // Plugin listener, to forward browser channel messages to us. + PluginListener ipc_plugin_listener_; + // Browser host for plugin process. + scoped_ptr<content::BrowserPpapiHost> ppapi_host_; + + int render_view_id_; + + DISALLOW_COPY_AND_ASSIGN(NaClProcessHost); +}; + +} // namespace nacl + +#endif // COMPONENTS_NACL_BROWSER_NACL_PROCESS_HOST_H_ diff --git a/components/nacl/browser/nacl_process_host_unittest.cc b/components/nacl/browser/nacl_process_host_unittest.cc new file mode 100644 index 0000000..0d97ed6 --- /dev/null +++ b/components/nacl/browser/nacl_process_host_unittest.cc @@ -0,0 +1,15 @@ +// 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 "components/nacl/browser/nacl_process_host.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +TEST(NaClProcessHostTest, AddressSpaceAllocation) { + size_t size = 1 << 20; // 1 MB + void* addr = nacl::AllocateAddressSpaceASLR(GetCurrentProcess(), size); + bool success = VirtualFree(addr, 0, MEM_RELEASE); + ASSERT_TRUE(success); +} +#endif diff --git a/components/nacl/browser/pnacl_host.cc b/components/nacl/browser/pnacl_host.cc new file mode 100644 index 0000000..2ed4d5d --- /dev/null +++ b/components/nacl/browser/pnacl_host.cc @@ -0,0 +1,634 @@ +// 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 "components/nacl/browser/pnacl_host.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/task_runner_util.h" +#include "base/threading/sequenced_worker_pool.h" +#include "components/nacl/browser/nacl_browser.h" +#include "components/nacl/browser/pnacl_translation_cache.h" +#include "content/public/browser/browser_thread.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" + +using content::BrowserThread; + +namespace { +static const base::FilePath::CharType kTranslationCacheDirectoryName[] = + FILE_PATH_LITERAL("PnaclTranslationCache"); +// Delay to wait for initialization of the cache backend +static const int kTranslationCacheInitializationDelayMs = 20; +} + +namespace pnacl { + +PnaclHost::PnaclHost() + : pending_backend_operations_(0), + cache_state_(CacheUninitialized), + weak_factory_(this) {} + +PnaclHost::~PnaclHost() { + // When PnaclHost is destroyed, it's too late to post anything to the cache + // thread (it will hang shutdown). So just leak the cache backend. + pnacl::PnaclTranslationCache* cache = disk_cache_.release(); + (void)cache; +} + +PnaclHost* PnaclHost::GetInstance() { return Singleton<PnaclHost>::get(); } + +PnaclHost::PendingTranslation::PendingTranslation() + : process_handle(base::kNullProcessHandle), + render_view_id(0), + nexe_fd(base::kInvalidPlatformFileValue), + got_nexe_fd(false), + got_cache_reply(false), + got_cache_hit(false), + is_incognito(false), + callback(NexeFdCallback()), + cache_info(nacl::PnaclCacheInfo()) {} +PnaclHost::PendingTranslation::~PendingTranslation() {} + +bool PnaclHost::TranslationMayBeCached( + const PendingTranslationMap::iterator& entry) { + return !entry->second.is_incognito && + !entry->second.cache_info.has_no_store_header; +} + +/////////////////////////////////////// Initialization + +static base::FilePath GetCachePath() { + NaClBrowserDelegate* browser_delegate = nacl::NaClBrowser::GetDelegate(); + // Determine where the translation cache resides in the file system. It + // exists in Chrome's cache directory and is not tied to any specific + // profile. If we fail, return an empty path. + // Start by finding the user data directory. + base::FilePath user_data_dir; + if (!browser_delegate || + !browser_delegate->GetUserDirectory(&user_data_dir)) { + return base::FilePath(); + } + // The cache directory may or may not be the user data directory. + base::FilePath cache_file_path; + browser_delegate->GetCacheDirectory(&cache_file_path); + + // Append the base file name to the cache directory. + return cache_file_path.Append(kTranslationCacheDirectoryName); +} + +void PnaclHost::OnCacheInitialized(int net_error) { + DCHECK(thread_checker_.CalledOnValidThread()); + // If the cache was cleared before the load completed, ignore. + if (cache_state_ == CacheReady) + return; + if (net_error != net::OK) { + // This will cause the cache to attempt to re-init on the next call to + // GetNexeFd. + cache_state_ = CacheUninitialized; + } else { + cache_state_ = CacheReady; + } +} + +void PnaclHost::Init() { + // Extra check that we're on the real IO thread since this version of + // Init isn't used in unit tests. + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK(thread_checker_.CalledOnValidThread()); + base::FilePath cache_path(GetCachePath()); + if (cache_path.empty() || cache_state_ != CacheUninitialized) + return; + disk_cache_.reset(new pnacl::PnaclTranslationCache()); + cache_state_ = CacheInitializing; + int rv = disk_cache_->InitOnDisk( + cache_path, + base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr())); + if (rv != net::ERR_IO_PENDING) + OnCacheInitialized(rv); +} + +// Initialize using the in-memory backend, and manually set the temporary file +// directory instead of using the system directory. +void PnaclHost::InitForTest(base::FilePath temp_dir) { + DCHECK(thread_checker_.CalledOnValidThread()); + disk_cache_.reset(new pnacl::PnaclTranslationCache()); + cache_state_ = CacheInitializing; + temp_dir_ = temp_dir; + int rv = disk_cache_->InitInMemory( + base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr())); + if (rv != net::ERR_IO_PENDING) + OnCacheInitialized(rv); +} + +///////////////////////////////////////// Temp files + +// Create a temporary file on the blocking pool +// static +void PnaclHost::DoCreateTemporaryFile(base::FilePath temp_dir, + TempFileCallback cb) { + DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); + + base::FilePath file_path; + base::PlatformFile file_handle(base::kInvalidPlatformFileValue); + bool rv = temp_dir.empty() + ? file_util::CreateTemporaryFile(&file_path) + : file_util::CreateTemporaryFileInDir(temp_dir, &file_path); + if (!rv) { + PLOG(ERROR) << "Temp file creation failed."; + } else { + base::PlatformFileError error; + file_handle = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_TEMPORARY | + base::PLATFORM_FILE_DELETE_ON_CLOSE, + NULL, + &error); + + if (error != base::PLATFORM_FILE_OK) { + PLOG(ERROR) << "Temp file open failed: " << error; + file_handle = base::kInvalidPlatformFileValue; + } + } + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, base::Bind(cb, file_handle)); +} + +void PnaclHost::CreateTemporaryFile(TempFileCallback cb) { + if (!BrowserThread::PostBlockingPoolSequencedTask( + "PnaclHostCreateTempFile", + FROM_HERE, + base::Bind(&PnaclHost::DoCreateTemporaryFile, temp_dir_, cb))) { + DCHECK(thread_checker_.CalledOnValidThread()); + cb.Run(base::kInvalidPlatformFileValue); + } +} + +///////////////////////////////////////// GetNexeFd implementation +////////////////////// Common steps + +void PnaclHost::GetNexeFd(int render_process_id, + int render_view_id, + int pp_instance, + bool is_incognito, + const nacl::PnaclCacheInfo& cache_info, + const NexeFdCallback& cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (cache_state_ == CacheUninitialized) { + Init(); + } + if (cache_state_ != CacheReady) { + // If the backend hasn't yet initialized, try the request again later. + BrowserThread::PostDelayedTask(BrowserThread::IO, + FROM_HERE, + base::Bind(&PnaclHost::GetNexeFd, + weak_factory_.GetWeakPtr(), + render_process_id, + render_view_id, + pp_instance, + is_incognito, + cache_info, + cb), + base::TimeDelta::FromMilliseconds( + kTranslationCacheInitializationDelayMs)); + return; + } + + TranslationID id(render_process_id, pp_instance); + PendingTranslationMap::iterator entry = pending_translations_.find(id); + if (entry != pending_translations_.end()) { + // Existing translation must have been abandonded. Clean it up. + LOG(ERROR) << "GetNexeFd for already-pending translation"; + pending_translations_.erase(entry); + } + + std::string cache_key(disk_cache_->GetKey(cache_info)); + if (cache_key.empty()) { + LOG(ERROR) << "GetNexeFd: Invalid cache info"; + cb.Run(base::kInvalidPlatformFileValue, false); + return; + } + + PendingTranslation pt; + pt.render_view_id = render_view_id; + pt.callback = cb; + pt.cache_info = cache_info; + pt.cache_key = cache_key; + pt.is_incognito = is_incognito; + pending_translations_[id] = pt; + SendCacheQueryAndTempFileRequest(cache_key, id); +} + +// Dispatch the cache read request and the temp file creation request +// simultaneously; currently we need a temp file regardless of whether the +// request hits. +void PnaclHost::SendCacheQueryAndTempFileRequest(const std::string& cache_key, + const TranslationID& id) { + pending_backend_operations_++; + disk_cache_->GetNexe( + cache_key, + base::Bind( + &PnaclHost::OnCacheQueryReturn, weak_factory_.GetWeakPtr(), id)); + + CreateTemporaryFile( + base::Bind(&PnaclHost::OnTempFileReturn, weak_factory_.GetWeakPtr(), id)); +} + +// Callback from the translation cache query. |id| is bound from +// SendCacheQueryAndTempFileRequest, |net_error| is a net::Error code (which for +// our purposes means a hit if it's net::OK (i.e. 0). |buffer| is allocated +// by PnaclTranslationCache and now belongs to PnaclHost. +// (Bound callbacks must re-lookup the TranslationID because the translation +// could be cancelled before they get called). +void PnaclHost::OnCacheQueryReturn( + const TranslationID& id, + int net_error, + scoped_refptr<net::DrainableIOBuffer> buffer) { + DCHECK(thread_checker_.CalledOnValidThread()); + pending_backend_operations_--; + PendingTranslationMap::iterator entry(pending_translations_.find(id)); + if (entry == pending_translations_.end()) { + LOG(ERROR) << "OnCacheQueryReturn: id not found"; + DeInitIfSafe(); + return; + } + PendingTranslation* pt = &entry->second; + pt->got_cache_reply = true; + pt->got_cache_hit = (net_error == net::OK); + if (pt->got_cache_hit) + pt->nexe_read_buffer = buffer; + CheckCacheQueryReady(entry); +} + +// Callback from temp file creation. |id| is bound from +// SendCacheQueryAndTempFileRequest, and fd is the created file descriptor. +// If there was an error, fd is kInvalidPlatformFileValue. +// (Bound callbacks must re-lookup the TranslationID because the translation +// could be cancelled before they get called). +void PnaclHost::OnTempFileReturn(const TranslationID& id, + base::PlatformFile fd) { + DCHECK(thread_checker_.CalledOnValidThread()); + PendingTranslationMap::iterator entry(pending_translations_.find(id)); + if (entry == pending_translations_.end()) { + // The renderer may have signaled an error or closed while the temp + // file was being created. + LOG(ERROR) << "OnTempFileReturn: id not found"; + BrowserThread::PostBlockingPoolTask( + FROM_HERE, base::Bind(base::IgnoreResult(base::ClosePlatformFile), fd)); + return; + } + if (fd == base::kInvalidPlatformFileValue) { + // This translation will fail, but we need to retry any translation + // waiting for its result. + LOG(ERROR) << "OnTempFileReturn: temp file creation failed"; + std::string key(entry->second.cache_key); + entry->second.callback.Run(fd, false); + bool may_be_cached = TranslationMayBeCached(entry); + pending_translations_.erase(entry); + // No translations will be waiting for entries that will not be stored. + if (may_be_cached) + RequeryMatchingTranslations(key); + return; + } + PendingTranslation* pt = &entry->second; + pt->got_nexe_fd = true; + pt->nexe_fd = fd; + CheckCacheQueryReady(entry); +} + +// Check whether both the cache query and the temp file have returned, and check +// whether we actually got a hit or not. +void PnaclHost::CheckCacheQueryReady( + const PendingTranslationMap::iterator& entry) { + PendingTranslation* pt = &entry->second; + if (!(pt->got_cache_reply && pt->got_nexe_fd)) + return; + if (!pt->got_cache_hit) { + // Check if there is already a pending translation for this file. If there + // is, we will wait for it to come back, to avoid redundant translations. + for (PendingTranslationMap::iterator it = pending_translations_.begin(); + it != pending_translations_.end(); + ++it) { + // Another translation matches if it's a request for the same file, + if (it->second.cache_key == entry->second.cache_key && + // and it's not this translation, + it->first != entry->first && + // and it can be stored in the cache, + TranslationMayBeCached(it) && + // and it's already gotten past this check and returned the miss. + it->second.got_cache_reply && + it->second.got_nexe_fd) { + return; + } + } + ReturnMiss(entry); + return; + } + + if (!base::PostTaskAndReplyWithResult( + BrowserThread::GetBlockingPool(), + FROM_HERE, + base::Bind( + &PnaclHost::CopyBufferToFile, pt->nexe_fd, pt->nexe_read_buffer), + base::Bind(&PnaclHost::OnBufferCopiedToTempFile, + weak_factory_.GetWeakPtr(), + entry->first))) { + pt->callback.Run(base::kInvalidPlatformFileValue, false); + } +} + +//////////////////// GetNexeFd miss path +// Return the temp fd to the renderer, reporting a miss. +void PnaclHost::ReturnMiss(const PendingTranslationMap::iterator& entry) { + // Return the fd + PendingTranslation* pt = &entry->second; + NexeFdCallback cb(pt->callback); + if (pt->nexe_fd == base::kInvalidPlatformFileValue) { + // Bad FD is unrecoverable, so clear out the entry + pending_translations_.erase(entry); + } + cb.Run(pt->nexe_fd, false); +} + +// On error, just return a null refptr. +// static +scoped_refptr<net::DrainableIOBuffer> PnaclHost::CopyFileToBuffer( + base::PlatformFile fd) { + base::PlatformFileInfo info; + scoped_refptr<net::DrainableIOBuffer> buffer; + bool error = false; + if (!base::GetPlatformFileInfo(fd, &info) || + info.size >= std::numeric_limits<int>::max()) { + PLOG(ERROR) << "GetPlatformFileInfo failed"; + error = true; + } else { + buffer = new net::DrainableIOBuffer( + new net::IOBuffer(static_cast<int>(info.size)), info.size); + if (base::ReadPlatformFile(fd, 0, buffer->data(), buffer->size()) != + info.size) { + PLOG(ERROR) << "CopyFileToBuffer file read failed"; + error = true; + } + } + if (error) { + buffer = NULL; + } + base::ClosePlatformFile(fd); + return buffer; +} + +// Called by the renderer in the miss path to report a finished translation +void PnaclHost::TranslationFinished(int render_process_id, + int pp_instance, + bool success) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (cache_state_ != CacheReady) + return; + TranslationID id(render_process_id, pp_instance); + PendingTranslationMap::iterator entry(pending_translations_.find(id)); + if (entry == pending_translations_.end()) { + LOG(ERROR) << "TranslationFinished: TranslationID " << render_process_id + << "," << pp_instance << " not found."; + return; + } + bool store_nexe = true; + // If this is a premature response (i.e. we haven't returned a temp file + // yet) or if it's an unsuccessful translation, or if we are incognito, + // don't store in the cache. + // TODO(dschuff): use a separate in-memory cache for incognito + // translations. + if (!entry->second.got_nexe_fd || !entry->second.got_cache_reply || + !success || !TranslationMayBeCached(entry)) { + store_nexe = false; + } else if (!base::PostTaskAndReplyWithResult( + BrowserThread::GetBlockingPool(), + FROM_HERE, + base::Bind(&PnaclHost::CopyFileToBuffer, + entry->second.nexe_fd), + base::Bind(&PnaclHost::StoreTranslatedNexe, + weak_factory_.GetWeakPtr(), + id))) { + store_nexe = false; + } + + if (!store_nexe) { + // If store_nexe is true, the fd will be closed by CopyFileToBuffer. + if (entry->second.got_nexe_fd) { + BrowserThread::PostBlockingPoolTask( + FROM_HERE, + base::Bind(base::IgnoreResult(base::ClosePlatformFile), + entry->second.nexe_fd)); + } + pending_translations_.erase(entry); + } +} + +// Store the translated nexe in the translation cache. Called back with the +// TranslationID from the host and the result of CopyFileToBuffer. +// (Bound callbacks must re-lookup the TranslationID because the translation +// could be cancelled before they get called). +void PnaclHost::StoreTranslatedNexe( + TranslationID id, + scoped_refptr<net::DrainableIOBuffer> buffer) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (cache_state_ != CacheReady) + return; + PendingTranslationMap::iterator it(pending_translations_.find(id)); + if (it == pending_translations_.end()) { + LOG(ERROR) << "StoreTranslatedNexe: TranslationID " << id.first << "," + << id.second << " not found."; + return; + } + + if (buffer.get() == NULL) { + LOG(ERROR) << "Error reading translated nexe"; + return; + } + pending_backend_operations_++; + disk_cache_->StoreNexe(it->second.cache_key, + buffer, + base::Bind(&PnaclHost::OnTranslatedNexeStored, + weak_factory_.GetWeakPtr(), + it->first)); +} + +// After we know the nexe has been stored, we can clean up, and unblock any +// outstanding requests for the same file. +// (Bound callbacks must re-lookup the TranslationID because the translation +// could be cancelled before they get called). +void PnaclHost::OnTranslatedNexeStored(const TranslationID& id, int net_error) { + PendingTranslationMap::iterator entry(pending_translations_.find(id)); + pending_backend_operations_--; + if (entry == pending_translations_.end()) { + // If the renderer closed while we were storing the nexe, we land here. + // Make sure we try to de-init. + DeInitIfSafe(); + return; + } + std::string key(entry->second.cache_key); + pending_translations_.erase(entry); + RequeryMatchingTranslations(key); +} + +// Check if any pending translations match |key|. If so, re-issue the cache +// query. In the overlapped miss case, we expect a hit this time, but a miss +// is also possible in case of an error. +void PnaclHost::RequeryMatchingTranslations(const std::string& key) { + // Check for outstanding misses to this same file + for (PendingTranslationMap::iterator it = pending_translations_.begin(); + it != pending_translations_.end(); + ++it) { + if (it->second.cache_key == key) { + // Re-send the cache read request. This time we expect a hit, but if + // something goes wrong, it will just handle it like a miss. + it->second.got_cache_reply = false; + pending_backend_operations_++; + disk_cache_->GetNexe(key, + base::Bind(&PnaclHost::OnCacheQueryReturn, + weak_factory_.GetWeakPtr(), + it->first)); + } + } +} + +//////////////////// GetNexeFd hit path + +// static +int PnaclHost::CopyBufferToFile(base::PlatformFile fd, + scoped_refptr<net::DrainableIOBuffer> buffer) { + int rv = base::WritePlatformFile(fd, 0, buffer->data(), buffer->size()); + if (rv == -1) + PLOG(ERROR) << "CopyBufferToFile write error"; + return rv; +} + +void PnaclHost::OnBufferCopiedToTempFile(const TranslationID& id, + int file_error) { + DCHECK(thread_checker_.CalledOnValidThread()); + PendingTranslationMap::iterator entry(pending_translations_.find(id)); + if (entry == pending_translations_.end()) { + return; + } + if (file_error == -1) { + // Write error on the temp file. Request a new file and start over. + BrowserThread::PostBlockingPoolTask( + FROM_HERE, + base::Bind(base::IgnoreResult(base::ClosePlatformFile), + entry->second.nexe_fd)); + entry->second.got_nexe_fd = false; + CreateTemporaryFile(base::Bind(&PnaclHost::OnTempFileReturn, + weak_factory_.GetWeakPtr(), + entry->first)); + return; + } + base::PlatformFile fd = entry->second.nexe_fd; + entry->second.callback.Run(fd, true); + BrowserThread::PostBlockingPoolTask( + FROM_HERE, base::Bind(base::IgnoreResult(base::ClosePlatformFile), fd)); + pending_translations_.erase(entry); +} + +/////////////////// + +void PnaclHost::RendererClosing(int render_process_id) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (cache_state_ != CacheReady) + return; + for (PendingTranslationMap::iterator it = pending_translations_.begin(); + it != pending_translations_.end();) { + PendingTranslationMap::iterator to_erase(it++); + if (to_erase->first.first == render_process_id) { + // Clean up the open files. + BrowserThread::PostBlockingPoolTask( + FROM_HERE, + base::Bind(base::IgnoreResult(base::ClosePlatformFile), + to_erase->second.nexe_fd)); + std::string key(to_erase->second.cache_key); + bool may_be_cached = TranslationMayBeCached(to_erase); + pending_translations_.erase(to_erase); + // No translations will be waiting for entries that will not be stored. + if (may_be_cached) + RequeryMatchingTranslations(key); + } + } + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&PnaclHost::DeInitIfSafe, weak_factory_.GetWeakPtr())); +} + +////////////////// Cache data removal +void PnaclHost::ClearTranslationCacheEntriesBetween( + base::Time initial_time, + base::Time end_time, + const base::Closure& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (cache_state_ == CacheUninitialized) { + Init(); + } + if (cache_state_ == CacheInitializing) { + // If the backend hasn't yet initialized, try the request again later. + BrowserThread::PostDelayedTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&PnaclHost::ClearTranslationCacheEntriesBetween, + weak_factory_.GetWeakPtr(), + initial_time, + end_time, + callback), + base::TimeDelta::FromMilliseconds( + kTranslationCacheInitializationDelayMs)); + return; + } + pending_backend_operations_++; + int rv = disk_cache_->DoomEntriesBetween( + initial_time, + end_time, + base::Bind( + &PnaclHost::OnEntriesDoomed, weak_factory_.GetWeakPtr(), callback)); + if (rv != net::ERR_IO_PENDING) + OnEntriesDoomed(callback, rv); +} + +void PnaclHost::OnEntriesDoomed(const base::Closure& callback, int net_error) { + DCHECK(thread_checker_.CalledOnValidThread()); + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, callback); + pending_backend_operations_--; + // When clearing the cache, the UI is blocked on all the cache-clearing + // operations, and freeing the backend actually blocks the IO thread. So + // instead of calling DeInitIfSafe directly, post it for later. + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&PnaclHost::DeInitIfSafe, weak_factory_.GetWeakPtr())); +} + +// Destroying the cache backend causes it to post tasks to the cache thread to +// flush to disk. Because PnaclHost is a singleton, it does not get destroyed +// until all the browser threads have gone away and it's too late to post +// anything (attempting to do so hangs shutdown). So we make sure to destroy it +// when we no longer have any outstanding operations that need it. These include +// pending translations, cache clear requests, and requests to read or write +// translated nexes. We check when renderers close, when cache clear requests +// finish, and when backend operations complete. + +// It is not safe to delete the backend while it is initializing, nor if it has +// outstanding entry open requests; it is in theory safe to delete it with +// outstanding read/write requests, but because that distinction is hidden +// inside PnaclTranslationCache, we do not delete the backend if there are any +// backend requests in flight. As a last resort in the destructor, we just leak +// the backend to avoid hanging shutdown. +void PnaclHost::DeInitIfSafe() { + DCHECK(pending_backend_operations_ >= 0); + if (pending_translations_.empty() && pending_backend_operations_ <= 0) { + cache_state_ = CacheUninitialized; + disk_cache_.reset(); + } +} + +} // namespace pnacl diff --git a/components/nacl/browser/pnacl_host.h b/components/nacl/browser/pnacl_host.h new file mode 100644 index 0000000..9111603 --- /dev/null +++ b/components/nacl/browser/pnacl_host.h @@ -0,0 +1,178 @@ +// 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. + +#ifndef COMPONENTS_NACL_BROWSER_PNACL_HOST_H_ +#define COMPONENTS_NACL_BROWSER_PNACL_HOST_H_ + +#include <map> + +#include "base/callback_forward.h" +#include "base/memory/singleton.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "components/nacl/browser/nacl_file_host.h" +#include "components/nacl/common/pnacl_types.h" +#include "ipc/ipc_platform_file.h" + +namespace net { +class DrainableIOBuffer; +} + +namespace pnacl { + +class PnaclHostTest; +class PnaclTranslationCache; + +// Shared state (translation cache) and common utilities (temp file creation) +// for all PNaCl translations. Unless otherwise specified, all methods should be +// called on the IO thread. +class PnaclHost { + public: + typedef base::Callback<void(base::PlatformFile)> TempFileCallback; + typedef base::Callback<void(base::PlatformFile, bool is_hit)> NexeFdCallback; + + static PnaclHost* GetInstance(); + + PnaclHost(); + ~PnaclHost(); + + // Initialize cache backend. GetNexeFd will also initialize the backend if + // necessary, but calling Init ahead of time will minimize the latency. + void Init(); + + // Creates a temporary file that will be deleted when the last handle + // is closed, or earlier. Returns a PlatformFile handle. + void CreateTemporaryFile(TempFileCallback cb); + + // Create a temporary file, which will be deleted by the time the last + // handle is closed (or earlier on POSIX systems), to use for the nexe + // with the cache information given in |cache_info|. The specific instance + // is identified by the combination of |render_process_id| and |pp_instance|. + // Returns by calling |cb| with a PlatformFile handle. + // If the nexe is already present + // in the cache, |is_hit| is set to true and the contents of the nexe + // have been copied into the temporary file. Otherwise |is_hit| is set to + // false and the temporary file will be writeable. + // Currently the implementation is a stub, which always sets is_hit to false + // and calls the implementation of CreateTemporaryFile. + // If the cache request was a miss, the caller is expected to call + // TranslationFinished after it finishes translation to allow the nexe to be + // stored in the cache. + // The returned temp fd may be closed at any time by PnaclHost, so it should + // be duplicated (e.g. with IPC::GetFileHandleForProcess) before the callback + // returns. + // If |is_incognito| is true, the nexe will not be stored + // in the cache, but the renderer is still expected to call + // TranslationFinished. + void GetNexeFd(int render_process_id, + int render_view_id, + int pp_instance, + bool is_incognito, + const nacl::PnaclCacheInfo& cache_info, + const NexeFdCallback& cb); + + // Called after the translation of a pexe instance identified by + // |render_process_id| and |pp_instance| finishes. If |success| is true, + // store the nexe translated for the instance in the cache. + void TranslationFinished(int render_process_id, + int pp_instance, + bool success); + + // Called when the renderer identified by |render_process_id| is closing. + // Clean up any outstanding translations for that renderer. If there are no + // more pending translations, the backend is freed, allowing it to flush. + void RendererClosing(int render_process_id); + + // Doom all entries between |initial_time| and |end_time|. Like disk_cache_, + // PnaclHost supports supports unbounded deletes in either direction by using + // null Time values for either argument. |callback| will be called on the UI + // thread when finished. + void ClearTranslationCacheEntriesBetween(base::Time initial_time, + base::Time end_time, + const base::Closure& callback); + + // Return the number of tracked translations or FD requests currently pending. + size_t pending_translations() { return pending_translations_.size(); } + + private: + // PnaclHost is a singleton because there is only one translation cache, and + // so that the BrowsingDataRemover can clear it even if no translation has + // ever been started. + friend struct DefaultSingletonTraits<PnaclHost>; + friend class pnacl::PnaclHostTest; + enum CacheState { + CacheUninitialized, + CacheInitializing, + CacheReady + }; + class PendingTranslation { + public: + PendingTranslation(); + ~PendingTranslation(); + base::ProcessHandle process_handle; + int render_view_id; + base::PlatformFile nexe_fd; + bool got_nexe_fd; + bool got_cache_reply; + bool got_cache_hit; + bool is_incognito; + scoped_refptr<net::DrainableIOBuffer> nexe_read_buffer; + NexeFdCallback callback; + std::string cache_key; + nacl::PnaclCacheInfo cache_info; + }; + + typedef std::pair<int, int> TranslationID; + typedef std::map<TranslationID, PendingTranslation> PendingTranslationMap; + static bool TranslationMayBeCached( + const PendingTranslationMap::iterator& entry); + + void InitForTest(base::FilePath temp_dir); + void OnCacheInitialized(int net_error); + + static void DoCreateTemporaryFile(base::FilePath temp_dir_, + TempFileCallback cb); + + // GetNexeFd common steps + void SendCacheQueryAndTempFileRequest(const std::string& key, + const TranslationID& id); + void OnCacheQueryReturn(const TranslationID& id, + int net_error, + scoped_refptr<net::DrainableIOBuffer> buffer); + void OnTempFileReturn(const TranslationID& id, base::PlatformFile fd); + void CheckCacheQueryReady(const PendingTranslationMap::iterator& entry); + + // GetNexeFd miss path + void ReturnMiss(const PendingTranslationMap::iterator& entry); + static scoped_refptr<net::DrainableIOBuffer> CopyFileToBuffer( + base::PlatformFile fd); + void StoreTranslatedNexe(TranslationID id, + scoped_refptr<net::DrainableIOBuffer>); + void OnTranslatedNexeStored(const TranslationID& id, int net_error); + void RequeryMatchingTranslations(const std::string& key); + + // GetNexeFd hit path + static int CopyBufferToFile(base::PlatformFile fd, + scoped_refptr<net::DrainableIOBuffer> buffer); + void OnBufferCopiedToTempFile(const TranslationID& id, int file_error); + + void OnEntriesDoomed(const base::Closure& callback, int net_error); + + void DeInitIfSafe(); + + // Operations which are pending with the cache backend, which we should + // wait for before destroying it (see comment on DeInitIfSafe). + int pending_backend_operations_; + CacheState cache_state_; + base::FilePath temp_dir_; + scoped_ptr<pnacl::PnaclTranslationCache> disk_cache_; + PendingTranslationMap pending_translations_; + base::ThreadChecker thread_checker_; + base::WeakPtrFactory<PnaclHost> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(PnaclHost); +}; + +} // namespace pnacl + +#endif // COMPONENTS_NACL_BROWSER_PNACL_HOST_H_ diff --git a/components/nacl/browser/pnacl_host_unittest.cc b/components/nacl/browser/pnacl_host_unittest.cc new file mode 100644 index 0000000..d9119da --- /dev/null +++ b/components/nacl/browser/pnacl_host_unittest.cc @@ -0,0 +1,432 @@ +// 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 "components/nacl/browser/pnacl_host.h" + +#include <stdio.h> +#include "base/bind.h" +#include "base/files/scoped_temp_dir.h" +#include "base/run_loop.h" +#include "base/threading/sequenced_worker_pool.h" +#include "components/nacl/browser/pnacl_translation_cache.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "net/base/test_completion_callback.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#define snprintf _snprintf +#endif + +namespace pnacl { + +class PnaclHostTest : public testing::Test { + protected: + PnaclHostTest() + : host_(NULL), + temp_callback_count_(0), + write_callback_count_(0), + thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {} + virtual void SetUp() { + host_ = new PnaclHost(); + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + host_->InitForTest(temp_dir_.path()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(PnaclHost::CacheReady, host_->cache_state_); + } + virtual void TearDown() { + EXPECT_EQ(0U, host_->pending_translations()); + // Give the host a chance to de-init the backend, and then delete it. + host_->RendererClosing(0); + FlushQueues(); + EXPECT_EQ(PnaclHost::CacheUninitialized, host_->cache_state_); + delete host_; + } + // Flush the blocking pool first, then any tasks it posted to the IO thread. + // Do 2 rounds of flushing, because some operations require 2 trips back and + // forth between the threads. + void FlushQueues() { + content::BrowserThread::GetBlockingPool()->FlushForTesting(); + base::RunLoop().RunUntilIdle(); + content::BrowserThread::GetBlockingPool()->FlushForTesting(); + base::RunLoop().RunUntilIdle(); + } + int GetCacheSize() { return host_->disk_cache_->Size(); } + int CacheIsInitialized() { + return host_->cache_state_ == PnaclHost::CacheReady; + } + void ReInitBackend() { + host_->InitForTest(temp_dir_.path()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(PnaclHost::CacheReady, host_->cache_state_); + } + + public: // Required for derived classes to bind this method + // Callbacks used by tests which call GetNexeFd. + // CallbackExpectMiss checks that the fd is valid and a miss is reported, + // and also writes some data into the file, which is read back by + // CallbackExpectHit + void CallbackExpectMiss(base::PlatformFile fd, bool is_hit) { + EXPECT_FALSE(is_hit); + ASSERT_FALSE(fd == base::kInvalidPlatformFileValue); + base::PlatformFileInfo info; + EXPECT_TRUE(base::GetPlatformFileInfo(fd, &info)); + EXPECT_FALSE(info.is_directory); + EXPECT_EQ(0LL, info.size); + char str[16]; + memset(str, 0x0, 16); + snprintf(str, 16, "testdata%d", ++write_callback_count_); + EXPECT_EQ(16, base::WritePlatformFile(fd, 0, str, 16)); + temp_callback_count_++; + } + void CallbackExpectHit(base::PlatformFile fd, bool is_hit) { + EXPECT_TRUE(is_hit); + ASSERT_FALSE(fd == base::kInvalidPlatformFileValue); + base::PlatformFileInfo info; + EXPECT_TRUE(base::GetPlatformFileInfo(fd, &info)); + EXPECT_FALSE(info.is_directory); + EXPECT_EQ(16LL, info.size); + char data[16]; + memset(data, 0x0, 16); + char str[16]; + memset(str, 0x0, 16); + snprintf(str, 16, "testdata%d", write_callback_count_); + EXPECT_EQ(16, base::ReadPlatformFile(fd, 0, data, 16)); + EXPECT_STREQ(str, data); + temp_callback_count_++; + } + + protected: + PnaclHost* host_; + int temp_callback_count_; + int write_callback_count_; + content::TestBrowserThreadBundle thread_bundle_; + base::ScopedTempDir temp_dir_; +}; + +static nacl::PnaclCacheInfo GetTestCacheInfo() { + nacl::PnaclCacheInfo info; + info.pexe_url = GURL("http://www.google.com"); + info.abi_version = 0; + info.opt_level = 0; + info.has_no_store_header = false; + return info; +} + +#define GET_NEXE_FD(renderer, instance, incognito, info, expect_hit) \ + do { \ + SCOPED_TRACE(""); \ + host_->GetNexeFd( \ + renderer, \ + 0, /* ignore render_view_id for now */ \ + instance, \ + incognito, \ + info, \ + base::Bind(expect_hit ? &PnaclHostTest::CallbackExpectHit \ + : &PnaclHostTest::CallbackExpectMiss, \ + base::Unretained(this))); \ + } while (0) + +TEST_F(PnaclHostTest, BasicMiss) { + nacl::PnaclCacheInfo info = GetTestCacheInfo(); + // Test cold miss. + GET_NEXE_FD(0, 0, false, info, false); + EXPECT_EQ(1U, host_->pending_translations()); + FlushQueues(); + EXPECT_EQ(1U, host_->pending_translations()); + EXPECT_EQ(1, temp_callback_count_); + host_->TranslationFinished(0, 0, true); + FlushQueues(); + EXPECT_EQ(0U, host_->pending_translations()); + // Test that a different cache info field also misses. + info.etag = std::string("something else"); + GET_NEXE_FD(0, 0, false, info, false); + FlushQueues(); + EXPECT_EQ(2, temp_callback_count_); + EXPECT_EQ(1U, host_->pending_translations()); + host_->RendererClosing(0); + FlushQueues(); + // Check that the cache has de-initialized after the last renderer goes away. + EXPECT_FALSE(CacheIsInitialized()); +} + +TEST_F(PnaclHostTest, BadArguments) { + nacl::PnaclCacheInfo info = GetTestCacheInfo(); + GET_NEXE_FD(0, 0, false, info, false); + EXPECT_EQ(1U, host_->pending_translations()); + host_->TranslationFinished(0, 1, true); // nonexistent translation + EXPECT_EQ(1U, host_->pending_translations()); + host_->RendererClosing(1); // nonexistent renderer + EXPECT_EQ(1U, host_->pending_translations()); + FlushQueues(); + EXPECT_EQ(1, temp_callback_count_); + host_->RendererClosing(0); // close without finishing +} + +TEST_F(PnaclHostTest, BasicHit) { + nacl::PnaclCacheInfo info = GetTestCacheInfo(); + GET_NEXE_FD(0, 0, false, info, false); + FlushQueues(); + EXPECT_EQ(1, temp_callback_count_); + host_->TranslationFinished(0, 0, true); + FlushQueues(); + GET_NEXE_FD(0, 1, false, info, true); + FlushQueues(); + EXPECT_EQ(2, temp_callback_count_); + EXPECT_EQ(0U, host_->pending_translations()); +} + +TEST_F(PnaclHostTest, TranslationErrors) { + nacl::PnaclCacheInfo info = GetTestCacheInfo(); + GET_NEXE_FD(0, 0, false, info, false); + // Early abort, before temp file request returns + host_->TranslationFinished(0, 0, false); + FlushQueues(); + EXPECT_EQ(0U, host_->pending_translations()); + EXPECT_EQ(0, temp_callback_count_); + // The backend will have been freed when the query comes back and there + // are no pending translations. + EXPECT_FALSE(CacheIsInitialized()); + ReInitBackend(); + // Check that another request for the same info misses successfully. + GET_NEXE_FD(0, 0, false, info, false); + FlushQueues(); + host_->TranslationFinished(0, 0, true); + FlushQueues(); + EXPECT_EQ(1, temp_callback_count_); + EXPECT_EQ(0U, host_->pending_translations()); + + // Now try sending the error after the temp file request returns + info.abi_version = 222; + GET_NEXE_FD(0, 0, false, info, false); + FlushQueues(); + EXPECT_EQ(2, temp_callback_count_); + host_->TranslationFinished(0, 0, false); + FlushQueues(); + EXPECT_EQ(0U, host_->pending_translations()); + // Check another successful miss + GET_NEXE_FD(0, 0, false, info, false); + FlushQueues(); + EXPECT_EQ(3, temp_callback_count_); + host_->TranslationFinished(0, 0, false); + EXPECT_EQ(0U, host_->pending_translations()); +} + +TEST_F(PnaclHostTest, OverlappedMissesAfterTempReturn) { + nacl::PnaclCacheInfo info = GetTestCacheInfo(); + GET_NEXE_FD(0, 0, false, info, false); + FlushQueues(); + EXPECT_EQ(1, temp_callback_count_); + EXPECT_EQ(1U, host_->pending_translations()); + // Test that a second request for the same nexe while the first one is still + // outstanding eventually hits. + GET_NEXE_FD(0, 1, false, info, true); + FlushQueues(); + EXPECT_EQ(2U, host_->pending_translations()); + // The temp file should not be returned to the second request until after the + // first is finished translating. + EXPECT_EQ(1, temp_callback_count_); + host_->TranslationFinished(0, 0, true); + FlushQueues(); + EXPECT_EQ(2, temp_callback_count_); + EXPECT_EQ(0U, host_->pending_translations()); +} + +TEST_F(PnaclHostTest, OverlappedMissesBeforeTempReturn) { + nacl::PnaclCacheInfo info = GetTestCacheInfo(); + GET_NEXE_FD(0, 0, false, info, false); + // Send the 2nd fd request before the first one returns a temp file. + GET_NEXE_FD(0, 1, false, info, true); + FlushQueues(); + EXPECT_EQ(1, temp_callback_count_); + EXPECT_EQ(2U, host_->pending_translations()); + FlushQueues(); + EXPECT_EQ(2U, host_->pending_translations()); + EXPECT_EQ(1, temp_callback_count_); + host_->TranslationFinished(0, 0, true); + FlushQueues(); + EXPECT_EQ(2, temp_callback_count_); + EXPECT_EQ(0U, host_->pending_translations()); +} + +TEST_F(PnaclHostTest, OverlappedHitsBeforeTempReturn) { + nacl::PnaclCacheInfo info = GetTestCacheInfo(); + // Store one in the cache and complete it. + GET_NEXE_FD(0, 0, false, info, false); + FlushQueues(); + EXPECT_EQ(1, temp_callback_count_); + host_->TranslationFinished(0, 0, true); + FlushQueues(); + EXPECT_EQ(0U, host_->pending_translations()); + GET_NEXE_FD(0, 0, false, info, true); + // Request the second before the first temp file returns. + GET_NEXE_FD(0, 1, false, info, true); + FlushQueues(); + EXPECT_EQ(3, temp_callback_count_); + EXPECT_EQ(0U, host_->pending_translations()); +} + +TEST_F(PnaclHostTest, OverlappedHitsAfterTempReturn) { + nacl::PnaclCacheInfo info = GetTestCacheInfo(); + // Store one in the cache and complete it. + GET_NEXE_FD(0, 0, false, info, false); + FlushQueues(); + EXPECT_EQ(1, temp_callback_count_); + host_->TranslationFinished(0, 0, true); + FlushQueues(); + EXPECT_EQ(0U, host_->pending_translations()); + GET_NEXE_FD(0, 0, false, info, true); + FlushQueues(); + GET_NEXE_FD(0, 1, false, info, true); + FlushQueues(); + EXPECT_EQ(3, temp_callback_count_); + EXPECT_EQ(0U, host_->pending_translations()); +} + +TEST_F(PnaclHostTest, OverlappedMissesRendererClosing) { + nacl::PnaclCacheInfo info = GetTestCacheInfo(); + GET_NEXE_FD(0, 0, false, info, false); + // Send the 2nd fd request from a different renderer. + // Test that it eventually gets an fd after the first renderer closes. + GET_NEXE_FD(1, 1, false, info, false); + FlushQueues(); + EXPECT_EQ(1, temp_callback_count_); + EXPECT_EQ(2U, host_->pending_translations()); + FlushQueues(); + EXPECT_EQ(2U, host_->pending_translations()); + EXPECT_EQ(1, temp_callback_count_); + host_->RendererClosing(0); + FlushQueues(); + EXPECT_EQ(2, temp_callback_count_); + EXPECT_EQ(1U, host_->pending_translations()); + host_->RendererClosing(1); +} + +TEST_F(PnaclHostTest, Incognito) { + nacl::PnaclCacheInfo info = GetTestCacheInfo(); + GET_NEXE_FD(0, 0, true, info, false); + FlushQueues(); + EXPECT_EQ(1, temp_callback_count_); + host_->TranslationFinished(0, 0, true); + FlushQueues(); + // Check that an incognito translation is not stored in the cache + GET_NEXE_FD(0, 0, false, info, false); + FlushQueues(); + EXPECT_EQ(2, temp_callback_count_); + host_->TranslationFinished(0, 0, true); + FlushQueues(); + // Check that an incognito translation can hit from a normal one. + GET_NEXE_FD(0, 0, true, info, true); + FlushQueues(); + EXPECT_EQ(3, temp_callback_count_); +} + +TEST_F(PnaclHostTest, IncognitoOverlappedMiss) { + nacl::PnaclCacheInfo info = GetTestCacheInfo(); + GET_NEXE_FD(0, 0, true, info, false); + GET_NEXE_FD(0, 1, false, info, false); + FlushQueues(); + // Check that both translations have returned misses, (i.e. that the + // second one has not blocked on the incognito one) + EXPECT_EQ(2, temp_callback_count_); + host_->TranslationFinished(0, 0, true); + host_->TranslationFinished(0, 1, true); + FlushQueues(); + EXPECT_EQ(0U, host_->pending_translations()); + + // Same test, but issue the 2nd request after the first has returned a miss. + info.abi_version = 222; + GET_NEXE_FD(0, 0, true, info, false); + FlushQueues(); + EXPECT_EQ(3, temp_callback_count_); + GET_NEXE_FD(0, 1, false, info, false); + FlushQueues(); + EXPECT_EQ(4, temp_callback_count_); + host_->RendererClosing(0); +} + +TEST_F(PnaclHostTest, IncognitoSecondOverlappedMiss) { + // If the non-incognito request comes first, it should + // behave exactly like OverlappedMissBeforeTempReturn + nacl::PnaclCacheInfo info = GetTestCacheInfo(); + GET_NEXE_FD(0, 0, false, info, false); + // Send the 2nd fd request before the first one returns a temp file. + GET_NEXE_FD(0, 1, true, info, true); + FlushQueues(); + EXPECT_EQ(1, temp_callback_count_); + EXPECT_EQ(2U, host_->pending_translations()); + FlushQueues(); + EXPECT_EQ(2U, host_->pending_translations()); + EXPECT_EQ(1, temp_callback_count_); + host_->TranslationFinished(0, 0, true); + FlushQueues(); + EXPECT_EQ(2, temp_callback_count_); + EXPECT_EQ(0U, host_->pending_translations()); +} + +// Test that pexes with the no-store header do not get cached. +TEST_F(PnaclHostTest, CacheControlNoStore) { + nacl::PnaclCacheInfo info = GetTestCacheInfo(); + info.has_no_store_header = true; + GET_NEXE_FD(0, 0, false, info, false); + FlushQueues(); + EXPECT_EQ(1, temp_callback_count_); + host_->TranslationFinished(0, 0, true); + FlushQueues(); + EXPECT_EQ(0U, host_->pending_translations()); + EXPECT_EQ(0, GetCacheSize()); +} + +// Test that no-store pexes do not wait, but do duplicate translations +TEST_F(PnaclHostTest, NoStoreOverlappedMiss) { + nacl::PnaclCacheInfo info = GetTestCacheInfo(); + info.has_no_store_header = true; + GET_NEXE_FD(0, 0, false, info, false); + GET_NEXE_FD(0, 1, false, info, false); + FlushQueues(); + // Check that both translations have returned misses, (i.e. that the + // second one has not blocked on the first one) + EXPECT_EQ(2, temp_callback_count_); + host_->TranslationFinished(0, 0, true); + host_->TranslationFinished(0, 1, true); + FlushQueues(); + EXPECT_EQ(0U, host_->pending_translations()); + + // Same test, but issue the 2nd request after the first has returned a miss. + info.abi_version = 222; + GET_NEXE_FD(0, 0, false, info, false); + FlushQueues(); + EXPECT_EQ(3, temp_callback_count_); + GET_NEXE_FD(0, 1, false, info, false); + FlushQueues(); + EXPECT_EQ(4, temp_callback_count_); + host_->RendererClosing(0); +} + +TEST_F(PnaclHostTest, ClearTranslationCache) { + nacl::PnaclCacheInfo info = GetTestCacheInfo(); + // Add 2 entries in the cache + GET_NEXE_FD(0, 0, false, info, false); + info.abi_version = 222; + GET_NEXE_FD(0, 1, false, info, false); + FlushQueues(); + EXPECT_EQ(2, temp_callback_count_); + host_->TranslationFinished(0, 0, true); + host_->TranslationFinished(0, 1, true); + FlushQueues(); + EXPECT_EQ(0U, host_->pending_translations()); + EXPECT_EQ(2, GetCacheSize()); + net::TestCompletionCallback cb; + // Since we are using a memory backend, the clear should happen immediately. + host_->ClearTranslationCacheEntriesBetween( + base::Time(), base::Time(), base::Bind(cb.callback(), 0)); + // Check that the translation cache has been cleared before flushing the + // queues, because the backend will be freed once it is. + EXPECT_EQ(0, GetCacheSize()); + EXPECT_EQ(0, cb.GetResult(net::ERR_IO_PENDING)); + // Now check that the backend has been freed. + EXPECT_FALSE(CacheIsInitialized()); +} + +} // namespace pnacl diff --git a/components/nacl/common/nacl_host_messages.cc b/components/nacl/common/nacl_host_messages.cc new file mode 100644 index 0000000..1b47272 --- /dev/null +++ b/components/nacl/common/nacl_host_messages.cc @@ -0,0 +1,34 @@ +// Copyright (c) 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. + +// Get basic type definitions. +#define IPC_MESSAGE_IMPL +#include "components/nacl/common/nacl_host_messages.h" + +// Generate constructors. +#include "ipc/struct_constructor_macros.h" +#include "components/nacl/common/nacl_host_messages.h" + +// Generate destructors. +#include "ipc/struct_destructor_macros.h" +#include "components/nacl/common/nacl_host_messages.h" + +// Generate param traits write methods. +#include "ipc/param_traits_write_macros.h" +namespace IPC { +#include "components/nacl/common/nacl_host_messages.h" +} // namespace IPC + +// Generate param traits read methods. +#include "ipc/param_traits_read_macros.h" +namespace IPC { +#include "components/nacl/common/nacl_host_messages.h" +} // namespace IPC + +// Generate param traits log methods. +#include "ipc/param_traits_log_macros.h" +namespace IPC { +#include "components/nacl/common/nacl_host_messages.h" +} // namespace IPC + diff --git a/components/nacl/common/nacl_switches.cc b/components/nacl/common/nacl_switches.cc index 0dfdc94..928b214 100644 --- a/components/nacl/common/nacl_switches.cc +++ b/components/nacl/common/nacl_switches.cc @@ -36,4 +36,10 @@ const char kNaClLoaderCmdPrefix[] = "nacl-loader-cmd-prefix"; // Causes the process to run as a NativeClient loader. const char kNaClLoaderProcess[] = "nacl-loader"; +// Disables crash throttling for Portable Native Client. +const char kDisablePnaclCrashThrottling[] = "disable-pnacl-crash-throttling"; + +// Disables the installation of Portable Native Client. +const char kDisablePnaclInstall[] = "disable-pnacl-install"; + } // namespace switches diff --git a/components/nacl/common/nacl_switches.h b/components/nacl/common/nacl_switches.h index 9bc1bcb..8791d47 100644 --- a/components/nacl/common/nacl_switches.h +++ b/components/nacl/common/nacl_switches.h @@ -18,6 +18,8 @@ extern const char kNaClGdb[]; extern const char kNaClGdbScript[]; extern const char kNaClLoaderCmdPrefix[]; extern const char kNaClLoaderProcess[]; +extern const char kDisablePnaclCrashThrottling[]; +extern const char kDisablePnaclInstall[]; } // namespace switches diff --git a/components/nacl_common.gyp b/components/nacl_common.gyp index 960eb1c..6865869 100644 --- a/components/nacl_common.gyp +++ b/components/nacl_common.gyp @@ -21,8 +21,12 @@ 'sources': [ 'nacl/common/nacl_cmd_line.cc', 'nacl/common/nacl_cmd_line.h', + 'nacl/common/nacl_host_messages.h', + 'nacl/common/nacl_host_messages.cc', 'nacl/common/nacl_messages.cc', 'nacl/common/nacl_messages.h', + 'nacl/common/nacl_process_type.h', + 'nacl/common/nacl_sandbox_type_mac.h', 'nacl/common/nacl_types.cc', 'nacl/common/nacl_types.h', 'nacl/common/pnacl_types.cc', |