High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionIncorrect boundary conditions in the Graphics: Canvas2D component
ComponentGraphics
Bug ClassLogic Error
Tracker2016349
Fix commit729a78095f30 (firefox) +53/-32
CISA KEVNot listed
CreditedSajeeb Lohani
Disclosed2026-03-24

Changed Functions

FunctionChangeNotes
mPreserveAlpha
gfx/2d/FilterNodeSoftware.cpp
modified
switch
gfx/2d/FilterNodeSoftware.cpp
modified

Files Changed

  • gfx/2d/FilterNodeSoftware.cpp
  • gfx/2d/FilterNodeSoftware.h
  • gfx/2d/Rect.h
diff --git a/gfx/2d/FilterNodeSoftware.cpp b/gfx/2d/FilterNodeSoftware.cpp
index 11f53d3a765..0130e7853b1 100644
--- a/gfx/2d/FilterNodeSoftware.cpp
+++ b/gfx/2d/FilterNodeSoftware.cpp
@@ -2178,6 +2178,7 @@ FilterNodeConvolveMatrixSoftware::FilterNodeConvolveMatrixSoftware()
     : mDivisor(0),
       mBias(0),
       mEdgeMode(EDGE_MODE_DUPLICATE),
+      mKernelUnitLength(1.0f, 1.0f),
       mPreserveAlpha(false) {}
 
 int32_t FilterNodeConvolveMatrixSoftware::InputIndex(uint32_t aInputEnumIndex) {
@@ -2223,7 +2224,21 @@ void FilterNodeConvolveMatrixSoftware::SetAttribute(
     uint32_t aIndex, const Size& aKernelUnitLength) {
   switch (aIndex) {
     case ATT_CONVOLVE_MATRIX_KERNEL_UNIT_LENGTH:
+      // Spec for feConvolveMatrix:
+      // If the attribute (kernelUnitLength) is not specified, the default value
+      // is one pixel in the offscreen bitmap. If a negative or zero value is
+      // specified the default value will be used instead. The first number is
+      // the x value. The second number is the y value. If the value is not
+      // specified, it defaults to the same value as x.
       mKernelUnitLength = aKernelUnitLength;
+      if (mKernelUnitLength.width <= 0.0f ||
+          !std::isfinite(mKernelUnitLength.width)) {
+        mKernelUnitLength.width = 1.0f;
+      }
+      if (mKernelUnitLength.height <= 0.0f ||
+          !std::isfinite(mKernelUnitLength.height)) {
+        mKernelUnitLength.height = mKernelUnitLength.width;
+      }
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeConvolveMatrixSoftware::SetAttribute");
@@ -2433,11 +2448,15 @@ already_AddRefed<DataSourceSurface> FilterNodeConvolveMatrixSoftware::DoRender(
                                             SurfaceFormat::B8G8R8A8, true);
   }
 
-  IntRect srcRect = InflatedSourceRect(aRect);
-
+  RectDouble srcRectD(aRect);
+  srcRectD.Inflate(GetInflateSourceMargin());
   // Inflate the source rect by another pixel because the bilinear filtering in
   // ColorComponentAtPoint may want to access the margins.
-  srcRect.Inflate(1);
+  srcRectD.Inflate(1);
+  if (!RectIsInt32Safe(srcRectD)) {
+    return nullptr;
+  }
+  IntRect srcRect = TruncatedToInt(srcRectD);
 
   RefPtr<DataSourceSurface> input =
       GetInputDataSourceSurface(IN_CONVOLVE_MATRIX_IN, srcRect,
@@ -2516,23 +2535,26 @@ IntRect FilterNodeConvolveMatrixSoftware::MapRectToSource(
                               aMax, aSourceNode);
 }
 
+MarginDouble FilterNodeConvolveMatrixSoftware::GetInflateSourceMargin() const {
+  double kulX = double(mKernelUnitLength.width);
+  double kulY = double(mKernelUnitLength.height);
+  MarginDouble margin;
+  margin.left = ceil(mTarget.x * kulX);
+  margin.top = ceil(mTarget.y * kulY);
+  margin.right = ceil((mKernelSize.width - mTarget.x - 1) * kulX);
+  margin.bottom = ceil((mKernelSize.height - mTarget.y - 1) * kulY);
+  return margin;
+}
+
 IntRect FilterNodeConvolveMatrixSoftware::InflatedSourceRect(
     const IntRect& aDestRect) {
   if (aDestRect.IsEmpty()) {
     return IntRect();
   }
 
-  IntMargin margin;
-  margin.left = static_cast<int32_t>(ceil(mTarget.x * mKernelUnitLength.width));
-  margin.top = static_cast<int32_t>(ceil(mTarget.y * mKernelUnitLength.height));
-  margin.right = static_cast<int32_t>(
-      ceil((mKernelSize.width - mTarget.x - 1) * mKernelUnitLength.width));
-  margin.bottom = static_cast<int32_t>(
-      ceil((mKernelSize.height - mTarget.y - 1) * mKernelUnitLength.height));
-
-  IntRect srcRect = aDestRect;
-  srcRect.Inflate(margin);
-  return srcRect;
+  RectDouble srcRect(aDestRect);
+  srcRect.Inflate(GetInflateSourceMargin());
+  return RectIsInt32Safe(srcRect) ? TruncatedToInt(srcRect) : aDestRect;
 }
 
 IntRect FilterNodeConvolveMatrixSoftware::InflatedDestRect(
@@ -2541,19 +2563,12 @@ IntRect FilterNodeConvolveMatrixSoftware::InflatedDestRect(
     return IntRect();
   }
 
-  IntMargin margin;
-  margin.left = static_cast<int32_t>(
-      ceil((mKernelSize.width - mTarget.x - 1) * mKernelUnitLength.width));
-  margin.top = static_cast<int32_t>(
-      ceil((mKernelSize.height - mTarget.y - 1) * mKernelUnitLength.height));
-  margin.right =
-      static_cast<int32_t>(ceil(mTarget.x * mKernelUnitLength.width));
-  margin.bottom =
-      static_cast<int32_t>(ceil(mTarget.y * mKernelUnitLength.height));
-
-  IntRect destRect = aSourceRect;
+  RectDouble destRect(aSourceRect);
+  MarginDouble margin = GetInflateSourceMargin();
+  std::swap(margin.left, margin.right);
+  std::swap(margin.top, margin.bottom);
   destRect.Inflate(margin);
-  return destRect;
+  return RectIsInt32Safe(destRect) ? TruncatedToInt(destRect) : aSourceRect;
 }
 
 IntRect FilterNodeConvolveMatrixSoftware::GetOutputRectInRect(
diff --git a/gfx/2d/FilterNodeSoftware.h b/gfx/2d/FilterNodeSoftware.h
index 5dcc0a8c524..280320bd148 100644
--- a/gfx/2d/FilterNodeSoftware.h
+++ b/gfx/2d/FilterNodeSoftware.h
@@ -502,6 +502,7 @@ class FilterNodeConvolveMatrixSoftware : public FilterNodeSoftware {
                                                CoordType aKernelUnitLengthX,
                                                CoordType aKernelUnitLengthY);
 
+  MarginDouble GetInflateSourceMargin() const;
   IntRect InflatedSourceRect(const IntRect& aDestRect);
   IntRect InflatedDestRect(const IntRect& aSourceRect);
 
diff --git a/gfx/2d/Rect.h b/gfx/2d/Rect.h
index e272c5570b9..f9e0f7fe4ec 100644
--- a/gfx/2d/Rect.h
+++ b/gfx/2d/Rect.h
@@ -174,6 +174,11 @@ struct MOZ_EMPTY_BASES IntRectTyped
                                   aRect.Height());
   }
 
+  static IntRectTyped<Units> Truncate(const RectTyped<Units, double>& aRect) {
+    return IntRectTyped(int32_t(aRect.X()), int32_t(aRect.Y()),
+                        int32_t(aRect.Width()), int32_t(aRect.Height()));
+  }
+
   // Rounding isn't meaningful on an integer rectangle.
   void Round() {}
   void RoundIn() {}
@@ -343,10 +348,10 @@ IntRectTyped<Units> RoundedToInt(const RectTyped<Units>& aRect) {
                              int32_t(copy.Width()), int32_t(copy.Height()));
 }
 
-template <class Units>
-bool RectIsInt32Safe(const RectTyped<Units>& aRect) {
-  float min = (float)std::numeric_limits<std::int32_t>::min();
-  float max = (float)std::numeric_limits<std::int32_t>::max();
+template <class Units, class F>
+bool RectIsInt32Safe(const RectTyped<Units, F>& aRect) {
+  F min = (F)std::numeric_limits<int32_t>::min();
+  F max = (F)std::numeric_limits<int32_t>::max();
   return aRect.x > min && aRect.y > min && aRect.width < max &&
          aRect.height < max && aRect.XMost() < max && aRect.YMost() < max;
 }
@@ -361,8 +366,8 @@ IntRectTyped<Units> RoundedOut(const RectTyped<Units>& aRect) {
   return IntRectTyped<Units>::RoundOut(aRect);
 }
 
-template <class Units>
-IntRectTyped<Units> TruncatedToInt(const RectTyped<Units>& aRect) {
+template <class Units, class F>
+IntRectTyped<Units> TruncatedToInt(const RectTyped<Units, F>& aRect) {
   return IntRectTyped<Units>::Truncate(aRect);
 }
 
Loading diff…