High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionA permission leak could have occurred from a trusted site to an untrusted site via <code>embed</code> or <code>object</code> elements.
ComponentDOM
Bug ClassLogic Error
Tracker1921733
Fix commite2c68ead0a29 (firefox) +100/-7
CISA KEVNot listed
CreditedJames Lee
Disclosed2024-10-29

Changed Functions

FunctionChangeNotes
if
dom/base/nsObjectLoadingContent.cpp
modified
GetFeaturePolicyDefaultOrigin
dom/base/nsObjectLoadingContent.cpp
modified
FeaturePolicy
dom/base/nsObjectLoadingContent.h
modified
Sequence
dom/base/nsObjectLoadingContent.h
modified
HTMLIFrameElement
dom/base/nsObjectLoadingContent.h
modified
if
dom/html/HTMLEmbedElement.cpp
modified
if
dom/html/HTMLObjectElement.cpp
modified

Files Changed

  • dom/base/nsObjectLoadingContent.cpp
  • dom/base/nsObjectLoadingContent.h
  • dom/html/HTMLEmbedElement.cpp
  • dom/html/HTMLObjectElement.cpp
  • netwerk/ipc/DocumentLoadListener.cpp
diff --git a/dom/base/nsObjectLoadingContent.cpp b/dom/base/nsObjectLoadingContent.cpp
index 866bcb9abcb..98c1272f8a6 100644
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -218,8 +218,6 @@ already_AddRefed<nsIDocShell> nsObjectLoadingContent::SetupDocShell(
     return nullptr;
   }
 
-  MaybeStoreCrossOriginFeaturePolicy();
-
   return docShell.forget();
 }
 
@@ -1554,6 +1552,7 @@ void nsObjectLoadingContent::Destroy() {
 void nsObjectLoadingContent::Traverse(nsObjectLoadingContent* tmp,
                                       nsCycleCollectionTraversalCallback& cb) {
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameLoader);
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy);
 }
 
 /* static */
@@ -1562,6 +1561,7 @@ void nsObjectLoadingContent::Unlink(nsObjectLoadingContent* tmp) {
     tmp->mFrameLoader->Destroy();
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameLoader);
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy);
 }
 
 void nsObjectLoadingContent::UnloadObject(bool aResetState) {
@@ -1720,6 +1720,13 @@ nsObjectLoadingContent::UpgradeLoadToDocument(
     return NS_ERROR_FAILURE;
   }
 
+  // At this point we know that we have a browsing context, so it's time to make
+  // sure that that browsing context gets the correct container feature policy.
+  // This is needed for `DocumentLoadListener::MaybeTriggerProcessSwitch` to be
+  // able to start loading the document with the correct container feature
+  // policy in the load info.
+  RefreshFeaturePolicy();
+
   bc.forget(aBrowsingContext);
   return NS_OK;
 }
