Bug 1542830: Part 8 - Rename the "untrustedModules" ping to "third-party-modules" and change the schema to support multiprocess; r=janerik

Note that to avoid introducing errors, I elected against renaming everything in
the code; internally to Firefox the code still refers to "UntrustedModules";
only the relevant fields have been renamed to reference the new ping schema.

A PR for backend schema changes is in the works.

Differential Revision: https://phabricator.services.mozilla.com/D43162

--HG--
rename : toolkit/components/telemetry/docs/data/untrusted-modules-ping.rst => toolkit/components/telemetry/docs/data/third-party-modules-ping.rst
rename : toolkit/components/telemetry/tests/unit/test_UntrustedModulesPing.js => toolkit/components/telemetry/tests/unit/test_ThirdPartyModulesPing.js
extra : moz-landing-system : lando
This commit is contained in:
Aaron Klotz 2019-09-20 16:26:57 +00:00
Родитель 462e5e5d67
Коммит 6530da2495
10 изменённых файлов: 516 добавлений и 441 удалений

Просмотреть файл

@ -0,0 +1,128 @@
"third-party-modules" ping
==========================
This ping contains information about events whereby third-party modules
were loaded into Firefox processes.
.. code-block:: js
{
"type": "third-party-modules",
... common ping data
"clientId": <UUID>,
"environment": { ... },
"payload": {
"structVersion": 1,
"modules" [
{
// The sanitized name of the module as resolved by the Windows loader.
"resolvedDllName": <string>,
// Version of the DLL as contained in its resources's fixed version information.
"fileVersion": <string>,
// The value of the CompanyName field as extracted from the DLL's version information. This property is only present when such version info is present, and when the 'signedBy' property is absent.
"companyName": <string>,
// The organization whose certificate was used to sign the DLL. Only present for signed modules.
"signedBy": <string>,
// Flags that indicate this module's level of trustworthiness. This corresponds to one or more mozilla::ModuleTrustFlags OR'd together.
"trustFlags": <unsigned int>
},
... Additional modules (maximum 100)
],
"processes": {
<string containing pid, formatted as "0x%x">: {
// Except for Default (which is remapped to "browser"), one of the process string names specified in xpcom/build/GeckoProcessTypes.h.
"processType": <string>,
// Elapsed time since process creation that this object was generated, in seconds.
"elapsed": <number>,
// Time spent loading xul.dll in milliseconds.
"xulLoadDurationMS": <number>,
// Number of dropped events due to failures sanitizing file paths.
"sanitizationFailures": <int>,
// Number of dropped events due to failures computing trust levels.
"trustTestFailures": <int>,
// Array of module load events for this process. The entries of this array are ordered to be in sync with the combinedStacks.stacks array (see below)
"events": [
{
// Elapsed time since process creation that this event was generated, in milliseconds.
"processUptimeMS": <int>,
// Time spent loading this module, in milliseconds.
"loadDurationMS": <number>,
// Thread ID for the thread that loaded the module.
"threadID": <int>,
// Name of the thread that loaded the module, when applicable.
"threadName": <string>,
// The sanitized name of the module that was requested by the invoking code. Only exists when it is different from resolvedDllName.
"requestedDllName": <string>,
// The base address to which the loader mapped the module.
"baseAddress": <string formatted as "0x%x">,
// Index of the element in the modules array that contains details about the module that was loaded during this event.
"moduleIndex": <int>
},
... Additional events (maximum 50)
],
"combinedStacks": [
"memoryMap": [
[
// Name of the module symbol file, e.g. ``xul.pdb``
<string>,
// Breakpad identifier of the module, e.g. ``08A541B5942242BDB4AEABD8C87E4CFF2``
<string>
],
... Additional modules
],
// Array of stacks for this process. These entries are ordered to be in sync with the events array
"stacks": [
[
[
// The module index or -1 for invalid module indices
<integer>,
// The program counter relative to its module base, or an absolute pc if the module index is -1
<unsigned integer>
],
... Additional stack frames (maximum 512)
],
... Additional stack traces (maximum 50)
]
]
},
... Additional processes (maximum 100)
}
}
}
payload.processes[...].events[...].resolvedDllName
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The effective path to the module file, sanitized to remove any potentially
sensitive information. In most cases, the directory path is removed leaving only
the leaf name, e.g. ``foo.dll``. There are three exceptions:
* Paths under ``%ProgramFiles%`` are preserved, e.g. ``%ProgramFiles%\FooApplication\foo.dll``
* Paths under ``%SystemRoot%`` are preserved, e.g. ``%SystemRoot%\System32\DriverStore\FileRepository\nvlt.inf_amd64_97992900c592012e\nvinitx.dll``
* Paths under the temporary path are preserved, e.g. ``%TEMP%\bin\foo.dll``
payload.processes[...].events[...].requestedDllName
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The name of the module as it was requested from the OS. This string is also
sanitized in a similar fashion to to ``resolvedDllName``. This string is
omitted from the ping when it is identical to ``resolvedDllName``.
Notes
~~~~~
* The client id is submitted with this ping.
* The :doc:`Telemetry Environment <../data/environment>` is submitted in this ping.
* String fields within ``payload`` are limited in length to 260 characters.
* This ping is sent once daily.
* If there are no events to report, this ping is not sent.
Version History
~~~~~~~~~~~~~~~
- Firefox 71:
- Renamed from untrustedModules to third-party-modules with a revised schema (`bug 1542830 <https://bugzilla.mozilla.org/show_bug.cgi?id=1542830>`_).
- Firefox 70:
- Added ``%SystemRoot%`` as an exemption to path sanitization (`bug 1573275 <https://bugzilla.mozilla.org/show_bug.cgi?id=1573275>`_).
- Firefox 66:
- Added Windows Side-by-side directory trust flag (`bug 1514694 <https://bugzilla.mozilla.org/show_bug.cgi?id=1514694>`_).
- Added module load times (``xulLoadDurationMS``, ``loadDurationMS``) and xul.dll trust flag (`bug 1518490 <https://bugzilla.mozilla.org/show_bug.cgi?id=1518490>`_).
- Added SysWOW64 trust flag (`bug 1518798 <https://bugzilla.mozilla.org/show_bug.cgi?id=1518798>`_).
- Firefox 65: Initial support (`bug 1435827 <https://bugzilla.mozilla.org/show_bug.cgi?id=1435827>`_).

