Chrome Β· Skia
CVE-2026-3931
OOB in Skia
Overview
Medium
Severity
β
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
skia/ext/image_operations.cc
Patch
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
- ./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
References
On This Page