High firefox Cross Origin 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionSame-origin policy bypass in the Graphics: Canvas2D component.
ComponentDOM
Bug ClassCross Origin
Tracker1979782
Fix commit1ea94eb72fb0 (firefox) +131/-63
CISA KEVNot listed
CreditedTom Van Goethem
Disclosed2025-08-19

Changed Functions

FunctionChangeNotes
EncodeCallback
dom/canvas/CanvasRenderingContextHelper.cpp
modified
if
dom/canvas/CanvasRenderingContextHelper.cpp
modified
if
dom/canvas/OffscreenCanvas.cpp
modified
if
dom/canvas/OffscreenCanvasDisplayHelper.cpp
modified
EncodeCallback
dom/html/HTMLCanvasElement.cpp
modified
mSubjectPrincipal
dom/html/HTMLCanvasElement.cpp
modified
if
dom/html/HTMLCanvasElement.cpp
modified

Files Changed

  • dom/canvas/CanvasRenderingContextHelper.cpp
  • dom/canvas/CanvasRenderingContextHelper.h
  • dom/canvas/OffscreenCanvas.cpp
  • dom/canvas/OffscreenCanvasDisplayHelper.cpp
  • dom/canvas/OffscreenCanvasDisplayHelper.h
  • dom/html/HTMLCanvasElement.cpp
  • dom/html/HTMLCanvasElement.h
