/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "UntrustedModules.h" #include "GMPServiceParent.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/MozPromise.h" #include "mozilla/net/SocketProcessParent.h" #include "mozilla/ipc/UtilityProcessParent.h" #include "mozilla/ipc/UtilityProcessManager.h" #include "mozilla/RDDChild.h" #include "mozilla/RDDProcessManager.h" #include "mozilla/WinDllServices.h" #include "nsISupportsImpl.h" #include "nsProxyRelease.h" #include "nsXULAppAPI.h" #include "UntrustedModulesDataSerializer.h" namespace mozilla { namespace Telemetry { static const uint32_t kMaxModulesArrayLen = 100; using UntrustedModulesIpcPromise = MozPromise, ipc::ResponseRejectReason, true>; using MultiGetUntrustedModulesPromise = MozPromise; class MOZ_HEAP_CLASS MultiGetUntrustedModulesData final { public: /** * @param aFlags [in] Combinations of the flags defined under nsITelemetry. * (See "Flags for getUntrustedModuleLoadEvents" * in nsITelemetry.idl) */ explicit MultiGetUntrustedModulesData(uint32_t aFlags) : mFlags(aFlags), mBackupSvc(UntrustedModulesBackupService::Get()), mPromise(new MultiGetUntrustedModulesPromise::Private(__func__)), mNumPending(0) {} NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MultiGetUntrustedModulesData) RefPtr GetUntrustedModuleLoadEvents(); void Serialize(RefPtr&& aPromise); MultiGetUntrustedModulesData(const MultiGetUntrustedModulesData&) = delete; MultiGetUntrustedModulesData(MultiGetUntrustedModulesData&&) = delete; MultiGetUntrustedModulesData& operator=(const MultiGetUntrustedModulesData&) = delete; MultiGetUntrustedModulesData& operator=(MultiGetUntrustedModulesData&&) = delete; private: ~MultiGetUntrustedModulesData() = default; void AddPending(RefPtr&& aNewPending) { MOZ_ASSERT(NS_IsMainThread()); ++mNumPending; RefPtr self(this); aNewPending->Then( GetMainThreadSerialEventTarget(), __func__, [self](Maybe&& aResult) { self->OnCompletion(std::move(aResult)); }, [self](nsresult aReason) { self->OnCompletion(); }); } void AddPending(RefPtr&& aNewPending) { MOZ_ASSERT(NS_IsMainThread()); ++mNumPending; RefPtr self(this); aNewPending->Then( GetMainThreadSerialEventTarget(), __func__, [self](Maybe&& aResult) { self->OnCompletion(std::move(aResult)); }, [self](ipc::ResponseRejectReason&& aReason) { self->OnCompletion(); }); } void OnCompletion() { MOZ_ASSERT(NS_IsMainThread() && mNumPending > 0); --mNumPending; if (mNumPending) { return; } mPromise->Resolve(true, __func__); } void OnCompletion(Maybe&& aResult) { MOZ_ASSERT(NS_IsMainThread()); if (aResult.isSome()) { mBackupSvc->Backup(std::move(aResult.ref())); } OnCompletion(); } private: // Combinations of the flags defined under nsITelemetry. // (See "Flags for getUntrustedModuleLoadEvents" in nsITelemetry.idl) uint32_t mFlags; RefPtr mBackupSvc; RefPtr mPromise; size_t mNumPending; }; RefPtr MultiGetUntrustedModulesData::GetUntrustedModuleLoadEvents() { MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread()); // Parent process RefPtr dllSvc(DllServices::Get()); AddPending(dllSvc->GetUntrustedModulesData()); // Child processes nsTArray contentParents; dom::ContentParent::GetAll(contentParents); for (auto&& contentParent : contentParents) { AddPending(contentParent->SendGetUntrustedModulesData()); } if (auto* socketActor = net::SocketProcessParent::GetSingleton()) { AddPending(socketActor->SendGetUntrustedModulesData()); } if (RDDProcessManager* rddMgr = RDDProcessManager::Get()) { if (RDDChild* rddChild = rddMgr->GetRDDChild()) { AddPending(rddChild->SendGetUntrustedModulesData()); } } if (RefPtr utilityManager = ipc::UtilityProcessManager::GetIfExists()) { for (RefPtr& parent : utilityManager->GetAllProcessesProcessParent()) { AddPending(parent->SendGetUntrustedModulesData()); } } if (RefPtr gmps = gmp::GeckoMediaPluginServiceParent::GetSingleton()) { nsTArray> promises; gmps->SendGetUntrustedModulesData(promises); for (auto& promise : promises) { AddPending(std::move(promise)); } } return mPromise; } void MultiGetUntrustedModulesData::Serialize(RefPtr&& aPromise) { MOZ_ASSERT(NS_IsMainThread()); dom::AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(aPromise->GetGlobalObject()))) { aPromise->MaybeReject(NS_ERROR_FAILURE); return; } JSContext* cx = jsapi.cx(); UntrustedModulesDataSerializer serializer(cx, kMaxModulesArrayLen, mFlags); if (!serializer) { aPromise->MaybeReject(NS_ERROR_FAILURE); return; } nsresult rv; if (mFlags & nsITelemetry::INCLUDE_OLD_LOADEVENTS) { // When INCLUDE_OLD_LOADEVENTS is set, we need to return instances // from both "Staging" and "Settled" backup. if (mFlags & nsITelemetry::KEEP_LOADEVENTS_NEW) { // When INCLUDE_OLD_LOADEVENTS and KEEP_LOADEVENTS_NEW are set, we need to // return a JS object consisting of all instances from both "Staging" and // "Settled" backups, keeping instances in those backups as is. if (mFlags & nsITelemetry::EXCLUDE_STACKINFO_FROM_LOADEVENTS) { // Without the stack info, we can add multiple UntrustedModulesData to // the serializer directly. rv = serializer.Add(mBackupSvc->Staging()); if (NS_WARN_IF(NS_FAILED(rv))) { aPromise->MaybeReject(rv); return; } rv = serializer.Add(mBackupSvc->Settled()); if (NS_WARN_IF(NS_FAILED(rv))) { aPromise->MaybeReject(rv); return; } } else { // Currently we don't have a method to merge UntrustedModulesData into // a serialized JS object because merging CombinedStack will be tricky. // Thus we return an error on this flag combination. aPromise->MaybeReject(NS_ERROR_INVALID_ARG); return; } } else { // When KEEP_LOADEVENTS_NEW is not set, we can move data from "Staging" // to "Settled" first, then add "Settled" to the serializer. mBackupSvc->SettleAllStagingData(); const UntrustedModulesBackupData& settledRef = mBackupSvc->Settled(); if (settledRef.IsEmpty()) { aPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE); return; } rv = serializer.Add(settledRef); if (NS_WARN_IF(NS_FAILED(rv))) { aPromise->MaybeReject(rv); return; } } } else { // When INCLUDE_OLD_LOADEVENTS is not set, we serialize only the "Staging" // into a JS object. const UntrustedModulesBackupData& stagingRef = mBackupSvc->Staging(); if (stagingRef.IsEmpty()) { aPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE); return; } rv = serializer.Add(stagingRef); if (NS_WARN_IF(NS_FAILED(rv))) { aPromise->MaybeReject(rv); return; } // When KEEP_LOADEVENTS_NEW is not set, we move all "Staging" instances // to the "Settled". if (!(mFlags & nsITelemetry::KEEP_LOADEVENTS_NEW)) { mBackupSvc->SettleAllStagingData(); } } #if defined(XP_WIN) RefPtr dllSvc(DllServices::Get()); nt::SharedSection* sharedSection = dllSvc->GetSharedSection(); if (sharedSection) { auto dynamicBlocklist = sharedSection->GetDynamicBlocklist(); nsTArray blockedModules; for (const auto& blockedEntry : dynamicBlocklist) { if (!blockedEntry.IsValidDynamicBlocklistEntry()) { break; } blockedModules.AppendElement( nsDependentSubstring(blockedEntry.mName.Buffer, blockedEntry.mName.Length / sizeof(wchar_t))); } rv = serializer.AddBlockedModules(blockedModules); if (NS_WARN_IF(NS_FAILED(rv))) { aPromise->MaybeReject(rv); return; } } #endif JS::Rooted jsval(cx); serializer.GetObject(&jsval); aPromise->MaybeResolve(jsval); } nsresult GetUntrustedModuleLoadEvents(uint32_t aFlags, JSContext* cx, dom::Promise** aPromise) { // Create a promise using global context. nsIGlobalObject* global = xpc::CurrentNativeGlobal(cx); if (NS_WARN_IF(!global)) { return NS_ERROR_FAILURE; } ErrorResult result; RefPtr promise(dom::Promise::Create(global, result)); if (NS_WARN_IF(result.Failed())) { return result.StealNSResult(); } auto multi = MakeRefPtr(aFlags); multi->GetUntrustedModuleLoadEvents()->Then( GetMainThreadSerialEventTarget(), __func__, [promise, multi](bool) mutable { multi->Serialize(std::move(promise)); }, [promise](nsresult aRv) { promise->MaybeReject(aRv); }); promise.forget(aPromise); return NS_OK; } } // namespace Telemetry } // namespace mozilla