Moderate firefox Logic Error 🔧 Commit mapped

Overview

Moderate
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionInvalid pointer in the Nimbus Desktop Client component
ComponentDOM
Bug ClassLogic Error
Tracker2058659
Fix commit5dfa60378e78 (firefox) +44/-31
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Files Changed

  • dom/chrome-webidl/Nimbus.webidl
  • dom/chrome-webidl/moz.build
  • toolkit/components/nimbus/lib/NimbusFeatures.cpp
diff --git a/dom/chrome-webidl/Nimbus.webidl b/dom/chrome-webidl/Nimbus.webidl
new file mode 100644
index 00000000000..85a3c350451
--- /dev/null
+++ b/dom/chrome-webidl/Nimbus.webidl
@@ -0,0 +1,31 @@
+/* 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/.
+ */
+
+/**
+ * Cached metadata about an active Nimbus experiment or rollout.
+ *
+ * This metadata is stored inside prefs of the form `nimbus.syncdatastore.*` and
+ * `nimbus.syncdefaultsstore.*` by ExperimentStore.sys.mjs for a subset of
+ * features that require either:
+ * - being accessible from platform code, or
+ * - being read very early in startup, before Nimbus has fully initialized.
+ */
+[GenerateInitFromJSON]
+dictionary CachedNimbusExperimentMetadata {
+  /** The experiment slug. */
+  required UTF8String slug;
+
+  /** The active branch. */
+  required CachedNimbusExperimentBranch branch;
+};
+
+/**
+ * Cached metadata about the active branch of an experiment or rollout.
+ */
+[GenerateInitFromJSON]
+dictionary CachedNimbusExperimentBranch {
+  /** The slug of the active branch. */
+  required UTF8String slug;
+};
diff --git a/dom/chrome-webidl/moz.build b/dom/chrome-webidl/moz.build
index b8a31d66125..ab69b1a0bd9 100644
--- a/dom/chrome-webidl/moz.build
+++ b/dom/chrome-webidl/moz.build
@@ -23,6 +23,9 @@ with Files("MatchGlob.webidl"):
 with Files("MatchPattern.webidl"):
     BUG_COMPONENT = ("WebExtensions", "General")
 
+with Files("Nimbus.webidl"):
+    BUG_COMPONENT = ("Firefox", "Nimbus Desktop Client")
+
 with Files("WebExtension*.webidl"):
     BUG_COMPONENT = ("WebExtensions", "General")
 
@@ -83,6 +86,7 @@ WEBIDL_FILES = [
     "MozStorageStatementParams.webidl",
     "MozStorageStatementRow.webidl",
     "NetDashboard.webidl",
+    "Nimbus.webidl",
     "OriginAttributes.webidl",
     "PathUtils.webidl",
     "PermissionSetParameters.webidl",
diff --git a/toolkit/components/nimbus/lib/NimbusFeatures.cpp b/toolkit/components/nimbus/lib/NimbusFeatures.cpp
index ddafe50fee0..5cc67e0a9c3 100644
--- a/toolkit/components/nimbus/lib/NimbusFeatures.cpp
+++ b/toolkit/components/nimbus/lib/NimbusFeatures.cpp
@@ -4,12 +4,9 @@
 
 #include "mozilla/browser/NimbusFeatures.h"
 #include "mozilla/browser/NimbusFeatureManifest.h"
+#include "mozilla/dom/NimbusBinding.h"
 #include "mozilla/Try.h"
-#include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/glean/NimbusMetrics.h"
-#include "jsapi.h"
-#include "js/JSON.h"
-#include "nsJSUtils.h"
 #include "nsPrintfCString.h"
 
 namespace mozilla {
@@ -207,44 +204,25 @@ nsresult NimbusFeatures::OffUpdate(const nsACString& aFeatureId,
 nsresult GetExperimentSlug(const nsACString& aFeatureId,
                            nsACString& aExperimentSlug,
                            nsACString& aBranchSlug) {
-  nsAutoCString prefName;
-  nsAutoString prefValue;
-
   aExperimentSlug.Truncate();
   aBranchSlug.Truncate();
 
+  nsAutoCString prefName;
   GetNimbusPrefName(kSyncDataPrefBranch, aFeatureId, EmptyCString(), prefName);
+
+  nsAutoString prefValue;
   MOZ_TRY(Preferences::GetString(prefName.get(), prefValue));
   if (prefValue.IsEmpty()) {
     return NS_ERROR_UNEXPECTED;
   }
-  dom::AutoJSAPI jsapi;
-  if (!jsapi.Init(xpc::PrivilegedJunkScope())) {
+
+  dom::CachedNimbusExperimentMetadata meta;
+  if (!meta.Init(prefValue)) {
     return NS_ERROR_UNEXPECTED;
   }
-  JSContext* cx = jsapi.cx();
-  JS::Rooted<JS::Value> json(cx, JS::NullValue());
-  if (JS_ParseJSON(cx, prefValue.BeginReading(), prefValue.Length(), &json) &&
-      json.isObject()) {
-    JS::Rooted<JSObject*> experimentJSON(cx, json.toObjectOrNull());
-    JS::Rooted<JS::Value> expSlugValue(cx);
-    if (!JS_GetProperty(cx, experimentJSON, "slug", &expSlugValue)) {
-      return NS_ERROR_UNEXPECTED;
-    }
-    AssignJSString(cx, aExperimentSlug, expSlugValue.toString());
 
-    JS::Rooted<JS::Value> branchJSON(cx);
-    if (!JS_GetProperty(cx, experimentJSON, "branch", &branchJSON) &&
-        !branchJSON.isObject()) {
-      return NS_ERROR_UNEXPECTED;
-    }
-    JS::Rooted<JSObject*> branchObj(cx, branchJSON.toObjectOrNull());
-    JS::Rooted<JS::Value> branchSlugValue(cx);
-    if (!JS_GetProperty(cx, branchObj, "slug", &branchSlugValue)) {
-      return NS_ERROR_UNEXPECTED;
-    }
-    AssignJSString(cx, aBranchSlug, branchSlugValue.toString());
-  }
+  aExperimentSlug.Assign(std::move(meta.mSlug));
+  aBranchSlug.Assign(std::move(meta.mBranch.mSlug));
 
   return NS_OK;
 }
Loading diff…