Bug 1434489: Add optional certSubject field to modules ping; r=chutten, data-review=francois (via bug 1430857)
authorAaron Klotz <aklotz@mozilla.com>
Mon, 05 Mar 2018 18:13:28 -0700
changeset 462122 22ba4f32a7afe64038a38cd2e1fc16ede661f9ab
parent 462121 7daaf289c085e05e0e4a0327a13748002fb467a0
child 462123 43f4672c5ca38a787fa0b316bd42bd0e865776c6
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerschutten
bugs1434489, 1430857
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1434489: Add optional certSubject field to modules ping; r=chutten, data-review=francois (via bug 1430857)
toolkit/components/telemetry/Telemetry.cpp
toolkit/components/telemetry/TelemetryModules.jsm
toolkit/components/telemetry/nsITelemetry.idl
toolkit/components/telemetry/tests/unit/test_TelemetryModules.js
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -77,16 +77,19 @@
 #include "mozilla/ProcessedStack.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/IOInterposer.h"
 #include "mozilla/PoisonIOInterposer.h"
 #include "mozilla/StartupTimeline.h"
 #include "mozilla/HangMonitor.h"
+#if defined(XP_WIN)
+#include "mozilla/WinDllServices.h"
+#endif
 #include "nsNativeCharsetUtils.h"
 #include "nsProxyRelease.h"
 #include "HangReports.h"
 
 #if defined(MOZ_GECKO_PROFILER)
 #include "shared-libraries.h"
 #include "KeyedStackCapturer.h"
 #endif // MOZ_GECKO_PROFILER
@@ -890,16 +893,40 @@ public:
         version.setNull();
       }
 
       if (!JS_DefineProperty(cx, moduleObj, "version", version, JSPROP_ENUMERATE)) {
         mPromise->MaybeReject(NS_ERROR_FAILURE);
         return NS_OK;
       }
 
+#if defined(XP_WIN)
+      // Cert Subject.
+      // NB: Currently we cannot lower this down to the profiler layer due to
+      // differing startup dependencies between the profiler and DllServices.
+      RefPtr<DllServices> dllSvc(DllServices::Get());
+      auto orgName = dllSvc->GetBinaryOrgName(info.GetModulePath().get());
+      if (orgName) {
+        JS::RootedString jsOrg(cx, JS_NewUCStringCopyZ(cx, (char16_t*) orgName.get()));
+        if (!jsOrg) {
+          mPromise->MaybeReject(NS_ERROR_FAILURE);
+          return NS_OK;
+        }
+
+        JS::RootedValue certSubject(cx);
+        certSubject.setString(jsOrg);
+
+        if (!JS_DefineProperty(cx, moduleObj, "certSubject", certSubject,
+                               JSPROP_ENUMERATE)) {
+          mPromise->MaybeReject(NS_ERROR_FAILURE);
+          return NS_OK;
+        }
+      }
+#endif // defined(XP_WIN)
+
       if (!JS_DefineElement(cx, moduleArray, i, moduleObj, JSPROP_ENUMERATE)) {
         mPromise->MaybeReject(NS_ERROR_FAILURE);
         return NS_OK;
       }
     }
 
     mPromise->MaybeResolve(moduleArray);
     return NS_OK;
