Critical watch ✔︎ verified CVSS 8.3 chrome Integer Overflow 📄 Reporter bug report 🔧 Commit mapped

Overview

Critical
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in libyuv
DescriptionInteger overflow in libyuv
ComponentChromium
Bug ClassInteger Overflow
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable — revised by independent review by AI
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Independent review by AIConfirmed (tier revised from patch-now)
Reviewer notes: Triggering needs width*height>INT_MAX (billions of pixels), a strong input precondition the claim downplays as 'default'. libyuv runs across processes; diff does not pin surface to renderer.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker517339758
Fix commitc98edcc8dcdb (libyuv/libyuv) +207/-100
CWECWE-472
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-28 (per vendor advisory)
Fix landed2026-05-29 (public git)
Disclosed2026-06-08
Patch gap10 days — fix public in source before the advisory

Background

libyuv
a widely-used YUV/RGB image conversion and comparison library bundled in Chromium and reachable through image/video decode and canvas paths.
Row coalescing
an optimization that, when every stride exactly equals the row width, flattens a 2D buffer into one row by doing width *= height; height = 1; so inner loops run once over width*height elements.

Root Cause Analysis

Many libyuv functions coalesced rows whenever the stride-equals-width condition held, executing width *= height without checking that the product fits in an int. For large images where width*height > INT_MAX, this signed multiplication overflows (undefined behavior), yielding a bogus coalesced width that no longer describes the real buffer, so subsequent row processing can read or write outside the intended bounds. The functions also did not validate their inputs, so a null pointer or non-positive width/height could reach the coalescing math.

The fix adds, per function, an early return on !ptr || width <= 0 || height <= 0 and extends each coalescing condition with (ptrdiff_t)width * height <= INT_MAX, performing the product in a wider type and only flattening when it is representable. This preserves the invariant that the coalesced single-row width equals the true element count, so no overflow-derived size drives the pixel loops.

Key insight
The core mistake was computing width *= height as 32-bit signed arithmetic before confirming the product fits in int; the fix pre-validates inputs and gates every coalescing site on a widened (ptrdiff_t)width * height <= INT_MAX check so the flatten only happens when the result is exact.

Attack Path

  1. Supply an oversized image Deliver image/frame data to a libyuv entry point (via decode, canvas, or conversion) with dimensions whose width*height exceeds INT_MAX while strides equal width so the coalescing branch is taken.
  2. Overflow the coalesced width width *= height overflows signed int, producing a small/negative width that misrepresents the flattened buffer length.
  3. Drive loops with a bogus size The conversion/compare routine iterates using the corrupted width, so pixel processing operates on a length inconsistent with the real allocation.
  4. Out-of-bounds access The mismatch leads to out-of-bounds reads or writes during row processing; the exact sink, offset, and whether a write primitive results are (inferred) as the diff only shows the guard, not the downstream memory operation.

Impact Assessment

An attacker who can route attacker-controlled image dimensions and buffers through these libyuv functions can trigger a signed-integer overflow in the row-coalescing size, leading to out-of-bounds memory access (inferred read/write) wherever libyuv runs, which in Chromium includes sandboxed renderer/GPU/media contexts. The vendor advisory rates this critical; the precondition is an image large enough that width*height > INT_MAX reaching a coalescing path, which requires very large dimensions but no special flag or privilege. Any escalation beyond the overflow-driven OOB the patch guards (controlled write, code execution, sandbox escape) is (inferred).

Changed Functions

FunctionChangeNotes
if
source/compare.cc
modified
if
source/convert.cc
modified
if
source/convert_argb.cc
modified

Files Changed

  • README.chromium
  • include/libyuv/version.h
  • source/compare.cc
  • source/convert.cc
  • source/convert_argb.cc

Audit Directions

  • Coalescing / flatten optimizations
    Audit stride-equals-width flatten paths across conversion libraries, since each independently repeats the same overflow-prone pattern.
  • Missing input validation at entry points
    Check public image routines for null-pointer and non-positive width/height guards before any arithmetic on dimensions.
