Chrome · Skia › Skif
CVE-2026-118340
Memory Corruption in Skia (Skif)
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
DEF_TESTtests/ImageFilterCacheTest.cpp |
modified |
Files Changed
tests/ImageFilterCacheTest.cppsrc/core/SkImageFilter_Base.hsrc/core/SkImageFilterCache.hsrc/core/SkImageFilter.cpp
Patch
From 87002299235987ace504133d8ed6570f3cb87bc2 Mon Sep 17 00:00:00 2001
From: alexis cruz-ayala <alexisdavidc@google.com>
Date: Mon, 19 Oct 2026 18:31:46 -0400
Subject: [PATCH] [skif] Update SkImageFilter_Base uniqueID to 64-bit
SkImageFilter_Base previously assigned each filter a 32-bit uniqueID
from an atomic counter that could wrap after 2^32 constructions and
cause cache collisions in SkImageFilterCache.
Update SkImageFilter_Base::fUniqueID and SkImageFilterCacheKey::fUniqueID to uint64_t, with an explicit zero-initialized padding field in SkImageFilterCacheKey to maintain tight packing for hashing.
Bug: b/568936530
Change-Id: If1b172c41e582bccb3c2323e54bbce43e0fd2671
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1398516
Reviewed-by: Julia Lavrova <jlavrova@google.com>
Commit-Queue: Alexis Cruz-Ayala <alexisdavidc@google.com>
Auto-Submit: Alexis Cruz-Ayala <alexisdavidc@google.com>
---
diff --git a/src/core/SkImageFilter.cpp b/src/core/SkImageFilter.cpp
index 35b9768..fc2cb43 100644
--- a/src/core/SkImageFilter.cpp
+++ b/src/core/SkImageFilter.cpp
@@ -135,10 +135,10 @@
// SkImageFilter_Base
///////////////////////////////////////////////////////////////////////////////////////////////////
-static int32_t next_image_filter_unique_id() {
- static std::atomic<int32_t> nextID{1};
+static uint64_t next_image_filter_unique_id() {
+ static std::atomic<uint64_t> nextID{1};
- int32_t id;
+ uint64_t id;
do {
id = nextID.fetch_add(1, std::memory_order_relaxed);
} while (id == 0);
diff --git a/src/core/SkImageFilterCache.h b/src/core/SkImageFilterCache.h
index a2909e0..92f40eb 100644
--- a/src/core/SkImageFilterCache.h
+++ b/src/core/SkImageFilterCache.h
@@ -21,7 +21,7 @@
namespace skif { class FilterResult; }
struct SkImageFilterCacheKey {
- SkImageFilterCacheKey(const uint32_t uniqueID, const SkMatrix& matrix,
+ SkImageFilterCacheKey(const uint64_t uniqueID, const SkMatrix& matrix,
const SkIRect& clipBounds, uint32_t srcGenID, const SkIRect& srcSubset)
: fUniqueID(uniqueID)
, fMatrix(matrix)
@@ -29,18 +29,20 @@
, fSrcGenID(srcGenID)
, fSrcSubset(srcSubset) {
// Assert that Key is tightly-packed, since it is hashed.
- static_assert(sizeof(SkImageFilterCacheKey) == sizeof(uint32_t) + sizeof(SkMatrix) +
- sizeof(SkIRect) + sizeof(uint32_t) + 4 * sizeof(int32_t),
+ static_assert(sizeof(SkImageFilterCacheKey) == sizeof(uint64_t) + sizeof(SkMatrix) +
+ sizeof(SkIRect) + sizeof(uint32_t) + 4 * sizeof(int32_t) +
+ sizeof(uint32_t),
"image_filter_key_tight_packing");
fMatrix.getType(); // force initialization of type, so hashes match
SkASSERT(fMatrix.isFinite()); // otherwise we can't rely on == self when comparing keys
}
- uint32_t fUniqueID;
+ uint64_t fUniqueID;
SkMatrix fMatrix;
SkIRect fClipBounds;
uint32_t fSrcGenID;
SkIRect fSrcSubset;
+ uint32_t fPad = 0;
bool operator==(const SkImageFilterCacheKey& other) const {
return fUniqueID == other.fUniqueID &&
diff --git a/src/core/SkImageFilter_Base.h b/src/core/SkImageFilter_Base.h
index 8beea3d..9b3d461 100644
--- a/src/core/SkImageFilter_Base.h
+++ b/src/core/SkImageFilter_Base.h
@@ -127,7 +127,7 @@
using MatrixCapability = skif::MatrixCapability;
MatrixCapability getCTMCapability() const;
- uint32_t uniqueID() const { return fUniqueID; }
+ uint64_t uniqueID() const { return fUniqueID; }
static SkFlattenable::Type GetFlattenableType() {
return kSkImageFilter_Type;
@@ -302,7 +302,7 @@
skia_private::AutoSTArray<2, sk_sp<SkImageFilter>> fInputs;
bool fUsesSrcInput;
- uint32_t fUniqueID; // Globally unique
+ uint64_t fUniqueID; // Globally unique
using INHERITED = SkImageFilter;
};
diff --git a/tests/ImageFilterCacheTest.cpp b/tests/ImageFilterCacheTest.cpp
index fa72b9c..3d7080f 100644
--- a/tests/ImageFilterCacheTest.cpp
+++ b/tests/ImageFilterCacheTest.cpp
@@ -47,6 +47,7 @@
#endif
#include <cstddef>
+#include <cstdint>
#include <tuple>
#include <utility>
@@ -109,10 +110,12 @@
SkIRect clip2 = SkIRect::MakeWH(200, 200);
SkImageFilterCacheKey key0(0, SkMatrix::I(), clip1, image->uniqueID(), image->subset());
SkImageFilterCacheKey key1(1, SkMatrix::I(), clip1, image->uniqueID(), image->subset());
- SkImageFilterCacheKey key2(0, SkMatrix::Translate(5, 5), clip1,
- image->uniqueID(), image->subset());
- SkImageFilterCacheKey key3(0, SkMatrix::I(), clip2, image->uniqueID(), image->subset());
- SkImageFilterCacheKey key4(0, SkMatrix::I(), clip1, subset->uniqueID(), subset->subset());
+ SkImageFilterCacheKey key2(1ULL << 32, SkMatrix::I(), clip1,
+ image->uniqueID(), image->subset());
+ SkImageFilterCacheKey key3(0, SkMatrix::Translate(5, 5), clip1,
+ image->uniqueID(), image->subset());
+ SkImageFilterCacheKey key4(0, SkMatrix::I(), clip2, image->uniqueID(), image->subset());
+ SkImageFilterCacheKey key5(0, SkMatrix::I(), clip1, subset->uniqueID(), subset->subset());
SkIPoint offset = SkIPoint::Make(3, 4);
auto filter = make_filter();
@@ -123,6 +126,7 @@
REPORTER_ASSERT(reporter, !cache->get(key2, &foundImage));
REPORTER_ASSERT(reporter, !cache->get(key3, &foundImage));
REPORTER_ASSERT(reporter, !cache->get(key4, &foundImage));
+ REPORTER_ASSERT(reporter, !cache->get(key5, &foundImage));
}
// Test purging when the max cache size is exceeded
@@ -362,3 +366,45 @@
REPORTER_ASSERT(r, cache);
}
}
+
+DEF_TEST(ImageFilterCache_64BitUniqueID, reporter) {
+ static const size_t kCacheSize = 1000000;
+ sk_sp<SkImageFilterCache> cache(SkImageFilterCache::Create(kCacheSize));
+
+ SkBitmap bm = create_bm();
+ const SkIRect full = SkIRect::MakeWH(kFullSize, kFullSize);
+ sk_sp<SkSpecialImage> img1(SkSpecialImages::MakeFromRaster(full, bm, SkSurfaceProps()));
+ sk_sp<SkSpecialImage> img2(SkSpecialImages::MakeFromRaster(full, bm, SkSurfaceProps()));
+
+ constexpr uint64_t kBaseId = 42;
+ constexpr uint64_t kUpperBitDiffId = kBaseId + (1ULL << 32);
+
+ SkIRect clip = SkIRect::MakeWH(100, 100);
+ SkImageFilterCacheKey key1(kBaseId, SkMatrix::I(), clip, img1->uniqueID(), img1->subset());
+ SkImageFilterCacheKey key2(
+ kUpperBitDiffId, SkMatrix::I(), clip, img1->uniqueID(), img1->subset());
+
+ REPORTER_ASSERT(reporter, key1 == key1);
+ REPORTER_ASSERT(reporter, key2 == key2);
+ REPORTER_ASSERT(reporter, !(key1 == key2));
+
+ auto filter1 = make_filter();
+ auto filter2 = make_filter();
+ SkIPoint offset1 = SkIPoint::Make(1, 2);
+ SkIPoint offset2 = SkIPoint::Make(5, 6);
+
+ cache->set(key1, filter1.get(), skif::FilterResult(img1, skif::LayerSpace<SkIPoint>(offset1)));
+
+ skif::FilterResult found;
+ REPORTER_ASSERT(reporter, cache->get(key1, &found));
+ REPORTER_ASSERT(reporter, found.image() == img1.get());
+ REPORTER_ASSERT(reporter, !cache->get(key2, &found));
+
+ cache->set(key2, filter2.get(), skif::FilterResult(img2, skif::LayerSpace<SkIPoint>(offset2)));
+ SkDEBUGCODE(REPORTER_ASSERT(reporter, 2 == cache->count());)
+
+ REPORTER_ASSERT(reporter, cache->get(key1, &found));
+ REPORTER_ASSERT(reporter, found.image() == img1.get());
+ REPORTER_ASSERT(reporter, cache->get(key2, &found));
+ REPORTER_ASSERT(reporter, found.image() == img2.get());
+}
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page