High firefox Memory Corruption 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionInternally found bugs present in Firefox 154, Firefox ESR 153.1 and Firefox ESR 140.14. Some of these bugs showed evidence of memory corruption or another security-relevant defect and we presume that with enough effort some of these could have been exploited.
ComponentCore
Bug ClassMemory Corruption
Tracker2048793
Fix commit4379c4ce4a68 (firefox) +85/-10
CISA KEVNot listed
CreditedJan de Mooij, Tom Ritter and the Mozilla Fuzzing Team
Disclosed2026-09-01

Files Changed

  • browser/components/tabbrowser/test/browser/tabs/browser_new_tab_in_privilegedabout_process_pref.js
  • docshell/base/nsDocShellLoadState.cpp
  • docshell/test/browser/browser_browsing_context_discarded.js
diff --git a/browser/components/tabbrowser/test/browser/tabs/browser_new_tab_in_privilegedabout_process_pref.js b/browser/components/tabbrowser/test/browser/tabs/browser_new_tab_in_privilegedabout_process_pref.js
index 75b83782e79..9e8b2f6a710 100644
--- a/browser/components/tabbrowser/test/browser/tabs/browser_new_tab_in_privilegedabout_process_pref.js
+++ b/browser/components/tabbrowser/test/browser/tabs/browser_new_tab_in_privilegedabout_process_pref.js
@@ -216,13 +216,6 @@ add_task(async function process_switching_through_navigation_features() {
       BrowserTestUtils.startLoadingURIString(browser, TEST_HTTP);
       await BrowserTestUtils.browserLoaded(browser, false, TEST_HTTP);
       checkBrowserRemoteType(browser, E10SUtils.WEB_REMOTE_TYPE);
-
-      // Check that location change causes a change in process type as well.
-      await SpecialPowers.spawn(browser, [ABOUT_NEWTAB], uri => {
-        content.location = uri;
-      });
-      await BrowserTestUtils.browserLoaded(browser, false, ABOUT_NEWTAB);
-      assertIsPrivilegedProcess(browser, "about:newtab after location change");
     }
   );
 
diff --git a/docshell/base/nsDocShellLoadState.cpp b/docshell/base/nsDocShellLoadState.cpp
index a82a99ee505..df093780f21 100644
--- a/docshell/base/nsDocShellLoadState.cpp
+++ b/docshell/base/nsDocShellLoadState.cpp
@@ -4,6 +4,7 @@
 
 #include "nsDocShellLoadState.h"
 #include "nsIDocShell.h"
+#include "nsContentSecurityUtils.h"
 #include "nsDocShell.h"
 #include "nsILoadInfo.h"
 #include "nsIProtocolHandler.h"
@@ -15,6 +16,7 @@
 #include "nsNetUtil.h"
 #include "nsQueryObject.h"
 #include "ReferrerInfo.h"
+#include "xpcpublic.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Components.h"
@@ -25,6 +27,7 @@
 #include "mozilla/dom/LoadURIOptionsBinding.h"
 #include "mozilla/dom/Navigation.h"
 #include "mozilla/dom/NavigationUtils.h"
+#include "mozilla/dom/ProcessIsolation.h"
 #include "mozilla/dom/SessionHistoryEntry.h"
 #include "mozilla/dom/nsHTTPSOnlyUtils.h"
 #include "mozilla/net/DocumentLoadListener.h"
@@ -44,6 +47,66 @@ using namespace mozilla::dom;
 // Global reference to the URI fixup service.
 static mozilla::StaticRefPtr<nsIURIFixup> sURIFixup;
 
+static bool ContentTriggeredURILoadIsAllowed(
+    nsIURI* aURI, const nsACString& aEffectiveRemoteType) {
+  MOZ_ASSERT(aEffectiveRemoteType != NOT_REMOTE_TYPE);
+  MOZ_ASSERT(!aURI->SchemeIs("javascript"), "Should have been blocked already");
+
+  // view-source: URIs are not linkable from web content, but the "View Page
+  // Source" context menu has the content process itself load them,
+  // so decide based on the inner URI instead.
+  if (aURI->SchemeIs("view-source")) {
+    nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(aURI);
+    MOZ_ASSERT(nestedURI);
+
+    nsCOMPtr<nsIURI> innerURI;
+    return NS_SUCCEEDED(nestedURI->GetInnerURI(getter_AddRefs(innerURI))) &&
+           ContentTriggeredURILoadIsAllowed(innerURI, aEffectiveRemoteType);
+  }
+
+  // A null principal is the least privileged principal there is, so any URI it
+  // is allowed to link to may be loaded from any content process.
+  nsCOMPtr<nsIPrincipal> genericNullPrincipal = NullPrincipal::Create({});
+
+  nsCOMPtr<nsIScriptSecurityManager> secMan =
+      do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+
+  if (NS_SUCCEEDED(secMan->CheckLoadURIWithPrincipal(
+          genericNullPrincipal, aURI,
+          nsIScriptSecurityManager::DISALLOW_SCRIPT |
+              nsIScriptSecurityManager::DONT_REPORT_ERRORS,
+          0))) {
+    return true;
+  }
+
+  nsCOMPtr<nsIPrincipal> principal =
+      BasePrincipal::CreateContentPrincipal(aURI, {});
+  if (principal->GetIsNullPrincipal()) {
+    // Only allow null principals from URIs that have the
+    // URI_LOADABLE_BY_SUBSUMERS (i.e. blob:) flag. Other null principals likely
+    // correspond to internal, unsafe-to-load in content, resources.
+    bool loadableBySubsumers = false;
+    if (NS_FAILED(NS_URIChainHasFlags(
+            aURI, nsIProtocolHandler::URI_LOADABLE_BY_SUBSUMERS,
+            &loadableBySubsumers))) {
+      return false;
+    }
+    return loadableBySubsumers;
+  }
+
+  // Automation-Only: Allow loading of chrome://reftest/* URLs.
+  if (aURI->SchemeIs("chrome") && xpc::IsInAutomation()) {
+    nsAutoCString host;
+    if (NS_SUCCEEDED(aURI->GetHost(host)) && host.EqualsLiteral("reftest")) {
+      return true;
+    }
+  }
+
+  return ValidatePrincipalCouldPotentiallyBeLoadedBy(
+      principal, aEffectiveRemoteType,
+      {ValidatePrincipalOptions::AllowNotLoadedOrigin});
+}
+
 nsDocShellLoadState::nsDocShellLoadState(nsIURI* aURI)
     : nsDocShellLoadState(aURI, nsContentUtils::GenerateLoadIdentifier()) {}
 