Просмотреть файл

@ -1,142 +0,0 @@
"untrustedModules" ping
=======================
This ping contains information about events whereby untrusted modules
were loaded into the Firefox process.
.. code-block:: js
{
"type": "untrustedModules",
... common ping data
"clientId": <UUID>,
"environment": { ... },
"payload": {
"structVersion": <number>, // See below
"xulLoadDurationMS": <number>, // Time spent loading xul.dll, in milliseconds (introduced in Firefox 66)
"errorModules": <number>, // Number of modules that failed to be evaluated
"events": [ ... ]
"combinedStacks": { ... },
}
}
payload.structVersion
---------------------
Version of this payload structure. Reserved for future use; this is currently
always ``1``.
payload.events
--------------
An array, each element representing the load of an untrusted module. Because
modules can invoke the load of other modules, there may be
multiple modules listed per event.
This array is synchronized with the ``payload.combinedStacks`` object which
contains the captured stack trace for each event.
A maximum of 50 events are captured.
.. code-block:: js
{
"processUptimeMS": <number>, // Number of milliseconds since app startup
"isStartup": <boolean>, // See below
"threadID": <unsigned integer>, // Thread ID where the event occurred
"threadName": <string>, // If available, the name of the thread
"modules": [
{
"moduleName": <string>, // See below
"loaderName": <string>, // See below
"loadDurationMS" : <number>, // Optional. Time spent loading this module, in milliseconds (introduced in Firefox 66)
"baseAddress": <string>, // Base address where the module was loaded, e.g. "0x7ffc01260000"
"fileVersion": <string>, // The module file version, e.g. "1.10.2.6502"
"moduleTrustFlags": <unsigned integer> // See below
},
... Additional modules
]
}
payload.events[...].isStartup
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* ``true`` if the event represents a module that was already loaded before monitoring was initiated.
* ``false`` if the event was captured during normal execution.
payload.events[...].modules[...].moduleName
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The path to the module file, modified to remove any potentially sensitive
information. In most cases, the directory path is removed leaving only the
file name, e.g. ``foo.dll``. There are three exceptions:
* Paths under ``%ProgramFiles%`` are preserved, e.g. ``%ProgramFiles%\FooApplication\foo.dll``
* Paths under ``%SystemRoot%`` are preserved, e.g. ``%SystemRoot%\System32\DriverStore\FileRepository\nvlt.inf_amd64_97992900c592012e\nvinitx.dll``
* Paths under the temporary path are preserved, e.g. ``%TEMP%\bin\foo.dll``
payload.events[...].modules[...].loaderName
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The name of the module as it was requested from the OS. Generally this will be
the same as the above ``moduleName``.
This is treated as a path and is modified for privacy in the same way as
``moduleName`` above.
payload.events[...].modules[...].moduleTrustFlags
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is a bitfield indicating whether various attributes apply to the module.
* ``1`` if the module is digitally signed by Mozilla
* ``2`` if the module is digitally signed by Microsoft
* ``4`` if the module's version info indicates it's a Microsoft module
* ``8`` if the module is located in the Firefox application directory
* ``0x10`` if the module has the same location and version information as the Firefox executable
* ``0x20`` if the module is located in the system directory
* ``0x40`` if the module is a known keyboard layout DLL
* ``0x80`` if the module is an internally-recognized JIT module
* ``0x100`` if the module is located in the Windows Side-by-side directory (introduced in Firefox 66)
* ``0x200`` if the module is the XPCOM module, xul.dll (introduced in Firefox 66)
* ``0x400`` if the module is located in the SysWOW64 directory (introduced in Firefox 66)
payload.combinedStacks
----------------------
This object holds stack traces that correspond to events in ``payload.events``.
.. code-block:: js
"combinedStacks": {
"memoryMap": [
[
<string>, // Name of the module symbol file, e.g. ``xul.pdb``
<string> // Breakpad identifier of the module, e.g. ``08A541B5942242BDB4AEABD8C87E4CFF2``
],
... Additional modules
],
"stacks": [
[
[
<integer>, // The module index or -1 for invalid module indices
<unsigned integer> // The program counter relative to its module base, or an absolute pc
],
... Additional stack frames (maximum 500)
],
... Additional stack traces (maximum 50)
]
},
Notes
~~~~~
* The client id is submitted with this ping.
* The :doc:`Telemetry Environment <../data/environment>` is submitted in this ping.
* String fields within ``payload`` are limited in length to 260 characters.
* This ping is only enabled on Nightly builds of Firefox Desktop for Windows.
* This ping is sent once daily.
* Only events occurring on the main browser process are recorded.
* If there are no events to report, this ping is not sent.
Version History
~~~~~~~~~~~~~~~
- Firefox 65: Initial support (`bug 1435827 <https://bugzilla.mozilla.org/show_bug.cgi?id=1435827>`_).
- Firefox 66:
- Added Windows Side-by-side directory trust flag (`bug 1514694 <https://bugzilla.mozilla.org/show_bug.cgi?id=1514694>`_).
- Added module load times (``xulLoadDurationMS``, ``loadDurationMS``) and xul.dll trust flag (`bug 1518490 <https://bugzilla.mozilla.org/show_bug.cgi?id=1518490>`_).
- Added SysWOW64 trust flag (`bug 1518798 <https://bugzilla.mozilla.org/show_bug.cgi?id=1518798>`_).

Просмотреть файл

