306 lines
9.8 KiB
C++
306 lines
9.8 KiB
C++
/* -*- 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<Maybe<UntrustedModulesData>, ipc::ResponseRejectReason, true>;
|
|
|
|
using MultiGetUntrustedModulesPromise =
|
|
MozPromise<bool /*aIgnored*/, nsresult, true>;
|
|
|
|
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<MultiGetUntrustedModulesPromise> GetUntrustedModuleLoadEvents();
|
|
void Serialize(RefPtr<dom::Promise>&& aPromise);
|
|
|
|
MultiGetUntrustedModulesData(const MultiGetUntrustedModulesData&) = delete;
|
|
MultiGetUntrustedModulesData(MultiGetUntrustedModulesData&&) = delete;
|
|
MultiGetUntrustedModulesData& operator=(const MultiGetUntrustedModulesData&) =
|
|
delete;
|
|
MultiGetUntrustedModulesData& operator=(MultiGetUntrustedModulesData&&) =
|
|
delete;
|
|
|
|
private:
|
|
~MultiGetUntrustedModulesData() = default;
|
|
|
|
void AddPending(RefPtr<UntrustedModulesPromise>&& aNewPending) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
++mNumPending;
|
|
|
|
RefPtr<MultiGetUntrustedModulesData> self(this);
|
|
aNewPending->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[self](Maybe<UntrustedModulesData>&& aResult) {
|
|
self->OnCompletion(std::move(aResult));
|
|
},
|
|
[self](nsresult aReason) { self->OnCompletion(); });
|
|
}
|
|
|
|
void AddPending(RefPtr<UntrustedModulesIpcPromise>&& aNewPending) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
++mNumPending;
|
|
|
|
RefPtr<MultiGetUntrustedModulesData> self(this);
|
|
aNewPending->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[self](Maybe<UntrustedModulesData>&& 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<UntrustedModulesData>&& 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<UntrustedModulesBackupService> mBackupSvc;
|
|
RefPtr<MultiGetUntrustedModulesPromise::Private> mPromise;
|
|
size_t mNumPending;
|
|
};
|
|
|
|
RefPtr<MultiGetUntrustedModulesPromise>
|
|
MultiGetUntrustedModulesData::GetUntrustedModuleLoadEvents() {
|
|
MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread());
|
|
|
|
// Parent process
|
|
RefPtr<DllServices> dllSvc(DllServices::Get());
|
|
AddPending(dllSvc->GetUntrustedModulesData());
|
|
|
|
// Child processes
|
|
nsTArray<dom::ContentParent*> contentParents;
|
|
dom::ContentParent::GetAll(contentParents);
|
|
for (auto&& contentParent : contentParents) {
|
|
AddPending(contentParent->SendGetUntrustedModulesData());
|
|
}
|
|
|
|
if (RefPtr<net::SocketProcessParent> socketActor =
|
|
net::SocketProcessParent::GetSingleton()) {
|
|
AddPending(socketActor->SendGetUntrustedModulesData());
|
|
}
|
|
|
|
if (RDDProcessManager* rddMgr = RDDProcessManager::Get()) {
|
|
if (RDDChild* rddChild = rddMgr->GetRDDChild()) {
|
|
AddPending(rddChild->SendGetUntrustedModulesData());
|
|
}
|
|
}
|
|
|
|
if (RefPtr<ipc::UtilityProcessManager> utilityManager =
|
|
ipc::UtilityProcessManager::GetIfExists()) {
|
|
for (RefPtr<ipc::UtilityProcessParent>& parent :
|
|
utilityManager->GetAllProcessesProcessParent()) {
|
|
AddPending(parent->SendGetUntrustedModulesData());
|
|
}
|
|
}
|
|
|
|
if (RefPtr<gmp::GeckoMediaPluginServiceParent> gmps =
|
|
gmp::GeckoMediaPluginServiceParent::GetSingleton()) {
|
|
nsTArray<RefPtr<
|
|
gmp::GeckoMediaPluginServiceParent::GetUntrustedModulesDataPromise>>
|
|
promises;
|
|
gmps->SendGetUntrustedModulesData(promises);
|
|
for (auto& promise : promises) {
|
|
AddPending(std::move(promise));
|
|
}
|
|
}
|
|
|
|
return mPromise;
|
|
}
|
|
|
|
void MultiGetUntrustedModulesData::Serialize(RefPtr<dom::Promise>&& 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<DllServices> dllSvc(DllServices::Get());
|
|
nt::SharedSection* sharedSection = dllSvc->GetSharedSection();
|
|
if (sharedSection) {
|
|
auto dynamicBlocklist = sharedSection->GetDynamicBlocklist();
|
|
|
|
nsTArray<nsDependentSubstring> 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<JS::Value> 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<dom::Promise> promise(dom::Promise::Create(global, result));
|
|
if (NS_WARN_IF(result.Failed())) {
|
|
return result.StealNSResult();
|
|
}
|
|
|
|
auto multi = MakeRefPtr<MultiGetUntrustedModulesData>(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
|