@@ -174,10 +237,29 @@ nsDocShellLoadState::nsDocShellLoadState(
       }
     }
 
+    const nsCString& effectiveRemoteType = GetEffectiveTriggeringRemoteType();
+    if (effectiveRemoteType != NOT_REMOTE_TYPE &&
+        !ContentTriggeredURILoadIsAllowed(mURI, effectiveRemoteType)) {
+      nsAutoCString aboutModuleOrScheme;
+      if (mURI->SchemeIs("about")) {
+        (void)NS_GetAboutModuleName(mURI, aboutModuleOrScheme);
+        aboutModuleOrScheme.InsertLiteral("about:", 0);
+      } else {
+        mURI->GetScheme(aboutModuleOrScheme);
+        aboutModuleOrScheme.AppendLiteral(":");
+      }
+      nsCString remotePrefix(RemoteTypePrefix(effectiveRemoteType));
+      aActor->FatalError(
+          nsPrintfCString("Illegal load attempt of %s URL from %s",
+                          aboutModuleOrScheme.get(), remotePrefix.get())
+              .get());
+      return;
+    }
+
     // NOTE: Eventually this should probably be called on a LoadedOriginSet, but
     // we don't track this on the load state yet.
     if (!ValidatePrincipalCouldPotentiallyBeLoadedBy(
-            mTriggeringPrincipal, GetEffectiveTriggeringRemoteType(),
+            mTriggeringPrincipal, effectiveRemoteType,
             {ValidatePrincipalOptions::AllowExpanded,
              ValidatePrincipalOptions::AlwaysAllowSystem,
              ValidatePrincipalOptions::AllowNotLoadedOrigin})) {
@@ -186,7 +268,7 @@ nsDocShellLoadState::nsDocShellLoadState(
       return;
     }
     if (!ValidatePrincipalCouldPotentiallyBeLoadedBy(
-            mPrincipalToInherit, GetEffectiveTriggeringRemoteType(),
+            mPrincipalToInherit, effectiveRemoteType,
             {ValidatePrincipalOptions::AllowNullPtr,
              ValidatePrincipalOptions::AllowNotLoadedOrigin})) {
       aActor->FatalError("nsDocShellLoadState with invalid principalToInherit");
diff --git a/docshell/test/browser/browser_browsing_context_discarded.js b/docshell/test/browser/browser_browsing_context_discarded.js
index 3dc5265a977..6f1b5be55b8 100644
--- a/docshell/test/browser/browser_browsing_context_discarded.js
+++ b/docshell/test/browser/browser_browsing_context_discarded.js
@@ -115,7 +115,7 @@ add_task(async function replaceToplevel() {
 
   const discarded = await observeDiscarded([...expected.keys()], async () => {
     await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
-      content.location = "about:newtab";
+      content.location = "about:blocked";
     });
   });
 
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/browser/components/tabbrowser/test/browser/tabs/browser_new_tab_in_privilegedabout_process_pref.js b/browser/components/tabbrowser/test/browser/tabs/browser_new_tab_in_privilegedabout_process_pref.js
index 75b83782e79..9e8b2f6a710 100644
--- a/browser/components/tabbrowser/test/browser/tabs/browser_new_tab_in_privilegedabout_process_pref.js
+++ b/browser/components/tabbrowser/test/browser/tabs/browser_new_tab_in_privilegedabout_process_pref.js
@@ -216,13 +216,6 @@ add_task(async function process_switching_through_navigation_features() {
       BrowserTestUtils.startLoadingURIString(browser, TEST_HTTP);
       await BrowserTestUtils.browserLoaded(browser, false, TEST_HTTP);
       checkBrowserRemoteType(browser, E10SUtils.WEB_REMOTE_TYPE);
-
-      // Check that location change causes a change in process type as well.
-      await SpecialPowers.spawn(browser, [ABOUT_NEWTAB], uri => {
-        content.location = uri;
-      });
-      await BrowserTestUtils.browserLoaded(browser, false, ABOUT_NEWTAB);
-      assertIsPrivilegedProcess(browser, "about:newtab after location change");
     }
   );
diff --git a/docshell/test/browser/browser_browsing_context_discarded.js b/docshell/test/browser/browser_browsing_context_discarded.js
index 3dc5265a977..6f1b5be55b8 100644
--- a/docshell/test/browser/browser_browsing_context_discarded.js
+++ b/docshell/test/browser/browser_browsing_context_discarded.js
@@ -115,7 +115,7 @@ add_task(async function replaceToplevel() {
 
   const discarded = await observeDiscarded([...expected.keys()], async () => {
     await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
-      content.location = "about:newtab";
+      content.location = "about:blocked";
     });
   });
Loading diff…