@ -7,40 +7,53 @@
#include "UntrustedModules.h"
#include "core/TelemetryCommon.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/UntrustedModulesProcessor.h"
#include "mozilla/WinDllServices.h"
#include "nsCOMPtr.h"
#include "nsDataHashtable.h"
#include "nsLocalFile.h"
#include "nsPrintfCString.h"
#include "nsProxyRelease.h"
#include "nsXPCOMCIDInternal.h"
#include "nsUnicharUtils.h"
#include "nsXULAppAPI.h"
namespace {
using IndexMap = nsDataHashtable<nsStringHashKey, uint32_t>;
} // anonymous namespace
namespace mozilla {
namespace Telemetry {
static const int32_t kUntrustedModuleLoadEventsTelemetryVersion = 1;
static const uint32_t kThirdPartyModulesPingVersion = 1;
static const uint32_t kMaxModulesArrayLen = 100;
/**
* Limits the length of a string by removing the middle of the string, replacing
* with ellipses.
* with ellipsis.
* e.g. LimitStringLength("hello world", 6) would result in "he...d"
*
* @param aStr [in,out] The string to transform
* @param aMaxFieldLength [in] The maximum length of the resulting string.
* this must be long enough to hold the ellipses.
*/
static void LimitStringLength(nsAString& aStr, size_t aMaxFieldLength) {
if (aStr.Length() <= aMaxFieldLength) {
return;
}
NS_NAMED_LITERAL_STRING(kEllipses, "...");
NS_NAMED_LITERAL_STRING(kEllipsis, "...");
MOZ_ASSERT(aMaxFieldLength >= kEllipses.Length());
size_t cutPos = (aMaxFieldLength - kEllipses.Length()) / 2;
size_t rightLen = aMaxFieldLength - kEllipses.Length() - cutPos;
if (aMaxFieldLength <= (kEllipsis.Length() + 3)) {
// An ellipsis is useless in this case, as it would obscure the string to
// the point that we cannot even determine the string's contents. We might
// as well just truncate.
aStr.Truncate(aMaxFieldLength);
return;
}
size_t cutPos = (aMaxFieldLength - kEllipsis.Length()) / 2;
size_t rightLen = aMaxFieldLength - kEllipsis.Length() - cutPos;
size_t cutLen = aStr.Length() - (cutPos + rightLen);
aStr.Replace(cutPos, cutLen, kEllipses);
aStr.Replace(cutPos, cutLen, kEllipsis);
}
/**
@ -57,7 +70,7 @@ static void LimitStringLength(nsAString& aStr, size_t aMaxFieldLength) {
*/
static bool AddLengthLimitedStringProp(JSContext* cx, JS::HandleObject aObj,
const char* aName, const nsAString& aVal,
size_t aMaxFieldLength = 260) {
size_t aMaxFieldLength = MAX_PATH) {
JS::RootedValue jsval(cx);
nsAutoString shortVal(aVal);
LimitStringLength(shortVal, aMaxFieldLength);
@ -65,6 +78,26 @@ static bool AddLengthLimitedStringProp(JSContext* cx, JS::HandleObject aObj,
return JS_DefineProperty(cx, aObj, aName, jsval, JSPROP_ENUMERATE);
};
static JSString* ModuleVersionToJSString(JSContext* aCx,
const ModuleVersion& aVersion) {
uint16_t major, minor, patch, build;
Tie(major, minor, patch, build) = aVersion.AsTuple();
NS_NAMED_LITERAL_STRING(dot, ".");
nsAutoString strVer;
strVer.AppendInt(major);
strVer.Append(dot);
strVer.AppendInt(minor);
strVer.Append(dot);
strVer.AppendInt(patch);
strVer.Append(dot);
strVer.AppendInt(build);
return Common::ToJSString(aCx, strVer);
}
/**
* Convert the given mozilla::Vector to a JavaScript array.
*
@ -79,18 +112,20 @@ static bool AddLengthLimitedStringProp(JSContext* cx, JS::HandleObject aObj,
* const ArrayElementT& aElement)
* @return true if aRet was successfully assigned to the new array object.
*/
template <typename T, size_t N, typename AllocPolicy, typename Converter>
template <typename T, size_t N, typename AllocPolicy, typename Converter,
typename... Args>
static bool VectorToJSArray(JSContext* cx, JS::MutableHandleObject aRet,
const Vector<T, N, AllocPolicy>& aContainer,
Converter&& aElementConverter) {
Converter&& aElementConverter, Args&&... aArgs) {
JS::RootedObject arr(cx, JS_NewArrayObject(cx, 0));
if (!arr) {
return false;
}
for (size_t i = 0; i < aContainer.length(); ++i) {
for (size_t i = 0, l = aContainer.length(); i < l; ++i) {
JS::RootedValue jsel(cx);
if (!aElementConverter(cx, &jsel, aContainer[i])) {
if (!aElementConverter(cx, &jsel, aContainer[i],
std::forward<Args>(aArgs)...)) {
return false;
}
if (!JS_DefineElement(cx, arr, i, jsel, JSPROP_ENUMERATE)) {
@ -102,271 +137,362 @@ static bool VectorToJSArray(JSContext* cx, JS::MutableHandleObject aRet,
return true;
}
/**
* Converts a ModuleLoadEvent::ModuleInfo to a JS object.
*
* @param cx [in] The JS context.
* @param aRet [out] This gets assigned to the newly created object.
* @param aModInfo [in] The source object to convert.
* @return true if aRet was successfully assigned.
*/
static bool ModuleInfoToJSObj(JSContext* cx, JS::MutableHandleObject aRet,
const ModuleLoadEvent::ModuleInfo& aModInfo) {
JS::RootedObject modObj(cx, JS_NewObject(cx, nullptr));
if (!modObj) {
static bool SerializeModule(JSContext* aCx, JS::MutableHandleValue aElement,
const RefPtr<ModuleRecord>& aModule) {
if (!aModule) {
return false;
}
JS::RootedValue jsval(cx);
nsPrintfCString strBaseAddress("0x%p", (void*)aModInfo.mBase);
jsval.setString(Common::ToJSString(cx, strBaseAddress));
if (!JS_DefineProperty(cx, modObj, "baseAddress", jsval, JSPROP_ENUMERATE)) {
JS::RootedObject obj(aCx, JS_NewPlainObject(aCx));
if (!obj) {
return false;
}
jsval.setString(Common::ToJSString(cx, aModInfo.mFileVersion));
if (!JS_DefineProperty(cx, modObj, "fileVersion", jsval, JSPROP_ENUMERATE)) {
if (!AddLengthLimitedStringProp(aCx, obj, "resolvedDllName",
aModule->mSanitizedDllName)) {
return false;
}
if (!AddLengthLimitedStringProp(cx, modObj, "loaderName",
aModInfo.mLdrName)) {
return false;
}
if (!AddLengthLimitedStringProp(cx, modObj, "moduleName",
aModInfo.mFilePathClean)) {
return false;
}
if (aModInfo.mLoadDurationMS.isSome()) {
jsval.setNumber(aModInfo.mLoadDurationMS.value());
if (!JS_DefineProperty(cx, modObj, "loadDurationMS", jsval,
if (aModule->mVersion.isSome()) {
JS::RootedValue jsModuleVersion(aCx);
jsModuleVersion.setString(
ModuleVersionToJSString(aCx, aModule->mVersion.ref()));
if (!JS_DefineProperty(aCx, obj, "fileVersion", jsModuleVersion,
JSPROP_ENUMERATE)) {
return false;
}
}
jsval.setNumber((uint32_t)aModInfo.mTrustFlags);
if (!JS_DefineProperty(cx, modObj, "moduleTrustFlags", jsval,
if (aModule->mVendorInfo.isSome()) {
const char* propName;
const VendorInfo& vendorInfo = aModule->mVendorInfo.ref();
switch (vendorInfo.mSource) {
case VendorInfo::Source::Signature:
propName = "signedBy";
break;
case VendorInfo::Source::VersionInfo:
propName = "companyName";
break;
default:
MOZ_ASSERT_UNREACHABLE("Unknown VendorInfo Source!");
return false;
}
MOZ_ASSERT(!vendorInfo.mVendor.IsEmpty());
if (vendorInfo.mVendor.IsEmpty()) {
return false;
}
if (!AddLengthLimitedStringProp(aCx, obj, propName, vendorInfo.mVendor)) {
return false;
}
}
JS::RootedValue jsTrustFlags(aCx);
jsTrustFlags.setNumber(static_cast<uint32_t>(aModule->mTrustFlags));
if (!JS_DefineProperty(aCx, obj, "trustFlags", jsTrustFlags,
JSPROP_ENUMERATE)) {
return false;
}
aRet.set(modObj);
aElement.setObject(*obj);
return true;
}
/**
* Converts a ModuleLoadEvent object to a Javascript array
*
* @param cx [in] The JS context
* @param aRet [out] Handle that receives the resulting array object
* @param aEvent [in] The event to convert from
* @return true upon success
*/
static bool ModuleLoadEventToJSArray(JSContext* cx, JS::MutableHandleValue aRet,
const ModuleLoadEvent& aEvent) {
static bool SerializeEvent(JSContext* aCx, JS::MutableHandleValue aElement,
const ProcessedModuleLoadEvent& aEvent,
const IndexMap& aModuleIndices) {
MOZ_ASSERT(NS_IsMainThread());
JS::RootedValue jsval(cx);
JS::RootedObject eObj(cx, JS_NewObject(cx, nullptr));
if (!eObj) {
return false;
}
jsval.setNumber((uint32_t)aEvent.mThreadID);
if (!JS_DefineProperty(cx, eObj, "threadID", jsval, JSPROP_ENUMERATE)) {
return false;
}
jsval.setBoolean(aEvent.mIsStartup);
if (!JS_DefineProperty(cx, eObj, "isStartup", jsval, JSPROP_ENUMERATE)) {
JS::RootedObject obj(aCx, JS_NewPlainObject(aCx));
if (!obj) {
return false;
}
JS::RootedValue jsProcessUptimeMS(aCx);
// Javascript doesn't like 64-bit integers; convert to double.
jsval.setNumber((double)aEvent.mProcessUptimeMS);
if (!JS_DefineProperty(cx, eObj, "processUptimeMS", jsval,
jsProcessUptimeMS.setNumber(static_cast<double>(aEvent.mProcessUptimeMS));
if (!JS_DefineProperty(aCx, obj, "processUptimeMS", jsProcessUptimeMS,
JSPROP_ENUMERATE)) {
return false;
}
// This function should always get called on the main thread
if (::GetCurrentThreadId() == aEvent.mThreadID) {
jsval.setString(Common::ToJSString(cx, NS_LITERAL_STRING("Main Thread")));
if (aEvent.mLoadDurationMS) {
JS::RootedValue jsLoadDurationMS(aCx);
jsLoadDurationMS.setNumber(aEvent.mLoadDurationMS.value());
if (!JS_DefineProperty(aCx, obj, "loadDurationMS", jsLoadDurationMS,
JSPROP_ENUMERATE)) {
return false;
}
}
JS::RootedValue jsThreadId(aCx);
jsThreadId.setNumber(static_cast<uint32_t>(aEvent.mThreadId));
if (!JS_DefineProperty(aCx, obj, "threadID", jsThreadId, JSPROP_ENUMERATE)) {
return false;
}
nsDependentCString effectiveThreadName;
if (aEvent.mThreadId == ::GetCurrentThreadId()) {
effectiveThreadName.Rebind(NS_LITERAL_CSTRING("Main Thread"), 0);
} else {
jsval.setString(Common::ToJSString(cx, aEvent.mThreadName));
effectiveThreadName.Rebind(aEvent.mThreadName, 0);
}
if (!JS_DefineProperty(cx, eObj, "threadName", jsval, JSPROP_ENUMERATE)) {
if (!effectiveThreadName.IsEmpty()) {
JS::RootedValue jsThreadName(aCx);
jsThreadName.setString(Common::ToJSString(aCx, effectiveThreadName));
if (!JS_DefineProperty(aCx, obj, "threadName", jsThreadName,
JSPROP_ENUMERATE)) {
return false;
}
}
// Don't add this property unless mRequestedDllName differs from
// the associated module's mSanitizedDllName
if (!aEvent.mRequestedDllName.IsEmpty() &&
!aEvent.mRequestedDllName.Equals(aEvent.mModule->mSanitizedDllName,
nsCaseInsensitiveStringComparator())) {
if (!AddLengthLimitedStringProp(aCx, obj, "requestedDllName",
aEvent.mRequestedDllName)) {
return false;
}
}
nsAutoString strBaseAddress;
strBaseAddress.AppendLiteral(u"0x");
strBaseAddress.AppendInt(aEvent.mBaseAddress, 16);
JS::RootedValue jsBaseAddress(aCx);
jsBaseAddress.setString(Common::ToJSString(aCx, strBaseAddress));
if (!JS_DefineProperty(aCx, obj, "baseAddress", jsBaseAddress,
JSPROP_ENUMERATE)) {
return false;
}
JS::RootedObject modulesArray(cx);
bool ok = VectorToJSArray(cx, &modulesArray, aEvent.mModules,
[](JSContext* cx, JS::MutableHandleValue aRet,
const ModuleLoadEvent::ModuleInfo& aModInfo) {
JS::RootedObject obj(cx);
if (!ModuleInfoToJSObj(cx, &obj, aModInfo)) {
return false;
}
aRet.setObject(*obj);
return true;
});
if (!ok) {
nsAutoString resolvedDllPath;
const nsCOMPtr<nsIFile>& resolvedDllName = aEvent.mModule->mResolvedDllName;
if (!resolvedDllName ||
NS_FAILED(resolvedDllName->GetPath(resolvedDllPath))) {
return false;
}
if (!JS_DefineProperty(cx, eObj, "modules", modulesArray, JSPROP_ENUMERATE)) {
uint32_t index;
if (!aModuleIndices.Get(resolvedDllPath, &index)) {
return false;
}
aRet.setObject(*eObj);
JS::RootedValue jsModuleIndex(aCx);
jsModuleIndex.setNumber(index);
if (!JS_DefineProperty(aCx, obj, "moduleIndex", jsModuleIndex,
JSPROP_ENUMERATE)) {
return false;
}
aElement.setObject(*obj);
return true;
}
/**
* Converts a UntrustedModuleLoadTelemetryData to a JS object.
*
* @param aData [in] The source object to convert.
* @param cx [in] The JS context.
* @param aRet [out] This gets assigned to the newly created object.
* @return nsresult
*/
nsresult GetUntrustedModuleLoadEventsJSValue(
const UntrustedModuleLoadTelemetryData& aData, JSContext* cx,
JS::MutableHandle<JS::Value> aRet) {
if (aData.mEvents.empty()) {
aRet.setNull();
return NS_OK;
static nsresult GetPerProcObject(JSContext* aCx, const IndexMap& aModuleIndices,
const UntrustedModulesData& aData,
JS::MutableHandleObject aObj) {
nsDependentCString strProcType;
if (aData.mProcessType == GeckoProcessType_Default) {
strProcType.Rebind(NS_LITERAL_CSTRING("browser"), 0);
} else {
strProcType.Rebind(XRE_ChildProcessTypeToString(aData.mProcessType));
}
JS::RootedValue jsval(cx);
JS::RootedObject mainObj(cx, JS_NewObject(cx, nullptr));
if (!mainObj) {
return NS_ERROR_FAILURE;
}
jsval.setNumber((uint32_t)aData.mErrorModules);
if (!JS_DefineProperty(cx, mainObj, "errorModules", jsval,
JS::RootedValue jsProcType(aCx);
jsProcType.setString(Common::ToJSString(aCx, strProcType));
if (!JS_DefineProperty(aCx, aObj, "processType", jsProcType,
JSPROP_ENUMERATE)) {
return NS_ERROR_FAILURE;
}
jsval.setNumber((uint32_t)kUntrustedModuleLoadEventsTelemetryVersion);
if (!JS_DefineProperty(cx, mainObj, "structVersion", jsval,
JSPROP_ENUMERATE)) {
JS::RootedValue jsElapsed(aCx);
jsElapsed.setNumber(aData.mElapsed.ToSecondsSigDigits());
if (!JS_DefineProperty(aCx, aObj, "elapsed", jsElapsed, JSPROP_ENUMERATE)) {
return NS_ERROR_FAILURE;
}
if (aData.mXULLoadDurationMS.isSome()) {
jsval.setNumber(aData.mXULLoadDurationMS.value());
if (!JS_DefineProperty(cx, mainObj, "xulLoadDurationMS", jsval,
JS::RootedValue jsXulLoadDurationMS(aCx);
jsXulLoadDurationMS.setNumber(aData.mXULLoadDurationMS.value());
if (!JS_DefineProperty(aCx, aObj, "xulLoadDurationMS", jsXulLoadDurationMS,
JSPROP_ENUMERATE)) {
return NS_ERROR_FAILURE;
}
}
JS::RootedObject eventsArray(cx);
if (!VectorToJSArray(cx, &eventsArray, aData.mEvents,
&ModuleLoadEventToJSArray)) {
JS::RootedValue jsSanitizationFailures(aCx);
jsSanitizationFailures.setNumber(aData.mSanitizationFailures);
if (!JS_DefineProperty(aCx, aObj, "sanitizationFailures",
jsSanitizationFailures, JSPROP_ENUMERATE)) {
return NS_ERROR_FAILURE;
}
if (!JS_DefineProperty(cx, mainObj, "events", eventsArray,
JS::RootedValue jsTrustTestFailures(aCx);
jsTrustTestFailures.setNumber(aData.mTrustTestFailures);
if (!JS_DefineProperty(aCx, aObj, "trustTestFailures", jsTrustTestFailures,
JSPROP_ENUMERATE)) {
return NS_ERROR_FAILURE;
}
JS::RootedObject combinedStacksObj(cx,
CreateJSStackObject(cx, aData.mStacks));
JS::RootedObject eventsArray(aCx);
if (!VectorToJSArray(aCx, &eventsArray, aData.mEvents, &SerializeEvent,
aModuleIndices)) {
return NS_ERROR_FAILURE;
}
if (!JS_DefineProperty(aCx, aObj, "events", eventsArray, JSPROP_ENUMERATE)) {
return NS_ERROR_FAILURE;
}
JS::RootedObject combinedStacksObj(aCx,
CreateJSStackObject(aCx, aData.mStacks));
if (!combinedStacksObj) {
return NS_ERROR_FAILURE;
}
if (!JS_DefineProperty(cx, mainObj, "combinedStacks", combinedStacksObj,
if (!JS_DefineProperty(aCx, aObj, "combinedStacks", combinedStacksObj,
JSPROP_ENUMERATE)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
/**
* Converts a UntrustedModulesData to a JS object.
*
* @param aData [in] The source object to convert.
* @param aCx [in] The JS context.
* @param aRet [out] This gets assigned to the newly created object.
* @return nsresult
*/
static nsresult GetUntrustedModuleLoadEventsJSValue(
const UntrustedModulesData& aData, JSContext* aCx,
JS::MutableHandleValue aRet) {
if (aData.mEvents.empty()) {
aRet.setNull();
return NS_OK;
}
if (aData.mModules.Count() > kMaxModulesArrayLen) {
return NS_ERROR_CANNOT_CONVERT_DATA;
}
JS::RootedObject mainObj(aCx, JS_NewPlainObject(aCx));
if (!mainObj) {
return NS_ERROR_FAILURE;
}
JS::RootedValue jsVersion(aCx);
jsVersion.setNumber(kThirdPartyModulesPingVersion);
if (!JS_DefineProperty(aCx, mainObj, "structVersion", jsVersion,
JSPROP_ENUMERATE)) {
return NS_ERROR_FAILURE;
}
IndexMap indexMap;
uint32_t curModulesArrayIdx = 0;
JS::RootedObject modulesArray(aCx, JS_NewArrayObject(aCx, 0));
if (!modulesArray) {
return NS_ERROR_FAILURE;
}
JS::RootedObject perProcObjContainer(aCx, JS_NewPlainObject(aCx));
if (!perProcObjContainer) {
return NS_ERROR_FAILURE;
}
// Serialize each entry in the modules hashtable out to the "modules" array
// and store the indices in |indexMap|
for (auto iter = aData.mModules.ConstIter(); !iter.Done(); iter.Next()) {
auto addPtr = indexMap.LookupForAdd(iter.Key());
if (!addPtr) {
addPtr.OrInsert([curModulesArrayIdx]() { return curModulesArrayIdx; });
JS::RootedValue jsModule(aCx);
if (!SerializeModule(aCx, &jsModule, iter.Data()) ||
!JS_DefineElement(aCx, modulesArray, curModulesArrayIdx, jsModule,
JSPROP_ENUMERATE)) {
return NS_ERROR_FAILURE;
}
++curModulesArrayIdx;
}
}
JS::RootedObject perProcObj(aCx, JS_NewPlainObject(aCx));
if (!perProcObj) {
return NS_ERROR_FAILURE;
}
nsresult rv = GetPerProcObject(aCx, indexMap, aData, &perProcObj);
if (NS_FAILED(rv)) {
return rv;
}
nsAutoCString strPid;
strPid.AppendLiteral("0x");
strPid.AppendInt(static_cast<uint32_t>(aData.mPid), 16);
JS::RootedValue jsPerProcObjValue(aCx);
jsPerProcObjValue.setObject(*perProcObj);
if (!JS_DefineProperty(aCx, perProcObjContainer, strPid.get(),
jsPerProcObjValue, JSPROP_ENUMERATE)) {
return NS_ERROR_FAILURE;
}
JS::RootedValue jsModulesArrayValue(aCx);
jsModulesArrayValue.setObject(*modulesArray);
if (!JS_DefineProperty(aCx, mainObj, "modules", jsModulesArrayValue,
JSPROP_ENUMERATE)) {
return NS_ERROR_FAILURE;
}
JS::RootedValue jsPerProcObjContainerValue(aCx);
jsPerProcObjContainerValue.setObject(*perProcObjContainer);
if (!JS_DefineProperty(aCx, mainObj, "processes", jsPerProcObjContainerValue,
JSPROP_ENUMERATE)) {
return NS_ERROR_FAILURE;
}
aRet.setObject(*mainObj);
return NS_OK;
}
class GetUntrustedModulesMainThreadRunnable final : public Runnable {
nsMainThreadPtrHandle<dom::Promise> mPromise;
bool mDataOK;
UntrustedModuleLoadTelemetryData mData;
nsCOMPtr<nsIThread> mWorkerThread;
static void Serialize(Maybe<UntrustedModulesData>&& aData,
RefPtr<dom::Promise>&& aPromise) {
MOZ_ASSERT(NS_IsMainThread());
public:
GetUntrustedModulesMainThreadRunnable(
const nsMainThreadPtrHandle<dom::Promise>& aPromise, bool aDataOK,
UntrustedModuleLoadTelemetryData&& aData)
: Runnable("GetUntrustedModulesMainThreadRunnable"),
mPromise(aPromise),
mDataOK(aDataOK),
mData(std::move(aData)),
mWorkerThread(do_GetCurrentThread()) {
MOZ_ASSERT(!NS_IsMainThread());
dom::AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(aPromise->GetGlobalObject()))) {
aPromise->MaybeReject(NS_ERROR_FAILURE);
return;
}
NS_IMETHOD
Run() override {
MOZ_ASSERT(NS_IsMainThread());
mWorkerThread->Shutdown();
dom::AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(mPromise->GetGlobalObject()))) {
mPromise->MaybeReject(NS_ERROR_FAILURE);
return NS_OK;
}
if (!mDataOK) {
mPromise->MaybeReject(NS_ERROR_FAILURE);
return NS_OK;
}
JSContext* cx = jsapi.cx();
JS::RootedValue jsval(cx);
nsresult rv = GetUntrustedModuleLoadEventsJSValue(mData, cx, &jsval);
if (NS_WARN_IF(NS_FAILED(rv))) {
mPromise->MaybeReject(rv);
return NS_OK;
}
mPromise->MaybeResolve(jsval);
return NS_OK;
}
};
class GetUntrustedModulesTelemetryDataRunnable final : public Runnable {
nsMainThreadPtrHandle<dom::Promise> mPromise;
public:
explicit GetUntrustedModulesTelemetryDataRunnable(
const nsMainThreadPtrHandle<dom::Promise>& aPromise)
: Runnable("GetUntrustedModulesTelemetryDataRunnable"),
mPromise(aPromise) {
MOZ_ASSERT(NS_IsMainThread());
if (aData.isNothing()) {
aPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
return;
}
NS_IMETHOD
Run() override {
MOZ_ASSERT(!NS_IsMainThread());
RefPtr<DllServices> dllSvc(DllServices::Get());
UntrustedModuleLoadTelemetryData data;
bool ok = dllSvc->GetUntrustedModuleTelemetryData(data);
JSContext* cx = jsapi.cx();
JS::RootedValue jsval(cx);
// Dispatch back to the main thread for remaining JS processing.
return NS_DispatchToMainThread(new GetUntrustedModulesMainThreadRunnable(
mPromise, ok, std::move(data)));
nsresult rv = GetUntrustedModuleLoadEventsJSValue(aData.ref(), cx, &jsval);
if (NS_WARN_IF(NS_FAILED(rv))) {
aPromise->MaybeReject(rv);
return;
}
};
aPromise->MaybeResolve(jsval);
}
nsresult GetUntrustedModuleLoadEvents(JSContext* cx, dom::Promise** aPromise) {
// Create a promise using global context.
@ -376,31 +502,21 @@ nsresult GetUntrustedModuleLoadEvents(JSContext* cx, dom::Promise** aPromise) {
}
ErrorResult result;
RefPtr<dom::Promise> promise = dom::Promise::Create(global, result);
RefPtr<dom::Promise> promise(dom::Promise::Create(global, result));
if (NS_WARN_IF(result.Failed())) {
return result.StealNSResult();
}
// Create a worker thread to perform the heavy work.
nsCOMPtr<nsIThread> workThread;
nsresult rv = NS_NewNamedThread("UntrustedDLLs", getter_AddRefs(workThread));
if (NS_WARN_IF(NS_FAILED(rv))) {
promise->MaybeReject(NS_ERROR_FAILURE);
return NS_OK;
}
RefPtr<DllServices> dllSvc(DllServices::Get());
dllSvc->GetUntrustedModulesData()->Then(
GetMainThreadSerialEventTarget(), __func__,
[promise](Maybe<UntrustedModulesData>&& aData) mutable {
Serialize(std::move(aData), std::move(promise));
},
[promise](nsresult aRv) { promise->MaybeReject(aRv); });
// In order to pass the promise through the worker thread to the main thread,
// this is needed.
nsMainThreadPtrHandle<dom::Promise> mainThreadPromise(
new nsMainThreadPtrHolder<dom::Promise>(
"Telemetry::UntrustedModuleLoadEvents::Promise", promise));
nsCOMPtr<nsIRunnable> runnable =
new GetUntrustedModulesTelemetryDataRunnable(mainThreadPromise);
promise.forget(aPromise);
return workThread->Dispatch(runnable.forget(),
nsIEventTarget::DISPATCH_NORMAL);
return NS_OK;
}
} // namespace Telemetry

Просмотреть файл

@ -7,8 +7,8 @@
#ifndef telemetry_UntrustedModules_h__
#define telemetry_UntrustedModules_h__
#include "nsCOMPtr.h"
#include "jsapi.h"
#include "mozilla/dom/Promise.h"
namespace mozilla {
namespace Telemetry {

Просмотреть файл

@ -6,7 +6,7 @@
* This module periodically sends a Telemetry ping containing information
* about untrusted module loads on Windows.
*
* https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/untrusted-modules-ping.html
* https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/third-party-modules-ping.html
*/
"use strict";
@ -35,7 +35,7 @@ const DEFAULT_INTERVAL_SECONDS = 24 * 60 * 60; // 1 day
const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetryUntrustedModulesPing::";
const TIMER_NAME = "telemetry_untrustedmodules_ping";
const PING_SUBMISSION_NAME = "untrustedModules";
const PING_SUBMISSION_NAME = "third-party-modules";
var TelemetryUntrustedModulesPing = Object.freeze({
_log: Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX),

Просмотреть файл

@ -22,7 +22,3 @@ if CONFIG['COMPILE_ENVIRONMENT']:
shared_library = '!%smodules-test%s' % (CONFIG['DLL_PREFIX'], CONFIG['DLL_SUFFIX'])
TEST_HARNESS_FILES.xpcshell.toolkit.components.telemetry.tests.unit += [shared_library]
if CONFIG['ENABLE_TESTS'] and CONFIG['OS_ARCH'] == 'WINNT':
DIRS += [
'untrusted-startup-test-dll',
]

Просмотреть файл

@ -16,6 +16,7 @@ const { ctypes } = ChromeUtils.import("resource://gre/modules/ctypes.jsm");
const kDllName = "modules-test.dll";
let gDllHandle;
let gCurrentPidStr;
add_task(async function setup() {
do_get_profile();
@ -31,6 +32,9 @@ add_task(async function setup() {
Preferences.set("toolkit.telemetry.untrustedModulesPing.frequency", 0);
Preferences.set("app.update.url", "http://localhost");
let currentPid = Services.appinfo.processID;
gCurrentPidStr = "0x" + currentPid.toString(16);
// Start the local ping server and setup Telemetry to use it during the tests.
PingServer.start();
Preferences.set(
@ -52,26 +56,16 @@ registerCleanupFunction(function() {
add_task(async function test_send_ping() {
let expectedModules = [
// This checks that a DLL loaded during runtime is evaluated properly.
// This is hard-coded as untrusted in ModuleEvaluator for testing.
// This is hard-coded as untrusted in toolkit/xre/UntrustedModules.cpp for
// testing purposes.
{
nameMatch: new RegExp(kDllName, "i"),
expectedTrusted: false,
isStartup: false,
wasFound: false,
},
// These check that a DLL loaded at startup is evaluated properly.
// This is hard-coded as untrusted in ModuleEvaluator for testing.
{
nameMatch: /untrusted-startup-test-dll.dll/i,
expectedTrusted: false,
isStartup: true,
wasFound: false,
},
{
nameMatch: /kernelbase.dll/i,
expectedTrusted: true,
isStartup: true,
wasFound: false,
},
];
@ -81,7 +75,7 @@ add_task(async function test_send_ping() {
let found;
while (true) {
found = await PingServer.promiseNextPing();
if (found.type == "untrustedModules") {
if (found.type == "third-party-modules") {
break;
}
}
@ -92,43 +86,55 @@ add_task(async function test_send_ping() {
Assert.ok(typeof found.clientId != "undefined", "Ping has a client ID");
Assert.equal(found.payload.structVersion, 1, "Version is correct");
Assert.ok(found.payload.combinedStacks, "'combinedStacks' array exists");
Assert.ok(found.payload.events, "'events' array exists");
Assert.equal(
found.payload.combinedStacks.stacks.length,
found.payload.events.length,
"combinedStacks.length == events.length"
Assert.ok(found.payload.modules, "'modules' object exists");
Assert.ok(Array.isArray(found.payload.modules), "'modules' is an array");
Assert.ok(found.payload.processes, "'processes' object exists");
Assert.ok(
gCurrentPidStr in found.payload.processes,
`Current process "${gCurrentPidStr}" is included in payload`
);
for (let event of found.payload.events) {
Assert.ok(event.modules, "'modules' array exists");
for (let mod of event.modules) {
Assert.ok(
typeof mod.moduleName != "undefined",
`Module contains moduleName: ${mod.moduleName}`
);
Assert.ok(
typeof mod.moduleTrustFlags != "undefined",
`Module contains moduleTrustFlags: ${mod.moduleTrustFlags}`
);
Assert.ok(
typeof mod.baseAddress != "undefined",
"Module contains baseAddress"
);
Assert.ok(
typeof mod.loaderName != "undefined",
"Module contains loaderName"
);
for (let x of expectedModules) {
if (x.nameMatch.test(mod.moduleName)) {
x.wasFound = true;
Assert.equal(
x.isStartup,
event.isStartup,
`isStartup == expected for module: ${x.nameMatch.source}`
);
}
}
let ourProcInfo = found.payload.processes[gCurrentPidStr];
Assert.equal(ourProcInfo.processType, "browser", "'processType' is correct");
Assert.ok(typeof ourProcInfo.elapsed == "number", "'elapsed' exists");
Assert.equal(
ourProcInfo.sanitizationFailures,
0,
"'sanitizationFailures' is 0"
);
Assert.equal(ourProcInfo.trustTestFailures, 0, "'trustTestFailures' is 0");
Assert.equal(
ourProcInfo.combinedStacks.stacks.length,
ourProcInfo.events.length,
"combinedStacks.stacks.length == events.length"
);
for (let event of ourProcInfo.events) {
Assert.ok(
typeof event.processUptimeMS == "number",
"'processUptimeMS' exists"
);
Assert.ok(typeof event.threadID == "number", "'threadID' exists");
Assert.ok(typeof event.baseAddress == "string", "'baseAddress' exists");
Assert.ok(typeof event.moduleIndex == "number", "'moduleIndex' exists");
Assert.ok(event.moduleIndex >= 0, "'moduleIndex' is non-negative");
let modRecord = found.payload.modules[event.moduleIndex];
Assert.ok(modRecord, "module record for this event exists");
Assert.ok(
typeof modRecord.resolvedDllName == "string",
"'resolvedDllName' exists"
);
Assert.ok(typeof modRecord.trustFlags == "number", "'trustFlags' exists");
let mod = expectedModules.find(function(elem) {
return elem.nameMatch.test(modRecord.resolvedDllName);
});
if (mod) {
mod.wasFound = true;
}
}

Просмотреть файл

@ -96,7 +96,7 @@ skip-if = (os == "android") || (os == "linux" && bits == 32)
[test_TelemetryGC.js]
[test_TelemetryAndroidEnvironment.js]
[test_TelemetryUtils.js]
[test_UntrustedModulesPing.js]
[test_ThirdPartyModulesPing.js]
run-if = nightly_build && (os == 'win')
[test_EcosystemTelemetry.js]
skip-if = (os == "linux" && bits == 32) # lack of support on test runners (see bug 1310703, comment78)

Просмотреть файл

@ -1,17 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# 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/.
DIST_INSTALL = False
SharedLibrary('untrusted-startup-test-dll')
UNIFIED_SOURCES = [
'untrusted-startup-test-dll.cpp',
]
NO_PGO = True
if CONFIG['COMPILE_ENVIRONMENT']:
TEST_HARNESS_FILES.xpcshell.toolkit.components.telemetry.tests.unit += ['!untrusted-startup-test-dll.dll']

Просмотреть файл

@ -1,12 +0,0 @@
/* 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/. */
/*
* This source file is used to build "untrusted-startup-test-dll.dll" on
* Windows. During xpcshell tests, it's loaded early enough to be detected as a
* startup module, and hard-coded to be reported in the untrusted modules
* telemetry ping, which allows for testing some code paths.
*/
void nothing() {}