Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read in Skia
DescriptionOut of bounds read in Skia
ComponentSkia
Bug ClassOOB
Tracker502103414
Fix commit3d08a21145b0 (skia) +13/-0
CISA KEVNot listed
CreditedGoogle Threat Intelligence
Disclosed2026-04-15

Changed Functions

FunctionChangeNotes
if
src/core/SkCanvasPriv.cpp
modified

Files Changed

  • src/core/SkCanvasPriv.cpp
From 3d08a21145b080f644d6b6a621ae33a452d27a35 Mon Sep 17 00:00:00 2001
From: Michael Ludwig <[email protected]>
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 <[email protected]>
Commit-Queue: Michael Ludwig <[email protected]>
Auto-Submit: Michael Ludwig <[email protected]>
---

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);
     }
Loading diff…

Original Bug Report

reported by [email protected]

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 skpbench and 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

View on issue tracker