Firefox · ImageLib
CVE-2025-0242
Memory Corruption in ImageLib
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifimage/AnimationFrameBuffer.cpp |
modified | |
forimage/AnimationFrameBuffer.cpp |
modified | |
ifimage/Decoder.cpp |
modified | |
ifimage/imgFrame.cpp |
modified |
Files Changed
image/AnimationFrameBuffer.cppimage/Decoder.cppimage/imgFrame.cppimage/imgFrame.himage/test/gtest/TestAnimationFrameBuffer.cpp
Patch
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…
References
On This Page