Overview

Medium
Severity
β€”
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactHeap buffer overflow in Skia
DescriptionHeap buffer overflow in Skia
ComponentSkia
Bug ClassOOB
Tracker417599694
Fix commit40dbd0b8cb41 (chromium/src) +19/-4
CISA KEVNot listed
CreditedHuinian Yang (@vmth6) of Amber Security Lab, OPPO Mobile Telecommunications Corp. Ltd.
Disclosed2026-03-10

Files Changed

  • skia/ext/image_operations.cc
From 40dbd0b8cb414e2ebd654421e25fd20263c306ba Mon Sep 17 00:00:00 2001
From: Kaylee Lubick <kjlubick@google.com>
Date: Wed, 21 Jan 2026 13:41:03 -0800
Subject: [PATCH] Add defensive checks for overflow in image resizing

The attached bug has a stacktrace that appears to be the result
of an integer overflow that happened in a full build of chromium,
not just a fuzzer.

This adds some checks that speculatively will protect against
that overflow from getting into the convolver.

Bug: b:417599694
Change-Id: I765317b1a6819c9bc68a73d4b83133ee85cdc036
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7506469
Reviewed-by: Florin Malita <fmalita@chromium.org>
Auto-Submit: Kaylee Lubick <kjlubick@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1572546}
---

diff --git a/skia/ext/image_operations.cc b/skia/ext/image_operations.cc
index b833dbd..53ffb218 100644
--- a/skia/ext/image_operations.cc
+++ b/skia/ext/image_operations.cc
@@ -343,9 +343,10 @@
 
   // If the size of source or destination is 0, i.e. 0x0, 0xN or Nx0, just
   // return empty.
-  if (source.width() < 1 || source.height() < 1 ||
-      dest_width < 1 || dest_height < 1)
+  if (source.width() < 1 || source.height() < 1 || dest_width < 1 ||
+      dest_height < 1) {
     return SkBitmap();
+  }
 
   SkIRect dest = {0, 0, dest_width, dest_height};
   DCHECK(dest.contains(dest_subset))
@@ -356,8 +357,21 @@
   DCHECK((ImageOperations::RESIZE_FIRST_ALGORITHM_METHOD <= method) &&
          (method <= ImageOperations::RESIZE_LAST_ALGORITHM_METHOD));
 
-  if (!source.addr() || source.colorType() != kN32_SkColorType)
+  if (!source.addr() || source.colorType() != kN32_SkColorType) {
     return SkBitmap();
+  }
+
+  // Avoid overflows in the convolver
+  if (source.rowBytes() >
+      static_cast<size_t>(std::numeric_limits<int>::max())) {
+    return SkBitmap();
+  }
+
+  if (static_cast<int64_t>(source.height()) *
+          static_cast<int64_t>(source.rowBytes()) >
+      static_cast<int64_t>(std::numeric_limits<int>::max())) {
+    return SkBitmap();
+  }
 
   ResizeFilter filter(method, source.width(), source.height(),
                       dest_width, dest_height, dest_subset);
@@ -372,8 +386,9 @@
   SkBitmap result;
   result.setInfo(
       source.info().makeWH(dest_subset.width(), dest_subset.height()));