From c98edcc8dcdba15beaf866a4a5ea8cccb2865cbe Mon Sep 17 00:00:00 2001
From: Wan-Teh Chang <[email protected]>
Date: Fri, 29 May 2026 11:29:01 -0700
Subject: [PATCH] Don't coalesce rows if width*height would overflow

Audit all occurrences of "width *= height;" in the libyuv source code.
Make sure height > 0 and (ptrdiff_t)width * height <= INT_MAX before
executing width *= height.

Bug: chromium:517339758
Change-Id: I143a41c66492a6e4c48b6aa2a1c4a2ae974ceeb1
Reviewed-on: https://chromium-review.googlesource.com/c/libyuv/libyuv/+/7883816
Commit-Queue: Wan-Teh Chang <[email protected]>
Reviewed-by: Frank Barchard <[email protected]>
---

diff --git a/README.chromium b/README.chromium
index c1f4164..ae8e037 100644
--- a/README.chromium
+++ b/README.chromium
@@ -1,6 +1,6 @@
 Name: libyuv
 URL: https://chromium.googlesource.com/libyuv/libyuv/
-Version: 1942
+Version: 1943
 Revision: DEPS
 License: BSD-3-Clause
 License File: LICENSE
diff --git a/include/libyuv/version.h b/include/libyuv/version.h
index ab2420c..d739e7e 100644
--- a/include/libyuv/version.h
+++ b/include/libyuv/version.h
@@ -11,6 +11,6 @@
 #ifndef INCLUDE_LIBYUV_VERSION_H_
 #define INCLUDE_LIBYUV_VERSION_H_
 
-#define LIBYUV_VERSION 1942
+#define LIBYUV_VERSION 1943
 
 #endif  // INCLUDE_LIBYUV_VERSION_H_
diff --git a/source/compare.cc b/source/compare.cc
index e85cc6d..1002330 100644
--- a/source/compare.cc
+++ b/source/compare.cc
@@ -11,6 +11,7 @@
 #include "libyuv/compare.h"
 
 #include <float.h>
+#include <limits.h>
 #include <math.h>
 #ifdef _OPENMP
 #include <omp.h>
@@ -106,8 +107,11 @@
   uint32_t fourcc = 0;
   int h;
 
+  if (!argb || width <= 0 || height <= 0) {
+    return fourcc;
+  }
   // Coalesce rows.
