Medium watch CVSS 6.5 chrome Uninitialized Memory 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUninitialized Use in WebCodecs
DescriptionUninitialized Use in WebCodecs
ComponentWebCodecs
Sub-componentMedia
Bug ClassUninitialized Memory
Action tierwatch — information disclosure — security-relevant, below the RCE-class bar
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.
Tracker486506202
Fix commitc0244cac701a (chromium/src) +46/-36
CWECWE-457 (Use of uninitialized)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedIdentified by the Octane Security Team: Giovanni Vignone, Paolo Gentry, Robert van Eijk
Reported2026-02-22 (per vendor advisory)
Fix landed2026-03-03 (public git)
Disclosed2026-04-07
Patch gap35 days — fix public in source before the advisory

Background

`FrameBufferPool`
A media helper that allocates and recycles backing buffers for decoded video frames, shared by the software video decoders.
`zero_initialize_memory_`
A per-pool boolean that selects whether new buffers are allocated with base::UncheckedCalloc (zeroed) or base::UncheckedMalloc (uninitialized).
Alpha plane
An optional extra buffer that holds per-pixel transparency for a decoded frame, allocated via AllocateAlphaPlaneForFrameBuffer.
Padding bytes
Bytes inside a reused/over-sized buffer that the decoder writes pixel data into but may not fully overwrite, leaving residual heap contents.

Root Cause Analysis

The dav1d (AV1) and libvpx (VP8/VP9) software decoders constructed their FrameBufferPool with the default constructor, which left zero_initialize_memory_ false, so the main frame-buffer path allocated memory with base::UncheckedMalloc and the removed comment explicitly noted “the new array is purposely not initialized.” Because a decoded frame’s strides, visible region, and padding need not cover every allocated byte, any bytes the decoder failed to write retained whatever heap contents previously lived there, and those frames are observable to web content (e.g. drawn to a canvas and read back).

The fix passes /*zero_initialize_memory=*/true when the dav1d and vpx decoders create their pools, and it also routes both the frame-buffer and the alpha-plane allocations through a new shared AllocateMemory helper so the alpha plane now honors the same flag; previously AllocateAlphaPlaneForFrameBuffer unconditionally used base::UncheckedMalloc and never zeroed. The invariant restored is that buffers handed to a decoder (and ultimately to the page) begin fully zeroed, so uninitialized heap bytes can no longer survive into a presented frame.

Key insight
The software VPx/AV1 decoder pools were created without requesting zero-initialization (and the alpha-plane allocator ignored the flag entirely), so uninitialized heap padding could leak into decoded frames; the fix opts those pools into zero_initialize_memory_ and makes the alpha-plane path respect it through the shared AllocateMemory helper.

Attack Path

  1. Craft media that under-fills buffers An attacker serves VP8/VP9 or AV1 content (via WebCodecs or a <video>/media element) whose plane geometry leaves padding or stride bytes that the decoder does not overwrite.
  2. Force an uninitialized allocation Decoding triggers FrameBufferPool::GetFrameBuffer (or AllocateAlphaPlaneForFrameBuffer) to allocate a new, non-zeroed buffer via base::UncheckedMalloc, which still contains prior heap contents.
  3. Read back residual bytes The decoded frame, including its uninitialized padding, is exposed to the page; reading it back (e.g. via canvas pixel access) discloses leftover heap memory (inferred as to what specific allocations it reveals).

Impact Assessment

Per the vendor advisory this is rated medium, “Uninitialized Use in WebCodecs,” with no exploited-in-the-wild claim. An attacker who can have the renderer decode attacker-chosen VP8/VP9/AV1 frames with the software decoders can read uninitialized heap bytes that leak into decoded output, yielding an information disclosure within the renderer/GPU process that handles decoding; the exact data recovered depends on heap state (inferred). No memory corruption or escalation is demonstrated by the patch.

Changed Functions

FunctionChangeNotes
if
media/base/frame_buffer_pool.cc
modified
for
media/base/frame_buffer_pool_unittest.cc
modified
if
media/filters/dav1d_video_decoder.cc
modified

Files Changed

  • media/base/frame_buffer_pool.cc
  • media/base/frame_buffer_pool_unittest.cc
  • media/filters/dav1d_video_decoder.cc
  • media/filters/vpx_video_decoder.cc

