High patch-now ~ plausible CVSS 8.8 chrome OOB 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactHeap buffer overflow in Codecs
DescriptionHeap buffer overflow in Codecs
ComponentCodecs
Bug ClassOOB
Action tierpatch-now — corruption primitive, web-reachable in default config (plausible) — factors set 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 AIPlausible
Reviewer notes: Buffer size matches plane_size when skipped, so overflow must come from stale row_bytes/stride. That is unshown. The libyuv writer and how the commit-message alpha-plane case arises are not in the material.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker478560268
Fix commita103c0de433f (CrabbyAvif (upstream, via Chromium mirror)) +0/-16
CWECWE-122, CWE-787 (Heap buffer overflow, Out-of-bounds write)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-01-26 (per vendor advisory)
Fix landed2026-02-04 (public git)
Disclosed2026-02-10
Patch gap6 days — fix public in source before the advisory

Background

CrabbyAvif
the Rust AVIF decoder Chrome uses for AVIF images, which stores decoded pixels in Image plane buffers.
`Pixels`
an enum (src/utils/pixels.rs:21) holding plane data as either a borrowed Pointer/Pointer16 or an owned Buffer/Buffer16.
`allocate_planes_with_default_values`
src/image.rs:378 routine that, per plane in a category, sizes and fills the plane’s owned buffer before pixels are written into it.

Root Cause Analysis

Verify before building a PoC

This analysis is consistent with the fixing commit but not fully provable from it: part of the vulnerable path lives in code the fix commit does not touch. Confirm the trigger and the sink against the source before relying on it for exploit development.
What the commit does not establish: Buffer size matches plane_size when skipped, so overflow must come from stale row_bytes/stride. That is unshown. The libyuv writer and how the commit-message alpha-plane case arises are not in the material.
Re-analyzed on 2026-10-03 against the actual fix commit a103c0de433f. An earlier analysis was withdrawn because it had been written against a dependency roll (4e749ba7969e).

allocate_planes_with_default_values iterates the planes of a Category, computes plane_size = width * height for each (src/image.rs:388), and is supposed to (re)allocate an owned buffer sized to the current dimensions. Pre-fix, src/image.rs:389-395 short-circuited this with if self.planes[plane_index].is_some() && self.planes[plane_index].unwrap_ref().size() == plane_size && (pixel_bit_size()==0 || pixel_bit_size()==pixel_size*8) { continue; }, skipping the allocation whenever a plane already held a buffer whose element count matched. When that continue fired, the body at src/image.rs:401-404 — pixels.resize(plane_size, ...) (src/utils/pixels.rs:82), the self.row_bytes[plane_index] update, and self.image_owns_planes[plane_index] = true — never ran. Because the skip condition at src/image.rs:390 requires size() == plane_size, the retained buffer’s element count already equals a fresh allocation; what is left stale is the plane’s row_bytes stride (:403), its default-value fill (:402), and its image_owns_planes flag (:404), none of which are refreshed for the current geometry. As the commit message states, this “may inadvertently not allocate the memory in some cases (e.g. alpha plane when the color planes have already been allocated).” A subsequent writer that fills/scales the plane using the stale row_bytes stride then writes past the buffer, matching the advisory’s heap-buffer-overflow WRITE in Codecs (inferred — the libyuv scaler that performs this write is not in the shown material).

The fix deletes the entire conditional skip (diff removes src/image.rs:389-395) so the buffer is always freshly allocated via resize and row_bytes is always reset, and removes the now-unused pixel_bit_size helper (src/utils/pixels.rs:64-71).

Key insight
A premature “already allocated, skip it” optimization kept a plane’s buffer whenever its element count matched plane_size, but left the plane’s row_bytes stride, default fill, and ownership flag stale for the current geometry; removing the skip forces every needed plane to be reallocated and its row_bytes reset.

