High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionUse-after-free in the Graphics: Canvas2D component
ComponentDOM
Bug ClassLogic Error
Tracker2079676
Fix commitb3cb7d1aba25 (firefox) +34/-12
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Changed Functions

FunctionChangeNotes
if
dom/canvas/OffscreenCanvasDisplayHelper.cpp
modified
GetCanvasForWorker
dom/canvas/OffscreenCanvasDisplayHelper.cpp
modified
mDisplayHelper
dom/canvas/OffscreenCanvasDisplayHelper.cpp
modified

Files Changed

  • dom/canvas/OffscreenCanvasDisplayHelper.cpp
  • dom/canvas/OffscreenCanvasDisplayHelper.h
diff --git a/dom/canvas/OffscreenCanvasDisplayHelper.cpp b/dom/canvas/OffscreenCanvasDisplayHelper.cpp
index 65fdd703a35..cc5c8ad787a 100644
--- a/dom/canvas/OffscreenCanvasDisplayHelper.cpp
+++ b/dom/canvas/OffscreenCanvasDisplayHelper.cpp
@@ -120,6 +120,13 @@ RefPtr<layers::ImageContainer> OffscreenCanvasDisplayHelper::GetImageContainer()
 bool OffscreenCanvasDisplayHelper::MayUpdateContext(WorkerPrivate* aWorker,
                                                     ErrorResult& aRv) {
   MutexAutoLock lock(mMutex);
+  // Allow context update on main thread to be more fault tolerant, whereas
+  // update on a worker needs to more strict in case of transfer.
+  if ((mCreatedOnMainThread && aWorker) || mCreatedOnWorker) {
+    aRv.ThrowInvalidStateError(
+        "Cannot update created context to different thread");
+    return false;
+  }
   if (!mOffscreenCanvas) {
     return true;
   }
@@ -153,6 +160,14 @@ void OffscreenCanvasDisplayHelper::UpdateContext(
   mType = aType;
   mContextChildId = aChildId;
 
+  if (mOffscreenCanvas) {
+    if (mWorkerRef) {
+      mCreatedOnWorker = true;
+    } else {
+      mCreatedOnMainThread = true;
+    }
+  }
+
   if (aChildId) {
     mContextManagerId = Some(gfx::CanvasManagerChild::Get()->Id());
   } else {
@@ -162,6 +177,16 @@ void OffscreenCanvasDisplayHelper::UpdateContext(
   MaybeQueueInvalidateElement();
 }
 
+already_AddRefed<OffscreenCanvas>
+OffscreenCanvasDisplayHelper::GetCanvasForWorker(WorkerPrivate* aWorker) {
+  MutexAutoLock lock(mMutex);
+  // A runnable queued to a worker somehow got assigned to another thread.
+  if (NS_WARN_IF(!mWorkerRef) || NS_WARN_IF(mWorkerRef->Private() != aWorker)) {
+    return nullptr;
+  }
+  return do_AddRef(mOffscreenCanvas);
+}
+
 void OffscreenCanvasDisplayHelper::FlushForDisplay() {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -189,17 +214,14 @@ void OffscreenCanvasDisplayHelper::FlushForDisplay() {
         : MainThreadWorkerRunnable("FlushWorkerRunnable"),
           mDisplayHelper(aDisplayHelper) {}
 
-    bool WorkerRun(JSContext*, WorkerPrivate*) override {
+    bool WorkerRun(JSContext*, WorkerPrivate* aWorkerPrivate) override {
       // The OffscreenCanvas can only be freed on the worker thread, so we
       // cannot be racing with an OffscreenCanvas::DestroyCanvas call and its
       // destructor. We just need to make sure we don't call into
       // OffscreenCanvas while holding the lock since it calls back into the
       // OffscreenCanvasDisplayHelper.
-      RefPtr<OffscreenCanvas> canvas;
-      {
-        MutexAutoLock lock(mDisplayHelper->mMutex);
-        canvas = mDisplayHelper->mOffscreenCanvas;
-      }
+      RefPtr<OffscreenCanvas> canvas =
+          mDisplayHelper->GetCanvasForWorker(aWorkerPrivate);
 
       if (canvas) {
         canvas->CommitFrameToCompositor();
@@ -513,17 +535,14 @@ OffscreenCanvasDisplayHelper::GetSurfaceSnapshot() {
           mMonitor("SnapshotWorkerRunnable::mMonitor"),
           mDisplayHelper(aDisplayHelper) {}
 
-    bool WorkerRun(JSContext*, WorkerPrivate*) override {
+    bool WorkerRun(JSContext*, WorkerPrivate* aWorkerPrivate) override {
       // The OffscreenCanvas can only be freed on the worker thread, so we
       // cannot be racing with an OffscreenCanvas::DestroyCanvas call and its
       // destructor. We just need to make sure we don't call into
       // OffscreenCanvas while holding the lock since it calls back into the
       // OffscreenCanvasDisplayHelper.
-      RefPtr<OffscreenCanvas> canvas;
-      {
-        MutexAutoLock lock(mDisplayHelper->mMutex);
-        canvas = mDisplayHelper->mOffscreenCanvas;
-      }
+      RefPtr<OffscreenCanvas> canvas =
+          mDisplayHelper->GetCanvasForWorker(aWorkerPrivate);
 
       // Now that we are on the correct thread, we can extract the snapshot. If
       // it is a Skia surface, perform a copy to threading issues.
diff --git a/dom/canvas/OffscreenCanvasDisplayHelper.h b/dom/canvas/OffscreenCanvasDisplayHelper.h
index 72d6e4a5b78..f7db258e2e1 100644
--- a/dom/canvas/OffscreenCanvasDisplayHelper.h
+++ b/dom/canvas/OffscreenCanvasDisplayHelper.h
@@ -89,6 +89,7 @@ class OffscreenCanvasDisplayHelper final {
  private:
   ~OffscreenCanvasDisplayHelper();
   void MaybeQueueInvalidateElement() MOZ_REQUIRES(mMutex);
+  already_AddRefed<OffscreenCanvas> GetCanvasForWorker(WorkerPrivate* aWorker);
   void InvalidateElement();
 
   already_AddRefed<gfx::SourceSurface> TransformSurface(
@@ -115,6 +116,8 @@ class OffscreenCanvasDisplayHelper final {
   bool mPendingPresent MOZ_GUARDED_BY(mMutex) = false;
   bool mIsWriteOnly MOZ_GUARDED_BY(mMutex) = false;
   RefPtr<nsIPrincipal> mExpandedReader MOZ_GUARDED_BY(mMutex);
+  bool mCreatedOnMainThread MOZ_GUARDED_BY(mMutex) = false;
+  bool mCreatedOnWorker MOZ_GUARDED_BY(mMutex) = false;
 };
 
 }  // namespace mozilla::dom
Loading diff…