High firefox Memory Corruption 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionMemory safety bugs present in Firefox 133, Thunderbird 133, Firefox ESR 115.18, Firefox ESR 128.5, Thunderbird 115.18, and Thunderbird 128.5. 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.
ComponentImageLib
Bug ClassMemory Corruption
Tracker1874523
Fix commite1368b4e31dd (firefox) +90/-80
CISA KEVNot listed
CreditedAndrew McCreight, Tooru Fujisawa, and the Mozilla Fuzzing Team
Disclosed2025-01-07

Changed Functions

FunctionChangeNotes
if
image/AnimationFrameBuffer.cpp
modified
for
image/AnimationFrameBuffer.cpp
modified
if
image/Decoder.cpp
modified
if
image/imgFrame.cpp
modified

Files Changed

  • image/AnimationFrameBuffer.cpp
  • image/Decoder.cpp
  • image/imgFrame.cpp
  • image/imgFrame.h
  • image/test/gtest/TestAnimationFrameBuffer.cpp
diff --git a/image/AnimationFrameBuffer.cpp b/image/AnimationFrameBuffer.cpp
index 6ef855bbd0e..630f59041fc 100644
--- a/image/AnimationFrameBuffer.cpp
+++ b/image/AnimationFrameBuffer.cpp
@@ -432,32 +432,38 @@ RawAccessFrameRef AnimationFrameRecyclingQueue::RecycleFrame(
 
   RawAccessFrameRef recycledFrame;
   if (mRecycle.front().mFrame) {
-    recycledFrame = mRecycle.front().mFrame->RawAccessRef();
-    MOZ_ASSERT(recycledFrame);
+    recycledFrame = mRecycle.front().mFrame->RawAccessRef(
+        gfx::DataSourceSurface::READ_WRITE);
     mRecycle.pop_front();
 
-    if (mForceUseFirstFrameRefreshArea) {
-      // We are still crossing the loop boundary and cannot rely upon the dirty
-      // rects of entries in mDisplay to be representative. E.g. The first frame
-      // is probably has a full frame dirty rect.
-      aRecycleRect = mFirstFrameRefreshArea;
-    } else {
-      // Calculate the recycle rect for the recycled frame. This is the
-      // cumulative dirty rect of all of the frames ahead of us to be displayed,
-      // and to be used for recycling. Or in other words, the dirty rect between
-      // the recycled frame and the decoded frame which reuses the buffer.
-      //
-      // We know at this point that mRecycle contains either frames from the end
-      // of the animation with the first frame refresh area as the dirty rect
-      // (plus the first frame likewise) and frames with their actual dirty rect
-      // from the start. mDisplay should also only contain frames from the start
-      // of the animation onwards.
-      aRecycleRect.SetRect(0, 0, 0, 0);
-      for (const RefPtr<imgFrame>& frame : mDisplay) {
-        aRecycleRect = aRecycleRect.Union(frame->GetDirtyRect());
-      }
-      for (const RecycleEntry& entry : mRecycle) {
-        aRecycleRect = aRecycleRect.Union(entry.mDirtyRect);
+    // If we couldn't map in the surface, it is probably because the frame was
+    // finalized and we did not expect to need to write into it again. This
+    // happens for the first frames produced during an animation.
+    if (recycledFrame) {
+      if (mForceUseFirstFrameRefreshArea) {
+        // We are still crossing the loop boundary and cannot rely upon the
+        // dirty rects of entries in mDisplay to be representative. E.g. The
+        // first frame is probably has a full frame dirty rect.
+        aRecycleRect = mFirstFrameRefreshArea;
+      } else {
+        // Calculate the recycle rect for the recycled frame. This is the
+        // cumulative dirty rect of all of the frames ahead of us to be
+        // displayed, and to be used for recycling. Or in other words, the dirty
+        // rect between the recycled frame and the decoded frame which reuses
+        // the buffer.
+        //
+        // We know at this point that mRecycle contains either frames from the
+        // end of the animation with the first frame refresh area as the dirty
+        // rect (plus the first frame likewise) and frames with their actual
+        // dirty rect from the start. mDisplay should also only contain frames
+        // from the start of the animation onwards.
+        aRecycleRect.SetRect(0, 0, 0, 0);
+        for (const RefPtr<imgFrame>& frame : mDisplay) {
+          aRecycleRect = aRecycleRect.Union(frame->GetDirtyRect());
+        }
+        for (const RecycleEntry& entry : mRecycle) {
+          aRecycleRect = aRecycleRect.Union(entry.mDirtyRect);
+        }
       }
     }
   } else {
diff --git a/image/Decoder.cpp b/image/Decoder.cpp
index 44b1547e136..abb58eab521 100644
--- a/image/Decoder.cpp
+++ b/image/Decoder.cpp
@@ -316,8 +316,7 @@ nsresult Decoder::AllocateFrame(const gfx::IntSize& aOutputSize,
   if (mCurrentFrame) {
     mHasFrameToTake = true;
 
-    // Gather the raw pointers the decoders will use.
-    mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
+    mImageData = mCurrentFrame.Data();
 
     // We should now be on |aFrameNum|. (Note that we're comparing the frame
     // number, which is zero-based, with the frame count, which is one-based.)
@@ -329,6 +328,9 @@ nsresult Decoder::AllocateFrame(const gfx::IntSize& aOutputSize,
     // Update our state to reflect the new frame.
     MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!");
     mInFrame = true;
+  } else {
+    mImageData = nullptr;
+    mImageDataLength = 0;
   }
 
   return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
@@ -389,7 +391,8 @@ RawAccessFrameRef Decoder::AllocateFrameInternal(
       // animation parameters elsewhere. For now we just drop it.
       bool blocked = ref.get() == mRestoreFrame.get();
       if (!blocked) {
-        blocked = NS_FAILED(ref->InitForDecoderRecycle(aAnimParams.ref()));
+        blocked = NS_FAILED(
+            ref->InitForDecoderRecycle(aAnimParams.ref(), &mImageDataLength));
       }
 
       if (blocked) {
@@ -408,12 +411,13 @@ RawAccessFrameRef Decoder::AllocateFrameInternal(
     bool nonPremult = bool(mSurfaceFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
     auto frame = MakeNotNull<RefPtr<imgFrame>>();
     if (NS_FAILED(frame->InitForDecoder(aOutputSize, aFormat, nonPremult,
-                                        aAnimParams, bool(mFrameRecycler)))) {
+                                        aAnimParams, bool(mFrameRecycler),
+                                        &mImageDataLength))) {
       NS_WARNING("imgFrame::Init should succeed");
       return RawAccessFrameRef();
     }
 
-    ref = frame->RawAccessRef();
+    ref = frame->RawAccessRef(gfx::DataSourceSurface::READ_WRITE);
     if (!ref) {
       frame->Abort();
       return RawAccessFrameRef();
diff --git a/image/imgFrame.cpp b/image/imgFrame.cpp
index fc21fb19659..95f23479df9 100644
--- a/image/imgFrame.cpp
+++ b/image/imgFrame.cpp
@@ -145,7 +145,8 @@ imgFrame::~imgFrame() {
 nsresult imgFrame::InitForDecoder(const nsIntSize& aImageSize,
                                   SurfaceFormat aFormat, bool aNonPremult,
                                   const Maybe<AnimationParams>& aAnimParams,
-                                  bool aShouldRecycle) {
+                                  bool aShouldRecycle,
+                                  uint32_t* aImageDataLength) {
   // Assert for properties that should be verified by decoders,
   // warn for properties related to bad content.
   if (!SurfaceCache::IsLegalSize(aImageSize)) {
@@ -217,10 +218,15 @@ nsresult imgFrame::InitForDecoder(const nsIntSize& aImageSize,
     }
   }
 
+  if (aImageDataLength) {
+    *aImageDataLength = GetImageDataLength();
+  }
+
   return NS_OK;
 }
 
-nsresult imgFrame::InitForDecoderRecycle(const AnimationParams& aAnimParams) {
+nsresult imgFrame::InitForDecoderRecycle(const AnimationParams& aAnimParams,
+                                         uint32_t* aImageDataLength) {
   // We want to recycle this frame, but there is no guarantee that consumers are
   // done with it in a timely manner. Let's ensure they are done with it first.
   MonitorAutoLock lock(mMonitor);
@@ -287,6 +293,10 @@ nsresult imgFrame::InitForDecoderRecycle(const AnimationParams& aAnimParams) {
   mDisposalMethod = aAnimParams.mDisposalMethod;
   mDirtyRect = GetRect();
 
+  if (aImageDataLength) {
+    *aImageDataLength = GetImageDataLength();
+  }
+
   return NS_OK;
 }
 
@@ -391,7 +401,10 @@ nsresult imgFrame::InitWithDrawable(gfxDrawable* aDrawable,
 
 DrawableFrameRef imgFrame::DrawableRef() { return DrawableFrameRef(this); }
 
-RawAccessFrameRef imgFrame::RawAccessRef() { return RawAccessFrameRef(this); }
+RawAccessFrameRef imgFrame::RawAccessRef(
+    gfx::DataSourceSurface::MapType aMapType) {
+  return RawAccessFrameRef(this, aMapType);
+}
 
 imgFrame::SurfaceWithFormat imgFrame::SurfaceForDrawing(
     bool aDoPartialDecode, bool aDoTile, ImageRegion& aRegion,
@@ -586,36 +599,6 @@ uint32_t imgFrame::GetImageDataLength() const {
   return GetImageBytesPerRow() * mImageSize.height;
 }
 
-void imgFrame::GetImageData(uint8_t** aData, uint32_t* aLength) const {
-  MonitorAutoLock lock(mMonitor);
-  GetImageDataInternal(aData, aLength);
-}
-
-void imgFrame::GetImageDataInternal(uint8_t** aData, uint32_t* aLength) const {
-  mMonitor.AssertCurrentThreadOwns();
-  MOZ_ASSERT(mRawSurface);
-
-  if (mRawSurface) {
-    // TODO: This is okay for now because we only realloc shared surfaces on
-    // the main thread after decoding has finished, but if animations want to
-    // read frame data off the main thread, we will need to reconsider this.
-    *aData = mRawSurface->GetData();
-    MOZ_ASSERT(*aData,
-               "mRawSurface is non-null, but GetData is null in GetImageData");
-  } else {
-    *aData = nullptr;
-  }
-
-  *aLength = GetImageDataLength();
-}
-
-uint8_t* imgFrame::GetImageData() const {
-  uint8_t* data;
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/image/test/gtest/TestAnimationFrameBuffer.cpp b/image/test/gtest/TestAnimationFrameBuffer.cpp
index 78186e50665..69e020083a7 100644
--- a/image/test/gtest/TestAnimationFrameBuffer.cpp
+++ b/image/test/gtest/TestAnimationFrameBuffer.cpp
@@ -759,8 +759,7 @@ TEST_F(ImageAnimationFrameBuffer, RecyclingResetBeforeComplete) {
   while (!buffer.Recycle().empty()) {
     gfx::IntRect recycleRect;
     RawAccessFrameRef frameRef = buffer.RecycleFrame(recycleRect);
-    EXPECT_TRUE(frameRef);
-    EXPECT_FALSE(ReinitForRecycle(frameRef));
+    EXPECT_FALSE(frameRef);
   }
 
   // Reinsert the first two frames as recyclable and reset again.
@@ -829,8 +828,7 @@ TEST_F(ImageAnimationFrameBuffer, RecyclingRect) {
   gfx::IntRect recycleRect;
   EXPECT_FALSE(buffer.Recycle().empty());
   RawAccessFrameRef frameRef = buffer.RecycleFrame(recycleRect);
-  EXPECT_TRUE(frameRef);
-  EXPECT_FALSE(ReinitForRecycle(frameRef));
+  EXPECT_FALSE(frameRef);
   EXPECT_TRUE(buffer.Recycle().empty());
 
   // Insert a recyclable partial frame. Its dirty rect shouldn't matter since
@@ -842,8 +840,7 @@ TEST_F(ImageAnimationFrameBuffer, RecyclingRect) {
   VerifyAdvance(buffer, 5, true);
   EXPECT_FALSE(buffer.Recycle().empty());
   frameRef = buffer.RecycleFrame(recycleRect);
-  EXPECT_TRUE(frameRef);
-  EXPECT_FALSE(ReinitForRecycle(frameRef));
+  EXPECT_FALSE(frameRef);
   EXPECT_TRUE(buffer.Recycle().empty());
 
   // Insert a recyclable partial frame. Its dirty rect should match the recycle
Loading diff…