-  if (stride_argb == width * 4) {
+  if (stride_argb == width * 4 && (ptrdiff_t)width * height <= INT_MAX) {
     width *= height;
     height = 1;
     stride_argb = 0;
@@ -245,8 +249,12 @@
                                     int height) {
   uint64_t sse = 0;
   int h;
+  if (!src_a || !src_b || width <= 0 || height <= 0) {
+    return sse;
+  }
   // Coalesce rows.
-  if (stride_a == width && stride_b == width) {
+  if (stride_a == width && stride_b == width &&
+      (ptrdiff_t)width * height <= INT_MAX) {
     width *= height;
     height = 1;
     stride_a = stride_b = 0;
diff --git a/source/convert.cc b/source/convert.cc
index 4a71365..4f5cca5 100644
--- a/source/convert.cc
+++ b/source/convert.cc
@@ -10,6 +10,8 @@
 
 #include "libyuv/convert.h"
 
+#include <limits.h>
+
 #include "libyuv/basic_types.h"
 #include "libyuv/cpu_id.h"
 #include "libyuv/planar_functions.h"
@@ -1435,7 +1437,8 @@
     src_stride_uv = -src_stride_uv;
   }
   // Coalesce rows.
-  if (src_stride_y == width && dst_stride_y == width) {
+  if (src_stride_y == width && dst_stride_y == width &&
+      (ptrdiff_t)width * height <= INT_MAX) {
     width *= height;
     height = 1;
     src_stride_y = dst_stride_y = 0;
@@ -4476,7 +4479,8 @@
     src_stride_rgb24 = -src_stride_rgb24;
   }
   // Coalesce rows.
-  if (src_stride_rgb24 == width * 3 && dst_stride_yj == width) {
+  if (src_stride_rgb24 == width * 3 && dst_stride_yj == width &&
+      (ptrdiff_t)width * height <= INT_MAX) {
     width *= height;
     height = 1;
     src_stride_rgb24 = dst_stride_yj = 0;
@@ -4641,7 +4645,8 @@
     src_stride_raw = -src_stride_raw;
   }
   // Coalesce rows.
-  if (src_stride_raw == width * 3 && dst_stride_yj == width) {
+  if (src_stride_raw == width * 3 && dst_stride_yj == width &&
+      (ptrdiff_t)width * height <= INT_MAX) {
     width *= height;
     height = 1;
     src_stride_raw = dst_stride_yj = 0;
diff --git a/source/convert_argb.cc b/source/convert_argb.cc
index 82f1096..1d73d1e 100644
--- a/source/convert_argb.cc
+++ b/source/convert_argb.cc
@@ -11,6 +11,7 @@
 #include "libyuv/convert_argb.h"
 
 #include <assert.h>
+#include <limits.h>
 
 #include "libyuv/convert_from_argb.h"
 #include "libyuv/cpu_id.h"
@@ -327,7 +328,8 @@
   }
   // Coalesce rows.
   if (src_stride_y == width && src_stride_u * 2 == width &&
-      src_stride_v * 2 == width && dst_stride_argb == width * 4) {
+      src_stride_v * 2 == width && dst_stride_argb == width * 4 &&
+      (ptrdiff_t)width * height <= INT_MAX) {
     width *= height;
     height = 1;
     src_stride_y = src_stride_u = src_stride_v = dst_stride_argb = 0;
@@ -581,7 +583,7 @@
   }
   // Coalesce rows.
   if (src_stride_y == width && src_stride_u == width && src_stride_v == width &&
-      dst_stride_argb == width * 4) {
+      dst_stride_argb == width * 4 && (ptrdiff_t)width * height <= INT_MAX) {
     width *= height;
     height = 1;
     src_stride_y = src_stride_u = src_stride_v = dst_stride_argb = 0;
@@ -818,7 +820,7 @@
   }
   // Coalesce rows.
   if (src_stride_y == width && src_stride_u == width && src_stride_v == width &&
-      dst_stride_rgb24 == width * 3) {
+      dst_stride_rgb24 == width * 3 && (ptrdiff_t)width * height <= INT_MAX) {
     width *= height;
     height = 1;
     src_stride_y = src_stride_u = src_stride_v = dst_stride_rgb24 = 0;
@@ -3181,7 +3183,8 @@
     dst_stride_argb = -dst_stride_argb;
   }
   // Coalesce rows.
-  if (src_stride_y == width && dst_stride_argb == width * 4) {
+  if (src_stride_y == width && dst_stride_argb == width * 4 &&
+      (ptrdiff_t)width * height <= INT_MAX) {
     width *= height;
     height = 1;
     src_stride_y = dst_stride_argb = 0;
@@ -3275,7 +3278,8 @@
     src_stride_y = -src_stride_y;
   }
   // Coalesce rows.
-  if (src_stride_y == width && dst_stride_argb == width * 4) {
+  if (src_stride_y == width && dst_stride_argb == width * 4 &&
+      (ptrdiff_t)width * height <= INT_MAX) {
     width *= height;
     height = 1;
     src_stride_y = dst_stride_argb = 0;
@@ -3449,7 +3453,8 @@
     src_stride_argb = -src_stride_argb;
   }
   // Coalesce rows.
-  if (src_stride_argb == width * 4 && dst_stride_bgra == width * 4) {
+  if (src_stride_argb == width * 4 && dst_stride_bgra == width * 4 &&
+      (ptrdiff_t)width * height <= INT_MAX) {
     width *= height;
     height = 1;
     src_stride_argb = dst_stride_bgra = 0;
@@ -3490,7 +3495,8 @@
     src_stride_argb = -src_stride_argb;
   }
   // Coalesce rows.
-  if (src_stride_argb == width * 4 && dst_stride_abgr == width * 4) {
+  if (src_stride_argb == width * 4 && dst_stride_abgr == width * 4 &&
+      (ptrdiff_t)width * height <= INT_MAX) {
     width *= height;
Loading diff…

Original Bug Report

reported by [email protected]

Potential Signed Integer Overflow in libyuv::CopyPlane Row-Coalescing Leading to Out-Of-Bounds Copy

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential signed integer overflow in libyuv::CopyPlane occurs during row-coalescing when the product of the width and height exceeds the limit of a 32-bit signed integer. This overflow can result in a negative width parameter being passed to copying kernels such as ERMS, SSE2, NEON, or standard memcpy. Consequently, this leads to a massive out-of-bounds copy or an infinite loop, potentially resulting in memory corruption within the GPU or browser process.

Affected files:

  • third_party/libyuv/source/planar_functions.cc
  • gpu/command_buffer/service/shared_image/copy_image_plane.cc
  • third_party/libyuv/source/row_gcc.cc
  • third_party/libyuv/source/row_common.cc

Estimated timestamp from git blame: 2024-07-05

Description

A potential memory safety vulnerability has been identified in libyuv::CopyPlane’s row-coalescing implementation. The function performs a signed 32-bit integer multiplication of the width and height parameters without validating that the product does not overflow:

// third_party/libyuv/source/planar_functions.cc
if (src_stride_y == width && dst_stride_y == width) {
  width *= height;      // Potential signed integer overflow
  height = 1;
  src_stride_y = dst_stride_y = 0;
}

If an image with extremely large dimensions is processed (for example, a 16384x16384 image using a format with 8 bytes per pixel, such as RGBA_F16), the row width in bytes is 131072 and the height is 16384. The multiplication 131072 * 16384 equals 2,147,483,648 (0x80000000), which overflows a signed 32-bit integer to INT_MIN (-2147483648).

Impact and Technical Mechanism

When a negative value like INT_MIN is assigned to width and passed down to downstream copy kernels, several behaviors can occur depending on the active CPU architecture and instruction set:

  1. ERMS Kernel (CopyRow_ERMS) & C Fallback (CopyRow_C):

    • The negative int width is cast to size_t. On 64-bit systems, this triggers sign-extension, converting -2147483648 to 0xFFFFFFFF80000000 (approximately 16 exabytes).
    • The rep movsb instruction or the underlying memcpy is invoked with this massive count, resulting in a wild out-of-bounds copy.
  2. SIMD Kernels (CopyRow_SSE2, CopyRow_NEON):

    • In CopyRow_SSE2 and CopyRow_NEON, assembly loop implementations subtract a fixed chunk size (e.g., 32 bytes) from the signed loop counter and branch if the remaining count is greater than zero.
    • When INT_MIN is decremented by 32, it underflows to a large positive integer (2147483616), causing the loop to run indefinitely and corrupt memory.

Potential Reachability in Chrome

This function is reached via gpu::CopyImagePlane in gpu/command_buffer/service/shared_image/copy_image_plane.cc during pixel data uploads or readbacks (e.g., in D3DImageBacking::UploadFromMemory or other platform-specific backing implementations). If a compromised renderer can create and upload to a large SharedImage that bypasses initial size allocations, this could serve as a potential sandbox escape path targeting the GPU process.

Note: These are potential steps analyzed statically; our security analysis tools do not have the capability to execute code or verify runtime exploits.

Suggested Remediation

To prevent this signed integer overflow, validate that the product of width and height does not overflow before coalescing the rows. For example:

if (src_stride_y == width && dst_stride_y == width) {
  if (height > 0 && width > 0x7fffffff / height) {
    return; // Or handle the error safely without coalescing
  }
  width *= height;
  height = 1;
  src_stride_y = dst_stride_y = 0;
}

Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker