CVE-2026-7981
Overview
Files Changed
DEPSthird_party/crabbyavif/README.chromiumthird_party/crabbyavif/src
Patch
From 63bbdf60d7c60aab16192319671abac847c0b509 Mon Sep 17 00:00:00 2001
From: Vignesh Venkat <vigneshv@google.com>
Date: Thu, 02 Apr 2026 17:28:03 -0700
Subject: [PATCH] Roll src/third_party/crabbyavif/src/ c05daf3e2..7466a44ac (39 commits)
https://chromium.googlesource.com/external/github.com/webmproject/CrabbyAvif.git/+log/c05daf3e2e6d..7466a44ac808
$ git log c05daf3e2..7466a44ac --date=short --no-merges --format='%ad %ae %s'
2026-03-31 vigneshv tile: Check for yuv_format compatibility
2026-03-31 vigneshv dav1d/avm: Use correct plane height for Pixels
2026-03-27 yguyon Collapse conditional blocks (#788)
2026-03-26 vigneshv libyuv: Use default constants on Android
2026-03-26 vigneshv cli: Add alt_clli arg
2026-03-26 vigneshv cli: Allow specifying alpha/gainmap quality
2026-03-26 vigneshv cli: Add support for extracting gainmap
2026-03-26 vigneshv jpeg: Support images in the old apple format
2026-03-26 yguyon Replace Pixels::try_clone() with match blocks (#782)
2026-03-25 vigneshv jpeg: Add tests for jpeg gainmap parsing
2026-03-25 vigneshv jpeg: Support gainmap conversion
2026-03-25 vigneshv tests: Normalize test file names
2026-03-24 vigneshv conformance: Remove avifdec comparison
2026-03-24 vigneshv jpeg: Use zune jpeg crate directly
2026-03-24 yguyon Inline Decoder::apply_sample_transform() (#775)
2026-03-24 yguyon Move row_bytes assignment closer to planes (#774)
2026-03-20 yguyon Rename Image::clone_properties() to shallow_clone() (#773)
2026-03-18 vigneshv cli: Add placeholder functions for gainmap
2026-03-18 vigneshv jpeg: Support additional metadata
2026-03-18 vigneshv Revert: "Add libjpeg-sys for JPG support"
2026-03-17 vigneshv image: Refactor xmp fix into a function
2026-03-16 vigneshv Add libjpeg-sys for JPG support
2026-03-16 vigneshv cli: Override irot/imir only if explicitly specified
2026-03-12 vigneshv png: Rename libpng callbacks with a crabbyavif_ prefix
2026-03-11 vigneshv png: Use libpng instead of the png crate
2026-03-11 vigneshv Remove libgav1 support from crabbyavif.
2026-03-09 vigneshv tests: Use PngReader instead of png crate
2026-03-06 vigneshv zlib: Build zlib in a separate directory
2026-03-04 yguyon Rename Image::shallow_clone() to clone_properties (#758)
2026-03-03 vigneshv Add libpng-sys for PNG support
2026-02-25 yguyon Fix AV1 encoder instead of decoder in README.md (#760)
2026-02-24 yguyon Remove Image::steal_or_copy_planes() (#759)
2026-02-23 yguyon Move copy_properties_from from image to dec mod (#755)
2026-02-20 vigneshv cli: Add flag for enabling sample transform
2026-02-20 yguyon Move copy_from_tile() from image to tile (#756)
2026-02-20 yguyon Remove unneccessary temp var final_recipe (#754)
2026-02-20 yguyon Remove unused Image::image_owns_planes (#753)
2026-02-19 vigneshv cli: Support cropped output
2026-02-19 qwf crabbyavif: make avifFree and avifAlloc call libc
Created with:
roll-dep src/third_party/crabbyavif/src
Bug: 497926602
Change-Id: I44f9450e77cae652ea255b64b67a8e844a4bc870
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7720378
Commit-Queue: James Zern <jzern@google.com>
Auto-Submit: Vignesh Venkat <vigneshv@google.com>
Reviewed-by: James Zern <jzern@google.com>
Cr-Commit-Position: refs/heads/main@{#1609584}
---
diff --git a/DEPS b/DEPS
index fa5c073..f6da8bc 100644
--- a/DEPS
+++ b/DEPS
@@ -459,7 +459,7 @@
# Three lines of non-changing comments so that
# the commit queue can handle CLs rolling crabbyavif
# and whatever else without interference from each other.
- 'crabbyavif_revision': 'c05daf3e2e6d83f2a359ab97094ce042944020a9',
+ 'crabbyavif_revision': '7466a44ac80893803d4a7168b98dc6cd02d1fe2d',
# Three lines of non-changing comments so that
# the commit queue can handle CLs rolling Speedometer main
# and whatever else without interference from each other.
diff --git a/third_party/crabbyavif/README.chromium b/third_party/crabbyavif/README.chromium
index 62323219..39c53066 100644
--- a/third_party/crabbyavif/README.chromium
+++ b/third_party/crabbyavif/README.chromium
@@ -2,7 +2,7 @@
Short Name: crabbyavif
URL: https://github.com/webmproject/CrabbyAvif
Version: N/A
-Revision: c05daf3e2e6d83f2a359ab97094ce042944020a9
+Revision: 7466a44ac80893803d4a7168b98dc6cd02d1fe2d
Update Mechanism: Manual
License: Apache-2.0
License File: LICENSE
diff --git a/third_party/crabbyavif/src b/third_party/crabbyavif/src
index c05daf3..7466a44 160000
--- a/third_party/crabbyavif/src
+++ b/third_party/crabbyavif/src
@@ -1 +1 @@
-Subproject commit c05daf3e2e6d83f2a359ab97094ce042944020a9
+Subproject commit 7466a44ac80893803d4a7168b98dc6cd02d1fe2d
Original Bug Report
Heap OOB Read in crabbyavif via incremental decode state loss and oversized slice
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A potential heap out-of-bounds read exists in the crabbyavif library during incremental grid image decoding. A state tracking flaw allows mixing incompatible YUV formats in a grid, which interacts with an oversized buffer slice in the dav1d wrapper to bypass Rust bounds checks. This can lead to renderer heap memory being leaked into the decoded image, which an attacker could read via a <canvas> element.
Affected files:
third_party/crabbyavif/src/src/decoder/mod.rsthird_party/crabbyavif/src/src/codecs/dav1d.rsthird_party/crabbyavif/src/src/image.rs
Estimated timestamp from git blame: 2025-04-11
Summary
A potential heap out-of-bounds (OOB) read vulnerability exists in the crabbyavif library when processing grid images during incremental decoding. Two separate logic flaws combine to create this issue: a validation bypass caused by state loss when resuming an incremental decode, and an oversized PointerSlice created in the dav1d codec wrapper. Together, these allow an attacker to leak adjacent heap memory from the renderer process into the decoded image pixels.
Technical Details
1. Validation Bypass in GridImageHelper
In third_party/crabbyavif/src/src/decoder/mod.rs, the decode_grid() function handles grid assembly. During incremental decoding (which is enabled for still images in Blink), if the network stalls, the function yields. When more data arrives, decode_grid() is called again.
On each call, it constructs a fresh GridImageHelper instance:
// decoder/mod.rs:1894
let mut grid_image_helper = GridImageHelper {
// ...
first_cell_image: None,
// ...
};
Because first_cell_image is initialized to None on every resume, copy_from_cell_image() fails to perform consistency checks on subsequent tiles if the decode was interrupted:
// decoder/mod.rs:425
} else if self.first_cell_image.is_some()
&& !cell_image.has_same_properties_and_cicp(self.first_cell_image.unwrap_ref())
{
return AvifError::invalid_image_grid("grid image contains mismatched tiles");
}
This bypass allows an attacker to mix tiles with different subsampling formats (e.g., a YUV 4:4:4 tile followed by a YUV 4:2:0 tile).
2. Oversized PointerSlice in dav1d Wrapper
In third_party/crabbyavif/src/src/codecs/dav1d.rs, the wrapper incorrectly uses the full image.height (the luma height) when creating PointerSlice wrappers for all planes:
// codecs/dav1d.rs:255
image.planes[plane] = Some(Pixels::from_raw_pointer(
dav1d_picture.data[plane] as *mut u8,
image.depth as u32,
image.height, // ERROR: Uses luma height for chroma planes
image.row_bytes[plane],
)?);
For YUV 4:2:0 formats, chroma buffers are half the luma height. Passing the full height creates a slice that overestimates the allocation size, defeating Rust’s runtime bounds checks.
3. Triggering the OOB Read
In third_party/crabbyavif/src/src/image.rs, copy_from_tile merges a tile into the grid. If a 1-row grid is initialized as YUV 4:4:4 (from the first tile), but a subsequent tile is YUV 4:2:0, the code calculates the copy height based on the destination grid’s height:
// image.rs:494
let src_height_to_copy = if row_index == grid.rows - 1 {
// ...
checked_sub!(u32_from_usize(self.height(plane))?, height_so_far)?
}
This evaluates to the full luma height. The decoder then copies luma_height rows from the source tile’s smaller chroma buffer. Because the PointerSlice from step 2 is oversized, the Rust bounds check succeeds, and crabbyavif silently reads past the end of the dav1d allocation, copying adjacent renderer heap memory into the image.
Suggested Attacker Steps (Theoretical)
Note: These are potential steps as our tooling does not currently run live code.
- Craft an AVIF image with a 1-row, multi-column grid.
- Encode Tile 0 as a YUV 4:4:4 keyframe.
- Encode Tile 1 as a YUV 4:2:0 keyframe.
- Serve the file over HTTP with chunked encoding. Send Tile 0 data, then stall the connection to trigger an incremental decode pause in Chrome.
- Send Tile 1 data. The decoder resumes, loses
first_cell_imagestate, bypasses validation, and OOB-reads the Tile 1 chroma buffer. - Render the resulting image to an offscreen
<canvas>and usegetImageData()to extract the leaked heap memory.
Suggested Fix
- Persist Grid State: Move
first_cell_imageout of the temporaryGridImageHelperand store it in a persistent structure (e.g.,DecoderorTileInfo) so format validation works across incremental decode boundaries. - Correct Slice Bounds: In
dav1d.rs, use the appropriate plane height when creating the slice:image.height(plane.into())instead ofimage.height.
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.