Medium firefox Cross Origin 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionSame-origin policy bypass in the Audio/Video: Playback component
ComponentDOM
Bug ClassCross Origin
Tracker2055697
Fix commit9389b9ac9cef (firefox) +26/-4
CISA KEVNot listed
CreditedThe Mozilla Fuzzing Team
Disclosed2026-08-18

Background

Same-origin policy
the browser rule that content from one origin may not read the response body or error detail of a resource from a different origin.
`HTMLMediaElement`
the Gecko C++ class backing <audio> and <video> elements, which loads media resources over the network and reports load errors to script.
`OnChannelRedirect`
the nsIChannelEventSink callback invoked when a media load’s HTTP channel follows a redirect to a new URI.
`nsIPrincipal::IsSameOrigin`
a security-check method that compares a principal’s origin against a given nsIURI to decide whether a load is same-origin.

Root Cause Analysis

When a media load failed, HTMLMediaElement::NoSupportedMediaSourceError decided whether to redact sensitive error details (MIME type, HTTP status) by comparing mSrcAttrTriggeringPrincipal against mLoadingSrc, which holds the original request URI and is never updated when the channel follows a cross-origin redirect. Because the redirect target’s real origin was never captured, an initially same-origin src that redirected to a cross-origin resource was still treated as same-origin, so the sensitive error detail was leaked. The check also failed open: the guard if (NS_SUCCEEDED(rv) && !isSameOriginLoad) only redacted when the origin comparison succeeded and differed, so any error path (rv failing) skipped redaction entirely.

The fix adds mLoadingSrcFinalURI, which starts equal to mLoadingSrc and is reassigned to the post-redirect URI inside OnChannelRedirect, and compares against mLoadingSrcTriggeringPrincipal and this final URI instead. It also inverts the guard to if (NS_FAILED(rv) || !isSameOriginLoad) so a failed comparison or a null captured URI now redacts (fail-closed). Together these restore the invariant that error detail is disclosed only when the resource that was actually fetched is provably same-origin.

Key insight
The single mistake was performing the cross-origin redaction decision against the pre-redirect request URI (mLoadingSrc) with fail-open logic; the fix tracks the true final loaded URI in mLoadingSrcFinalURI across redirects and treats an unknown or failed comparison as cross-origin.

Attack Path

  1. Host a same-origin redirector The attacker’s page references a media src on its own origin that server-side redirects (e.g. 302) to a victim cross-origin URL.
  2. Trigger the media load A <video> or <audio> element loads the src, and the channel follows the redirect to the cross-origin target while mLoadingSrc stays pinned to the original same-origin URI.
  3. Force a load error The cross-origin response is chosen so the media pipeline reports an unsupported/failed load, invoking NoSupportedMediaSourceError.
  4. Bypass the origin check The stale mLoadingSrc compares as same-origin (or the comparison fails and falls through the fail-open guard), so the detailed, non-generic error is delivered to script.
  5. Read cross-origin detail The page observes the leaked MIME type or HTTP status of the cross-origin resource, inferring cross-origin state.

Impact Assessment

An attacker gains a same-origin-policy bypass that leaks cross-origin response metadata (MIME type or HTTP status code) from media load errors, running in the content process in the context of a script-controlled web page. The precondition is that the attacker can cause a media load starting from a same-origin (or otherwise mis-compared) URI to redirect to the cross-origin resource it wants to probe. The disclosure is limited to error-detail metadata rather than full response bodies, consistent with the moderate severity rating.

Changed Functions

FunctionChangeNotes
if
dom/media/mediaelement/HTMLMediaElement.cpp
modified

Files Changed

  • dom/media/mediaelement/HTMLMediaElement.cpp
  • dom/media/mediaelement/HTMLMediaElement.h

Audit Directions

  • Redirect-stale origin checks
    Audit any security or same-origin decision that reads a request URI (mLoadingSrc-style fields) rather than the post-redirect final URI captured in an OnChannelRedirect/nsIChannelEventSink callback.
  • Fail-open guard logic
    Flag origin/permission checks shaped like if (NS_SUCCEEDED(rv) && !isSameOrigin) that skip protection when the comparison itself fails; correct forms should treat error (NS_FAILED(rv)) or unknown state as untrusted.
  • Lifecycle reset of URI state
    Verify that newly added URI/principal tracking fields are cleared in teardown paths like AbortExistingLoads so a prior load’s redirected URI cannot be reused for a subsequent load’s checks.
