High firefox Memory Corruption 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionMemory safety bugs present in Firefox ESR 115.37, Firefox ESR 140.12 and Firefox 152. Some of these bugs showed evidence of memory corruption and we presume that with enough effort some of these could have been exploited to run arbitrary code.
ComponentGraphics
Bug ClassMemory Corruption
Tracker2022635
Fix commit6ef63c5869cf (firefox) +62/-25
CISA KEVNot listed
CreditedAndrew McCreight, Jan de Mooij, Tom Ritter, Vincent Hilla and the Mozilla Fuzzing Team
Disclosed2026-07-21

Changed Functions

FunctionChangeNotes
if
gfx/layers/ImageContainer.cpp
modified
CachedSurface
gfx/layers/ImageContainer.h
modified
if
gfx/layers/ImageContainer.h
modified

Files Changed

  • gfx/layers/ImageContainer.cpp
  • gfx/layers/ImageContainer.h
diff --git a/gfx/layers/ImageContainer.cpp b/gfx/layers/ImageContainer.cpp
index 901ced77a29..a243e651089 100644
--- a/gfx/layers/ImageContainer.cpp
+++ b/gfx/layers/ImageContainer.cpp
@@ -794,10 +794,11 @@ nsresult PlanarYCbCrImage::BuildSurfaceDescriptorBuffer(
 
     // If we can copy directly from the surface, let's do that to avoid the YUV
     // to RGB conversion.
-    if (mSourceSurface && mSourceSurface->GetSize() == size) {
-      DataSourceSurface::ScopedMap map(mSourceSurface, DataSourceSurface::READ);
+    RefPtr<gfx::DataSourceSurface> sourceSurface = mSourceSurface.Get();
+    if (sourceSurface && sourceSurface->GetSize() == size) {
+      DataSourceSurface::ScopedMap map(sourceSurface, DataSourceSurface::READ);
       if (map.IsMapped() && SwizzleData(map.GetData(), map.GetStride(),
-                                        mSourceSurface->GetFormat(), buffer,
+                                        sourceSurface->GetFormat(), buffer,
                                         stride, format, size)) {
         return NS_OK;
       }
@@ -987,9 +988,8 @@ nsresult PlanarYCbCrImage::AdoptData(const Data& aData) {
 }
 
 already_AddRefed<gfx::SourceSurface> PlanarYCbCrImage::GetAsSourceSurface() {
-  if (mSourceSurface) {
-    RefPtr<gfx::SourceSurface> surface(mSourceSurface);
-    return surface.forget();
+  if (RefPtr<gfx::DataSourceSurface> cached = mSourceSurface.Get()) {
+    return cached.forget();
   }
 
   gfx::IntSize size(mSize);
@@ -1019,30 +1019,24 @@ already_AddRefed<gfx::SourceSurface> PlanarYCbCrImage::GetAsSourceSurface() {
     return nullptr;
   }
 
-  mSourceSurface = surface;
+  mSourceSurface.Set(surface);
 
   return surface.forget();
 }
 
-PlanarYCbCrImage::~PlanarYCbCrImage() {
-  NS_ReleaseOnMainThread("PlanarYCbCrImage::mSourceSurface",
-                         mSourceSurface.forget());
-}
+PlanarYCbCrImage::~PlanarYCbCrImage() = default;
 
 NVImage::NVImage() : Image(nullptr, ImageFormat::NV_IMAGE), mBufferSize(0) {}
 
-NVImage::~NVImage() {
-  NS_ReleaseOnMainThread("NVImage::mSourceSurface", mSourceSurface.forget());
-}
+NVImage::~NVImage() = default;
 
 IntSize NVImage::GetSize() const { return mSize; }
 
 IntRect NVImage::GetPictureRect() const { return mData.mPictureRect; }
 
 already_AddRefed<SourceSurface> NVImage::GetAsSourceSurface() {
-  if (mSourceSurface) {
-    RefPtr<gfx::SourceSurface> surface(mSourceSurface);
-    return surface.forget();
+  if (RefPtr<gfx::DataSourceSurface> cached = mSourceSurface.Get()) {
+    return cached.forget();
   }
 
   // Convert the current NV12 or NV21 data to YUV420P so that we can follow the
@@ -1100,7 +1094,7 @@ already_AddRefed<SourceSurface> NVImage::GetAsSourceSurface() {
     return nullptr;
   }
 
-  mSourceSurface = surface;
+  mSourceSurface.Set(surface);
 
   return surface.forget();
 }
@@ -1122,7 +1116,8 @@ nsresult NVImage::BuildSurfaceDescriptorBuffer(
 
   UniquePtr<uint8_t[]> buffer;
 
-  if (!mSourceSurface) {
+  RefPtr<gfx::DataSourceSurface> sourceSurface = mSourceSurface.Get();
+  if (!sourceSurface) {
     const int bufferLength =
         ySize.height * mData.mYStride + cbcrSize.height * cbcrSize.width * 2;
     buffer = MakeUnique<uint8_t[]>(bufferLength);
@@ -1152,7 +1147,7 @@ nsresult NVImage::BuildSurfaceDescriptorBuffer(
     return NS_ERROR_FAILURE;
   }
 
-  if (mSourceSurface && mSourceSurface->GetSize() != size) {
+  if (sourceSurface && sourceSurface->GetSize() != size) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
@@ -1164,7 +1159,7 @@ nsresult NVImage::BuildSurfaceDescriptorBuffer(
     return rv;
   }
 
-  if (!mSourceSurface) {
+  if (!sourceSurface) {
     rv = gfx::ConvertYCbCrToRGB(aData, format, size, output, stride);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       MOZ_ASSERT_UNREACHABLE("Failed to convert YUV into RGB data");
@@ -1173,12 +1168,12 @@ nsresult NVImage::BuildSurfaceDescriptorBuffer(
     return NS_OK;
   }
 
-  DataSourceSurface::ScopedMap map(mSourceSurface, DataSourceSurface::WRITE);
+  DataSourceSurface::ScopedMap map(sourceSurface, DataSourceSurface::WRITE);
   if (NS_WARN_IF(!map.IsMapped())) {
     return NS_ERROR_FAILURE;
   }
 
-  if (!SwizzleData(map.GetData(), map.GetStride(), mSourceSurface->GetFormat(),
+  if (!SwizzleData(map.GetData(), map.GetStride(), sourceSurface->GetFormat(),
                    output, stride, format, size)) {
     return NS_ERROR_FAILURE;
   }
diff --git a/gfx/layers/ImageContainer.h b/gfx/layers/ImageContainer.h
index 0fe66df2a34..952fe1a3c60 100644
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -9,6 +9,7 @@
 #include "ImageTypes.h"  // for ImageFormat, etc
 #include "mozilla/AlreadyAddRefed.h"
 #include "mozilla/Assertions.h"      // for MOZ_ASSERT_HELPER2
+#include "mozilla/DataMutex.h"       // for DataMutex
 #include "mozilla/Mutex.h"           // for Mutex
 #include "mozilla/RecursiveMutex.h"  // for RecursiveMutex, etc
 #include "mozilla/ThreadSafeWeakPtr.h"
@@ -24,6 +25,7 @@
 #include "nsISupportsImpl.h"  // for Image::Release, etc
 #include "nsTArray.h"         // for nsTArray
 #include "nsThreadUtils.h"    // for NS_IsMainThread
+#include "nsProxyRelease.h"   // for NS_ReleaseOnMainThread
 #include "mozilla/Atomics.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/EnumeratedArray.h"
@@ -219,6 +221,46 @@ class Image {
   static mozilla::Atomic<int32_t> sSerialCounter;
 };
 
+/**
+ * A thread-safe, lazily-populated cache of an Image's rasterized surface.
+ */
+class CachedSurface final {
+ public:
+  CachedSurface() : mSurface("layers::CachedSurface") {}
+
+  ~CachedSurface() {
+    // The surface may have been created off the main thread, but like the rest
+    // of the gfx surface machinery it must be released on the main thread.
+    RefPtr<gfx::DataSourceSurface> surface;
+    {
+      auto guard = mSurface.Lock();
+      surface = guard->forget();
+    }
+    NS_ReleaseOnMainThread("layers::CachedSurface", surface.forget());
+  }
+
+  // Returns the cached surface, or nullptr if nothing has been cached yet.
+  already_AddRefed<gfx::DataSourceSurface> Get() {
+    auto guard = mSurface.Lock();
+    RefPtr<gfx::DataSourceSurface> surface = *guard;
+    return surface.forget();
+  }
+
+  // Stores aSurface unless another thread has already cached one. The caller
+  // may keep using its own surface regardless: both are equivalent
+  // rasterizations of the same image, and keeping the first one avoids dropping
+  // a surface that a concurrent caller may already have handed out.
+  void Set(gfx::DataSourceSurface* aSurface) {
+    auto guard = mSurface.Lock();
+    if (!*guard) {
+      *guard = aSurface;
+    }
+  }
+
+ private:
+  mozilla::DataMutex<RefPtr<gfx::DataSourceSurface>> mSurface;
+};
+
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Image::BuildSdbFlags)
 
 /**
@@ -915,7 +957,7 @@ class PlanarYCbCrImage : public Image {
   gfx::IntSize mSize;
   gfx::ColorDepth mColorDepth = gfx::ColorDepth::COLOR_8;
   gfxImageFormat mOffscreenFormat;
-  RefPtr<gfx::DataSourceSurface> mSourceSurface;
+  CachedSurface mSourceSurface;
   uint32_t mBufferSize;
 };
 
@@ -978,7 +1020,7 @@ class NVImage final : public Image {
   uint32_t mBufferSize;
   gfx::IntSize mSize;
   Data mData;
-  RefPtr<gfx::DataSourceSurface> mSourceSurface;
Loading diff…