-  if (!result.tryAllocPixels(allocator) || !result.readyToDraw())
+  if (!result.tryAllocPixels(allocator) || !result.readyToDraw()) {
     return SkBitmap();
+  }
 
   BGRAConvolve2D(source_subset, static_cast<int>(source.rowBytes()),
                  !source.isOpaque(), filter.x_filter(), filter.y_filter(),
Loading diff…

Original Bug Report

reported by vm...@gmail.com

OOB read in skia::BGRAConvolve2D

Steps to reproduce the problem

  1. ./image_operations_resize_fuzzer__better ./out_better1crash-a849b610d97bc4bf8be154b2d226a4353b8fd93e

Problem Description

In skia::ImageOperations::Resize of VizCompositorTh process, source_byte_row_stride in skia::BGRAConvolve2D can larger than source_byte_row_stride, causing heap-buffer-overflow in skia::Convolve4RowsHorizontally_SSE2[1] and skia::ConvolveHorizontally_SSE2[2]. ImageOperations::Resize is a regular operation in renderer process, and through calling of viz/service/display/software_renderer.cc:CopyOutputRequest, this can compromise VizCompositorTh process.

void BGRAConvolve2D(const unsigned char* source_data,
                    int source_byte_row_stride,
                    bool source_has_alpha,
                    const ConvolutionFilter1D& filter_x,
                    const ConvolutionFilter1D& filter_y,
                    int output_byte_row_stride,
                    unsigned char* output,
                    bool use_simd_if_possible) {
  ConvolveProcs simd;
  simd.extra_horizontal_reads = 0;
  simd.convolve_vertically = NULL;
  simd.convolve_4rows_horizontally = NULL;
  simd.convolve_horizontally = NULL;
  if (use_simd_if_possible) {
    SetupSIMD(&simd);
  }

//...

  for (int out_y = 0; out_y < num_output_rows; out_y++) {
    filter_values = filter_y.FilterForValue(out_y,
                                            &filter_offset, &filter_length);

    // Generate output rows until we have enough to run the current filter.
    while (next_x_row < filter_offset + filter_length) {
      if (simd.convolve_4rows_horizontally &&
          next_x_row + 3 < last_filter_offset + last_filter_length -
          avoid_simd_rows) {
        const unsigned char* src[4];
        unsigned char* out_row[4];
        for (int i = 0; i < 4; ++i) {
          src[i] = &source_data[(next_x_row + i) * source_byte_row_stride];                  //<--- [1]
          out_row[i] = row_buffer.AdvanceRow();
        }
        simd.convolve_4rows_horizontally(src, filter_x, out_row);
        next_x_row += 4;
      } else {
        // Check if we need to avoid SSE2 for this row.
        if (simd.convolve_horizontally &&
            next_x_row < last_filter_offset + last_filter_length -
            avoid_simd_rows) {
          simd.convolve_horizontally(                                                       //<--- [2]
              &source_data[next_x_row * source_byte_row_stride],
              filter_x, row_buffer.AdvanceRow(), source_has_alpha);
        } else {
─── Breakpoints ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
[1] break at 0x00007ffff313874f in ../../skia/ext/convolver.cc:475 for ../../skia/ext/convolver.cc:475 hit 1 time
─── Expressions ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
─── History ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
─── Memory ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
─── Registers ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    rax 0x00007bffe71e7110    rbx 0x00007fffffffcee0       rcx 0x000000000000025d    rdx 0x4000000000000000    rsi 0x003ffff989c3977e
    rdi 0x000055555619ae00    rbp 0x00007fffffffd180       rsp 0x00007fffffffcee0     r8 0x0000010000000000     r9 0x00007fffffffff01
    r10 0x0000000000001f01    r11 0x0000000000000001       r12 0x0000555555624ca0    r13 0x00007fffffffdc50    r14 0x00007c3fe81e08f0
    r15 0x00007bffe72e1a50    rip 0x00007ffff313874f    eflags [ CF PF AF SF IF ]     cs 0x00000033             ss 0x0000002b        
     ds 0x00000000             es 0x00000000                fs 0x00000000             gs 0x00000000        
─── Source ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Cannot display "convolver.cc"
─── Stack ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
[0] from 0x00007ffff313874f in skia::BGRAConvolve2D(unsigned char const*, int, bool, skia::ConvolutionFilter1D const&, skia::ConvolutionFilter1D const&, int, unsigned char*, bool)+1727 at ../../skia/ext/convolver.cc:475
[1] from 0x00007ffff316c95b in skia::ImageOperations::Resize(SkPixmap const&, skia::ImageOperations::ResizeMethod, int, int, SkIRect const&, SkBitmap::Allocator*)+2731 at ../../skia/ext/image_operations.cc:378
[2] from 0x000055555570c11d in LLVMFuzzerTestOneInput(uint8_t const*, size_t)+1933 at ../../skia/ext/image_operations_resize_fuzzer__better.cc:101
[3] from 0x000055555578d5db in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long)+1147 at ../../third_party/libFuzzer/src/FuzzerLoop.cpp:619
[4] from 0x000055555573bb0c in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long)+460 ```

# Additional Comments
```patch
diff --git a/skia/ext/convolver.cc b/skia/ext/convolver.cc
index b77fcdb165309..59519fa1ab970 100644
--- a/skia/ext/convolver.cc
+++ b/skia/ext/convolver.cc
@@ -439,6 +439,7 @@ void BGRAConvolve2D(const unsigned char* source_data,
   // Loop over every possible output row, processing just enough horizontal
   // convolutions to run each subsequent vertical convolution.
   SkASSERT(output_byte_row_stride >= filter_x.num_values() * 4);
+  SkASSERT(source_byte_row_stride < output_byte_row_stride);
   int num_output_rows = filter_y.num_values();
Running: ./out_better1crash-a849b610d97bc4bf8be154b2d226a4353b8fd93e
[0514/161817.940809:FATAL:skia/ext/convolver.cc:442] check(source_byte_row_stride < output_byte_row_stride)
#0 0x5628ff98e806 <unknown>
#1 0x7f5e0acbcd70 <unknown>
#2 0x7f5e0ab9dd62 <unknown>

Summary

OOB read in skia::BGRAConvolve2D

Custom Questions

Type of crash:

VizCompositorTh

Crash state:

#6526	REDUCE cov: 37 ft: 28 corp: 19/480b lim: 43 exec/s: 0 rss: 121Mb L: 39/39 MS: 2 ChangeASCIIInt-EraseBytes-
#7319	REDUCE cov: 37 ft: 28 corp: 19/479b lim: 48 exec/s: 0 rss: 121Mb L: 38/38 MS: 3 InsertByte-ChangeByte-EraseBytes-
=================================================================
==3824374==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7b787d1071b0 at pc 0x560bb534fd07 bp 0x7ffc8448c700 sp 0x7ffc8448c6f8
READ of size 16 at 0x7b787d1071b0 thread T0
    #0 0x560bb534fd06 in skia::Convolve4RowsHorizontally_SSE2(unsigned char const**, skia::ConvolutionFilter1D const&, unsigned char**) skia/ext/convolver_SSE2.cc:212:7
    #1 0x560bb534b8e6 in skia::BGRAConvolve2D(unsigned char const*, int, bool, skia::ConvolutionFilter1D const&, skia::ConvolutionFilter1D const&, int, unsigned char*, bool) skia/ext/convolver.cc:478:9
    #2 0x560bb5343739 in skia::ImageOperations::Resize(SkPixmap const&, skia::ImageOperations::ResizeMethod, int, int, SkIRect const&, SkBitmap::Allocator*) skia/ext/image_operations.cc:378:3
    #3 0x560bb391933a in LLVMFuzzerTestOneInput skia/ext/image_operations_resize_fuzzer__better.cc:101:7
    #4 0x560bb395866c in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) third_party/libFuzzer/src/FuzzerLoop.cpp:619:13
    #5 0x560bb3957538 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool, bool*) third_party/libFuzzer/src/FuzzerLoop.cpp:516:7
    #6 0x560bb395ab67 in fuzzer::Fuzzer::MutateAndTestOne() third_party/libFuzzer/src/FuzzerLoop.cpp:765:19
    #7 0x560bb395c9ba in fuzzer::Fuzzer::Loop(std::__Cr::vector<fuzzer::SizedFile, std::__Cr::allocator<fuzzer::SizedFile>>&) third_party/libFuzzer/src/FuzzerLoop.cpp:910:5
    #8 0x560bb39333a4 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) third_party/libFuzzer/src/FuzzerDriver.cpp:915:6
    #9 0x560bb39198b5 in main third_party/libFuzzer/src/FuzzerMain.cpp:20:10
    #10 0x7f387de14082 in __libc_start_main /build/glibc-FcRMwW/glibc-2.31/csu/../csu/libc-start.c:308:16

0x7b787d1071b0 is located 242 bytes after 46-byte region [0x7b787d107090,0x7b787d1070be)
allocated by thread T0 here:
    #0 0x560bb3917a0d in operator new[](unsigned long) /b/s/w/ir/cache/builder/src/third_party/llvm/compiler-rt/lib/asan/asan_new_delete.cpp:89:3
    #1 0x560bb3958418 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) third_party/libFuzzer/src/FuzzerLoop.cpp:601:23
    #2 0x560bb3957538 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool, bool*) third_party/libFuzzer/src/FuzzerLoop.cpp:516:7
    #3 0x560bb395ab67 in fuzzer::Fuzzer::MutateAndTestOne() third_party/libFuzzer/src/FuzzerLoop.cpp:765:19
    #4 0x560bb395c9ba in fuzzer::Fuzzer::Loop(std::__Cr::vector<fuzzer::SizedFile, std::__Cr::allocator<fuzzer::SizedFile>>&) third_party/libFuzzer/src/FuzzerLoop.cpp:910:5
    #5 0x560bb39333a4 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) third_party/libFuzzer/src/FuzzerDriver.cpp:915:6
    #6 0x560bb39198b5 in main third_party/libFuzzer/src/FuzzerMain.cpp:20:10
    #7 0x7f387de14082 in __libc_start_main /build/glibc-FcRMwW/glibc-2.31/csu/../csu/libc-start.c:308:16

SUMMARY: AddressSanitizer: heap-buffer-overflow skia/ext/convolver_SSE2.cc:212:7 in skia::Convolve4RowsHorizontally_SSE2(unsigned char const**, skia::ConvolutionFilter1D const&, unsigned char**)
Shadow bytes around the buggy address:
  0x7b787d106f00: fa fa fd fd fd fd fd fa fa fa fd fd fd fd fd fd
  0x7b787d106f80: fa fa fd fd fd fd fd fd fa fa fd fd fd fd fd fd
  0x7b787d107000: fa fa fd fd fd fd fd fa fa fa fd fd fd fd fd fa
  0x7b787d107080: fa fa 00 00 00 00 00 06 fa fa fa fa fa fa fa fa
  0x7b787d107100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x7b787d107180: fa fa fa fa fa fa[fa]fa fa fa fa fa fa fa fa fa
  0x7b787d107200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7b787d107280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7b787d107300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7b787d107380: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7b787d107400: 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
==3824374==ABORTING
MS: 1 PersAutoDict- DE: "\034\000\000\000\000\000\000\000"-; base unit: 3acaf8e9917854385c881681874a2b4fa25c52dd
0xa,0xa,0x47,0x47,0x47,0x47,0x47,0x47,0x47,0x0,0xa,0x0,0x4,0x0,0x1c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xb6,0x0,0x0,0x0,0x7a,0x98,0x0,0x0,0xd3,0x41,0x0,0x0,0xff,0xff,0x0,0x34,0x2d,0x30,0x47,0x98,0xf7,0x61,
\012\012GGGGGGG\000\012\000\004\000\034\000\000\000\000\000\000\000\000\000\266\000\000\000z\230\000\000\323A\000\000\377\377\0004-0G\230\367a
artifact_prefix='./out_better1'; Test unit written to ./out_better1crash-a849b610d97bc4bf8be154b2d226a4353b8fd93e
Base64: CgpHR0dHR0dHAAoABAAcAAAAAAAAAAAAtgAAAHqYAADTQQAA//8ANC0wR5j3YQ==

Reporter credit:

/

Additional Data

Category: Security
Chrome Channel: Not sure
Regression: N/A

View on issue tracker