CVE-2026-6364
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsrc/core/SkCanvasPriv.cpp |
modified |
Files Changed
src/core/SkCanvasPriv.cpp
Patch
From 3d08a21145b080f644d6b6a621ae33a452d27a35 Mon Sep 17 00:00:00 2001
From: Michael Ludwig <michaelludwig@google.com>
Date: Mon, 13 Apr 2026 14:30:46 -0400
Subject: [PATCH] [skp] Validate image lattice counts
Bug: b/502103414
Change-Id: Id13b2dfd30830c238d19914f3231f76383cee80b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1208956
Reviewed-by: Kaylee Lubick <kjlubick@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Auto-Submit: Michael Ludwig <michaelludwig@google.com>
---
diff --git a/src/core/SkCanvasPriv.cpp b/src/core/SkCanvasPriv.cpp
index 5eaa7dd..bfd4d4e 100644
--- a/src/core/SkCanvasPriv.cpp
+++ b/src/core/SkCanvasPriv.cpp
@@ -25,6 +25,7 @@
#include "src/core/SkWriteBuffer.h"
#include "src/core/SkWriter32.h"
+#include <limits>
#include <utility>
#include <cstdint>
@@ -55,13 +56,25 @@
bool SkCanvasPriv::ReadLattice(SkReadBuffer& buffer, SkCanvas::Lattice* lattice) {
lattice->fXCount = buffer.readInt();
+ if (lattice->fXCount < 0) {
+ return false;
+ }
lattice->fXDivs = buffer.skipT<int32_t>(lattice->fXCount);
lattice->fYCount = buffer.readInt();
+ if (lattice->fYCount < 0) {
+ return false;
+ }
lattice->fYDivs = buffer.skipT<int32_t>(lattice->fYCount);
int flagCount = buffer.readInt();
lattice->fRectTypes = nullptr;
lattice->fColors = nullptr;
+ // flagCount was serialized as (xCount + 1) * (yCount + 1) if there were rect types and colors,
+ // so if that isn't still the case the buffer is invalid.
if (flagCount) {
+ if (flagCount < 0 || (uint64_t) flagCount != ((uint64_t) lattice->fXCount + 1) *
+ ((uint64_t) lattice->fYCount + 1)) {
+ return false;
+ }
lattice->fRectTypes = buffer.skipT<SkCanvas::Lattice::RectType>(flagCount);
lattice->fColors = buffer.skipT<SkColor>(flagCount);
}
Original Bug Report
Skia heap oob read in SkRecordCanvas::onDrawImageLattice2
VULNERABILITY DETAILS
There is a heap oob read in SkRecordCanvas::onDrawImageLattice2 during the parsing of SKP files containing DRAW_IMAGE_LATTICE or DRAW_IMAGE_LATTICE2 operations. This vulnerability can be triggered via a malformed SKP file.
The issue stems from a mismatch in how SkCanvasPriv::ReadLattice validates the lattice data versus how SkRecordCanvas::onDrawImageLattice2 copies it.
When parsing a lattice operation, SkCanvasPriv::ReadLattice reads flagCount from the buffer and skips that many elements to set lattice->fRectTypes and lattice->fColors:
// In src/core/SkCanvasPriv.cpp
bool SkCanvasPriv::ReadLattice(SkReadBuffer& buffer, SkCanvas::Lattice* lattice) {
lattice->fXCount = buffer.readInt();
lattice->fXDivs = buffer.skipT<int32_t>(lattice->fXCount);
lattice->fYCount = buffer.readInt();
lattice->fYDivs = buffer.skipT<int32_t>(lattice->fYCount);
int flagCount = buffer.readInt();
lattice->fRectTypes = nullptr;
lattice->fColors = nullptr;
if (flagCount) {
lattice->fRectTypes = buffer.skipT<SkCanvas::Lattice::RectType>(flagCount);
lattice->fColors = buffer.skipT<SkColor>(flagCount);
}
lattice->fBounds = buffer.skipT<SkIRect>();
return buffer.isValid();
}
However, it fails to validate that flagCount is equal to (lattice->fXCount + 1) * (lattice->fYCount + 1). An attacker can provide large values for fXCount and fYCount but a small value for flagCount (e.g., 1).
Next, in SkRecordCanvas::onDrawImageLattice2, the code assumes that the number of elements in fRectTypes and fColors is strictly defined by the grid dimensions:
// In src/core/SkRecordCanvas.cpp
void SkRecordCanvas::onDrawImageLattice2(...) {
int flagCount = lattice.fRectTypes ? (lattice.fXCount + 1) * (lattice.fYCount + 1) : 0;
SkASSERT(lattice.fBounds);
this->append<SkRecords::DrawImageLattice>(this->copy(paint),
sk_ref_sp(image),
lattice.fXCount,
this->copy(lattice.fXDivs, lattice.fXCount),
lattice.fYCount,
this->copy(lattice.fYDivs, lattice.fYCount),
flagCount,
this->copy(lattice.fRectTypes, flagCount),
this->copy(lattice.fColors, flagCount),
*lattice.fBounds, ...);
flagCount is recalculated here as (lattice.fXCount + 1) * (lattice.fYCount + 1).
The this->copy() method then reads flagCount elements from lattice.fColors (which only has 1 element allocated in the SkReadBuffer, so an oob read is triggered).
VERSION
Skia Version: 0cb35533022b3912904831c2290680bb8eea0363
REPRODUCTION CASE
- Check out Skia at commit 0cb35533022b3912904831c2290680bb8eea0363
- Build ASan version of skpbench according to instructions
- Generate a proof of concept skp file:
> python3 gen_poc.py poc.skp
- Feed the skp file to
skpbenchand observe an ASan crash:
> ASAN_SYMBOLIZER_PATH=$HOME/clang/bin/llvm-symbolizer ASAN_OPTIONS=symbolize=1 ./out/asan/skpbench --src poc.skp --config gles
accum median max min stddev samples sample_ms clock metric config bench
=================================================================
==39469==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x51300000116c at pc 0x55cd9fb64c1e bp 0x7fffb906a0e0 sp 0x7fffb906a0d8
READ of size 1 at 0x51300000116c thread T0
error: failed to decompress '.debug_aranges', LLVM was not built with LLVM_ENABLE_ZLIB or did not find zlib at build time
error: failed to decompress '.debug_info', LLVM was not built with LLVM_ENABLE_ZLIB or did not find zlib at build time
error: failed to decompress '.debug_abbrev', LLVM was not built with LLVM_ENABLE_ZLIB or did not find zlib at build time
error: failed to decompress '.debug_line', LLVM was not built with LLVM_ENABLE_ZLIB or did not find zlib at build time
error: failed to decompress '.debug_str', LLVM was not built with LLVM_ENABLE_ZLIB or did not find zlib at build time
error: failed to decompress '.debug_line_str', LLVM was not built with LLVM_ENABLE_ZLIB or did not find zlib at build time
error: failed to decompress '.debug_loclists', LLVM was not built with LLVM_ENABLE_ZLIB or did not find zlib at build time
error: failed to decompress '.debug_rnglists', LLVM was not built with LLVM_ENABLE_ZLIB or did not find zlib at build time
#0 0x55cd9fb64c1d in SkCanvas::Lattice::RectType* SkRecordCanvas::copy<SkCanvas::Lattice::RectType>(SkCanvas::Lattice::RectType const*, unsigned long) /usr/local/google/home/bsevens/repos/skia/out/asan/../../src/core/SkRecordCanvas.cpp:144:25
#1 0x55cd9fb5873a in SkRecordCanvas::onDrawImageLattice2(SkImage const*, SkCanvas::Lattice const&, SkRect const&, SkFilterMode, SkPaint const*) /usr/local/google/home/bsevens/repos/skia/out/asan/../../src/core/SkRecordCanvas.cpp:254:53
#2 0x55cd9ebaf3fe in SkCanvas::drawImageLattice(SkImage const*, SkCanvas::Lattice const&, SkRect const&, SkFilterMode, SkPaint const*) /usr/local/google/home/bsevens/repos/skia/out/asan/../../src/core/SkCanvas.cpp:1829:15
#3 0x55cd9f60c574 in SkPicturePlayback::handleOp(SkReadBuffer*, DrawType, unsigned int, SkCanvas*, SkM44 const&) /usr/local/google/home/bsevens/repos/skia/out/asan/../../src/core/SkPicturePlayback.cpp:471:21
#4 0x55cd9f60553e in SkPicturePlayback::draw(SkCanvas*, SkPicture::AbortCallback*, SkReadBuffer*) /usr/local/google/home/bsevens/repos/skia/out/asan/../../src/core/SkPicturePlayback.cpp:96:15
#5 0x55cd9f5cd43e in SkPicture::Forwardport(SkPictInfo const&, SkPictureData const*, SkReadBuffer*) /usr/local/google/home/bsevens/repos/skia/out/asan/../../src/core/SkPicture.cpp:141:14
#6 0x55cd9f5cda21 in SkPicture::MakeFromStreamPriv(SkStream*, SkDeserialProcs const*, SkTypefacePlayback*, int) /usr/local/google/home/bsevens/repos/skia/out/asan/../../src/core/SkPicture.cpp:190:20
#7 0x55cd9f5cd539 in SkPicture::MakeFromStream(SkStream*, SkDeserialProcs const*) /usr/local/google/home/bsevens/repos/skia/out/asan/../../src/core/SkPicture.cpp:148:12
#8 0x55cd9e5a611f in main /usr/local/google/home/bsevens/repos/skia/out/asan/../../tools/skpbench/skpbench.cpp:561:19
#9 0x7f1672229f74 (/usr/lib/x86_64-linux-gnu/libc.so.6+0x29f74) (BuildId: f13b8a5d5cd39727792736394456f00d222c38c4)
#10 0x7f167222a026 in __libc_start_main (/usr/lib/x86_64-linux-gnu/libc.so.6+0x2a026) (BuildId: f13b8a5d5cd39727792736394456f00d222c38c4)
#11 0x55cd9e4abf10 in _start (/usr/local/google/home/bsevens/repos/skia/out/asan/skpbench+0x4432f10)
0x51300000116c is located 0 bytes after 364-byte region [0x513000001000,0x51300000116c)
allocated by thread T0 here:
/usr/local/google/home/bsevens/clang/bin/llvm-symbolizer: error: '[stack]': No such file or directory
#0 0x55cd9e58237d in operator new(unsigned long) /tmp/clang/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:86:3
#1 0x55cd9edf9c00 in SkData::PrivateNewWithCopy(void const*, unsigned long) /usr/local/google/home/bsevens/repos/skia/out/asan/../../src/core/SkData.cpp:114:21
#2 0x55cd9edfa0db in SkData::MakeUninitialized(unsigned long) /usr/local/google/home/bsevens/repos/skia/out/asan/../../src/core/SkData.cpp:147:12
#3 0x55cd9edfa92a in SkData::MakeFromStream(SkStream*, unsigned long) /usr/local/google/home/bsevens/repos/skia/out/asan/../../src/core/SkData.cpp:217:24
#4 0x55cd9f5e10d5 in SkPictureData::parseStreamTag(SkStream*, unsigned int, unsigned int, SkDeserialProcs const&, SkTypefacePlayback*, int) /usr/local/google/home/bsevens/repos/skia/out/asan/../../src/core/SkPictureData.cpp:315:23
#5 0x55cd9f5e4287 in SkPictureData::parseStream(SkStream*, SkDeserialProcs const&, SkTypefacePlayback*, int) /usr/local/google/home/bsevens/repos/skia/out/asan/../../src/core/SkPictureData.cpp:560:20
#6 0x55cd9f5e3dd0 in SkPictureData::CreateFromStream(SkStream*, SkPictInfo const&, SkDeserialProcs const&, SkTypefacePlayback*, int) /usr/local/google/home/bsevens/repos/skia/out/asan/../../src/core/SkPictureData.cpp:530:16
#7 0x55cd9f5cd9ec in SkPicture::MakeFromStreamPriv(SkStream*, SkDeserialProcs const*, SkTypefacePlayback*, int) /usr/local/google/home/bsevens/repos/skia/out/asan/../../src/core/SkPicture.cpp:188:21
#8 0x55cd9f5cd539 in SkPicture::MakeFromStream(SkStream*, SkDeserialProcs const*) /usr/local/google/home/bsevens/repos/skia/out/asan/../../src/core/SkPicture.cpp:148:12
#9 0x55cd9e5a611f in main /usr/local/google/home/bsevens/repos/skia/out/asan/../../tools/skpbench/skpbench.cpp:561:19
#10 0x7f1672229f74 (/usr/lib/x86_64-linux-gnu/libc.so.6+0x29f74) (BuildId: f13b8a5d5cd39727792736394456f00d222c38c4)
#11 0x7fffb906db0f ([stack]+0x20b0f)
SUMMARY: AddressSanitizer: heap-buffer-overflow /usr/local/google/home/bsevens/repos/skia/out/asan/../../src/core/SkRecordCanvas.cpp:144:25 in SkCanvas::Lattice::RectType* SkRecordCanvas::copy<SkCanvas::Lattice::RectType>(SkCanvas::Lattice::RectType const*, unsigned long)
Shadow bytes around the buggy address:
0x513000000e80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x513000000f00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x513000000f80: fd fd fd fd fa fa fa fa fa fa fa fa fa fa fa fa
0x513000001000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x513000001080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x513000001100: 00 00 00 00 00 00 00 00 00 00 00 00 00[04]fa fa
0x513000001180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x513000001200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x513000001280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x513000001300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x513000001380: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==39469==ABORTING
CREDIT INFORMATION
Reporter credit: Google Threat Analysis Group