High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionPrivilege escalation in the Remote Settings Client component
ComponentCore
Bug ClassLogic Error
Tracker2056571
Fix commitb638013b451a (firefox) +16/-11
CISA KEVNot listed
CreditedGal Ankonina
Disclosed2026-08-18

Background

Remote Settings
Firefox’s mechanism for delivering signed configuration data (blocklists, feature settings) from a Mozilla server to the client.
`RemoteSettingsClient`
the client class that syncs remote records into a local IndexedDB and gates their use on a content-signature check.
`InvalidSignatureError`
the specific error thrown when the collection’s content signature does not verify against the fetched x5u certificate chain.
`verifySignatureLocalData`
an executor passed to a Promise that re-verifies the signature over the data that existed locally before the current sync.

Root Cause Analysis

During maybeSync, remote records are imported into the local database before their signature is verified, so on a verification failure the code must roll those unverified records back. The vulnerable path guarded the rollback behind if (!(e instanceof InvalidSignatureError)) { throw e; }, so any failure that was not exactly an InvalidSignatureError — a malformed signature or a failed x5u cert-chain fetch — escaped the handler and threw before the just-imported unverified records were cleared, leaving them persisted and trusted. A second gap was that verifySignatureLocalData was awaited directly (hasLocalData && (await new Promise(verifySignatureLocalData))), so if that local re-verification itself threw, the exception again skipped the reset-and-clear logic. The invariant violated is that no unverified imported record may remain in the local DB after signature validation fails for any reason.

The fix removes the early throw and wraps the local re-verification in a try/catch that defaults localTrustworthy to false, so every failure class now funnels into the clear/reset-to-dump path instead of leaving poisoned data behind.

Key insight
The single mistake was treating only InvalidSignatureError as a rollback-worthy failure while letting all other verification errors (malformed signatures, x5u fetch failures, exceptions from local re-verification) bypass cleanup; the fix makes any failure default to untrustworthy and force a clear/reset of the freshly imported records.

Attack Path

  1. Serve tampered data An attacker positioned to feed the sync response (or a compromised/hijacked local DB) supplies records whose signature fails in a way other than a clean InvalidSignatureError, e.g. a malformed signature or an unreachable/invalid x5u chain.
  2. Trigger import A sync imports the attacker’s records into the local database before verification runs.
  3. Escape the rollback Verification fails with a non-InvalidSignatureError (or the local re-verification throws), so the original throw e exits the handler before the clear/reset step executes.
  4. Persist unverified records The imported, unverified records remain in the local store and are consumed by Remote Settings consumers as if trusted.
  5. Gain effect The malicious configuration takes effect (e.g. altered blocklists or feature settings), escalating the attacker’s control over privileged browser behavior.

Impact Assessment

An attacker who can influence the synced data or the local settings database can persist unverified Remote Settings records, effectively bypassing the content-signature trust boundary that protects browser configuration. This runs in the privileged parent process where Remote Settings consumers act on the data, so it constitutes a privilege escalation. The precondition is the ability to induce a non-InvalidSignatureError verification failure during sync (malformed signature, failed x5u fetch) or a throwing local re-verification.

Changed Functions

FunctionChangeNotes
if
services/settings/RemoteSettingsClient.sys.mjs
modified

Files Changed

  • services/settings/RemoteSettingsClient.sys.mjs

Audit Directions

  • Import-before-verify
    Flag any sync/update path that writes records to persistent storage before signature or integrity checks complete, and confirm every failure branch rolls the writes back.
  • Narrow error-type gating
    Look for instanceof-based guards that treat one error subtype as recoverable while re-throwing (and thereby skipping cleanup for) all others; cleanup should be error-type-agnostic.
  • Throwing verification helpers
    Ensure awaited verification promises like verifySignatureLocalData are wrapped so an exception maps to “untrustworthy” and still triggers reset, rather than propagating past the cleanup code.
diff --git a/services/settings/RemoteSettingsClient.sys.mjs b/services/settings/RemoteSettingsClient.sys.mjs
index 3ee4ae65a40..f4f9b1c0bce 100644
--- a/services/settings/RemoteSettingsClient.sys.mjs
+++ b/services/settings/RemoteSettingsClient.sys.mjs
@@ -1226,21 +1226,26 @@ export class RemoteSettingsClient extends EventEmitter {
         lazy.console.error(
           `${this.identifier} Signature failed ${retry ? "again" : ""} ${e}`
         );
-        if (!(e instanceof InvalidSignatureError)) {
-          // If it failed for any other kind of error (eg. shutdown)
-          // then give up quickly.
-          throw e;
-        }
 
-        // In order to distinguish signature errors that happen
-        // during sync, from hijacks of local DBs, we will verify
-        // the signature on the data that we had before syncing
-        // (if any).
+        // Any verification failure, invalid signature, malformed signature,
+        // or a failed x5u cert-chain fetch, must roll back the records just
+        // imported above, which are still unverified.
         if (!hasLocalData) {
           lazy.console.debug(`${this.identifier} No previous data to restore`);
         }
-        const localTrustworthy =
-          hasLocalData && (await new Promise(verifySignatureLocalData));
+
+        let localTrustworthy = false;
+        if (hasLocalData) {
+          try {
+            localTrustworthy = await new Promise(verifySignatureLocalData);
+          } catch (_) {
+            // We either throw a CorruptedDataError below which will lead to a
+            // telemetry event, or we have retried already and this is the second
+            // failure. We will reset to dump, clear everything, and throw the error
+            // so that the caller can report the sync status.
+          }
+        }
+
         if (!localTrustworthy && !retry) {
           // Signature failed, clear local DB because it contains
           // bad data (local + remote changes).
Loading diff…