Audit Directions

  • Allocator flag parity across siblings
    When one decoder/pool already zero-initializes (here, the ffmpeg decoder) and a sibling does not, audit every construction site of the shared pool/allocator for a missing flag.
  • Flag-ignoring allocation paths
    Where a type carries a “zero on allocate” flag, check that every allocation method (main buffer, alpha/aux planes, resize paths) actually consults it rather than hard-coding malloc.
  • Buffer reuse and partial writes
    Audit recycled/over-sized buffers whose consumers write only a geometry-dependent subset of bytes, since stride and padding gaps are classic uninitialized-leak sources that reach the page.
From c0244cac701a5bfadcd275dc10ee3d12ef593908 Mon Sep 17 00:00:00 2001
From: Eugene Zemtsov <[email protected]>
Date: Mon, 02 Mar 2026 19:16:41 -0800
Subject: [PATCH] media: Zero-initialize frames in software decoders to prevent info leaks

Pass `zero_initialize_memory=true` when creating FrameBufferPools for
libvpx and dav1d decoders. Also update AllocateAlphaPlaneForFrameBuffer
to respect this flag. This prevents potential heap information
disclosure from uninitialized padding bytes.

We already do it for ffmpeg decoder.

Bug: 486506202
Change-Id: I5e88f827f7043cfebef140524092e5543d7d03a3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7616614
Reviewed-by: Dale Curtis <[email protected]>
Commit-Queue: Eugene Zemtsov <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1592929}
---

diff --git a/media/base/frame_buffer_pool.cc b/media/base/frame_buffer_pool.cc
index e90f070..ceb0313c 100644
--- a/media/base/frame_buffer_pool.cc
+++ b/media/base/frame_buffer_pool.cc
@@ -63,6 +63,33 @@
   base::TimeTicks last_use_time;
 };
 
+namespace {
+
+BytesArray AllocateMemory(size_t min_size,
+                          bool zero_initialize,
+                          bool force_error) {
+  if (force_error) {
+    return {};
+  }
+
+  uint8_t* data = nullptr;
+  const bool result =
+      zero_initialize
+          ? base::UncheckedCalloc(1u, min_size, reinterpret_cast<void**>(&data))
+          : base::UncheckedMalloc(min_size, reinterpret_cast<void**>(&data));
+
+  // Unclear why, but the docs indicate both that `data` will be null on
+  // failure, and also that the return value must not be discarded.
+  if (!result || !data) {
+    return {};
+  }
+
+  // SAFETY: We have just allocated `min_size` of memory for `data`.
+  return UNSAFE_BUFFERS(BytesArray::FromOwningPointer(data, min_size));
+}
+
+}  // namespace
+
 FrameBufferPool::FrameBufferPool(bool zero_initialize_memory)
     : zero_initialize_memory_(zero_initialize_memory),
       tick_clock_(base::DefaultTickClock::GetInstance()) {}
@@ -98,35 +125,15 @@
   frame_buffer->held_by_library = true;
   if (frame_buffer->data.size() < min_size) {
     // Free the existing |data| first so that the memory can be reused,
-    // if possible. Note that the new array is purposely not initialized.
+    // if possible.
     frame_buffer->data = {};
+    frame_buffer->data = AllocateMemory(min_size, zero_initialize_memory_,
+                                        force_allocation_error_);
 
-    uint8_t* data = nullptr;
-    if (!force_allocation_error_) {
-      bool result = false;
-      if (zero_initialize_memory_) {
-        result = base::UncheckedCalloc(1u, min_size,
-                                       reinterpret_cast<void**>(&data));
-      } else {
-        result =
-            base::UncheckedMalloc(min_size, reinterpret_cast<void**>(&data));
-      }
-
-      // Unclear why, but the docs indicate both that `data` will be null on
-      // failure, and also that the return value must not be discarded.
-      if (!result) {
-        data = nullptr;
-      }
-    }
-
-    if (!data) {
+    if (frame_buffer->data.empty()) {
       frame_buffers_.erase(it);
       return {};
     }
-
-    // SAFETY: We have just allocated `min_size` of memory for `data`.
-    frame_buffer->data =
-        UNSAFE_BUFFERS(BytesArray::FromOwningPointer(data, min_size));
   }
 
   // Provide the client with a private identifier.
@@ -158,17 +165,10 @@
   DCHECK(IsUsedLocked(frame_buffer));
   if (frame_buffer->alpha_data.size() < min_size) {
     // Free the existing |alpha_data| first so that the memory can be reused,
-    // if possible. Note that the new array is purposely not initialized.
+    // if possible.
     frame_buffer->alpha_data = {};
-    uint8_t* data = nullptr;
-    if (force_allocation_error_ ||
-        !base::UncheckedMalloc(min_size, reinterpret_cast<void**>(&data)) ||
-        !data) {
-      return {};
-    }
-    // SAFETY: We have just allocated `min_size` of memory for `data`.
-    frame_buffer->alpha_data =
-        UNSAFE_BUFFERS(BytesArray::FromOwningPointer(data, min_size));
+    frame_buffer->alpha_data = AllocateMemory(min_size, zero_initialize_memory_,
+                                              force_allocation_error_);
   }
   return frame_buffer->alpha_data;
 }
diff --git a/media/base/frame_buffer_pool_unittest.cc b/media/base/frame_buffer_pool_unittest.cc
index a5b7bff2..893e941 100644
--- a/media/base/frame_buffer_pool_unittest.cc
+++ b/media/base/frame_buffer_pool_unittest.cc
@@ -131,6 +131,14 @@
     nonzero |= !!buf[i];
   }
   EXPECT_FALSE(nonzero);
+
+  auto alpha_buf = pool->AllocateAlphaPlaneForFrameBuffer(kBufferSize, priv1);
+  nonzero = false;
+  for (size_t i = 0; i < kBufferSize; i++) {
+    nonzero |= !!alpha_buf[i];
+  }
+  EXPECT_FALSE(nonzero);
+
   pool->Shutdown();
 }
 
diff --git a/media/filters/dav1d_video_decoder.cc b/media/filters/dav1d_video_decoder.cc
index baef967..3456cd5 100644
--- a/media/filters/dav1d_video_decoder.cc
+++ b/media/filters/dav1d_video_decoder.cc
@@ -285,7 +285,8 @@
   }
 
   if (!frame_pool_) {
-    frame_pool_ = base::MakeRefCounted<FrameBufferPool>();
+    frame_pool_ =
+        base::MakeRefCounted<FrameBufferPool>(/*zero_initialize_memory=*/true);
   }
 
   // Clear any previously initialized decoder.
diff --git a/media/filters/vpx_video_decoder.cc b/media/filters/vpx_video_decoder.cc
index 14b4044..d197a8feb 100644
--- a/media/filters/vpx_video_decoder.cc
+++ b/media/filters/vpx_video_decoder.cc
@@ -245,7 +245,8 @@
            VPX_CODEC_CAP_EXTERNAL_FRAME_BUFFER);
 
     DCHECK(!memory_pool_);
-    memory_pool_ = base::MakeRefCounted<FrameBufferPool>();
+    memory_pool_ =
+        base::MakeRefCounted<FrameBufferPool>(/*zero_initialize_memory=*/true);
 
     if (vpx_codec_set_frame_buffer_functions(
             vpx_codec_.get(), &GetVP9FrameBuffer, &ReleaseVP9FrameBuffer,
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/media/base/frame_buffer_pool_unittest.cc b/media/base/frame_buffer_pool_unittest.cc
index a5b7bff2..893e941 100644
--- a/media/base/frame_buffer_pool_unittest.cc
+++ b/media/base/frame_buffer_pool_unittest.cc
@@ -131,6 +131,14 @@
     nonzero |= !!buf[i];
   }
   EXPECT_FALSE(nonzero);
+
+  auto alpha_buf = pool->AllocateAlphaPlaneForFrameBuffer(kBufferSize, priv1);
+  nonzero = false;
+  for (size_t i = 0; i < kBufferSize; i++) {
+    nonzero |= !!alpha_buf[i];
+  }
+  EXPECT_FALSE(nonzero);
+
   pool->Shutdown();
 }
Loading diff…

Original Bug Report

reported by [email protected]

Uninitialized alpha-plane padding in VP9-with-alpha external frame buffer decode causes renderer heap data disclosure via WebCodecs VideoFrame.copyTo(codedRect)


Report description

Uninitialized alpha-plane padding in VP9-with-alpha external frame buffer decode causes renderer heap data disclosure via WebCodecs VideoFrame.copyTo(codedRect)


Bug location

Where do you want to report your vulnerability?

Chrome VRP – Report security issues affecting the Chrome browser. See program rules

Which URL (or repository) have you found the vulnerability in?

https://github.com/chromium/chromium


The problem

Please describe the technical details of the vulnerability

When decoding WebM VP9 content with alpha enabled, Chromium allocates an uninitialized alpha-plane buffer and fills only the visible-width alpha pixels. If the decoded frame’s coded width is greater than its visible width (e.g. due to stride alignment), the per-row padding bytes in the alpha plane are never written and remain uninitialized. WebCodecs VideoFrame.copyTo() allows JavaScript to request a copy using a rectangle bounded by the frame’s coded size. For CPU-backed (mappable) frames, the Blink copy path copies src_rect.width() bytes per row from each plane and does not clamp to the visible rect. By calling copyTo() with a rect that spans the full coded width and height, an attacker can copy and then read the uninitialized alpha-plane padding bytes from JavaScript, resulting in renderer heap information disclosure.

Root cause chain

  1. WebM / demuxer: When the track has AlphaMode = 1 and a block has block-additional data whose first 8 bytes (BlockAddID, big endian) equal 1, the remainder is stored in DecoderBuffer::side_data()->alpha_data (see media/formats/webm/webm_cluster_parser.cc and webm_video_client.cc).

  2. Decoder: In VpxVideoDecoder, the VP9 external frame buffer path allocates the alpha plane in FrameBufferPool::AllocateAlphaPlaneForFrameBuffer() (media/base/frame_buffer_pool.cc), which uses base::UncheckedMalloc() and does not initialize the memory (comment: “the new array is purposely not initialized”). The alpha plane is then filled via libyuv::CopyPlane() with width = vpx_image_alpha->d_w (visible width) and height = d_h. The allocation size is stride × height (e.g. vpx_image_alpha->stride[VPX_PLANE_Y] * d_h). When stride (coded width) > d_w, the bytes at the end of each row are never written.

  3. VideoFrame: The resulting VideoFrame has coded_size() from (vpx_image->w, vpx_image->d_h) and visible_rect() from (d_w, d_h). So coded_size.width() can exceed visible_rect().width().

  4. WebCodecs: In Blink, VideoFrame.copyTo(options) validates the source rect against the frame’s coded size (ParseCopyToOptions → ToGfxRect(..., frame.coded_size(), ...)). For mappable frames, CopyMappablePlanes() copies PlaneSize(src_rect.width(), ...) bytes per row from each plane (third_party/blink/renderer/modules/webcodecs/video_frame.cc). No clamping to the visible rect is applied, so a rect covering the full coded size causes the uninitialized padding to be copied into the destination buffer and exposed to script.

Precondition: The decoded frame has coded width > visible width (e.g. odd or non-aligned width such as 9 or 17). When this holds, the read is deterministic; the content of the leaked bytes depends on allocator state (prior heap usage).

Impact analysis

Who can exploit it

Any attacker who can run JavaScript in the renderer can exploit this, for example by hosting a page that the victim visits (e.g. via link, ad, or compromised site), or by utilizing an XSS primitive or other bug that allows execution of script in a Chrome tab. No special permissions or user interaction beyond loading the page are required.

What they gain

The attacker gains read access to uninitialized renderer heap memory (the alpha-plane padding). That can include pointers (e.g. heap addresses), which can support ASLR bypass or heap layout inference, leftover data from previous allocations (strings, structures, etc.), and stable information disclosure when the same allocation patterns are repeated (e.g. repeated decoding with the same or similar frame sizes). By itself, it does not escape the renderer sandbox or escalate privileges, but it turns an information-disclosure primitive into javascript-readable data that can support further exploitation (e.g. heap grooming, pointer leaks for a separate bug).

Severity Analysis

Given the Severity Guidelines for Security Issues, a medium severity classification is well-supported for this finding.


The cause

What version of Chrome have you found the security issue in?

Current Live Stable Release: 145.0.7632.110 and 145.0.7632.103

No, it is not related to a crash.

Choose the type of vulnerability

Information Leak

How would you like to be publicly acknowledged for your report?

Identified by the Octane Security Team: Giovanni Vignone, Paolo Gentry, Robert van Eijk

View on issue tracker