// Copyright 2013 The Chromium Authors // 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 #include #include #include #include #include #include #include #include #include "base/command_line.h" #include "base/cpu.h" #include "base/files/file.h" #include "base/functional/bind.h" #include "base/functional/callback_helpers.h" #include "base/json/json_reader.h" #include "base/lazy_instance.h" #include "base/location.h" #include "base/logging.h" #include "base/process/process_handle.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/task/single_thread_task_runner.h" #include "base/time/time.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_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_frame.h" #include "content/public/renderer/render_thread.h" #include "content/public/renderer/renderer_ppapi_host.h" #include "mojo/public/cpp/bindings/pending_receiver.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/blink/public/platform/web_security_origin.h" #include "third_party/blink/public/platform/web_url_request.h" #include "third_party/blink/public/platform/web_url_response.h" #include "third_party/blink/public/web/web_associated_url_loader.h" #include "third_party/blink/public/web/web_associated_url_loader_client.h" #include "third_party/blink/public/web/web_document.h" #include "third_party/blink/public/web/web_local_frame.h" #include "third_party/blink/public/web/web_plugin_container.h" #if BUILDFLAG(IS_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>:: 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 json_manifest; std::unique_ptr 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> InstanceMap; base::LazyInstance::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 GetFrameRoutingID(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; auto* render_frame = host->GetRenderFrameForInstance(instance); if (!render_frame) return 0; return render_frame->GetRoutingID(); } // 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::BindOnce(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::OnceCallback DownloadFileCallback; void DownloadFile(PP_Instance instance, const std::string& url, 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(const ManifestServiceProxy&) = delete; ManifestServiceProxy& operator=(const ManifestServiceProxy&) = delete; ~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; JsonManifest::ErrorInfo error_info; if (manifest->GetProgramURL(&full_url, &pnacl_options, &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, 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::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(std::move(callback), 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::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(std::move(callback), 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::BindOnce(&ManifestServiceProxy::DidDownloadFile, std::move(callback))); } private: static void DidDownloadFile( ManifestServiceChannel::OpenResourceCallback callback, int32_t pp_error, const PP_NaClFileInfo& file_info) { if (pp_error != PP_OK) { std::move(callback).Run(base::File(), 0, 0); return; } std::move(callback).Run(base::File(file_info.handle), file_info.token_lo, file_info.token_hi); } PP_Instance pp_instance_; NaClAppProcessType process_type_; }; std::unique_ptr 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.SetMode(network::mojom::RequestMode::kSameOrigin); request.SetCredentialsMode(network::mojom::CredentialsMode::kSameOrigin); } else { request.SetMode(network::mojom::RequestMode::kCors); request.SetCredentialsMode(network::mojom::CredentialsMode::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.SetSkipServiceWorker(true); 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(pp) == static_cast(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(pp_process_type); } } // 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_NaClAppProcessType pp_process_type, std::unique_ptr* 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 manifest_service_proxy( new ManifestServiceProxy(instance, process_type)); IPC::Sender* sender = content::RenderThread::Get(); DCHECK(sender); NexeLoadManager* load_manager = GetNexeLoadManager(instance); DCHECK(load_manager); content::PepperPluginInstance* plugin_instance = content::PepperPluginInstance::Get(instance); DCHECK(plugin_instance); if (!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::BindOnce(callback.func, callback.user_data, static_cast(PP_ERROR_FAILED))); return; } InstanceInfo instance_info; instance_info.url = GURL(alleged_url); // Keep backwards-compatible, but no other permissions. uint32_t perm_bits = ppapi::PERMISSION_DEFAULT; instance_info.permissions = ppapi::PpapiPermissions::GetForCommandLine(perm_bits); std::vector 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 BUILDFLAG(IS_POSIX) if (nexe_file_info->handle != PP_kInvalidFileHandle) nexe_for_transit = base::FileDescriptor(nexe_file_info->handle, true); #elif BUILDFLAG(IS_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, GetFrameRoutingID(instance), perm_bits, process_type), &launch_result, &error_message_string))) { ppapi::PpapiGlobals::Get()->GetMainThreadMessageLoop()->PostTask( FROM_HERE, base::BindOnce(callback.func, callback.user_data, static_cast(PP_ERROR_FAILED))); return; } 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 (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::BindOnce(callback.func, callback.user_data, static_cast(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, /* listener = */ nullptr, content::RenderThread::Get()->GetIOTaskRunner(), base::SingleThreadTaskRunner::GetCurrentDefault(), 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 = std::make_unique(instance_info); } } // Store the crash information shared memory handle. load_manager->set_crash_info_shmem_region( std::move(launch_result.crash_info_shmem_region)); // 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 trusted_plugin_channel( new TrustedPluginChannel( load_manager, mojo::PendingReceiver( 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 manifest_service_channel( new ManifestServiceChannel( launch_result.manifest_service_ipc_channel_handle, base::BindOnce(&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 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()); // Replace characters that are not allowed with '_'. size_t replace_pos; static const char kAllowList[] = "abcdefghijklmnopqrstuvwxyz0123456789_"; replace_pos = r.find_first_not_of(kAllowList); while (replace_pos != std::string::npos) { r = r.replace(replace_pos, 1, "_"); replace_pos = r.find_first_not_of(kAllowList); } 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, PnaclTranslationResourceHost::RequestNexeFdCallback callback) { if (!InitializePnaclResourceHost()) { ppapi::PpapiGlobals::Get()->GetMainThreadMessageLoop()->PostTask( FROM_HERE, base::BindOnce(std::move(callback), static_cast(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(instance, cache_info, std::move(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( GetFrameRoutingID(instance), GURL(file_url), &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 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 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::BindOnce(callback.func, callback.user_data, static_cast(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::BindOnce(callback.func, callback.user_data, static_cast(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::BindOnce(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::BindOnce(callback.func, callback.user_data, static_cast(PP_ERROR_FAILED))); return; } const blink::WebDocument& document = plugin_instance->GetContainer()->GetDocument(); const GURL& gurl = load_manager->manifest_base_url(); std::unique_ptr 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(request.GetSkipServiceWorker()); // ManifestDownloader deletes itself after invoking the callback. ManifestDownloader* manifest_downloader = new ManifestDownloader( std::move(url_loader), load_manager->is_installed(), base::BindOnce(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(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 j(new nacl::JsonManifest( manifest_url.c_str(), isa_type, 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 list of allowed 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) { nacl::NexeLoadManager* load_manager = GetNexeLoadManager(instance); JsonManifest* manifest = GetJsonManifest(instance); if (manifest == NULL) return PP_FALSE; std::string full_url; JsonManifest::ErrorInfo error_info; if (manifest->GetProgramURL(&full_url, pnacl_options, &error_info)) { *pp_full_url = ppapi::StringVar::StringToPPVar(full_url); 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 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). auto parsed_json = base::JSONReader::ReadAndReturnValueWithError(buffer.get()); if (!parsed_json.has_value()) { load_manager->ReportLoadError( PP_NACL_ERROR_PNACL_RESOURCE_FETCH, std::string("Parsing resource info failed: JSON parse error: ") + parsed_json.error().message); return PP_FALSE; } auto* json_dict = parsed_json->GetIfDict(); if (!json_dict) { load_manager->ReportLoadError( PP_NACL_ERROR_PNACL_RESOURCE_FETCH, std::string("Parsing resource info failed: JSON parse error: Not a " "dictionary.")); return PP_FALSE; } if (auto* pnacl_llc_name = json_dict->FindString("pnacl-llc-name")) *llc_tool_name = ppapi::StringVar::StringToPPVar(*pnacl_llc_name); if (auto* pnacl_ld_name = json_dict->FindString("pnacl-ld-name")) *ld_tool_name = ppapi::StringVar::StringToPPVar(*pnacl_ld_name); if (auto* pnacl_sz_name = json_dict->FindString("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::Milliseconds(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::BindOnce(callback.func, callback.user_data, static_cast(PP_ERROR_FAILED))); return; } const blink::WebDocument& document = plugin_instance->GetContainer()->GetDocument(); std::unique_ptr 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::BindOnce(&DownloadNexeCompletion, request, out_file_info), base::BindRepeating(&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(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; } std::move(callback).Run(pp_error, file_info); } void DownloadFile(PP_Instance instance, const std::string& url, DownloadFileCallback callback) { DCHECK(ppapi::PpapiGlobals::Get()->GetMainThreadMessageLoop()-> BelongsToCurrentThread()); NexeLoadManager* load_manager = GetNexeLoadManager(instance); DCHECK(load_manager); if (!load_manager) { base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(std::move(callback), static_cast(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::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(std::move(callback), static_cast(PP_ERROR_FAILED), kInvalidNaClFileInfo)); return; } file_info.handle = handle; base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(std::move(callback), static_cast(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::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(std::move(callback), static_cast(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::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(std::move(callback), static_cast(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::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(std::move(callback), static_cast(PP_ERROR_FAILED), kInvalidNaClFileInfo)); return; } const blink::WebDocument& document = plugin_instance->GetContainer()->GetDocument(); std::unique_ptr 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::BindOnce(&DownloadFileCompletion, std::move(callback)), base::BindRepeating(&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::BindOnce(&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 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) {} 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; std::ignore = 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::BindOnce(&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() 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 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 weak_factory_{this}; }; } // 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::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(handler->DidFinishStream, handler_user_data, static_cast(PP_ERROR_FAILED))); return; } GURL gurl(pexe_url); const blink::WebDocument& document = plugin_instance->GetContainer()->GetDocument(); std::unique_ptr 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::mojom::RequestContextType::OBJECT); downloader->Load(url_request); } } // namespace nacl