CVE-2026-117990
Overview
Background
- `wr::ImageDescriptor`
- Describes an image given to WebRender:
size(width/height),stride(bytes per row), andformat. It is sent over IPC from the content process to the GPU/compositor process. - `ParamTraits<ImageDescriptor>` (`WebRenderMessageUtils.h`)
- The IPC (de)serializer for
ImageDescriptor. On read it validates the untrusted descriptor; returning{}(emptyMaybe) rejects the message. - Stride/width/bpp invariant
- For a given
stride, a row must actually holdwidthpixels:stride / bpp >= width(withbpp = BytesPerPixel(format)), otherwise consumers would read past the row’s backing bytes. - `image_templates` / `set_image_visible_rect` (`resource_cache.rs`)
- WebRender’s resource cache stores image templates with a validated
descriptor(includingsize).set_image_visible_rectupdates an image’svisible_rect. - Blob images
- Images rendered on demand; their size legitimately follows the visible rect. Other image templates (raw/buffer) have a fixed size validated against their data and stride.
Root Cause Analysis
Two related boundary defects let an image’s effective size be trusted without matching its validated backing. First, in ParamTraits<mozilla::wr::ImageDescriptor>::Read (gfx/layers/wr/WebRenderMessageUtils.h) the stride check was if (bpp <= 0 || stride / bpp < width). When width <= 0 (e.g. a negative or zero width from a compromised/buggy content process), stride / bpp < width is false, so a non-positive width passed validation while a non-zero stride was present. The patch adds width <= 0 to the rejection condition, so a descriptor that specifies a stride but a non-positive width is now rejected.
Second, ResourceCache::set_image_visible_rect (gfx/wr/webrender/src/resource_cache.rs) unconditionally overwrote image.descriptor.size = rect.size() (and image.visible_rect) for any image template. For non-blob images the size was already validated against the image’s data and stride, so letting the visible rect redefine size desynchronizes the descriptor from the bytes that back it. The patch returns early unless image.data.is_blob(), so only blob images — which are legitimately sized by their visible rect — have their size updated; raw/buffer templates keep the size their data and stride were validated against. The advisory’s “incorrect boundary conditions” matches the diff: both changes close paths where an image’s declared geometry could exceed/diverge from its validated backing, with the out-of-bounds read/write in the GPU process being the inferred security sink.
size/width could be set to a value inconsistent with the stride/data it was validated against — either by a non-positive width slipping past IPC deserialization, or by set_image_visible_rect resizing a non-blob template to its visible rect — so downstream consumers computed buffer extents against a size the backing memory does not satisfy.Attack Path
- Compromised/buggy content process
The attacker controls the content-process side that sends
ImageDescriptors and visible-rect updates to WebRender in the GPU/compositor process. (threat model inferred) - Bad descriptor
Send an
ImageDescriptorwith a non-zerostridebutwidth <= 0; pre-patchParamTraits::Readaccepts it, admitting an image whose geometry does not match its stride-validated backing. - Or resize via visible rect
For a non-blob image template, call the path that invokes
set_image_visible_rectwith arectlarger than the template’s validated size; pre-patch this overwritesdescriptor.size. - Geometry/backing mismatch
WebRender now holds an image whose declared
size/widthexceeds or diverges from the bytes it was validated to contain. - Primitive A consumer that computes read/write extents from the (now-inflated/invalid) size indexes outside the image’s backing buffer: out-of-bounds access in the GPU process. (sink inferred)
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifgfx/layers/wr/WebRenderMessageUtils.h |
modified |
Files Changed
gfx/layers/wr/WebRenderMessageUtils.hgfx/wr/webrender/src/resource_cache.rs
Audit Directions
- IPC descriptor validatorsAudit other
ParamTraitsreaders for gfx/WebRender types that check an upper bound (stride/bpp < width) without also rejecting non-positive dimensions, which can invert comparisons. - Size vs. backing coherenceReview every writer of
image.descriptor.size/visible_rectand other geometry fields for assumptions that the new size still matches the validated data and stride. - Blob vs. non-blob branchingSearch for resource-cache operations that treat blob and non-blob templates uniformly where only blob images may be dynamically resized.
Patch
diff --git a/gfx/layers/wr/WebRenderMessageUtils.h b/gfx/layers/wr/WebRenderMessageUtils.h
index a3360dd1fea..3becfd81064 100644
--- a/gfx/layers/wr/WebRenderMessageUtils.h
+++ b/gfx/layers/wr/WebRenderMessageUtils.h
@@ -59,7 +59,8 @@ struct ParamTraits<mozilla::wr::ImageDescriptor> {
if (stride != 0) {
int bpp = mozilla::gfx::BytesPerPixel(
mozilla::wr::ImageFormatToSurfaceFormat(format));
- if (bpp <= 0 || stride / bpp < width) {
+ // If stride specified, width must be > 0.
+ if (bpp <= 0 || width <= 0 || stride / bpp < width) {
return {};
}
}
diff --git a/gfx/wr/webrender/src/resource_cache.rs b/gfx/wr/webrender/src/resource_cache.rs
index 0efa7b917f8..bdbda9b3a91 100644
--- a/gfx/wr/webrender/src/resource_cache.rs
+++ b/gfx/wr/webrender/src/resource_cache.rs
@@ -1283,6 +1283,11 @@ impl ResourceCache {
fn set_image_visible_rect(&mut self, key: ImageKey, rect: &DeviceIntRect) {
if let Some(image) = self.resources.image_templates.get_mut(key) {
+ // Only blob images are sized by their visible rect; other templates must
+ // keep the size that their data and stride were validated against.
+ if !image.data.is_blob() {
+ return;
+ }
image.visible_rect = *rect;
image.descriptor.size = rect.size();
}