| // 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/renderer/ppb_nacl_private.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <numeric> |
| #include <string> |
| #include <unordered_map> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/command_line.h" |
| #include "base/cpu.h" |
| #include "base/files/file.h" |
| #include "base/json/json_reader.h" |
| #include "base/lazy_instance.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/process/process_handle.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "components/nacl/common/nacl_host_messages.h" |
| #include "components/nacl/common/nacl_messages.h" |
| #include "components/nacl/common/nacl_nonsfi_util.h" |
| #include "components/nacl/common/nacl_switches.h" |
| #include "components/nacl/common/nacl_types.h" |
| #include "components/nacl/renderer/file_downloader.h" |
| #include "components/nacl/renderer/histogram.h" |
| #include "components/nacl/renderer/json_manifest.h" |
| #include "components/nacl/renderer/manifest_downloader.h" |
| #include "components/nacl/renderer/manifest_service_channel.h" |
| #include "components/nacl/renderer/nexe_load_manager.h" |
| #include "components/nacl/renderer/platform_info.h" |
| #include "components/nacl/renderer/pnacl_translation_resource_host.h" |
| #include "components/nacl/renderer/progress_event.h" |
| #include "components/nacl/renderer/trusted_plugin_channel.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/renderer/pepper_plugin_instance.h" |
| #include "content/public/renderer/render_thread.h" |
| #include "content/public/renderer/render_view.h" |
| #include "content/public/renderer/renderer_ppapi_host.h" |
| #include "net/base/data_url.h" |
| #include "net/base/net_errors.h" |
| #include "net/http/http_util.h" |
| #include "ppapi/c/pp_bool.h" |
| #include "ppapi/c/private/pp_file_handle.h" |
| #include "ppapi/shared_impl/ppapi_globals.h" |
| #include "ppapi/shared_impl/ppapi_permissions.h" |
| #include "ppapi/shared_impl/ppapi_preferences.h" |
| #include "ppapi/shared_impl/var.h" |
| #include "ppapi/shared_impl/var_tracker.h" |
| #include "ppapi/thunk/enter.h" |
| #include "third_party/WebKit/public/platform/WebSecurityOrigin.h" |
| #include "third_party/WebKit/public/platform/WebURLRequest.h" |
| #include "third_party/WebKit/public/platform/WebURLResponse.h" |
| #include "third_party/WebKit/public/web/WebAssociatedURLLoader.h" |
| #include "third_party/WebKit/public/web/WebAssociatedURLLoaderClient.h" |
| #include "third_party/WebKit/public/web/WebDocument.h" |
| #include "third_party/WebKit/public/web/WebLocalFrame.h" |
| #include "third_party/WebKit/public/web/WebPluginContainer.h" |
| |
| #if defined(OS_WIN) |
| #include "base/win/scoped_handle.h" |
| #endif |
| |
| namespace nacl { |
| namespace { |
| |
| // The pseudo-architecture used to indicate portable native client. |
| const char* const kPortableArch = "portable"; |
| |
| // The base URL for resources used by the PNaCl translator processes. |
| const char* kPNaClTranslatorBaseUrl = "chrome://pnacl-translator/"; |
| |
| base::LazyInstance<scoped_refptr<PnaclTranslationResourceHost>>:: |
| DestructorAtExit g_pnacl_resource_host = LAZY_INSTANCE_INITIALIZER; |
| |
| bool InitializePnaclResourceHost() { |
| // Must run on the main thread. |
| content::RenderThread* render_thread = content::RenderThread::Get(); |
| if (!render_thread) |
| return false; |
| if (!g_pnacl_resource_host.Get().get()) { |
| g_pnacl_resource_host.Get() = |
| new PnaclTranslationResourceHost(render_thread->GetIOTaskRunner()); |
| render_thread->AddFilter(g_pnacl_resource_host.Get().get()); |
| } |
| return true; |
| } |
| |
| bool CanOpenViaFastPath(content::PepperPluginInstance* plugin_instance, |
| const GURL& gurl) { |
| // Fast path only works for installed file URLs. |
| if (!gurl.SchemeIs("chrome-extension")) |
| return PP_kInvalidFileHandle; |
| |
| // IMPORTANT: Make sure the document can request the given URL. If we don't |
| // check, a malicious app could probe the extension system. This enforces a |
| // same-origin policy which prevents the app from requesting resources from |
| // another app. |
| blink::WebSecurityOrigin security_origin = |
| plugin_instance->GetContainer()->GetDocument().GetSecurityOrigin(); |
| return security_origin.CanRequest(gurl); |
| } |
| |
| // This contains state that is produced by LaunchSelLdr() and consumed |
| // by StartPpapiProxy(). |
| struct InstanceInfo { |
| InstanceInfo() : plugin_pid(base::kNullProcessId), plugin_child_id(0) {} |
| GURL url; |
| ppapi::PpapiPermissions permissions; |
| base::ProcessId plugin_pid; |
| int plugin_child_id; |
| IPC::ChannelHandle channel_handle; |
| }; |
| |
| class NaClPluginInstance { |
| public: |
| explicit NaClPluginInstance(PP_Instance instance) |
| : nexe_load_manager(instance), pexe_size(0) {} |
| ~NaClPluginInstance() { |
| // Make sure that we do not leak a mojo handle if the NaCl loader |
| // process never called ppapi_start() to initialize PPAPI. |
| if (instance_info) { |
| DCHECK(instance_info->channel_handle.is_mojo_channel_handle()); |
| instance_info->channel_handle.mojo_handle.Close(); |
| } |
| } |
| |
| NexeLoadManager nexe_load_manager; |
| std::unique_ptr<JsonManifest> json_manifest; |
| std::unique_ptr<InstanceInfo> instance_info; |
| |
| // When translation is complete, this records the size of the pexe in |
| // bytes so that it can be reported in a later load event. |
| uint64_t pexe_size; |
| }; |
| |
| typedef std::unordered_map<PP_Instance, std::unique_ptr<NaClPluginInstance>> |
| InstanceMap; |
| base::LazyInstance<InstanceMap>::DestructorAtExit g_instance_map = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| NaClPluginInstance* GetNaClPluginInstance(PP_Instance instance) { |
| InstanceMap& map = g_instance_map.Get(); |
| auto iter = map.find(instance); |
| if (iter == map.end()) |
| return NULL; |
| return iter->second.get(); |
| } |
| |
| NexeLoadManager* GetNexeLoadManager(PP_Instance instance) { |
| NaClPluginInstance* nacl_plugin_instance = GetNaClPluginInstance(instance); |
| if (!nacl_plugin_instance) |
| return NULL; |
| return &nacl_plugin_instance->nexe_load_manager; |
| } |
| |
| JsonManifest* GetJsonManifest(PP_Instance instance) { |
| NaClPluginInstance* nacl_plugin_instance = GetNaClPluginInstance(instance); |
| if (!nacl_plugin_instance) |
| return NULL; |
| return nacl_plugin_instance->json_manifest.get(); |
| } |
| |
| static const PP_NaClFileInfo kInvalidNaClFileInfo = { |
| PP_kInvalidFileHandle, |
| 0, // token_lo |
| 0, // token_hi |
| }; |
| |
| int GetRoutingID(PP_Instance instance) { |
| // Check that we are on the main renderer thread. |
| DCHECK(content::RenderThread::Get()); |
| content::RendererPpapiHost* host = |
| content::RendererPpapiHost::GetForPPInstance(instance); |
| if (!host) |
| return 0; |
| return host->GetRoutingIDForWidget(instance); |
| } |
| |
| // Returns whether the channel_handle is valid or not. |
| bool IsValidChannelHandle(const IPC::ChannelHandle& channel_handle) { |
| DCHECK(channel_handle.is_mojo_channel_handle()); |
| return channel_handle.is_mojo_channel_handle(); |
| } |
| |
| void PostPPCompletionCallback(PP_CompletionCallback callback, |
| int32_t status) { |
| ppapi::PpapiGlobals::Get()->GetMainThreadMessageLoop()->PostTask( |
| FROM_HERE, |
| base::Bind(callback.func, callback.user_data, status)); |
| } |
| |
| bool ManifestResolveKey(PP_Instance instance, |
| bool is_helper_process, |
| const std::string& key, |
| std::string* full_url, |
| PP_PNaClOptions* pnacl_options); |
| |
| typedef base::Callback<void(int32_t, const PP_NaClFileInfo&)> |
| DownloadFileCallback; |
| |
| void DownloadFile(PP_Instance instance, |
| const std::string& url, |
| const DownloadFileCallback& callback); |
| |
| PP_Bool StartPpapiProxy(PP_Instance instance); |
| |
| // Thin adapter from PPP_ManifestService to ManifestServiceChannel::Delegate. |
| // Note that user_data is managed by the caller of LaunchSelLdr. Please see |
| // also PP_ManifestService's comment for more details about resource |
| // management. |
| class ManifestServiceProxy : public ManifestServiceChannel::Delegate { |
| public: |
| ManifestServiceProxy(PP_Instance pp_instance, NaClAppProcessType process_type) |
| : pp_instance_(pp_instance), process_type_(process_type) {} |
| |
| ~ManifestServiceProxy() override {} |
| |
| void StartupInitializationComplete() override { |
| if (StartPpapiProxy(pp_instance_) == PP_TRUE) { |
| NaClPluginInstance* nacl_plugin_instance = |
| GetNaClPluginInstance(pp_instance_); |
| JsonManifest* manifest = GetJsonManifest(pp_instance_); |
| if (nacl_plugin_instance && manifest) { |
| NexeLoadManager* load_manager = |
| &nacl_plugin_instance->nexe_load_manager; |
| std::string full_url; |
| PP_PNaClOptions pnacl_options; |
| bool uses_nonsfi_mode; |
| JsonManifest::ErrorInfo error_info; |
| if (manifest->GetProgramURL(&full_url, |
| &pnacl_options, |
| &uses_nonsfi_mode, |
| &error_info)) { |
| int64_t exe_size = nacl_plugin_instance->pexe_size; |
| if (exe_size == 0) |
| exe_size = load_manager->nexe_size(); |
| load_manager->ReportLoadSuccess(full_url, exe_size, exe_size); |
| } |
| } |
| } |
| } |
| |
| void OpenResource( |
| const std::string& key, |
| const ManifestServiceChannel::OpenResourceCallback& callback) override { |
| DCHECK(ppapi::PpapiGlobals::Get()->GetMainThreadMessageLoop()-> |
| BelongsToCurrentThread()); |
| |
| // For security hardening, disable open_resource() when it is isn't |
| // needed. PNaCl pexes can't use open_resource(), but general nexes |
| // and the PNaCl translator nexes may use it. |
| if (process_type_ != kNativeNaClProcessType && |
| process_type_ != kPNaClTranslatorProcessType) { |
| // Return an error. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(callback, base::Passed(base::File()), 0, 0)); |
| return; |
| } |
| |
| std::string url; |
| // TODO(teravest): Clean up pnacl_options logic in JsonManifest so we don't |
| // have to initialize it like this here. |
| PP_PNaClOptions pnacl_options; |
| pnacl_options.translate = PP_FALSE; |
| pnacl_options.is_debug = PP_FALSE; |
| pnacl_options.use_subzero = PP_FALSE; |
| pnacl_options.opt_level = 2; |
| bool is_helper_process = process_type_ == kPNaClTranslatorProcessType; |
| if (!ManifestResolveKey(pp_instance_, is_helper_process, key, &url, |
| &pnacl_options)) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(callback, base::Passed(base::File()), 0, 0)); |
| return; |
| } |
| |
| // We have to call DidDownloadFile, even if this object is destroyed, so |
| // that the handle inside PP_NaClFileInfo isn't leaked. This means that the |
| // callback passed to this function shouldn't have a weak pointer to an |
| // object either. |
| // |
| // TODO(teravest): Make a type like PP_NaClFileInfo to use for DownloadFile |
| // that would close the file handle on destruction. |
| DownloadFile(pp_instance_, url, |
| base::Bind(&ManifestServiceProxy::DidDownloadFile, callback)); |
| } |
| |
| private: |
| static void DidDownloadFile( |
| ManifestServiceChannel::OpenResourceCallback callback, |
| int32_t pp_error, |
| const PP_NaClFileInfo& file_info) { |
| if (pp_error != PP_OK) { |
| callback.Run(base::File(), 0, 0); |
| return; |
| } |
| callback.Run(base::File(file_info.handle), |
| file_info.token_lo, |
| file_info.token_hi); |
| } |
| |
| PP_Instance pp_instance_; |
| NaClAppProcessType process_type_; |
| DISALLOW_COPY_AND_ASSIGN(ManifestServiceProxy); |
| }; |
| |
| blink::WebAssociatedURLLoader* CreateAssociatedURLLoader( |
| const blink::WebDocument& document, |
| const GURL& gurl) { |
| blink::WebAssociatedURLLoaderOptions options; |
| options.untrusted_http = true; |
| return document.GetFrame()->CreateAssociatedURLLoader(options); |
| } |
| |
| blink::WebURLRequest CreateWebURLRequest(const blink::WebDocument& document, |
| const GURL& gurl) { |
| blink::WebURLRequest request(gurl); |
| request.SetSiteForCookies(document.SiteForCookies()); |
| |
| // Follow the original behavior in the trusted plugin and |
| // PepperURLLoaderHost. |
| if (document.GetSecurityOrigin().CanRequest(gurl)) { |
| request.SetFetchRequestMode(network::mojom::FetchRequestMode::kSameOrigin); |
| request.SetFetchCredentialsMode( |
| network::mojom::FetchCredentialsMode::kSameOrigin); |
| } else { |
| request.SetFetchRequestMode(network::mojom::FetchRequestMode::kCORS); |
| request.SetFetchCredentialsMode( |
| network::mojom::FetchCredentialsMode::kOmit); |
| } |
| |
| // Plug-ins should not load via service workers as plug-ins may have their own |
| // origin checking logic that may get confused if service workers respond with |
| // resources from another origin. |
| // https://w3c.github.io/ServiceWorker/#implementer-concerns |
| request.SetServiceWorkerMode(blink::WebURLRequest::ServiceWorkerMode::kNone); |
| |
| return request; |
| } |
| |
| int32_t FileDownloaderToPepperError(FileDownloader::Status status) { |
| switch (status) { |
| case FileDownloader::SUCCESS: |
| return PP_OK; |
| case FileDownloader::ACCESS_DENIED: |
| return PP_ERROR_NOACCESS; |
| case FileDownloader::FAILED: |
| return PP_ERROR_FAILED; |
| // No default case, to catch unhandled Status values. |
| } |
| return PP_ERROR_FAILED; |
| } |
| |
| NaClAppProcessType PP_ToNaClAppProcessType( |
| PP_NaClAppProcessType pp_process_type) { |
| #define STATICALLY_CHECK_NACLAPPPROCESSTYPE_EQ(pp, nonpp) \ |
| static_assert(static_cast<int>(pp) == static_cast<int>(nonpp), \ |
| "PP_NaClAppProcessType differs from NaClAppProcessType"); |
| STATICALLY_CHECK_NACLAPPPROCESSTYPE_EQ(PP_UNKNOWN_NACL_PROCESS_TYPE, |
| kUnknownNaClProcessType); |
| STATICALLY_CHECK_NACLAPPPROCESSTYPE_EQ(PP_NATIVE_NACL_PROCESS_TYPE, |
| kNativeNaClProcessType); |
| STATICALLY_CHECK_NACLAPPPROCESSTYPE_EQ(PP_PNACL_PROCESS_TYPE, |
| kPNaClProcessType); |
| STATICALLY_CHECK_NACLAPPPROCESSTYPE_EQ(PP_PNACL_TRANSLATOR_PROCESS_TYPE, |
| kPNaClTranslatorProcessType); |
| STATICALLY_CHECK_NACLAPPPROCESSTYPE_EQ(PP_NUM_NACL_PROCESS_TYPES, |
| kNumNaClProcessTypes); |
| #undef STATICALLY_CHECK_NACLAPPPROCESSTYPE_EQ |
| DCHECK(pp_process_type > PP_UNKNOWN_NACL_PROCESS_TYPE && |
| pp_process_type < PP_NUM_NACL_PROCESS_TYPES); |
| return static_cast<NaClAppProcessType>(pp_process_type); |
| } |
| |
| // A dummy IPC::Listener object with a no-op message handler. We use |
| // this with an IPC::SyncChannel where we only send synchronous |
| // messages and don't need to handle any messages other than sync |
| // replies. |
| class NoOpListener : public IPC::Listener { |
| public: |
| bool OnMessageReceived(const IPC::Message& message) override { return false; } |
| void OnChannelError() override {} |
| }; |
| |
| } // namespace |
| |
| // Launch NaCl's sel_ldr process. |
| // static |
| void PPBNaClPrivate::LaunchSelLdr( |
| PP_Instance instance, |
| PP_Bool main_service_runtime, |
| const char* alleged_url, |
| const PP_NaClFileInfo* nexe_file_info, |
| PP_Bool uses_nonsfi_mode, |
| PP_NaClAppProcessType pp_process_type, |
| std::unique_ptr<IPC::SyncChannel>* translator_channel, |
| PP_CompletionCallback callback) { |
| CHECK(ppapi::PpapiGlobals::Get()->GetMainThreadMessageLoop()-> |
| BelongsToCurrentThread()); |
| NaClAppProcessType process_type = PP_ToNaClAppProcessType(pp_process_type); |
| // Create the manifest service proxy here, so on error case, it will be |
| // destructed (without passing it to ManifestServiceChannel). |
| std::unique_ptr<ManifestServiceChannel::Delegate> manifest_service_proxy( |
| new ManifestServiceProxy(instance, process_type)); |
| |
| IPC::Sender* sender = content::RenderThread::Get(); |
| DCHECK(sender); |
| int routing_id = GetRoutingID(instance); |
| NexeLoadManager* load_manager = GetNexeLoadManager(instance); |
| DCHECK(load_manager); |
| content::PepperPluginInstance* plugin_instance = |
| content::PepperPluginInstance::Get(instance); |
| DCHECK(plugin_instance); |
| if (!routing_id || !load_manager || !plugin_instance) { |
| if (nexe_file_info->handle != PP_kInvalidFileHandle) { |
| base::File closer(nexe_file_info->handle); |
| } |
| ppapi::PpapiGlobals::Get()->GetMainThreadMessageLoop()->PostTask( |
| FROM_HERE, base::Bind(callback.func, callback.user_data, |
| static_cast<int32_t>(PP_ERROR_FAILED))); |
| return; |
| } |
| |
| InstanceInfo instance_info; |
| instance_info.url = GURL(alleged_url); |
| |
| uint32_t perm_bits = ppapi::PERMISSION_NONE; |
| // Conditionally block 'Dev' interfaces. We do this for the NaCl process, so |
| // it's clearer to developers when they are using 'Dev' inappropriately. We |
| // must also check on the trusted side of the proxy. |
| if (load_manager->DevInterfacesEnabled()) |
| perm_bits |= ppapi::PERMISSION_DEV; |
| instance_info.permissions = |
| ppapi::PpapiPermissions::GetForCommandLine(perm_bits); |
| |
| std::vector<NaClResourcePrefetchRequest> resource_prefetch_request_list; |
| if (process_type == kNativeNaClProcessType) { |
| JsonManifest* manifest = GetJsonManifest(instance); |
| if (manifest) { |
| manifest->GetPrefetchableFiles(&resource_prefetch_request_list); |
| |
| for (size_t i = 0; i < resource_prefetch_request_list.size(); ++i) { |
| const GURL gurl(resource_prefetch_request_list[i].resource_url); |
| // Important security check. Do not remove. |
| if (!CanOpenViaFastPath(plugin_instance, gurl)) { |
| resource_prefetch_request_list.clear(); |
| break; |
| } |
| } |
| } |
| } |
| |
| IPC::PlatformFileForTransit nexe_for_transit = |
| IPC::InvalidPlatformFileForTransit(); |
| #if defined(OS_POSIX) |
| if (nexe_file_info->handle != PP_kInvalidFileHandle) |
| nexe_for_transit = base::FileDescriptor(nexe_file_info->handle, true); |
| #elif defined(OS_WIN) |
| nexe_for_transit = IPC::PlatformFileForTransit(nexe_file_info->handle); |
| #else |
| # error Unsupported target platform. |
| #endif |
| |
| std::string error_message_string; |
| NaClLaunchResult launch_result; |
| if (!sender->Send(new NaClHostMsg_LaunchNaCl( |
| NaClLaunchParams( |
| instance_info.url.spec(), |
| nexe_for_transit, |
| nexe_file_info->token_lo, |
| nexe_file_info->token_hi, |
| resource_prefetch_request_list, |
| routing_id, |
| perm_bits, |
| PP_ToBool(uses_nonsfi_mode), |
| process_type), |
| &launch_result, |
| &error_message_string))) { |
| ppapi::PpapiGlobals::Get()->GetMainThreadMessageLoop()->PostTask( |
| FROM_HERE, |
| base::Bind(callback.func, callback.user_data, |
| static_cast<int32_t>(PP_ERROR_FAILED))); |
| return; |
| } |
| |
| load_manager->set_nonsfi(PP_ToBool(uses_nonsfi_mode)); |
| |
| if (!error_message_string.empty()) { |
| // Even on error, some FDs/handles may be passed to here. |
| // We must release those resources. |
| // See also nacl_process_host.cc. |
| if (base::SharedMemory::IsHandleValid( |
| launch_result.crash_info_shmem_handle)) |
| base::SharedMemory::CloseHandle(launch_result.crash_info_shmem_handle); |
| |
| if (PP_ToBool(main_service_runtime)) { |
| load_manager->ReportLoadError(PP_NACL_ERROR_SEL_LDR_LAUNCH, |
| "ServiceRuntime: failed to start", |
| error_message_string); |
| } |
| ppapi::PpapiGlobals::Get()->GetMainThreadMessageLoop()->PostTask( |
| FROM_HERE, |
| base::Bind(callback.func, callback.user_data, |
| static_cast<int32_t>(PP_ERROR_FAILED))); |
| return; |
| } |
| |
| instance_info.channel_handle = launch_result.ppapi_ipc_channel_handle; |
| instance_info.plugin_pid = launch_result.plugin_pid; |
| instance_info.plugin_child_id = launch_result.plugin_child_id; |
| |
| // Don't save instance_info if channel handle is invalid. |
| if (IsValidChannelHandle(instance_info.channel_handle)) { |
| if (process_type == kPNaClTranslatorProcessType) { |
| // Return an IPC channel which allows communicating with a PNaCl |
| // translator process. |
| *translator_channel = IPC::SyncChannel::Create( |
| instance_info.channel_handle, IPC::Channel::MODE_CLIENT, |
| new NoOpListener, content::RenderThread::Get()->GetIOTaskRunner(), |
| base::ThreadTaskRunnerHandle::Get(), true, |
| content::RenderThread::Get()->GetShutdownEvent()); |
| } else { |
| // Save the channel handle for when StartPpapiProxy() is called. |
| NaClPluginInstance* nacl_plugin_instance = |
| GetNaClPluginInstance(instance); |
| nacl_plugin_instance->instance_info.reset( |
| new InstanceInfo(instance_info)); |
| } |
| } |
| |
| // Store the crash information shared memory handle. |
| load_manager->set_crash_info_shmem_handle( |
| launch_result.crash_info_shmem_handle); |
| |
| // Create the trusted plugin channel. |
| if (!IsValidChannelHandle(launch_result.trusted_ipc_channel_handle)) { |
| PostPPCompletionCallback(callback, PP_ERROR_FAILED); |
| return; |
| } |
| bool is_helper_nexe = !PP_ToBool(main_service_runtime); |
| std::unique_ptr<TrustedPluginChannel> trusted_plugin_channel( |
| new TrustedPluginChannel( |
| load_manager, |
| mojom::NaClRendererHostRequest(mojo::ScopedMessagePipeHandle( |
| launch_result.trusted_ipc_channel_handle.mojo_handle)), |
| is_helper_nexe)); |
| load_manager->set_trusted_plugin_channel(std::move(trusted_plugin_channel)); |
| |
| // Create the manifest service handle as well. |
| if (IsValidChannelHandle(launch_result.manifest_service_ipc_channel_handle)) { |
| std::unique_ptr<ManifestServiceChannel> manifest_service_channel( |
| new ManifestServiceChannel( |
| launch_result.manifest_service_ipc_channel_handle, |
| base::Bind(&PostPPCompletionCallback, callback), |
| std::move(manifest_service_proxy), |
| content::RenderThread::Get()->GetShutdownEvent())); |
| load_manager->set_manifest_service_channel( |
| std::move(manifest_service_channel)); |
| } |
| } |
| |
| namespace { |
| |
| PP_Bool StartPpapiProxy(PP_Instance instance) { |
| NexeLoadManager* load_manager = GetNexeLoadManager(instance); |
| DCHECK(load_manager); |
| if (!load_manager) |
| return PP_FALSE; |
| |
| content::PepperPluginInstance* plugin_instance = |
| content::PepperPluginInstance::Get(instance); |
| if (!plugin_instance) { |
| DLOG(ERROR) << "GetInstance() failed"; |
| return PP_FALSE; |
| } |
| |
| NaClPluginInstance* nacl_plugin_instance = GetNaClPluginInstance(instance); |
| if (!nacl_plugin_instance->instance_info) { |
| DLOG(ERROR) << "Could not find instance ID"; |
| return PP_FALSE; |
| } |
| std::unique_ptr<InstanceInfo> instance_info = |
| std::move(nacl_plugin_instance->instance_info); |
| |
| PP_ExternalPluginResult result = plugin_instance->SwitchToOutOfProcessProxy( |
| base::FilePath().AppendASCII(instance_info->url.spec()), |
| instance_info->permissions, |
| instance_info->channel_handle, |
| instance_info->plugin_pid, |
| instance_info->plugin_child_id); |
| |
| if (result == PP_EXTERNAL_PLUGIN_OK) { |
| // Log the amound of time that has passed between the trusted plugin being |
| // initialized and the untrusted plugin being initialized. This is |
| // (roughly) the cost of using NaCl, in terms of startup time. |
| load_manager->ReportStartupOverhead(); |
| return PP_TRUE; |
| } |
| if (result == PP_EXTERNAL_PLUGIN_ERROR_MODULE) { |
| load_manager->ReportLoadError(PP_NACL_ERROR_START_PROXY_MODULE, |
| "could not initialize module."); |
| } else if (result == PP_EXTERNAL_PLUGIN_ERROR_INSTANCE) { |
| load_manager->ReportLoadError(PP_NACL_ERROR_START_PROXY_MODULE, |
| "could not create instance."); |
| } |
| return PP_FALSE; |
| } |
| |
| // Convert a URL to a filename for GetReadonlyPnaclFd. |
| // Must be kept in sync with PnaclCanOpenFile() in |
| // components/nacl/browser/nacl_file_host.cc. |
| std::string PnaclComponentURLToFilename(const std::string& url) { |
| // PNaCl component URLs aren't arbitrary URLs; they are always either |
| // generated from ManifestResolveKey or PnaclResources::ReadResourceInfo. |
| // So, it's safe to just use string parsing operations here instead of |
| // URL-parsing ones. |
| DCHECK(base::StartsWith(url, kPNaClTranslatorBaseUrl, |
| base::CompareCase::SENSITIVE)); |
| std::string r = url.substr(std::string(kPNaClTranslatorBaseUrl).length()); |
| |
| // Use white-listed-chars. |
| size_t replace_pos; |
| static const char kWhiteList[] = "abcdefghijklmnopqrstuvwxyz0123456789_"; |
| replace_pos = r.find_first_not_of(kWhiteList); |
| while (replace_pos != std::string::npos) { |
| r = r.replace(replace_pos, 1, "_"); |
| replace_pos = r.find_first_not_of(kWhiteList); |
| } |
| return r; |
| } |
| |
| PP_FileHandle GetReadonlyPnaclFd(const char* url, |
| bool is_executable, |
| uint64_t* nonce_lo, |
| uint64_t* nonce_hi) { |
| std::string filename = PnaclComponentURLToFilename(url); |
| IPC::PlatformFileForTransit out_fd = IPC::InvalidPlatformFileForTransit(); |
| IPC::Sender* sender = content::RenderThread::Get(); |
| DCHECK(sender); |
| if (!sender->Send(new NaClHostMsg_GetReadonlyPnaclFD( |
| std::string(filename), is_executable, |
| &out_fd, nonce_lo, nonce_hi))) { |
| return PP_kInvalidFileHandle; |
| } |
| if (out_fd == IPC::InvalidPlatformFileForTransit()) { |
| return PP_kInvalidFileHandle; |
| } |
| return IPC::PlatformFileForTransitToPlatformFile(out_fd); |
| } |
| |
| } // namespace |
| |
| // static |
| void PPBNaClPrivate::GetReadExecPnaclFd(const char* url, |
| PP_NaClFileInfo* out_file_info) { |
| *out_file_info = kInvalidNaClFileInfo; |
| out_file_info->handle = GetReadonlyPnaclFd(url, true /* is_executable */, |
| &out_file_info->token_lo, |
| &out_file_info->token_hi); |
| } |
| |
| // static |
| PP_FileHandle PPBNaClPrivate::CreateTemporaryFile(PP_Instance instance) { |
| IPC::PlatformFileForTransit transit_fd = IPC::InvalidPlatformFileForTransit(); |
| IPC::Sender* sender = content::RenderThread::Get(); |
| DCHECK(sender); |
| if (!sender->Send(new NaClHostMsg_NaClCreateTemporaryFile( |
| &transit_fd))) { |
| return PP_kInvalidFileHandle; |
| } |
| |
| if (transit_fd == IPC::InvalidPlatformFileForTransit()) { |
| return PP_kInvalidFileHandle; |
| } |
| |
| return IPC::PlatformFileForTransitToPlatformFile(transit_fd); |
| } |
| |
| // static |
| int32_t PPBNaClPrivate::GetNumberOfProcessors() { |
| IPC::Sender* sender = content::RenderThread::Get(); |
| DCHECK(sender); |
| int32_t num_processors = 1; |
| return sender->Send(new NaClHostMsg_NaClGetNumProcessors(&num_processors)) ? |
| num_processors : 1; |
| } |
| |
| namespace { |
| |
| void GetNexeFd(PP_Instance instance, |
| const std::string& pexe_url, |
| uint32_t opt_level, |
| const base::Time& last_modified_time, |
| const std::string& etag, |
| bool has_no_store_header, |
| bool use_subzero, |
| base::Callback<void(int32_t, bool, PP_FileHandle)> callback) { |
| if (!InitializePnaclResourceHost()) { |
| ppapi::PpapiGlobals::Get()->GetMainThreadMessageLoop()->PostTask( |
| FROM_HERE, |
| base::Bind(callback, |
| static_cast<int32_t>(PP_ERROR_FAILED), |
| false, |
| PP_kInvalidFileHandle)); |
| return; |
| } |
| |
| PnaclCacheInfo cache_info; |
| cache_info.pexe_url = GURL(pexe_url); |
| // TODO(dschuff): Get this value from the pnacl json file after it |
| // rolls in from NaCl. |
| cache_info.abi_version = 1; |
| cache_info.opt_level = opt_level; |
| cache_info.last_modified = last_modified_time; |
| cache_info.etag = etag; |
| cache_info.has_no_store_header = has_no_store_header; |
| cache_info.use_subzero = use_subzero; |
| cache_info.sandbox_isa = GetSandboxArch(); |
| cache_info.extra_flags = GetCpuFeatures(); |
| |
| g_pnacl_resource_host.Get()->RequestNexeFd( |
| GetRoutingID(instance), |
| instance, |
| cache_info, |
| callback); |
| } |
| |
| void LogTranslationFinishedUMA(const std::string& uma_suffix, |
| int32_t opt_level, |
| int32_t unknown_opt_level, |
| int64_t nexe_size, |
| int64_t pexe_size, |
| int64_t compile_time_us, |
| base::TimeDelta total_time) { |
| HistogramEnumerate("NaCl.Options.PNaCl.OptLevel" + uma_suffix, opt_level, |
| unknown_opt_level + 1); |
| HistogramKBPerSec("NaCl.Perf.PNaClLoadTime.CompileKBPerSec" + uma_suffix, |
| pexe_size / 1024, compile_time_us); |
| HistogramSizeKB("NaCl.Perf.Size.PNaClTranslatedNexe" + uma_suffix, |
| nexe_size / 1024); |
| HistogramSizeKB("NaCl.Perf.Size.Pexe" + uma_suffix, pexe_size / 1024); |
| HistogramRatio("NaCl.Perf.Size.PexeNexeSizePct" + uma_suffix, pexe_size, |
| nexe_size); |
| HistogramTimeTranslation( |
| "NaCl.Perf.PNaClLoadTime.TotalUncachedTime" + uma_suffix, |
| total_time.InMilliseconds()); |
| HistogramKBPerSec( |
| "NaCl.Perf.PNaClLoadTime.TotalUncachedKBPerSec" + uma_suffix, |
| pexe_size / 1024, total_time.InMicroseconds()); |
| } |
| |
| } // namespace |
| |
| // static |
| void PPBNaClPrivate::ReportTranslationFinished(PP_Instance instance, |
| PP_Bool success, |
| int32_t opt_level, |
| PP_Bool use_subzero, |
| int64_t nexe_size, |
| int64_t pexe_size, |
| int64_t compile_time_us) { |
| NexeLoadManager* load_manager = GetNexeLoadManager(instance); |
| DCHECK(load_manager); |
| if (success == PP_TRUE && load_manager) { |
| base::TimeDelta total_time = |
| base::Time::Now() - load_manager->pnacl_start_time(); |
| static const int32_t kUnknownOptLevel = 4; |
| if (opt_level < 0 || opt_level > 3) |
| opt_level = kUnknownOptLevel; |
| // Log twice: once to cover all PNaCl UMA, and then a second |
| // time with the more specific UMA (Subzero vs LLC). |
| std::string uma_suffix(use_subzero ? ".Subzero" : ".LLC"); |
| LogTranslationFinishedUMA("", opt_level, kUnknownOptLevel, nexe_size, |
| pexe_size, compile_time_us, total_time); |
| LogTranslationFinishedUMA(uma_suffix, opt_level, kUnknownOptLevel, |
| nexe_size, pexe_size, compile_time_us, |
| total_time); |
| } |
| |
| // If the resource host isn't initialized, don't try to do that here. |
| // Just return because something is already very wrong. |
| if (g_pnacl_resource_host.Get().get() == NULL) |
| return; |
| g_pnacl_resource_host.Get()->ReportTranslationFinished(instance, success); |
| |
| // Record the pexe size for reporting in a later load event. |
| NaClPluginInstance* nacl_plugin_instance = GetNaClPluginInstance(instance); |
| if (nacl_plugin_instance) { |
| nacl_plugin_instance->pexe_size = pexe_size; |
| } |
| } |
| |
| namespace { |
| |
| PP_FileHandle OpenNaClExecutable(PP_Instance instance, |
| const char* file_url, |
| uint64_t* nonce_lo, |
| uint64_t* nonce_hi) { |
| NexeLoadManager* load_manager = GetNexeLoadManager(instance); |
| DCHECK(load_manager); |
| if (!load_manager) |
| return PP_kInvalidFileHandle; |
| |
| content::PepperPluginInstance* plugin_instance = |
| content::PepperPluginInstance::Get(instance); |
| if (!plugin_instance) |
| return PP_kInvalidFileHandle; |
| |
| GURL gurl(file_url); |
| // Important security check. Do not remove. |
| if (!CanOpenViaFastPath(plugin_instance, gurl)) |
| return PP_kInvalidFileHandle; |
| |
| IPC::PlatformFileForTransit out_fd = IPC::InvalidPlatformFileForTransit(); |
| IPC::Sender* sender = content::RenderThread::Get(); |
| DCHECK(sender); |
| *nonce_lo = 0; |
| *nonce_hi = 0; |
| base::FilePath file_path; |
| if (!sender->Send( |
| new NaClHostMsg_OpenNaClExecutable(GetRoutingID(instance), |
| GURL(file_url), |
| !load_manager->nonsfi(), |
| &out_fd, |
| nonce_lo, |
| nonce_hi))) { |
| return PP_kInvalidFileHandle; |
| } |
| |
| if (out_fd == IPC::InvalidPlatformFileForTransit()) |
| return PP_kInvalidFileHandle; |
| |
| return IPC::PlatformFileForTransitToPlatformFile(out_fd); |
| } |
| |
| } // namespace |
| |
| // static |
| void PPBNaClPrivate::DispatchEvent(PP_Instance instance, |
| PP_NaClEventType event_type, |
| const char* resource_url, |
| PP_Bool length_is_computable, |
| uint64_t loaded_bytes, |
| uint64_t total_bytes) { |
| ProgressEvent event(event_type, |
| resource_url, |
| PP_ToBool(length_is_computable), |
| loaded_bytes, |
| total_bytes); |
| DispatchProgressEvent(instance, event); |
| } |
| |
| // static |
| void PPBNaClPrivate::ReportLoadError(PP_Instance instance, |
| PP_NaClError error, |
| const char* error_message) { |
| NexeLoadManager* load_manager = GetNexeLoadManager(instance); |
| if (load_manager) |
| load_manager->ReportLoadError(error, error_message); |
| } |
| |
| // static |
| void PPBNaClPrivate::InstanceCreated(PP_Instance instance) { |
| InstanceMap& map = g_instance_map.Get(); |
| CHECK(map.find(instance) == map.end()); // Sanity check. |
| std::unique_ptr<NaClPluginInstance> new_instance( |
| new NaClPluginInstance(instance)); |
| map[instance] = std::move(new_instance); |
| } |
| |
| // static |
| void PPBNaClPrivate::InstanceDestroyed(PP_Instance instance) { |
| InstanceMap& map = g_instance_map.Get(); |
| auto iter = map.find(instance); |
| CHECK(iter != map.end()); |
| // The erase may call NexeLoadManager's destructor prior to removing it from |
| // the map. In that case, it is possible for the trusted Plugin to re-enter |
| // the NexeLoadManager (e.g., by calling ReportLoadError). Passing out the |
| // NexeLoadManager to a local scoped_ptr just ensures that its entry is gone |
| // from the map prior to the destructor being invoked. |
| std::unique_ptr<NaClPluginInstance> temp = std::move(iter->second); |
| map.erase(iter); |
| } |
| |
| // static |
| void PPBNaClPrivate::TerminateNaClLoader(PP_Instance instance) { |
| auto* load_mgr = GetNexeLoadManager(instance); |
| if (load_mgr) |
| load_mgr->CloseTrustedPluginChannel(); |
| } |
| |
| namespace { |
| |
| PP_Bool NaClDebugEnabledForURL(const char* alleged_nmf_url) { |
| if (!base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableNaClDebug)) |
| return PP_FALSE; |
| IPC::Sender* sender = content::RenderThread::Get(); |
| DCHECK(sender); |
| bool should_debug = false; |
| return PP_FromBool( |
| sender->Send(new NaClHostMsg_NaClDebugEnabledForURL(GURL(alleged_nmf_url), |
| &should_debug)) && |
| should_debug); |
| } |
| |
| } // namespace |
| |
| // static |
| void PPBNaClPrivate::InitializePlugin(PP_Instance instance, |
| uint32_t argc, |
| const char* argn[], |
| const char* argv[]) { |
| NexeLoadManager* load_manager = GetNexeLoadManager(instance); |
| DCHECK(load_manager); |
| if (load_manager) |
| load_manager->InitializePlugin(argc, argn, argv); |
| } |
| |
| namespace { |
| |
| void DownloadManifestToBuffer(PP_Instance instance, |
| struct PP_CompletionCallback callback); |
| |
| bool CreateJsonManifest(PP_Instance instance, |
| const std::string& manifest_url, |
| const std::string& manifest_data); |
| |
| } // namespace |
| |
| // static |
| void PPBNaClPrivate::RequestNaClManifest(PP_Instance instance, |
| PP_CompletionCallback callback) { |
| NexeLoadManager* load_manager = GetNexeLoadManager(instance); |
| DCHECK(load_manager); |
| if (!load_manager) { |
| ppapi::PpapiGlobals::Get()->GetMainThreadMessageLoop()->PostTask( |
| FROM_HERE, |
| base::Bind(callback.func, callback.user_data, |
| static_cast<int32_t>(PP_ERROR_FAILED))); |
| return; |
| } |
| |
| std::string url = load_manager->GetManifestURLArgument(); |
| if (url.empty() || !load_manager->RequestNaClManifest(url)) { |
| ppapi::PpapiGlobals::Get()->GetMainThreadMessageLoop()->PostTask( |
| FROM_HERE, |
| base::Bind(callback.func, callback.user_data, |
| static_cast<int32_t>(PP_ERROR_FAILED))); |
| return; |
| } |
| |
| const GURL& base_url = load_manager->manifest_base_url(); |
| if (base_url.SchemeIs("data")) { |
| GURL gurl(base_url); |
| std::string mime_type; |
| std::string charset; |
| std::string data; |
| int32_t error = PP_ERROR_FAILED; |
| if (net::DataURL::Parse(gurl, &mime_type, &charset, &data)) { |
| if (data.size() <= ManifestDownloader::kNaClManifestMaxFileBytes) { |
| if (CreateJsonManifest(instance, base_url.spec(), data)) |
| error = PP_OK; |
| } else { |
| load_manager->ReportLoadError(PP_NACL_ERROR_MANIFEST_TOO_LARGE, |
| "manifest file too large."); |
| } |
| } else { |
| load_manager->ReportLoadError(PP_NACL_ERROR_MANIFEST_LOAD_URL, |
| "could not load manifest url."); |
| } |
| ppapi::PpapiGlobals::Get()->GetMainThreadMessageLoop()->PostTask( |
| FROM_HERE, |
| base::Bind(callback.func, callback.user_data, error)); |
| } else { |
| DownloadManifestToBuffer(instance, callback); |
| } |
| } |
| |
| // static |
| PP_Var PPBNaClPrivate::GetManifestBaseURL(PP_Instance instance) { |
| NexeLoadManager* load_manager = GetNexeLoadManager(instance); |
| DCHECK(load_manager); |
| if (!load_manager) |
| return PP_MakeUndefined(); |
| const GURL& gurl = load_manager->manifest_base_url(); |
| if (!gurl.is_valid()) |
| return PP_MakeUndefined(); |
| return ppapi::StringVar::StringToPPVar(gurl.spec()); |
| } |
| |
| // static |
| void PPBNaClPrivate::ProcessNaClManifest(PP_Instance instance, |
| const char* program_url) { |
| nacl::NexeLoadManager* load_manager = GetNexeLoadManager(instance); |
| if (load_manager) |
| load_manager->ProcessNaClManifest(program_url); |
| } |
| |
| namespace { |
| |
| void DownloadManifestToBufferCompletion(PP_Instance instance, |
| struct PP_CompletionCallback callback, |
| base::Time start_time, |
| PP_NaClError pp_nacl_error, |
| const std::string& data); |
| |
| void DownloadManifestToBuffer(PP_Instance instance, |
| struct PP_CompletionCallback callback) { |
| nacl::NexeLoadManager* load_manager = GetNexeLoadManager(instance); |
| DCHECK(load_manager); |
| content::PepperPluginInstance* plugin_instance = |
| content::PepperPluginInstance::Get(instance); |
| if (!load_manager || !plugin_instance) { |
| ppapi::PpapiGlobals::Get()->GetMainThreadMessageLoop()->PostTask( |
| FROM_HERE, |
| base::Bind(callback.func, callback.user_data, |
| static_cast<int32_t>(PP_ERROR_FAILED))); |
| return; |
| } |
| const blink::WebDocument& document = |
| plugin_instance->GetContainer()->GetDocument(); |
| |
| const GURL& gurl = load_manager->manifest_base_url(); |
| std::unique_ptr<blink::WebAssociatedURLLoader> url_loader( |
| CreateAssociatedURLLoader(document, gurl)); |
| blink::WebURLRequest request = CreateWebURLRequest(document, gurl); |
| |
| // Requests from plug-ins must skip service workers, see the comment in |
| // CreateWebURLRequest. |
| DCHECK_EQ(request.GetServiceWorkerMode(), |
| blink::WebURLRequest::ServiceWorkerMode::kNone); |
| |
| // ManifestDownloader deletes itself after invoking the callback. |
| ManifestDownloader* manifest_downloader = new ManifestDownloader( |
| std::move(url_loader), load_manager->is_installed(), |
| base::Bind(DownloadManifestToBufferCompletion, instance, callback, |
| base::Time::Now())); |
| manifest_downloader->Load(request); |
| } |
| |
| void DownloadManifestToBufferCompletion(PP_Instance instance, |
| struct PP_CompletionCallback callback, |
| base::Time start_time, |
| PP_NaClError pp_nacl_error, |
| const std::string& data) { |
| base::TimeDelta download_time = base::Time::Now() - start_time; |
| HistogramTimeSmall("NaCl.Perf.StartupTime.ManifestDownload", |
| download_time.InMilliseconds()); |
| |
| nacl::NexeLoadManager* load_manager = GetNexeLoadManager(instance); |
| if (!load_manager) { |
| callback.func(callback.user_data, PP_ERROR_ABORTED); |
| return; |
| } |
| |
| int32_t pp_error; |
| switch (pp_nacl_error) { |
| case PP_NACL_ERROR_LOAD_SUCCESS: |
| pp_error = PP_OK; |
| break; |
| case PP_NACL_ERROR_MANIFEST_LOAD_URL: |
| pp_error = PP_ERROR_FAILED; |
| load_manager->ReportLoadError(PP_NACL_ERROR_MANIFEST_LOAD_URL, |
| "could not load manifest url."); |
| break; |
| case PP_NACL_ERROR_MANIFEST_TOO_LARGE: |
| pp_error = PP_ERROR_FILETOOBIG; |
| load_manager->ReportLoadError(PP_NACL_ERROR_MANIFEST_TOO_LARGE, |
| "manifest file too large."); |
| break; |
| case PP_NACL_ERROR_MANIFEST_NOACCESS_URL: |
| pp_error = PP_ERROR_NOACCESS; |
| load_manager->ReportLoadError(PP_NACL_ERROR_MANIFEST_NOACCESS_URL, |
| "access to manifest url was denied."); |
| break; |
| default: |
| NOTREACHED(); |
| pp_error = PP_ERROR_FAILED; |
| load_manager->ReportLoadError(PP_NACL_ERROR_MANIFEST_LOAD_URL, |
| "could not load manifest url."); |
| } |
| |
| if (pp_error == PP_OK) { |
| std::string base_url = load_manager->manifest_base_url().spec(); |
| if (!CreateJsonManifest(instance, base_url, data)) |
| pp_error = PP_ERROR_FAILED; |
| } |
| callback.func(callback.user_data, pp_error); |
| } |
| |
| bool CreateJsonManifest(PP_Instance instance, |
| const std::string& manifest_url, |
| const std::string& manifest_data) { |
| HistogramSizeKB("NaCl.Perf.Size.Manifest", |
| static_cast<int32_t>(manifest_data.length() / 1024)); |
| |
| nacl::NexeLoadManager* load_manager = GetNexeLoadManager(instance); |
| if (!load_manager) |
| return false; |
| |
| const char* isa_type; |
| if (load_manager->IsPNaCl()) |
| isa_type = kPortableArch; |
| else |
| isa_type = GetSandboxArch(); |
| |
| std::unique_ptr<nacl::JsonManifest> j(new nacl::JsonManifest( |
| manifest_url.c_str(), isa_type, IsNonSFIModeEnabled(), |
| PP_ToBool(NaClDebugEnabledForURL(manifest_url.c_str())))); |
| JsonManifest::ErrorInfo error_info; |
| if (j->Init(manifest_data.c_str(), &error_info)) { |
| GetNaClPluginInstance(instance)->json_manifest = std::move(j); |
| return true; |
| } |
| load_manager->ReportLoadError(error_info.error, error_info.string); |
| return false; |
| } |
| |
| bool ShouldUseSubzero(const PP_PNaClOptions* pnacl_options) { |
| // Always use Subzero if explicitly overridden on the command line. |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kForcePNaClSubzero)) |
| return true; |
| // Otherwise, don't use Subzero for a debug pexe file since Subzero's parser |
| // is likely to reject an unfinalized pexe. |
| if (pnacl_options->is_debug) |
| return false; |
| // Only use Subzero for optlevel=0. |
| if (pnacl_options->opt_level != 0) |
| return false; |
| // Check a whitelist of architectures. |
| const char* arch = GetSandboxArch(); |
| if (strcmp(arch, "x86-32") == 0) |
| return true; |
| if (strcmp(arch, "x86-64") == 0) |
| return true; |
| if (strcmp(arch, "arm") == 0) |
| return true; |
| |
| return false; |
| } |
| |
| } // namespace |
| |
| // static |
| PP_Bool PPBNaClPrivate::GetManifestProgramURL(PP_Instance instance, |
| PP_Var* pp_full_url, |
| PP_PNaClOptions* pnacl_options, |
| PP_Bool* pp_uses_nonsfi_mode) { |
| nacl::NexeLoadManager* load_manager = GetNexeLoadManager(instance); |
| |
| JsonManifest* manifest = GetJsonManifest(instance); |
| if (manifest == NULL) |
| return PP_FALSE; |
| |
| bool uses_nonsfi_mode; |
| std::string full_url; |
| JsonManifest::ErrorInfo error_info; |
| if (manifest->GetProgramURL(&full_url, pnacl_options, &uses_nonsfi_mode, |
| &error_info)) { |
| *pp_full_url = ppapi::StringVar::StringToPPVar(full_url); |
| *pp_uses_nonsfi_mode = PP_FromBool(uses_nonsfi_mode); |
| if (ShouldUseSubzero(pnacl_options)) { |
| pnacl_options->use_subzero = PP_TRUE; |
| // Subzero -O2 is closer to LLC -O0, so indicate -O2. |
| pnacl_options->opt_level = 2; |
| } |
| return PP_TRUE; |
| } |
| |
| if (load_manager) |
| load_manager->ReportLoadError(error_info.error, error_info.string); |
| return PP_FALSE; |
| } |
| |
| namespace { |
| |
| bool ManifestResolveKey(PP_Instance instance, |
| bool is_helper_process, |
| const std::string& key, |
| std::string* full_url, |
| PP_PNaClOptions* pnacl_options) { |
| // For "helper" processes (llc and ld, for PNaCl translation), we resolve |
| // keys manually as there is no existing .nmf file to parse. |
| if (is_helper_process) { |
| pnacl_options->translate = PP_FALSE; |
| *full_url = std::string(kPNaClTranslatorBaseUrl) + GetSandboxArch() + "/" + |
| key; |
| return true; |
| } |
| |
| JsonManifest* manifest = GetJsonManifest(instance); |
| if (manifest == NULL) |
| return false; |
| |
| return manifest->ResolveKey(key, full_url, pnacl_options); |
| } |
| |
| } // namespace |
| |
| // static |
| PP_Bool PPBNaClPrivate::GetPnaclResourceInfo(PP_Instance instance, |
| PP_Var* llc_tool_name, |
| PP_Var* ld_tool_name, |
| PP_Var* subzero_tool_name) { |
| static const char kFilename[] = "chrome://pnacl-translator/pnacl.json"; |
| NexeLoadManager* load_manager = GetNexeLoadManager(instance); |
| DCHECK(load_manager); |
| if (!load_manager) |
| return PP_FALSE; |
| |
| uint64_t nonce_lo = 0; |
| uint64_t nonce_hi = 0; |
| base::File file(GetReadonlyPnaclFd(kFilename, false /* is_executable */, |
| &nonce_lo, &nonce_hi)); |
| if (!file.IsValid()) { |
| load_manager->ReportLoadError( |
| PP_NACL_ERROR_PNACL_RESOURCE_FETCH, |
| "The Portable Native Client (pnacl) component is not " |
| "installed. Please consult chrome://components for more " |
| "information."); |
| return PP_FALSE; |
| } |
| |
| int64_t file_size = file.GetLength(); |
| if (file_size < 0) { |
| load_manager->ReportLoadError( |
| PP_NACL_ERROR_PNACL_RESOURCE_FETCH, |
| std::string("GetPnaclResourceInfo, GetLength failed for: ") + |
| kFilename); |
| return PP_FALSE; |
| } |
| |
| if (file_size > 1 << 20) { |
| load_manager->ReportLoadError( |
| PP_NACL_ERROR_PNACL_RESOURCE_FETCH, |
| std::string("GetPnaclResourceInfo, file too large: ") + kFilename); |
| return PP_FALSE; |
| } |
| |
| std::unique_ptr<char[]> buffer(new char[file_size + 1]); |
| int rc = file.Read(0, buffer.get(), file_size); |
| if (rc < 0 || rc != file_size) { |
| load_manager->ReportLoadError( |
| PP_NACL_ERROR_PNACL_RESOURCE_FETCH, |
| std::string("GetPnaclResourceInfo, reading failed for: ") + kFilename); |
| return PP_FALSE; |
| } |
| |
| // Null-terminate the bytes we we read from the file. |
| buffer.get()[rc] = 0; |
| |
| // Expect the JSON file to contain a top-level object (dictionary). |
| base::JSONReader json_reader; |
| int json_read_error_code; |
| std::string json_read_error_msg; |
| std::unique_ptr<base::DictionaryValue> json_dict( |
| base::DictionaryValue::From(json_reader.ReadAndReturnError( |
| buffer.get(), base::JSON_PARSE_RFC, &json_read_error_code, |
| &json_read_error_msg))); |
| if (!json_dict) { |
| load_manager->ReportLoadError( |
| PP_NACL_ERROR_PNACL_RESOURCE_FETCH, |
| std::string("Parsing resource info failed: JSON parse error: ") + |
| json_read_error_msg); |
| return PP_FALSE; |
| } |
| |
| std::string pnacl_llc_name; |
| if (json_dict->GetString("pnacl-llc-name", &pnacl_llc_name)) |
| *llc_tool_name = ppapi::StringVar::StringToPPVar(pnacl_llc_name); |
| |
| std::string pnacl_ld_name; |
| if (json_dict->GetString("pnacl-ld-name", &pnacl_ld_name)) |
| *ld_tool_name = ppapi::StringVar::StringToPPVar(pnacl_ld_name); |
| |
| std::string pnacl_sz_name; |
| if (json_dict->GetString("pnacl-sz-name", &pnacl_sz_name)) |
| *subzero_tool_name = ppapi::StringVar::StringToPPVar(pnacl_sz_name); |
| |
| return PP_TRUE; |
| } |
| |
| // static |
| const char* PPBNaClPrivate::GetSandboxArch() { |
| return nacl::GetSandboxArch(); |
| } |
| |
| // static |
| PP_Var PPBNaClPrivate::GetCpuFeatureAttrs() { |
| return ppapi::StringVar::StringToPPVar(GetCpuFeatures()); |
| } |
| |
| namespace { |
| |
| // Encapsulates some of the state for a call to DownloadNexe to prevent |
| // argument lists from getting too long. |
| struct DownloadNexeRequest { |
| PP_Instance instance; |
| std::string url; |
| PP_CompletionCallback callback; |
| base::Time start_time; |
| }; |
| |
| // A utility class to ensure that we don't send progress events more often than |
| // every 10ms for a given file. |
| class ProgressEventRateLimiter { |
| public: |
| explicit ProgressEventRateLimiter(PP_Instance instance) |
| : instance_(instance) { } |
| |
| void ReportProgress(const std::string& url, |
| int64_t total_bytes_received, |
| int64_t total_bytes_to_be_received) { |
| base::Time now = base::Time::Now(); |
| if (now - last_event_ > base::TimeDelta::FromMilliseconds(10)) { |
| DispatchProgressEvent(instance_, |
| ProgressEvent(PP_NACL_EVENT_PROGRESS, |
| url, |
| total_bytes_to_be_received >= 0, |
| total_bytes_received, |
| total_bytes_to_be_received)); |
| last_event_ = now; |
| } |
| } |
| |
| private: |
| PP_Instance instance_; |
| base::Time last_event_; |
| }; |
| |
| void DownloadNexeCompletion(const DownloadNexeRequest& request, |
| PP_NaClFileInfo* out_file_info, |
| FileDownloader::Status status, |
| base::File target_file, |
| int http_status); |
| |
| } // namespace |
| |
| // static |
| void PPBNaClPrivate::DownloadNexe(PP_Instance instance, |
| const char* url, |
| PP_NaClFileInfo* out_file_info, |
| PP_CompletionCallback callback) { |
| CHECK(url); |
| CHECK(out_file_info); |
| DownloadNexeRequest request; |
| request.instance = instance; |
| request.url = url; |
| request.callback = callback; |
| request.start_time = base::Time::Now(); |
| |
| // Try the fast path for retrieving the file first. |
| PP_FileHandle handle = OpenNaClExecutable(instance, |
| url, |
| &out_file_info->token_lo, |
| &out_file_info->token_hi); |
| if (handle != PP_kInvalidFileHandle) { |
| DownloadNexeCompletion(request, |
| out_file_info, |
| FileDownloader::SUCCESS, |
| base::File(handle), |
| 200); |
| return; |
| } |
| |
| // The fast path didn't work, we'll fetch the file using URLLoader and write |
| // it to local storage. |
| base::File target_file(PPBNaClPrivate::CreateTemporaryFile(instance)); |
| GURL gurl(url); |
| |
| content::PepperPluginInstance* plugin_instance = |
| content::PepperPluginInstance::Get(instance); |
| if (!plugin_instance) { |
| ppapi::PpapiGlobals::Get()->GetMainThreadMessageLoop()->PostTask( |
| FROM_HERE, |
| base::Bind(callback.func, callback.user_data, |
| static_cast<int32_t>(PP_ERROR_FAILED))); |
| return; |
| } |
| const blink::WebDocument& document = |
| plugin_instance->GetContainer()->GetDocument(); |
| std::unique_ptr<blink::WebAssociatedURLLoader> url_loader( |
| CreateAssociatedURLLoader(document, gurl)); |
| blink::WebURLRequest url_request = CreateWebURLRequest(document, gurl); |
| |
| ProgressEventRateLimiter* tracker = new ProgressEventRateLimiter(instance); |
| |
| // FileDownloader deletes itself after invoking DownloadNexeCompletion. |
| FileDownloader* file_downloader = new FileDownloader( |
| std::move(url_loader), std::move(target_file), |
| base::Bind(&DownloadNexeCompletion, request, out_file_info), |
| base::Bind(&ProgressEventRateLimiter::ReportProgress, |
| base::Owned(tracker), std::string(url))); |
| file_downloader->Load(url_request); |
| } |
| |
| namespace { |
| |
| void DownloadNexeCompletion(const DownloadNexeRequest& request, |
| PP_NaClFileInfo* out_file_info, |
| FileDownloader::Status status, |
| base::File target_file, |
| int http_status) { |
| int32_t pp_error = FileDownloaderToPepperError(status); |
| int64_t bytes_read = -1; |
| if (pp_error == PP_OK && target_file.IsValid()) { |
| base::File::Info info; |
| if (target_file.GetInfo(&info)) |
| bytes_read = info.size; |
| } |
| |
| if (bytes_read == -1) { |
| target_file.Close(); |
| pp_error = PP_ERROR_FAILED; |
| } |
| |
| base::TimeDelta download_time = base::Time::Now() - request.start_time; |
| |
| NexeLoadManager* load_manager = GetNexeLoadManager(request.instance); |
| if (load_manager) { |
| load_manager->NexeFileDidOpen(pp_error, |
| target_file, |
| http_status, |
| bytes_read, |
| request.url, |
| download_time); |
| } |
| |
| if (pp_error == PP_OK && target_file.IsValid()) |
| out_file_info->handle = target_file.TakePlatformFile(); |
| else |
| out_file_info->handle = PP_kInvalidFileHandle; |
| |
| request.callback.func(request.callback.user_data, pp_error); |
| } |
| |
| void DownloadFileCompletion( |
| const DownloadFileCallback& callback, |
| FileDownloader::Status status, |
| base::File file, |
| int http_status) { |
| int32_t pp_error = FileDownloaderToPepperError(status); |
| PP_NaClFileInfo file_info; |
| if (pp_error == PP_OK) { |
| file_info.handle = file.TakePlatformFile(); |
| file_info.token_lo = 0; |
| file_info.token_hi = 0; |
| } else { |
| file_info = kInvalidNaClFileInfo; |
| } |
| |
| callback.Run(pp_error, file_info); |
| } |
| |
| void DownloadFile(PP_Instance instance, |
| const std::string& url, |
| const DownloadFileCallback& callback) { |
| DCHECK(ppapi::PpapiGlobals::Get()->GetMainThreadMessageLoop()-> |
| BelongsToCurrentThread()); |
| |
| NexeLoadManager* load_manager = GetNexeLoadManager(instance); |
| DCHECK(load_manager); |
| if (!load_manager) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(callback, static_cast<int32_t>(PP_ERROR_FAILED), |
| kInvalidNaClFileInfo)); |
| return; |
| } |
| |
| // Handle special PNaCl support files which are installed on the user's |
| // machine. |
| if (base::StartsWith(url, kPNaClTranslatorBaseUrl, |
| base::CompareCase::SENSITIVE)) { |
| PP_NaClFileInfo file_info = kInvalidNaClFileInfo; |
| PP_FileHandle handle = GetReadonlyPnaclFd(url.c_str(), |
| false /* is_executable */, |
| &file_info.token_lo, |
| &file_info.token_hi); |
| if (handle == PP_kInvalidFileHandle) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(callback, static_cast<int32_t>(PP_ERROR_FAILED), |
| kInvalidNaClFileInfo)); |
| return; |
| } |
| file_info.handle = handle; |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::Bind(callback, static_cast<int32_t>(PP_OK), file_info)); |
| return; |
| } |
| |
| // We have to ensure that this url resolves relative to the plugin base url |
| // before downloading it. |
| const GURL& test_gurl = load_manager->plugin_base_url().Resolve(url); |
| if (!test_gurl.is_valid()) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(callback, static_cast<int32_t>(PP_ERROR_FAILED), |
| kInvalidNaClFileInfo)); |
| return; |
| } |
| |
| // Try the fast path for retrieving the file first. |
| uint64_t file_token_lo = 0; |
| uint64_t file_token_hi = 0; |
| PP_FileHandle file_handle = OpenNaClExecutable(instance, |
| url.c_str(), |
| &file_token_lo, |
| &file_token_hi); |
| if (file_handle != PP_kInvalidFileHandle) { |
| PP_NaClFileInfo file_info; |
| file_info.handle = file_handle; |
| file_info.token_lo = file_token_lo; |
| file_info.token_hi = file_token_hi; |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::Bind(callback, static_cast<int32_t>(PP_OK), file_info)); |
| return; |
| } |
| |
| // The fast path didn't work, we'll fetch the file using URLLoader and write |
| // it to local storage. |
| base::File target_file(PPBNaClPrivate::CreateTemporaryFile(instance)); |
| GURL gurl(url); |
| |
| content::PepperPluginInstance* plugin_instance = |
| content::PepperPluginInstance::Get(instance); |
| if (!plugin_instance) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(callback, static_cast<int32_t>(PP_ERROR_FAILED), |
| kInvalidNaClFileInfo)); |
| return; |
| } |
| const blink::WebDocument& document = |
| plugin_instance->GetContainer()->GetDocument(); |
| std::unique_ptr<blink::WebAssociatedURLLoader> url_loader( |
| CreateAssociatedURLLoader(document, gurl)); |
| blink::WebURLRequest url_request = CreateWebURLRequest(document, gurl); |
| |
| ProgressEventRateLimiter* tracker = new ProgressEventRateLimiter(instance); |
| |
| // FileDownloader deletes itself after invoking DownloadNexeCompletion. |
| FileDownloader* file_downloader = |
| new FileDownloader(std::move(url_loader), std::move(target_file), |
| base::Bind(&DownloadFileCompletion, callback), |
| base::Bind(&ProgressEventRateLimiter::ReportProgress, |
| base::Owned(tracker), std::string(url))); |
| file_downloader->Load(url_request); |
| } |
| |
| } // namespace |
| |
| // static |
| void PPBNaClPrivate::LogTranslateTime(const char* histogram_name, |
| int64_t time_in_us) { |
| ppapi::PpapiGlobals::Get()->GetMainThreadMessageLoop()->PostTask( |
| FROM_HERE, |
| base::Bind(&HistogramTimeTranslation, |
| std::string(histogram_name), |
| time_in_us / 1000)); |
| } |
| |
| // static |
| void PPBNaClPrivate::LogBytesCompiledVsDownloaded( |
| PP_Bool use_subzero, |
| int64_t pexe_bytes_compiled, |
| int64_t pexe_bytes_downloaded) { |
| HistogramRatio("NaCl.Perf.PNaClLoadTime.PctCompiledWhenFullyDownloaded", |
| pexe_bytes_compiled, pexe_bytes_downloaded); |
| HistogramRatio( |
| use_subzero |
| ? "NaCl.Perf.PNaClLoadTime.PctCompiledWhenFullyDownloaded.Subzero" |
| : "NaCl.Perf.PNaClLoadTime.PctCompiledWhenFullyDownloaded.LLC", |
| pexe_bytes_compiled, pexe_bytes_downloaded); |
| } |
| |
| // static |
| void PPBNaClPrivate::SetPNaClStartTime(PP_Instance instance) { |
| NexeLoadManager* load_manager = GetNexeLoadManager(instance); |
| if (load_manager) |
| load_manager->set_pnacl_start_time(base::Time::Now()); |
| } |
| |
| namespace { |
| |
| // PexeDownloader is responsible for deleting itself when the download |
| // finishes. |
| class PexeDownloader : public blink::WebAssociatedURLLoaderClient { |
| public: |
| PexeDownloader(PP_Instance instance, |
| std::unique_ptr<blink::WebAssociatedURLLoader> url_loader, |
| const std::string& pexe_url, |
| int32_t pexe_opt_level, |
| bool use_subzero, |
| const PPP_PexeStreamHandler* stream_handler, |
| void* stream_handler_user_data) |
| : instance_(instance), |
| url_loader_(std::move(url_loader)), |
| pexe_url_(pexe_url), |
| pexe_opt_level_(pexe_opt_level), |
| use_subzero_(use_subzero), |
| stream_handler_(stream_handler), |
| stream_handler_user_data_(stream_handler_user_data), |
| success_(false), |
| expected_content_length_(-1), |
| weak_factory_(this) {} |
| |
| void Load(const blink::WebURLRequest& request) { |
| url_loader_->LoadAsynchronously(request, this); |
| } |
| |
| private: |
| void DidReceiveResponse(const blink::WebURLResponse& response) override { |
| success_ = (response.HttpStatusCode() == 200); |
| if (!success_) |
| return; |
| |
| expected_content_length_ = response.ExpectedContentLength(); |
| |
| // Defer loading after receiving headers. This is because we may already |
| // have a cached translated nexe, so check for that now. |
| url_loader_->SetDefersLoading(true); |
| |
| std::string etag = response.HttpHeaderField("etag").Utf8(); |
| |
| // Parse the "last-modified" date string. An invalid string will result |
| // in a base::Time value of 0, which is supported by the only user of |
| // the |CacheInfo::last_modified| field (see |
| // pnacl::PnaclTranslationCache::GetKey()). |
| std::string last_modified = |
| response.HttpHeaderField("last-modified").Utf8(); |
| base::Time last_modified_time; |
| ignore_result( |
| base::Time::FromString(last_modified.c_str(), &last_modified_time)); |
| |
| bool has_no_store_header = false; |
| std::string cache_control = |
| response.HttpHeaderField("cache-control").Utf8(); |
| |
| for (const std::string& cur : base::SplitString( |
| cache_control, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { |
| if (base::ToLowerASCII(cur) == "no-store") |
| has_no_store_header = true; |
| } |
| |
| GetNexeFd( |
| instance_, pexe_url_, pexe_opt_level_, last_modified_time, etag, |
| has_no_store_header, use_subzero_, |
| base::Bind(&PexeDownloader::didGetNexeFd, weak_factory_.GetWeakPtr())); |
| } |
| |
| void didGetNexeFd(int32_t pp_error, |
| bool cache_hit, |
| PP_FileHandle file_handle) { |
| if (!content::PepperPluginInstance::Get(instance_)) { |
| delete this; |
| return; |
| } |
| |
| HistogramEnumerate("NaCl.Perf.PNaClCache.IsHit", cache_hit, 2); |
| HistogramEnumerate(use_subzero_ ? "NaCl.Perf.PNaClCache.IsHit.Subzero" |
| : "NaCl.Perf.PNaClCache.IsHit.LLC", |
| cache_hit, 2); |
| if (cache_hit) { |
| stream_handler_->DidCacheHit(stream_handler_user_data_, file_handle); |
| |
| // We delete the PexeDownloader at this point since we successfully got a |
| // cached, translated nexe. |
| delete this; |
| return; |
| } |
| stream_handler_->DidCacheMiss(stream_handler_user_data_, |
| expected_content_length_, |
| file_handle); |
| |
| // No translated nexe was found in the cache, so we should download the |
| // file to start streaming it. |
| url_loader_->SetDefersLoading(false); |
| } |
| |
| void DidReceiveData(const char* data, int data_length) override { |
| if (content::PepperPluginInstance::Get(instance_)) { |
| // Stream the data we received to the stream callback. |
| stream_handler_->DidStreamData(stream_handler_user_data_, |
| data, |
| data_length); |
| } |
| } |
| |
| void DidFinishLoading(double finish_time) override { |
| int32_t result = success_ ? PP_OK : PP_ERROR_FAILED; |
| |
| if (content::PepperPluginInstance::Get(instance_)) |
| stream_handler_->DidFinishStream(stream_handler_user_data_, result); |
| delete this; |
| } |
| |
| void DidFail(const blink::WebURLError& error) override { |
| if (content::PepperPluginInstance::Get(instance_)) |
| stream_handler_->DidFinishStream(stream_handler_user_data_, |
| PP_ERROR_FAILED); |
| delete this; |
| } |
| |
| PP_Instance instance_; |
| std::unique_ptr<blink::WebAssociatedURLLoader> url_loader_; |
| std::string pexe_url_; |
| int32_t pexe_opt_level_; |
| bool use_subzero_; |
| const PPP_PexeStreamHandler* stream_handler_; |
| void* stream_handler_user_data_; |
| bool success_; |
| int64_t expected_content_length_; |
| base::WeakPtrFactory<PexeDownloader> weak_factory_; |
| }; |
| |
| } // namespace |
| |
| // static |
| void PPBNaClPrivate::StreamPexe(PP_Instance instance, |
| const char* pexe_url, |
| int32_t opt_level, |
| PP_Bool use_subzero, |
| const PPP_PexeStreamHandler* handler, |
| void* handler_user_data) { |
| content::PepperPluginInstance* plugin_instance = |
| content::PepperPluginInstance::Get(instance); |
| if (!plugin_instance) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(handler->DidFinishStream, handler_user_data, |
| static_cast<int32_t>(PP_ERROR_FAILED))); |
| return; |
| } |
| |
| GURL gurl(pexe_url); |
| const blink::WebDocument& document = |
| plugin_instance->GetContainer()->GetDocument(); |
| std::unique_ptr<blink::WebAssociatedURLLoader> url_loader( |
| CreateAssociatedURLLoader(document, gurl)); |
| PexeDownloader* downloader = |
| new PexeDownloader(instance, std::move(url_loader), pexe_url, opt_level, |
| PP_ToBool(use_subzero), handler, handler_user_data); |
| |
| blink::WebURLRequest url_request = CreateWebURLRequest(document, gurl); |
| // Mark the request as requesting a PNaCl bitcode file, |
| // so that component updater can detect this user action. |
| url_request.AddHTTPHeaderField( |
| blink::WebString::FromUTF8("Accept"), |
| blink::WebString::FromUTF8("application/x-pnacl, */*")); |
| url_request.SetRequestContext(blink::WebURLRequest::kRequestContextObject); |
| downloader->Load(url_request); |
| } |
| |
| } // namespace nacl |