Moderate firefox Type Confusion 🔧 Commit mapped

Overview

Moderate
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionSandbox escape due to use-after-free in the Graphics: WebRender component
ComponentGraphics
Bug ClassType Confusion
Tracker2079499
Fix commit74b7fd62f5cc (firefox) +19/-9
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Background

WebRender `prepare.rs`
The frame-preparation stage of WebRender (gfx/wr/webrender), which turns primitives into GPU draw commands. Runs in the GPU/compositor process.
YUV image primitive
A multi-plane image (e.g. 2–3 planes for Y/Cb/Cr). Each plane is an image resource referenced by a key in yuv_image_data.yuv_key.
`ImageBufferKind` / sampler kind
Describes how a plane’s texture is sampled: e.g. Texture2D (texture-cache backed: raw/blob/buffer) vs. an external-texture kind (ExternalImageType::TextureHandle(kind), e.g. a hardware/external image). The sampler kind selects which ps_quad_yuv shader variant is used.
`yuv_planes_sampler_kind`
Helper that computes the single sampler kind for a YUV image’s planes, used to pick the shader variant and fill sampler_kind in the quad descriptor.
Shader-variant/sampler contract
A given ps_quad_yuv variant binds and samples all planes through one sampler type; the planes must therefore agree on their sampler kind.

Root Cause Analysis

yuv_planes_sampler_kind in gfx/wr/webrender/src/prepare.rs returned a single ImageBufferKind for the whole YUV image but did not verify that all planes actually shared that kind. The old loop returned the kind of the first plane whose image properties carried an ExternalImageType::TextureHandle(kind), and otherwise fell through to ImageBufferKind::Texture2D. If the planes had mismatched sampler kinds (e.g. one external TextureHandle, another texture-cache Texture2D), the function still produced one kind and prepare_prim_for_render proceeded to draw: it selected a ps_quad_yuv shader variant for that one kind and sampled every plane through that single sampler type. Sampling a plane whose real backing/texture type does not match the bound sampler is an invalid GPU operation against mismatched external-image bindings.

The patch changes yuv_planes_sampler_kind to return Option<ImageBufferKind>: it now evaluates each plane’s kind (external TextureHandle kind, else Texture2D) and, via sampler_kind.get_or_insert(kind), returns None the moment any plane disagrees. prepare_prim_for_render matches on the result and returns early (skips drawing) when it is None, so a YUV image whose planes do not all share one sampler kind is never drawn. Note the advisory labels this a “sandbox escape due to use-after-free”; the diff substantiates a sampler-kind consistency bug — planes sampled through the wrong sampler/shader variant — rather than an explicit freed-memory access. The step from “external texture-handle plane bound/sampled as the wrong type” to a memory-safety/UAF primitive in the GPU process is the inferred escalation, consistent with the advisory, but is not shown by the diff.

Key insight
yuv_planes_sampler_kind assumed all planes of a YUV image shared one sampler kind and returned the first plane’s kind unchecked, so a YUV image with mismatched plane kinds was drawn through a single shader variant that samples every plane — including external texture handles — through the wrong sampler type.

Attack Path

  1. Craft a YUV image Web content constructs a YUV image primitive whose planes resolve to different image kinds (e.g. one plane an external TextureHandle, another a texture-cache Texture2D). (reachability inferred)
  2. Reach non-composited path Arrange for the non-composited “draw directly through the quad path” branch of prepare_prim_for_render, which calls yuv_planes_sampler_kind.
  3. Mis-selected variant Pre-patch, the helper returns one kind despite the mismatch; a single ps_quad_yuv variant is chosen for all planes.
  4. Wrong-sampler draw The GPU samples each plane through the one sampler type of that variant, including an external texture-handle plane bound as the wrong type.
  5. Primitive Invalid cross-type texture sampling in the GPU process; the advisory’s use-after-free of an external-image resource is the inferred memory-safety consequence. (inferred)

Impact Assessment

The code runs in the GPU/compositor process; the trigger is web content that produces a YUV image with planes of differing sampler kinds (no special privilege). The diff concretely proves a sampler-kind mismatch that causes planes to be sampled through the wrong shader variant/sampler — a logic/type-confusion defect in draw-command preparation. The advisory frames the resulting bug as a use-after-free sandbox escape in the GPU process; that freed-memory primitive and the sandbox-escape value are consistent with the advisory but are inferred, since the diff shows the mis-binding rather than the dereference of freed memory.

Files Changed

  • gfx/wr/webrender/src/prepare.rs

Audit Directions

  • Per-plane property assumptions
    Audit other WebRender multi-plane/multi-resource paths that derive one property (format, sampler kind, image type) from a single plane and apply it to all.
  • External vs. texture-cache sampling
    Review shader-variant selection wherever ExternalImageType::TextureHandle and Texture2D-backed resources can coexist, ensuring the bound sampler matches each sampled texture’s real type.
  • "Cannot be drawn" early returns
    Check that other preparation helpers which can legitimately fail to produce a draw signal it (via Option/early return) rather than falling back to a default that forces an invalid draw.
diff --git a/gfx/wr/webrender/src/prepare.rs b/gfx/wr/webrender/src/prepare.rs
index 813885c68ae..a42c65235f9 100644
--- a/gfx/wr/webrender/src/prepare.rs
+++ b/gfx/wr/webrender/src/prepare.rs
@@ -236,21 +236,26 @@ fn prepare_primitives(
 }
 
 /// Returns the texture sampler kind used by a YUV image's planes, which selects
-/// the matching ps_quad_yuv shader variant. All planes are expected to share the
-/// same kind. Texture-cache backed images (raw/blob/buffer) are always Texture2D.
+/// the matching ps_quad_yuv shader variant. Texture-cache backed images
+/// (raw/blob/buffer) are always Texture2D. Returns None if the planes do not all
+/// share the same kind: every plane is sampled through the one sampler type of
+/// the selected shader variant, so such an image cannot be drawn.
 fn yuv_planes_sampler_kind(
     yuv_image_data: &crate::prim_store::image::YuvImageData,
     resource_cache: &crate::resource_cache::ResourceCache,
-) -> ImageBufferKind {
+) -> Option<ImageBufferKind> {
     let plane_count = yuv_image_data.format.get_plane_num();
+    let mut sampler_kind = None;
     for key in &yuv_image_data.yuv_key[.. plane_count] {
-        if let Some(ExternalImageData { image_type: ExternalImageType::TextureHandle(kind), .. }) =
-            resource_cache.get_image_properties(*key).and_then(|props| props.external_image)
-        {
-            return kind;
+        let kind = match resource_cache.get_image_properties(*key).and_then(|props| props.external_image) {
+            Some(ExternalImageData { image_type: ExternalImageType::TextureHandle(kind), .. }) => kind,
+            _ => ImageBufferKind::Texture2D,
+        };
+        if *sampler_kind.get_or_insert(kind) != kind {
+            return None;
         }
     }
-    ImageBufferKind::Texture2D
+    sampler_kind
 }
 
 fn prepare_prim_for_render(
@@ -679,6 +684,11 @@ fn prepare_prim_for_render(
                 return;
             }
 
+            let sampler_kind = match yuv_planes_sampler_kind(yuv_image_data, frame_state.resource_cache) {
+                Some(sampler_kind) => sampler_kind,
+                None => return,
+            };
+
             // Non-composited: draw the YUV image directly through the quad path.
             let planes = yuv_image_data.update(
                 prim_info.compositor_surface_kind.is_composited(),
@@ -690,7 +700,7 @@ fn prepare_prim_for_render(
                 format: yuv_image_data.format,
                 color_space: yuv_image_data.color_space.with_range(yuv_image_data.color_range),
                 channel_bit_depth: yuv_image_data.color_depth.bit_depth(),
-                sampler_kind: yuv_planes_sampler_kind(yuv_image_data, frame_state.resource_cache),
+                sampler_kind,
             };
 
             quad::prepare_quad(
Loading diff…