@@ -1818,6 +1825,9 @@ void nsObjectLoadingContent::SubdocumentImageLoadComplete(nsresult aResult) {
 
 void nsObjectLoadingContent::MaybeStoreCrossOriginFeaturePolicy() {
   MOZ_DIAGNOSTIC_ASSERT(mFrameLoader);
+  if (!mFrameLoader) {
+    return;
+  }
 
   // If the browsingContext is not ready (because docshell is dead), don't try
   // to create one.
@@ -1831,15 +1841,54 @@ void nsObjectLoadingContent::MaybeStoreCrossOriginFeaturePolicy() {
     return;
   }
 
-  Element* el = AsElement();
+  auto* el = nsGenericHTMLElement::FromNode(AsElement());
   if (!el->IsInComposedDoc()) {
     return;
   }
 
-  FeaturePolicy* featurePolicy = el->OwnerDoc()->FeaturePolicy();
-
-  if (ContentChild* cc = ContentChild::GetSingleton(); cc && featurePolicy) {
+  if (ContentChild* cc = ContentChild::GetSingleton()) {
     Unused << cc->SendSetContainerFeaturePolicy(
-        browsingContext, Some(featurePolicy->ToFeaturePolicyInfo()));
+        browsingContext, Some(mFeaturePolicy->ToFeaturePolicyInfo()));
+  }
+}
+
+/* static */ already_AddRefed<nsIPrincipal>
+nsObjectLoadingContent::GetFeaturePolicyDefaultOrigin(nsINode* aNode) {
+  auto* el = nsGenericHTMLElement::FromNode(aNode);
+  nsCOMPtr<nsIURI> nodeURI;
+  // Different elements keep this in various locations
+  if (el->NodeInfo()->Equals(nsGkAtoms::object)) {
+    el->GetURIAttr(nsGkAtoms::data, nullptr, getter_AddRefs(nodeURI));
+  } else if (el->NodeInfo()->Equals(nsGkAtoms::embed)) {
+    el->GetURIAttr(nsGkAtoms::src, nullptr, getter_AddRefs(nodeURI));
   }
+
+  nsCOMPtr<nsIPrincipal> principal;
+  if (nodeURI) {
+    principal = BasePrincipal::CreateContentPrincipal(
+        nodeURI,
+        BasePrincipal::Cast(el->NodePrincipal())->OriginAttributesRef());
+  } else {
+    principal = el->NodePrincipal();
+  }
+
+  return principal.forget();
+}
+
+void nsObjectLoadingContent::RefreshFeaturePolicy() {
+  if (mType != ObjectType::Document) {
+    return;
+  }
+
+  if (!mFeaturePolicy) {
+    mFeaturePolicy = MakeAndAddRef<FeaturePolicy>(AsElement());
+  }
+
+  // The origin can change if 'src' or 'data' attributes change.
+  nsCOMPtr<nsIPrincipal> origin = GetFeaturePolicyDefaultOrigin(AsElement());
+  MOZ_ASSERT(origin);
+  mFeaturePolicy->SetDefaultOrigin(origin);
+
+  mFeaturePolicy->InheritPolicy(AsElement()->OwnerDoc()->FeaturePolicy());
+  MaybeStoreCrossOriginFeaturePolicy();
 }
diff --git a/dom/base/nsObjectLoadingContent.h b/dom/base/nsObjectLoadingContent.h
index 51973a8d7c5..f13f8fa3eb5 100644
--- a/dom/base/nsObjectLoadingContent.h
+++ b/dom/base/nsObjectLoadingContent.h
@@ -27,6 +27,7 @@ class nsFrameLoader;
 
 namespace mozilla::dom {
 struct BindContext;
+class FeaturePolicy;
 template <typename T>
 class Sequence;
 class HTMLIFrameElement;
@@ -213,6 +214,14 @@ class nsObjectLoadingContent : public nsIStreamListener,
    */
   bool BlockEmbedOrObjectContentLoading();
 
+  /**
+   * Updates and stores the container's feature policy in its canonical browsing
+   * context. This gets called whenever the feature policy has changed, which
+   * can happen when this element is upgraded to a container or when the URI of
+   * the element has changed.
+   */
+  void RefreshFeaturePolicy();
+
  private:
   // Object parameter changes returned by UpdateObjectParameters
   enum ParameterUpdateFlags {
@@ -393,6 +402,14 @@ class nsObjectLoadingContent : public nsIStreamListener,
    */
   void MaybeStoreCrossOriginFeaturePolicy();
 
+  /**
+   * Return the value of either `data` or `src`, depending on element type,
+   * parsed as a URL. If URL is invalid or the attribute is missing this returns
+   * the document's origin.
+   */
+  static already_AddRefed<nsIPrincipal> GetFeaturePolicyDefaultOrigin(
+      nsINode* aNode);
+
   // The final listener for mChannel (uriloader, pluginstreamlistener, etc.)
   nsCOMPtr<nsIStreamListener> mFinalListener;
 
@@ -464,6 +481,14 @@ class nsObjectLoadingContent : public nsIStreamListener,
   // our own frame.
   mozilla::Maybe<mozilla::IntrinsicSize> mSubdocumentIntrinsicSize;
   mozilla::Maybe<mozilla::AspectRatio> mSubdocumentIntrinsicRatio;
+
+  // This gets created on the first call of `RefreshFeaturePolicy`, and will be
+  // kept after that. Navigations of this element will use this if they're
+  // targetting documents, which is how iframe element works. If it's a
+  // non-document the feature policy isn't used, but it doesn't hurt to keep it
+  // around, and a subsequent document load will continue using it after
+  // refreshing it.
+  RefPtr<mozilla::dom::FeaturePolicy> mFeaturePolicy;
 };
 
 #endif
diff --git a/dom/html/HTMLEmbedElement.cpp b/dom/html/HTMLEmbedElement.cpp
index 7cd9fd916c4..c307838b33b 100644
--- a/dom/html/HTMLEmbedElement.cpp
+++ b/dom/html/HTMLEmbedElement.cpp
@@ -79,6 +79,10 @@ void HTMLEmbedElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
     AfterMaybeChangeAttr(aNamespaceID, aName, aNotify);
   }
 
+  if (aName == nsGkAtoms::src) {
+    RefreshFeaturePolicy();
+  }
+
   if (aNamespaceID == kNameSpaceID_None &&
       aName == nsGkAtoms::allowfullscreen && mFrameLoader) {
     if (auto* bc = mFrameLoader->GetExtantBrowsingContext()) {
diff --git a/dom/html/HTMLObjectElement.cpp b/dom/html/HTMLObjectElement.cpp
index d449e484074..7f7eca1e15d 100644
--- a/dom/html/HTMLObjectElement.cpp
+++ b/dom/html/HTMLObjectElement.cpp
@@ -100,6 +100,11 @@ void HTMLObjectElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
                                      nsIPrincipal* aSubjectPrincipal,
                                      bool aNotify) {
   AfterMaybeChangeAttr(aNamespaceID, aName, aNotify);
+
+  if (aName == nsGkAtoms::data) {
+    RefreshFeaturePolicy();
+  }
+
   return nsGenericHTMLFormControlElement::AfterSetAttr(
       aNamespaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
Loading diff…