High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionInteger overflow in the Graphics component
ComponentGraphics
Bug ClassLogic Error
Tracker2074896
Fix commitcaff59c64b2e (firefox) +100/-7
CISA KEVNot listed
CreditedRobert Longson
Disclosed2026-10-13

Changed Functions

FunctionChangeNotes
for
gfx/2d/FilterNodeSoftware.cpp
modified
TileTestInput
gfx/tests/gtest/TestFilterNodeSoftware.cpp
modified
if
gfx/tests/gtest/TestFilterNodeSoftware.cpp
modified
TileTestNode
gfx/tests/gtest/TestFilterNodeSoftware.cpp
modified
for
gfx/tests/gtest/TestFilterNodeSoftware.cpp
modified

Files Changed

  • gfx/2d/FilterNodeSoftware.cpp
  • gfx/tests/gtest/TestFilterNodeSoftware.cpp
  • gfx/tests/gtest/moz.build
diff --git a/gfx/2d/FilterNodeSoftware.cpp b/gfx/2d/FilterNodeSoftware.cpp
index 730fc34f8f0..38ec454dd6c 100644
--- a/gfx/2d/FilterNodeSoftware.cpp
+++ b/gfx/2d/FilterNodeSoftware.cpp
@@ -1633,9 +1633,7 @@ int32_t FilterNodeTileSoftware::InputIndex(uint32_t aInputEnumIndex) {
 void FilterNodeTileSoftware::SetAttribute(uint32_t aIndex,
                                           const IntRect& aSourceRect) {
   MOZ_ASSERT(aIndex == ATT_TILE_SOURCE_RECT);
-  mSourceRect.SetRect(int32_t(aSourceRect.X()), int32_t(aSourceRect.Y()),
-                      int32_t(aSourceRect.Width()),
-                      int32_t(aSourceRect.Height()));
+  mSourceRect = aSourceRect;
   Invalidate();
 }
 
@@ -1673,14 +1671,24 @@ already_AddRefed<DataSourceSurface> FilterNodeTileSoftware::Render(
       InputMap;
   InputMap inputs;
 
+  auto IntRectToInt64Rect = [](const IntRect& aRect) {
+    return RectTyped<UnknownUnits, int64_t>(aRect.X(), aRect.Y(), aRect.Width(),
+                                            aRect.Height());
+  };
+
   IntPoint startIndex = TileIndex(mSourceRect, aRect.TopLeft());
   IntPoint endIndex = TileIndex(mSourceRect, aRect.BottomRight());
   for (int32_t ix = startIndex.x; ix <= endIndex.x; ix++) {
     for (int32_t iy = startIndex.y; iy <= endIndex.y; iy++) {
-      IntPoint sourceToDestOffset(ix * mSourceRect.Width(),
-                                  iy * mSourceRect.Height());
-      IntRect destRect = aRect.Intersect(mSourceRect + sourceToDestOffset);
-      IntRect srcRect = destRect - sourceToDestOffset;
+      auto sourceToDestOffset = PointTyped<UnknownUnits, int64_t>(
+          ix * mSourceRect.Width(), iy * mSourceRect.Height());
+      auto destRect64 = IntRectToInt64Rect(aRect).Intersect(
+          IntRectToInt64Rect(mSourceRect) + sourceToDestOffset);
+      auto srcRect64 = destRect64 - sourceToDestOffset;
+      IntRect destRect, srcRect;
+      if (!destRect64.ToIntRect(&destRect) || !srcRect64.ToIntRect(&srcRect)) {
+        return nullptr;
+      }
       if (srcRect.IsEmpty()) {
         continue;
       }
diff --git a/gfx/tests/gtest/TestFilterNodeSoftware.cpp b/gfx/tests/gtest/TestFilterNodeSoftware.cpp
new file mode 100644
index 00000000000..ce336c43e6c
--- /dev/null
+++ b/gfx/tests/gtest/TestFilterNodeSoftware.cpp
@@ -0,0 +1,84 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <cstring>
+#include <iterator>
+#include <limits>
+#include <vector>
+
+#include "2D.h"
+#include "FilterNodeSoftware.h"
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+namespace {
+
+class TileTestInput final : public FilterNodeSoftware {
+ public:
+  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(TileTestInput, override)
+
+  std::vector<IntRect> mRects;
+
+ protected:
+  IntRect GetOutputRectInRect(const IntRect& aRect) override { return aRect; }
+
+  already_AddRefed<DataSourceSurface> GetOutput(const IntRect& aRect) override {
+    return Render(aRect);
+  }
+
+  already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override {
+    mRects.push_back(aRect);
+    RefPtr<DataSourceSurface> surface =
+        Factory::CreateDataSourceSurface(aRect.Size(), SurfaceFormat::A8, true);
+    if (!surface) {
+      return nullptr;
+    }
+    DataSourceSurface::ScopedMap map(surface, DataSourceSurface::WRITE);
+    if (!map.IsMapped()) {
+      return nullptr;
+    }
+    for (int32_t y = 0; y < aRect.Height(); ++y) {
+      std::memset(map.GetData() + y * map.GetStride(), 0x7f, aRect.Width());
+    }
+    return surface.forget();
+  }
+};
+
+class TileTestNode final : public FilterNodeTileSoftware {
+ public:
+  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(TileTestNode, override)
+  using FilterNodeTileSoftware::Render;
+};
+
+}  // namespace
+
+TEST(Moz2D, TileAcrossCoordinateLimits)
+{
+  constexpr int32_t extent = std::numeric_limits<int32_t>::max() - 1;
+  constexpr int32_t last = extent - 6;
+  RefPtr<TileTestInput> input = MakeRefPtr<TileTestInput>();
+  RefPtr<TileTestNode> tile = MakeRefPtr<TileTestNode>();
+  tile->SetInput(IN_TILE_IN, input.get());
+  tile->SetAttribute(ATT_TILE_SOURCE_RECT, IntRect(-4, -4, extent, extent));
+
+  RefPtr<DataSourceSurface> output = tile->Render(IntRect(-6, -6, 4, 4));
+  ASSERT_TRUE(output);
+  const IntRect expected[] = {IntRect(last, last, 2, 2),
+                              IntRect(last, -4, 2, 2), IntRect(-4, last, 2, 2),
+                              IntRect(-4, -4, 2, 2)};
+  ASSERT_EQ(input->mRects.size(), std::size(expected));
+  for (size_t i = 0; i < std::size(expected); ++i) {
+    EXPECT_EQ(input->mRects[i], expected[i]);
+  }
+
+  DataSourceSurface::ScopedMap map(output, DataSourceSurface::READ);
+  ASSERT_TRUE(map.IsMapped());
+  for (int32_t y = 0; y < 4; ++y) {
+    for (int32_t x = 0; x < 4; ++x) {
+      EXPECT_EQ(map.GetData()[y * map.GetStride() + x], 0x7f);
+    }
+  }
+}
diff --git a/gfx/tests/gtest/moz.build b/gfx/tests/gtest/moz.build
index f5dac2b29b3..458b9eed04f 100644
--- a/gfx/tests/gtest/moz.build
+++ b/gfx/tests/gtest/moz.build
@@ -14,6 +14,7 @@ UNIFIED_SOURCES += [
     "TestConvolutionFilter.cpp",
     "TestCoord.cpp",
     "TestCopyPlane.cpp",
+    "TestFilterNodeSoftware.cpp",
     "TestFontFallback.cpp",
     "TestFontOverlapPrevention.cpp",
     "TestGfxWidgets.cpp",
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/gfx/tests/gtest/TestFilterNodeSoftware.cpp b/gfx/tests/gtest/TestFilterNodeSoftware.cpp
new file mode 100644
index 00000000000..ce336c43e6c
--- /dev/null
+++ b/gfx/tests/gtest/TestFilterNodeSoftware.cpp
@@ -0,0 +1,84 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <cstring>
+#include <iterator>
+#include <limits>
+#include <vector>
+
+#include "2D.h"
+#include "FilterNodeSoftware.h"
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+namespace {
+
+class TileTestInput final : public FilterNodeSoftware {
+ public:
+  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(TileTestInput, override)
+
+  std::vector<IntRect> mRects;
+
+ protected:
+  IntRect GetOutputRectInRect(const IntRect& aRect) override { return aRect; }
+
+  already_AddRefed<DataSourceSurface> GetOutput(const IntRect& aRect) override {
+    return Render(aRect);
+  }
+
+  already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override {
+    mRects.push_back(aRect);
+    RefPtr<DataSourceSurface> surface =
+        Factory::CreateDataSourceSurface(aRect.Size(), SurfaceFormat::A8, true);
+    if (!surface) {
+      return nullptr;
+    }
+    DataSourceSurface::ScopedMap map(surface, DataSourceSurface::WRITE);
+    if (!map.IsMapped()) {
+      return nullptr;
+    }
+    for (int32_t y = 0; y < aRect.Height(); ++y) {
+      std::memset(map.GetData() + y * map.GetStride(), 0x7f, aRect.Width());
+    }
+    return surface.forget();
+  }
+};
+
+class TileTestNode final : public FilterNodeTileSoftware {
+ public:
+  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(TileTestNode, override)
+  using FilterNodeTileSoftware::Render;
+};
+
+}  // namespace
+
+TEST(Moz2D, TileAcrossCoordinateLimits)
+{
+  constexpr int32_t extent = std::numeric_limits<int32_t>::max() - 1;
+  constexpr int32_t last = extent - 6;
+  RefPtr<TileTestInput> input = MakeRefPtr<TileTestInput>();
+  RefPtr<TileTestNode> tile = MakeRefPtr<TileTestNode>();
+  tile->SetInput(IN_TILE_IN, input.get());
+  tile->SetAttribute(ATT_TILE_SOURCE_RECT, IntRect(-4, -4, extent, extent));
+
+  RefPtr<DataSourceSurface> output = tile->Render(IntRect(-6, -6, 4, 4));
+  ASSERT_TRUE(output);
+  const IntRect expected[] = {IntRect(last, last, 2, 2),
+                              IntRect(last, -4, 2, 2), IntRect(-4, last, 2, 2),
+                              IntRect(-4, -4, 2, 2)};
+  ASSERT_EQ(input->mRects.size(), std::size(expected));
+  for (size_t i = 0; i < std::size(expected); ++i) {
+    EXPECT_EQ(input->mRects[i], expected[i]);
+  }
+
+  DataSourceSurface::ScopedMap map(output, DataSourceSurface::READ);
+  ASSERT_TRUE(map.IsMapped());
+  for (int32_t y = 0; y < 4; ++y) {
+    for (int32_t x = 0; x < 4; ++x) {
+      EXPECT_EQ(map.GetData()[y * map.GetStride() + x], 0x7f);
+    }
+  }
+}
diff --git a/gfx/tests/gtest/moz.build b/gfx/tests/gtest/moz.build
index f5dac2b29b3..458b9eed04f 100644
--- a/gfx/tests/gtest/moz.build
+++ b/gfx/tests/gtest/moz.build
@@ -14,6 +14,7 @@ UNIFIED_SOURCES += [
     "TestConvolutionFilter.cpp",
     "TestCoord.cpp",
     "TestCopyPlane.cpp",
+    "TestFilterNodeSoftware.cpp",
     "TestFontFallback.cpp",
     "TestFontOverlapPrevention.cpp",
     "TestGfxWidgets.cpp",
Loading diff…