Firefox · DOM
CVE-2025-9180
Cross Origin in DOM
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
EncodeCallbackdom/canvas/CanvasRenderingContextHelper.cpp |
modified | |
ifdom/canvas/CanvasRenderingContextHelper.cpp |
modified | |
ifdom/canvas/OffscreenCanvas.cpp |
modified | |
ifdom/canvas/OffscreenCanvasDisplayHelper.cpp |
modified | |
EncodeCallbackdom/html/HTMLCanvasElement.cpp |
modified | |
mSubjectPrincipaldom/html/HTMLCanvasElement.cpp |
modified | |
ifdom/html/HTMLCanvasElement.cpp |
modified |
Files Changed
dom/canvas/CanvasRenderingContextHelper.cppdom/canvas/CanvasRenderingContextHelper.hdom/canvas/OffscreenCanvas.cppdom/canvas/OffscreenCanvasDisplayHelper.cppdom/canvas/OffscreenCanvasDisplayHelper.hdom/html/HTMLCanvasElement.cppdom/html/HTMLCanvasElement.h
Patch
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…
References
On This Page