Attack Path

  1. Supply a crafted AVIF The renderer decodes an attacker-controlled AVIF whose plane allocation reuses an Image across categories/calls. (inferred — reuse pattern)
  2. Hit the skip branch A plane whose existing buffer size matches plane_size triggers continue at src/image.rs:394, so its row_bytes, default fill, and ownership flag are never refreshed for the current geometry.
  3. Write past the buffer Later pixel production writes rows of that plane using the stale row_bytes stride, which no longer matches the current geometry, overflowing the buffer (advisory: heap-buffer-overflow WRITE). (inferred — the writer is not in the shown material)

Impact Assessment

An attacker who gets the renderer to decode a malicious AVIF can cause a heap buffer overflow write during plane handling in the codec path, corrupting adjacent renderer-process heap memory. The advisory rates this high severity and reports no in-the-wild exploitation; escalation beyond the overflow write is (inferred).

Files Changed

  • src/image.rs
  • src/utils/pixels.rs

Audit Directions

  • Conditional (re)allocation
    Flag “skip allocation if a buffer already exists” optimizations that compare only element count, since they can leave size, stride, or depth metadata stale.
  • Buffer/stride coupling
    Verify that any path which skips a buffer allocation also keeps the companion row_bytes/stride and ownership flags consistent with the current dimensions.
diff --git a/src/image.rs b/src/image.rs
index 88e5216..a611822 100644
--- a/src/image.rs
+++ b/src/image.rs
@@ -386,13 +386,6 @@
             let plane_index = plane.as_usize();
             let width = round2_usize(self.width(plane));
             let plane_size = checked_mul!(width, round2_usize(self.height(plane)))?;
-            if self.planes[plane_index].is_some()
-                && self.planes[plane_index].unwrap_ref().size() == plane_size
-                && (self.planes[plane_index].unwrap_ref().pixel_bit_size() == 0
-                    || self.planes[plane_index].unwrap_ref().pixel_bit_size() == pixel_size * 8)
-            {
-                continue;
-            }
             self.planes[plane_index] = Some(if self.depth == 8 {
                 Pixels::Buffer(Vec::new())
             } else {
diff --git a/src/utils/pixels.rs b/src/utils/pixels.rs
index e5002bc..5f78e16 100644
--- a/src/utils/pixels.rs
+++ b/src/utils/pixels.rs
@@ -61,15 +61,6 @@
         }
     }
 
-    pub(crate) fn pixel_bit_size(&self) -> usize {
-        match self {
-            Pixels::Pointer(_) => 0,
-            Pixels::Pointer16(_) => 0,
-            Pixels::Buffer(_) => 8,
-            Pixels::Buffer16(_) => 16,
-        }
-    }
-
     pub(crate) fn has_data(&self) -> bool {
         match self {
             Pixels::Pointer(ptr) => !ptr.is_empty(),
Loading diff…

Original Bug Report

reported by [email protected]

blink_avif_decoder_fuzzer: Heap-buffer-overflow in InterpolateRow_Any_AVX2

Detailed Report: https://clusterfuzz.com/testcase?key=5869883250114560

Fuzzing Engine: centipede Fuzz Target: blink_avif_decoder_fuzzer Job Type: centipede_chrome_asan Platform Id: linux

Crash Type: Heap-buffer-overflow WRITE 1 Crash Address: 0x782a69fe8f80 Crash State: InterpolateRow_Any_AVX2 libyuv::ScalePlaneBilinearUp ScalePlane

Sanitizer: address (ASAN)

Recommended Security Severity: High

Regressed: https://clusterfuzz.com/revisions?job=centipede_chrome_asan&range=1567088:1567158

Reproducer Testcase: https://clusterfuzz.com/download?testcase_id=5869883250114560

Issue filed automatically.

See https://chromium.googlesource.com/chromium/src/+/master/testing/libfuzzer/reproducing.md for instructions on reproducing this bug locally.

View on issue tracker