Firefox · Graphics
CVE-2026-74981
Logic Error in Graphics
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifgfx/layers/ImageContainer.cpp |
modified |
Files Changed
gfx/layers/ImageContainer.cpp
Patch
diff --git a/gfx/layers/ImageContainer.cpp b/gfx/layers/ImageContainer.cpp
index 5e9b8316dda..0c8d3321c55 100644
--- a/gfx/layers/ImageContainer.cpp
+++ b/gfx/layers/ImageContainer.cpp
@@ -1034,6 +1034,53 @@ IntSize NVImage::GetSize() const { return mSize; }
IntRect NVImage::GetPictureRect() const { return mData.mPictureRect; }
+// Planar I420 conversion of an NV12/NV21 image: mBuffer owns the pixels, mData
+// points into it.
+struct I420Buffer {
+ UniquePtr<uint8_t[]> mBuffer;
+ PlanarYCbCrData mData;
+};
+
+// Convert |aData| (NV12 or NV21) to planar I420 in a freshly allocated buffer.
+// Returns an empty buffer (null mBuffer) if the required length overflows.
+static I420Buffer CreateI420Buffer(const PlanarYCbCrData& aData) {
+ // NV12/NV21 store interleaved chroma with Cb and Cr in adjacent bytes.
+ MOZ_ASSERT(aData.mCbSkip == 1 && aData.mCrSkip == 1,
+ "CreateI420Buffer expects NV12 or NV21 (interleaved chroma)");
+ MOZ_ASSERT((int)std::abs(aData.mCbChannel - aData.mCrChannel) == 1,
+ "NV12/NV21 chroma planes must be adjacent (interleaved)");
+ auto ySize = aData.YDataSize();
+ auto cbcrSize = aData.CbCrDataSize();
+ const CheckedInt32 bufferLength =
+ CheckedInt32(ySize.height) * aData.mYStride +
+ CheckedInt32(cbcrSize.height) * cbcrSize.width * 2;
+ if (NS_WARN_IF(!bufferLength.isValid())) {
+ return {};
+ }
+ I420Buffer result{MakeUnique<uint8_t[]>(bufferLength.value()), aData};
+
+ PlanarYCbCrData& dst = result.mData;
+ dst.mCbCrStride = cbcrSize.width;
+ dst.mCbSkip = 0;
+ dst.mCrSkip = 0;
+ dst.mYChannel = result.mBuffer.get();
+ dst.mCbChannel = dst.mYChannel + ySize.height * dst.mYStride;
+ dst.mCrChannel = dst.mCbChannel + cbcrSize.height * dst.mCbCrStride;
+
+ if (aData.mCbChannel < aData.mCrChannel) { // NV12
+ libyuv::NV12ToI420(aData.mYChannel, aData.mYStride, aData.mCbChannel,
+ aData.mCbCrStride, dst.mYChannel, dst.mYStride,
+ dst.mCbChannel, dst.mCbCrStride, dst.mCrChannel,
+ dst.mCbCrStride, ySize.width, ySize.height);
+ } else { // NV21
+ libyuv::NV21ToI420(aData.mYChannel, aData.mYStride, aData.mCrChannel,
+ aData.mCbCrStride, dst.mYChannel, dst.mYStride,
+ dst.mCbChannel, dst.mCbCrStride, dst.mCrChannel,
+ dst.mCbCrStride, ySize.width, ySize.height);
+ }
+ return result;
+}
+
already_AddRefed<SourceSurface> NVImage::GetAsSourceSurface() {
if (RefPtr<gfx::DataSourceSurface> cached = mSourceSurface.Get()) {
return cached.forget();
@@ -1041,42 +1088,16 @@ already_AddRefed<SourceSurface> NVImage::GetAsSourceSurface() {
// Convert the current NV12 or NV21 data to YUV420P so that we can follow the
// logics in PlanarYCbCrImage::GetAsSourceSurface().
- auto ySize = mData.YDataSize();
- auto cbcrSize = mData.CbCrDataSize();
- auto bufferLength = CheckedInt32(ySize.height) * mData.mYStride +
- CheckedInt32(cbcrSize.height) * cbcrSize.width * 2;
- if (!bufferLength.isValid()) {
- NS_ERROR("Image buffer length exceeds integer limits.");
+ auto i420 = CreateI420Buffer(mData);
+ if (!i420.mBuffer) {
return nullptr;
}
- auto buffer = MakeUnique<uint8_t[]>(bufferLength.value());
-
- Data aData = mData;
- aData.mCbCrStride = cbcrSize.width;
- aData.mCbSkip = 0;
- aData.mCrSkip = 0;
- aData.mYChannel = buffer.get();
- aData.mCbChannel = aData.mYChannel + ySize.height * aData.mYStride;
- aData.mCrChannel = aData.mCbChannel + cbcrSize.height * aData.mCbCrStride;
-
- if (mData.mCbChannel < mData.mCrChannel) { // NV12
- libyuv::NV12ToI420(mData.mYChannel, mData.mYStride, mData.mCbChannel,
- mData.mCbCrStride, aData.mYChannel, aData.mYStride,
- aData.mCbChannel, aData.mCbCrStride, aData.mCrChannel,
- aData.mCbCrStride, ySize.width, ySize.height);
- } else { // NV21
- libyuv::NV21ToI420(mData.mYChannel, mData.mYStride, mData.mCrChannel,
- mData.mCbCrStride, aData.mYChannel, aData.mYStride,
- aData.mCbChannel, aData.mCbCrStride, aData.mCrChannel,
- aData.mCbCrStride, ySize.width, ySize.height);
- }
-
// The logics in PlanarYCbCrImage::GetAsSourceSurface().
gfx::IntSize size(mSize);
gfx::SurfaceFormat format = gfx::ImageFormatToSurfaceFormat(
gfxPlatform::GetPlatform()->GetOffscreenFormat());
- gfx::GetYCbCrToRGBDestFormatAndSize(aData, format, size);
+ gfx::GetYCbCrToRGBDestFormatAndSize(i420.mData, format, size);
if (mSize.width > PlanarYCbCrImage::MAX_DIMENSION ||
mSize.height > PlanarYCbCrImage::MAX_DIMENSION) {
NS_ERROR("Illegal image dest width or height");
@@ -1095,7 +1116,7 @@ already_AddRefed<SourceSurface> NVImage::GetAsSourceSurface() {
}
if (NS_WARN_IF(NS_FAILED(gfx::ConvertYCbCrToRGB(
- aData, format, size, mapping.GetData(), mapping.GetStride())))) {
+ i420.mData, format, size, mapping.GetData(), mapping.GetStride())))) {
return nullptr;
}
@@ -1109,49 +1130,23 @@ nsresult NVImage::BuildSurfaceDescriptorBuffer(
const std::function<MemoryOrShmem(uint32_t)>& aAllocate) {
// Convert the current NV12 or NV21 data to YUV420P so that we can follow the
// logics in PlanarYCbCrImage::GetAsSourceSurface().
- auto ySize = mData.YDataSize();
- auto cbcrSize = mData.CbCrDataSize();
-
- Data aData = mData;
- aData.mCbCrStride = cbcrSize.width;
- aData.mCbSkip = 0;
- aData.mCrSkip = 0;
- aData.mCbChannel = aData.mYChannel + ySize.height * aData.mYStride;
- aData.mCrChannel = aData.mCbChannel + cbcrSize.height * aData.mCbCrStride;
-
- UniquePtr<uint8_t[]> buffer;
-
RefPtr<gfx::DataSourceSurface> sourceSurface = mSourceSurface.Get();
+ I420Buffer i420;
if (!sourceSurface) {
- auto bufferLength = CheckedInt32(ySize.height) * mData.mYStride +
- CheckedInt32(cbcrSize.height) * cbcrSize.width * 2;
-
- if (!bufferLength.isValid()) {
- NS_ERROR("Image buffer length exceeds integer limits.");
- return NS_ERROR_FAILURE;
- }
-
- buffer = MakeUnique<uint8_t[]>(bufferLength.value());
- aData.mYChannel = buffer.get();
-
- if (mData.mCbChannel < mData.mCrChannel) { // NV12
- libyuv::NV12ToI420(mData.mYChannel, mData.mYStride, mData.mCbChannel,
- mData.mCbCrStride, aData.mYChannel, aData.mYStride,
- aData.mCbChannel, aData.mCbCrStride, aData.mCrChannel,
- aData.mCbCrStride, ySize.width, ySize.height);
- } else { // NV21
- libyuv::NV21ToI420(mData.mYChannel, mData.mYStride, mData.mCrChannel,
- mData.mCbCrStride, aData.mYChannel, aData.mYStride,
- aData.mCbChannel, aData.mCbCrStride, aData.mCrChannel,
- aData.mCbCrStride, ySize.width, ySize.height);
+ i420 = CreateI420Buffer(mData);
+ if (!i420.mBuffer) {
+ return NS_ERROR_INVALID_ARG;
}
}
- // The logics in PlanarYCbCrImage::GetAsSourceSurface().
+ // The logics in PlanarYCbCrImage::GetAsSourceSurface(). mData is used here
+ // rather than i420.mData because only the chroma subsampling and picture size
+ // are read, which are identical, and i420.mData is left unset on the
+ // cached-surface path.
gfx::IntSize size(mSize);
gfx::SurfaceFormat format = gfx::ImageFormatToSurfaceFormat(
gfxPlatform::GetPlatform()->GetOffscreenFormat());
- gfx::GetYCbCrToRGBDestFormatAndSize(aData, format, size);
+ gfx::GetYCbCrToRGBDestFormatAndSize(mData, format, size);
if (mSize.width > PlanarYCbCrImage::MAX_DIMENSION ||
mSize.height > PlanarYCbCrImage::MAX_DIMENSION) {
NS_ERROR("Illegal image dest width or height");
@@ -1171,7 +1166,7 @@ nsresult NVImage::BuildSurfaceDescriptorBuffer(
}
if (!sourceSurface) {
- rv = gfx::ConvertYCbCrToRGB(aData, format, size, output, stride);
+ rv = gfx::ConvertYCbCrToRGB(i420.mData, format, size, output, stride);
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_ASSERT_UNREACHABLE("Failed to convert YUV into RGB data");
return rv;
Loading diff…
References
On This Page