--- a/toolkit/components/telemetry/TelemetryModules.jsm
+++ b/toolkit/components/telemetry/TelemetryModules.jsm
@@ -71,16 +71,20 @@ var TelemetryModules = Object.freeze({
           for (let module of modules) {
             if (module.name.length > MAX_NAME_LENGTH) {
               module.name = module.name.substr(0, MAX_NAME_LENGTH - 1) + TRUNCATION_DELIMITER;
             }
 
             if (module.debugName !== null && module.debugName.length > MAX_NAME_LENGTH) {
               module.debugName = module.debugName.substr(0, MAX_NAME_LENGTH - 1) + TRUNCATION_DELIMITER;
             }
+
+            if (module.certSubject !== undefined && module.certSubject.length > MAX_NAME_LENGTH) {
+              module.certSubject = module.certSubject.substr(0, MAX_NAME_LENGTH - 1) + TRUNCATION_DELIMITER;
+            }
           }
 
           TelemetryController.submitExternalPing("modules",
             {
               version: 1,
               modules,
             },
             {
--- a/toolkit/components/telemetry/nsITelemetry.idl
+++ b/toolkit/components/telemetry/nsITelemetry.idl
@@ -184,16 +184,17 @@ interface nsITelemetry : nsISupports
    * The data has the following structure:
    *
    * [
    *   {
    *     "name": <string>, // Name of the module file (e.g. xul.dll)
    *     "version": <string>, // Version of the module
    *     "debugName": <string>, // ID of the debug information file
    *     "debugID": <string>, // Name of the debug information file
+   *     "certSubject": <string>, // Name of the organization that signed the binary (Optional, only defined when present)
    *   },
    *   ...
    * ]
    *
    * @return A promise that resolves to an array of modules or rejects with
              NS_ERROR_FAILURE on failure.
    * @throws NS_ERROR_NOT_IMPLEMENTED if the Gecko profiler is not enabled.
    */
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryModules.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryModules.js
@@ -59,16 +59,24 @@ if (AppConstants.platform === "win") {
       debugName: "libmodĪ¼les.pdb",
       version: null,
     },
     {
       name: libNoPDB,
       debugName: null,
       version: null,
     },
+    {
+      // We choose this DLL because it's guaranteed to exist in our process and
+      // be signed on all Windows versions that we support.
+      name: "ntdll.dll",
+      // debugName changes depending on OS version and is irrelevant to this test
+      // version changes depending on OS version and is irrelevant to this test
+      certSubject: "Microsoft Windows",
+    },
   ];
 } else if (AppConstants.platform === "android") {
   // Listing shared libraries doesn't work in Android xpcshell tests.
   // https://hg.mozilla.org/mozilla-central/file/0eef1d5a39366059677c6d7944cfe8a97265a011/tools/profiler/core/shared-libraries-linux.cc#l95
   expectedLibs = [];
 } else {
   expectedLibs = [
     {
@@ -108,17 +116,17 @@ add_task(async function setup() {
       libUnicodePDBHandle = ctypes.open(libUnicodePDBFile);
       libNoPDBHandle = ctypes.open(libNoPDBFile);
     }
   }
 
   // Force the timer to fire (using a small interval).
   Cc["@mozilla.org/updates/timer-manager;1"].getService(Ci.nsIObserver).observe(null, "utm-test-init", "");
   Preferences.set("toolkit.telemetry.modulesPing.interval", 0);
-  Preferences.set("app.update.url", "http:/localhost");
+  Preferences.set("app.update.url", "http://localhost");
 
   // Start the local ping server and setup Telemetry to use it during the tests.
   PingServer.start();
   Preferences.set(TelemetryUtils.Preferences.Server, "http://localhost:" + PingServer.port);
 });
 
 registerCleanupFunction(function() {
   if (libModulesHandle) {
@@ -149,31 +157,55 @@ add_task({
 
   let found = await PingServer.promiseNextPing();
   Assert.ok(!!found, "Telemetry ping submitted.");
   Assert.strictEqual(found.type, "modules", "Ping type is 'modules'");
   Assert.ok(found.environment, "'modules' ping has an environment.");
   Assert.ok(!!found.clientId, "'modules' ping has a client ID.");
   Assert.ok(!!found.payload.modules, "Telemetry ping payload contains the 'modules' array.");
 
+  let nameComparator;
+  if (AppConstants.platform === "win") {
+    // Do case-insensitive checking of file/module names on Windows
+    nameComparator = function(a, b) {
+      if (typeof a === "string" && typeof b === "string") {
+        return a.toLowerCase() === b.toLowerCase();
+      }
+
+      return a === b;
+    };
+  } else {
+    nameComparator = function(a, b) {
+      return a === b;
+    };
+  }
+
   for (let lib of expectedLibs) {
-    let test_lib = found.payload.modules.find(module => module.name === lib.name);
+    let test_lib = found.payload.modules.find(module => nameComparator(module.name, lib.name));
 
     Assert.ok(!!test_lib, "There is a '" + lib.name + "' module.");
 
-    if (lib.version !== null) {
-      Assert.ok(test_lib.version.startsWith(lib.version), "The version of the " + lib.name + " module (" + test_lib.version + ") is correct (it starts with '" + lib.version + "').");
-    } else {
-      Assert.strictEqual(test_lib.version, null, "The version of the " + lib.name + " module is null.");
+    if ("version" in lib) {
+      if (lib.version !== null) {
+        Assert.ok(test_lib.version.startsWith(lib.version), "The version of the " + lib.name + " module (" + test_lib.version + ") is correct (it starts with '" + lib.version + "').");
+      } else {
+        Assert.strictEqual(test_lib.version, null, "The version of the " + lib.name + " module is null.");
+      }
     }
 
-    Assert.strictEqual(test_lib.debugName, lib.debugName, "The " + lib.name + " module has the correct debug name.");
+    if ("debugName" in lib) {
+      Assert.ok(nameComparator(test_lib.debugName, lib.debugName), "The " + lib.name + " module has the correct debug name.");
+    }
 
     if (lib.debugName === null) {
       Assert.strictEqual(test_lib.debugID, null, "The " + lib.name + " module doesn't have a debug ID.");
     } else {
       Assert.greater(test_lib.debugID.length, 0, "The " + lib.name + " module has a debug ID.");
     }
+
+    if ("certSubject" in lib) {
+      Assert.strictEqual(test_lib.certSubject, lib.certSubject, "The " + lib.name + " module has the expected cert subject.");
+    }
   }
 
   let test_lib = found.payload.modules.find(module => module.name === libLongName);
   Assert.ok(!test_lib, "There isn't a '" + libLongName + "' module.");
 });