diff --git a/dom/media/mediaelement/HTMLMediaElement.cpp b/dom/media/mediaelement/HTMLMediaElement.cpp
index 5db7e05ccff..7272c99c7d0 100644
--- a/dom/media/mediaelement/HTMLMediaElement.cpp
+++ b/dom/media/mediaelement/HTMLMediaElement.cpp
@@ -2575,6 +2575,18 @@ nsresult HTMLMediaElement::OnChannelRedirect(nsIChannel* aChannel,
                                              nsIChannel* aNewChannel,
                                              uint32_t aFlags) {
   MOZ_ASSERT(mChannelLoader);
+  if (aNewChannel) {
+    nsCOMPtr<nsIURI> oldURI;
+    if (aChannel) {
+      aChannel->GetURI(getter_AddRefs(oldURI));
+    }
+    aNewChannel->GetURI(getter_AddRefs(mLoadingSrcFinalURI));
+    LOG(LogLevel::Debug,
+        ("{} OnChannelRedirect: from {} to {}", fmt::ptr(this),
+         oldURI ? oldURI->GetSpecOrDefault().get() : "null",
+         mLoadingSrcFinalURI ? mLoadingSrcFinalURI->GetSpecOrDefault().get()
+                             : "null"));
+  }
   return mChannelLoader->Redirect(aChannel, aNewChannel, aFlags);
 }
 
@@ -2617,6 +2629,8 @@ void HTMLMediaElement::AbortExistingLoads() {
     mChannelLoader = nullptr;
   }
 
+  mLoadingSrcFinalURI = nullptr;
+
   bool fireTimeUpdate = false;
 
   if (mDecoder) {
@@ -2731,12 +2745,12 @@ void HTMLMediaElement::NoSupportedMediaSourceError(
 
   bool isSameOriginLoad = false;
   nsresult rv = NS_ERROR_NOT_AVAILABLE;
-  if (mSrcAttrTriggeringPrincipal && mLoadingSrc) {
-    rv = mSrcAttrTriggeringPrincipal->IsSameOrigin(mLoadingSrc,
-                                                   &isSameOriginLoad);
+  if (mLoadingSrcTriggeringPrincipal && mLoadingSrcFinalURI) {
+    rv = mLoadingSrcTriggeringPrincipal->IsSameOrigin(mLoadingSrcFinalURI,
+                                                      &isSameOriginLoad);
   }
 
-  if (NS_SUCCEEDED(rv) && !isSameOriginLoad) {
+  if (NS_FAILED(rv) || !isSameOriginLoad) {
     // aErrorDetails can include sensitive details like MimeType or HTTP Status
     // Code. In case we're loading a 3rd party resource we should not leak this
     // and pass a Generic Error Message
@@ -2968,6 +2982,7 @@ void HTMLMediaElement::SelectResource(
       } else {
         mLoadingSrc = nullptr;
       }
+      mLoadingSrcFinalURI = mLoadingSrc;
       mLoadingSrcTriggeringPrincipal = mSrcAttrTriggeringPrincipal;
       DDLOG(DDLogCategory::Property, "loading_src",
             nsCString(NS_ConvertUTF16toUTF8(src)));
@@ -3255,6 +3270,7 @@ void HTMLMediaElement::LoadFromSourceChildren(
 
     RemoveMediaElementFromURITable();
     mLoadingSrc = uri;
+    mLoadingSrcFinalURI = mLoadingSrc;
     mLoadingSrcTriggeringPrincipal = child->GetSrcTriggeringPrincipal();
     DDLOG(DDLogCategory::Property, "loading_src",
           nsCString(NS_ConvertUTF16toUTF8(src)));
diff --git a/dom/media/mediaelement/HTMLMediaElement.h b/dom/media/mediaelement/HTMLMediaElement.h
index e0e0d3658af..163b2151b23 100644
--- a/dom/media/mediaelement/HTMLMediaElement.h
+++ b/dom/media/mediaelement/HTMLMediaElement.h
@@ -1646,6 +1646,12 @@ class HTMLMediaElement : public nsGenericHTMLElement,
   // redirects etc.
   nsCOMPtr<nsIURI> mLoadingSrc;
 
+  // The URI of the resource actually loaded. Starts equal to mLoadingSrc and
+  // is updated to the post-redirect URI on each redirect. Used to decide
+  // cross-origin load-error redaction; null means we have no captured URI, and
+  // is treated as cross-origin.
+  nsCOMPtr<nsIURI> mLoadingSrcFinalURI;
+
   // The triggering principal for the current source.
   nsCOMPtr<nsIPrincipal> mLoadingSrcTriggeringPrincipal;
 
Loading diff…