diff --git a/dom/canvas/CanvasRenderingContextHelper.cpp b/dom/canvas/CanvasRenderingContextHelper.cpp
index 64d0123da75..d4004eb64f9 100644
--- a/dom/canvas/CanvasRenderingContextHelper.cpp
+++ b/dom/canvas/CanvasRenderingContextHelper.cpp
@@ -27,55 +27,6 @@ namespace mozilla::dom {
 CanvasRenderingContextHelper::CanvasRenderingContextHelper()
     : mCurrentContextType(CanvasContextType::NoContext) {}
 
-void CanvasRenderingContextHelper::ToBlob(
-    JSContext* aCx, nsIGlobalObject* aGlobal, BlobCallback& aCallback,
-    const nsAString& aType, JS::Handle<JS::Value> aParams, bool aUsePlaceholder,
-    ErrorResult& aRv) {
-  // Encoder callback when encoding is complete.
-  class EncodeCallback : public EncodeCompleteCallback {
-   public:
-    EncodeCallback(nsIGlobalObject* aGlobal, BlobCallback* aCallback)
-        : mGlobal(aGlobal), mBlobCallback(aCallback) {}
-
-    // This is called on main thread.
-    MOZ_CAN_RUN_SCRIPT
-    nsresult ReceiveBlobImpl(already_AddRefed<BlobImpl> aBlobImpl) override {
-      MOZ_ASSERT(NS_IsMainThread());
-
-      RefPtr<BlobImpl> blobImpl = aBlobImpl;
-
-      RefPtr<Blob> blob;
-
-      if (blobImpl) {
-        blob = Blob::Create(mGlobal, blobImpl);
-      }
-
-      RefPtr<BlobCallback> callback(std::move(mBlobCallback));
-      ErrorResult rv;
-
-      callback->Call(blob, rv);
-
-      mGlobal = nullptr;
-      MOZ_ASSERT(!mBlobCallback);
-
-      return rv.StealNSResult();
-    }
-
-    bool CanBeDeletedOnAnyThread() override {
-      // EncodeCallback is used from the main thread only.
-      return false;
-    }
-
-    nsCOMPtr<nsIGlobalObject> mGlobal;
-    RefPtr<BlobCallback> mBlobCallback;
-  };
-
-  RefPtr<EncodeCompleteCallback> callback =
-      new EncodeCallback(aGlobal, &aCallback);
-
-  ToBlob(aCx, callback, aType, aParams, aUsePlaceholder, aRv);
-}
-
 void CanvasRenderingContextHelper::ToBlob(
     JSContext* aCx, EncodeCompleteCallback* aCallback, const nsAString& aType,
     JS::Handle<JS::Value> aParams, bool aUsePlaceholder, ErrorResult& aRv) {
diff --git a/dom/canvas/CanvasRenderingContextHelper.h b/dom/canvas/CanvasRenderingContextHelper.h
index 68c1212c76e..5fc67a547b4 100644
--- a/dom/canvas/CanvasRenderingContextHelper.h
+++ b/dom/canvas/CanvasRenderingContextHelper.h
@@ -58,10 +58,6 @@ class CanvasRenderingContextHelper {
                                nsAString& outParams,
                                bool* const outCustomParseOptions);
 
-  void ToBlob(JSContext* aCx, nsIGlobalObject* global, BlobCallback& aCallback,
-              const nsAString& aType, JS::Handle<JS::Value> aParams,
-              bool aUsePlaceholder, ErrorResult& aRv);
-
   void ToBlob(JSContext* aCx, EncodeCompleteCallback* aCallback,
               const nsAString& aType, JS::Handle<JS::Value> aParams,
               bool aUsePlaceholder, ErrorResult& aRv);
diff --git a/dom/canvas/OffscreenCanvas.cpp b/dom/canvas/OffscreenCanvas.cpp
index 9f4606bd934..baf2e0abd76 100644
--- a/dom/canvas/OffscreenCanvas.cpp
+++ b/dom/canvas/OffscreenCanvas.cpp
@@ -573,6 +573,10 @@ void OffscreenCanvas::SetWriteOnly(RefPtr<nsIPrincipal>&& aExpandedReader) {
                          mExpandedReader.forget());
   mExpandedReader = std::move(aExpandedReader);
   mIsWriteOnly = true;
+
+  if (mDisplay) {
+    mDisplay->SetWriteOnly(mExpandedReader);
+  }
 }
 
 bool OffscreenCanvas::CallerCanRead(nsIPrincipal& aPrincipal) const {
diff --git a/dom/canvas/OffscreenCanvasDisplayHelper.cpp b/dom/canvas/OffscreenCanvasDisplayHelper.cpp
index 914440ea4b8..545600d6797 100644
--- a/dom/canvas/OffscreenCanvasDisplayHelper.cpp
+++ b/dom/canvas/OffscreenCanvasDisplayHelper.cpp
@@ -33,7 +33,11 @@ OffscreenCanvasDisplayHelper::OffscreenCanvasDisplayHelper(
   mData.mSize.height = aHeight;
 }
 
-OffscreenCanvasDisplayHelper::~OffscreenCanvasDisplayHelper() = default;
+OffscreenCanvasDisplayHelper::~OffscreenCanvasDisplayHelper() {
+  MutexAutoLock lock(mMutex);
+  NS_ReleaseOnMainThread("OffscreenCanvas::mExpandedReader",
+                         mExpandedReader.forget());
+}
 
 void OffscreenCanvasDisplayHelper::DestroyElement() {
   MOZ_ASSERT(NS_IsMainThread());
@@ -62,6 +66,32 @@ void OffscreenCanvasDisplayHelper::DestroyCanvas() {
   mWorkerRef = nullptr;
 }
 
+void OffscreenCanvasDisplayHelper::SetWriteOnly(nsIPrincipal* aExpandedReader) {
+  MutexAutoLock lock(mMutex);
+  NS_ReleaseOnMainThread("OffscreenCanvasDisplayHelper::mExpandedReader",
+                         mExpandedReader.forget());
+  mExpandedReader = aExpandedReader;
+  mIsWriteOnly = true;
+}
+
+bool OffscreenCanvasDisplayHelper::CallerCanRead(
+    nsIPrincipal& aPrincipal) const {
+  MutexAutoLock lock(mMutex);
+  if (!mIsWriteOnly) {
+    return true;
+  }
+
+  // If mExpandedReader is set, this canvas was tainted only by
+  // mExpandedReader's resources. So allow reading if the subject
+  // principal subsumes mExpandedReader.
+  if (mExpandedReader && aPrincipal.Subsumes(mExpandedReader)) {
+    return true;
+  }
+
+  return nsContentUtils::PrincipalHasPermission(aPrincipal,
+                                                nsGkAtoms::all_urlsPermission);
+}
+
 bool OffscreenCanvasDisplayHelper::CanElementCaptureStream() const {
   MutexAutoLock lock(mMutex);
   return !!mWorkerRef;
diff --git a/dom/canvas/OffscreenCanvasDisplayHelper.h b/dom/canvas/OffscreenCanvasDisplayHelper.h
index a098f7dbe79..bcec29646e9 100644
--- a/dom/canvas/OffscreenCanvasDisplayHelper.h
+++ b/dom/canvas/OffscreenCanvasDisplayHelper.h
@@ -57,6 +57,19 @@ class OffscreenCanvasDisplayHelper final {
   void DestroyCanvas();
   void DestroyElement();
 
+  bool IsWriteOnly() const {
+    MutexAutoLock lock(mMutex);
+    return mIsWriteOnly;
+  }
+
+  bool HasWorkerRef() const {
+    MutexAutoLock lock(mMutex);
+    return !!mWorkerRef;
+  }
+
+  void SetWriteOnly(nsIPrincipal* aExpandedReader = nullptr);
+  bool CallerCanRead(nsIPrincipal& aPrincipal) const;
+
   bool CanElementCaptureStream() const;
   bool UsingElementCaptureStream() const;
 
@@ -90,6 +103,8 @@ class OffscreenCanvasDisplayHelper final {
   mozilla::layers::ImageContainer::FrameID mLastFrameID MOZ_GUARDED_BY(mMutex) =
       0;
   bool mPendingInvalidate MOZ_GUARDED_BY(mMutex) = false;
+  bool mIsWriteOnly MOZ_GUARDED_BY(mMutex) = false;
+  RefPtr<nsIPrincipal> mExpandedReader MOZ_GUARDED_BY(mMutex);
 };
 
 }  // namespace mozilla::dom
diff --git a/dom/html/HTMLCanvasElement.cpp b/dom/html/HTMLCanvasElement.cpp
index 196f66e5389..d41e1194ced 100644
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -767,16 +767,26 @@ void HTMLCanvasElement::ToDataURL(JSContext* aCx, const nsAString& aType,
                                   nsAString& aDataURL,
                                   nsIPrincipal& aSubjectPrincipal,
                                   ErrorResult& aRv) {
-  // mWriteOnly check is redundant, but optimizes for the common case.
-  if (mWriteOnly && !CallerCanRead(aSubjectPrincipal)) {
+  bool recheckCanRead = mOffscreenDisplay && mOffscreenDisplay->HasWorkerRef();
+
+  if (!CallerCanRead(aSubjectPrincipal)) {
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return;
+  }
+
+  nsString dataURL;
+  nsresult rv = ToDataURLImpl(aCx, aSubjectPrincipal, aType, aParams, dataURL);
+  if (recheckCanRead && !CallerCanRead(aSubjectPrincipal)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
-  nsresult rv = ToDataURLImpl(aCx, aSubjectPrincipal, aType, aParams, aDataURL);
   if (NS_FAILED(rv)) {
-    aDataURL.AssignLiteral("data:,");
+    aDataURL.Assign(u"data:,"_ns);
+    return;
   }
+
+  aDataURL = std::move(dataURL);
 }
 
 void HTMLCanvasElement::SetMozPrintCallback(PrintCallback* aCallback) {
@@ -997,8 +1007,9 @@ void HTMLCanvasElement::ToBlob(JSContext* aCx, BlobCallback& aCallback,
                                JS::Handle<JS::Value> aParams,
                                nsIPrincipal& aSubjectPrincipal,
                                ErrorResult& aRv) {
-  // mWriteOnly check is redundant, but optimizes for the common case.
-  if (mWriteOnly && !CallerCanRead(aSubjectPrincipal)) {
+  bool recheckCanRead = mOffscreenDisplay && mOffscreenDisplay->HasWorkerRef();
+
+  if (!CallerCanRead(aSubjectPrincipal)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
@@ -1023,7 +1034,59 @@ void HTMLCanvasElement::ToBlob(JSContext* aCx, BlobCallback& aCallback,
   // If no permission, return all-white, opaque image data.
   bool usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(
       OwnerDoc(), aCx, aSubjectPrincipal);
-  CanvasRenderingContextHelper::ToBlob(aCx, global, aCallback, aType, aParams,
+
+  // Encoder callback when encoding is complete.
+  class EncodeCallback : public EncodeCompleteCallback {
+   public:
+    EncodeCallback(nsIGlobalObject* aGlobal, BlobCallback* aCallback,
+                   OffscreenCanvasDisplayHelper* aOffscreenDisplay,
+                   nsIPrincipal* aSubjectPrincipal)
+        : mGlobal(aGlobal),
+          mBlobCallback(aCallback),
+          mOffscreenDisplay(aOffscreenDisplay),
+          mSubjectPrincipal(aSubjectPrincipal) {}
+
+    // This is called on main thread.
+    MOZ_CAN_RUN_SCRIPT
+    nsresult ReceiveBlobImpl(already_AddRefed<BlobImpl> aBlobImpl) override {
+      MOZ_ASSERT(NS_IsMainThread());
+
+      RefPtr<BlobImpl> blobImpl = aBlobImpl;
+
+      RefPtr<Blob> blob;
+
+      if (blobImpl && (!mOffscreenDisplay ||
+                       mOffscreenDisplay->CallerCanRead(*mSubjectPrincipal))) {
+        blob = Blob::Create(mGlobal, blobImpl);
+      }
+
+      RefPtr<BlobCallback> callback(std::move(mBlobCallback));
+      ErrorResult rv;
+
+      callback->Call(blob, rv);
+
+      mGlobal = nullptr;
+      MOZ_ASSERT(!mBlobCallback);
+
+      return rv.StealNSResult();
+    }
+
+    bool CanBeDeletedOnAnyThread() override {
+      // EncodeCallback is used from the main thread only.
+      return false;
+    }
+
+    nsCOMPtr<nsIGlobalObject> mGlobal;
+    RefPtr<BlobCallback> mBlobCallback;
+    RefPtr<OffscreenCanvasDisplayHelper> mOffscreenDisplay;
+    RefPtr<nsIPrincipal> mSubjectPrincipal;
+  };
+
+  RefPtr<EncodeCompleteCallback> callback = new EncodeCallback(
+      global, &aCallback, recheckCanRead ? mOffscreenDisplay.get() : nullptr,
+      recheckCanRead ? &aSubjectPrincipal : nullptr);
+
+  CanvasRenderingContextHelper::ToBlob(aCx, callback, aType, aParams,
                                        usePlaceholder, aRv);
 }
 
@@ -1091,7 +1154,12 @@ already_AddRefed<nsISupports> HTMLCanvasElement::GetContext(
 
 CSSIntSize HTMLCanvasElement::GetSize() { return GetWidthHeight(); }
 
-bool HTMLCanvasElement::IsWriteOnly() const { return mWriteOnly; }
+bool HTMLCanvasElement::IsWriteOnly() const {
+  if (mOffscreenDisplay && mOffscreenDisplay->IsWriteOnly()) {
+    return true;
+  }
+  return mWriteOnly;
+}
 
 void HTMLCanvasElement::SetWriteOnly(
     nsIPrincipal* aExpandedReader /* = nullptr */) {
@@ -1103,6 +1171,10 @@ void HTMLCanvasElement::SetWriteOnly(
 }
 
 bool HTMLCanvasElement::CallerCanRead(nsIPrincipal& aPrincipal) const {
+  if (mOffscreenDisplay && !mOffscreenDisplay->CallerCanRead(aPrincipal)) {
+    return false;
+  }
+
   if (!mWriteOnly) {
     return true;
   }
diff --git a/dom/html/HTMLCanvasElement.h b/dom/html/HTMLCanvasElement.h
index d87b04f3a5c..281280a7e80 100644
--- a/dom/html/HTMLCanvasElement.h
+++ b/dom/html/HTMLCanvasElement.h
@@ -371,13 +371,13 @@ class HTMLCanvasElement final : public nsGenericHTMLElement,
   RefPtr<layers::ImageContainer> mImageContainer;
   RefPtr<HTMLCanvasElementObserver> mContextObserver;
 
- public:
   // Record whether this canvas should be write-only or not.
   // We set this when script paints an image from a different origin.
   // We also transitively set it when script paints a canvas which
   // is itself write-only.
   bool mWriteOnly;
 
+ public:
   // When this canvas is (only) tainted by an image from an extension
   // content script, allow reads from the same extension afterwards.
   RefPtr<nsIPrincipal> mExpandedReader;
Loading diff…