High patch-now ✔︎ verified CVSS 8.8 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebCodecs
DescriptionUse after free in WebCodecs
ComponentWebCodecs
Sub-componentMedia
Bug ClassUAF
Action tierpatch-now — corruption primitive, web-reachable in default config (confirmed) — upheld 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 AIConfirmed
Reviewer notes: none substantive -- diff and commit message confirm the missing memory-ownership comparison in has_changed and the new vpx_image_owns_memory_ member; direction/UAF details are inferred but appropriately flagged.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker491515787
Fix commit46bc46aeb674 (chromium/src) +56/-1
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
Creditedheapracer (@heapracer)
Reported2026-03-11 (per vendor advisory)
Fix landed2026-03-18 (public git)
Disclosed2026-03-31
Patch gap13 days — fix public in source before the advisory

Background

WebCodecs `VideoEncoder`
a JavaScript API that lets web content feed VideoFrames to a browser-side encoder such as the software VP9 encoder in media.
`vpx_image_t` / `vpx_image_`
libvpx’s image wrapper struct; the encoder keeps one instance (vpx_image_) and reuses it across frames to avoid per-frame allocation.
`vpx_img_alloc` / `vpx_img_free`
libvpx calls that allocate (encoder-owned) and free a vpx_image_t’s backing buffers; a wrapper can instead point at external memory it does not own.
Memory ownership (`needs_memory`)
a per-frame flag: true means the encoder must allocate its own buffer for vpx_image_, false means the wrapper will point at the frame’s externally supplied planes.

Root Cause Analysis

The vpx image (re)allocation helper in vpx_video_encoder.cc (whose enclosing function name is not shown in the @@ hunk — only its trailing parameter line bool needs_memory) is visible) decides whether to rebuild vpx_image_ by computing has_changed.

Before the fix, has_changed compared only the pixel format (vpx_image_.fmt) and dimensions (d_w/d_h); it never compared whether the wrapper’s current memory-ownership mode matched the new needs_memory requirement. So when a frame kept the same format and size but flipped the ownership requirement — e.g. a prior frame supplied external memory (needs_memory == false) and the next frame required an encoder-allocated buffer (or the reverse) — has_changed stayed false, the function returned early, and vpx_img_free/vpx_img_alloc were skipped, leaving vpx_image_ configured for the old ownership mode.

The fix adds vpx_image_owns_memory_ != needs_memory to has_changed and records the mode in the new vpx_image_owns_memory_ member right after freeing, forcing the wrapper to be torn down and rebuilt whenever ownership changes so stale external pointers are never reused (inferred use-after-free of the previous frame’s external planes).

Key insight
The reuse check tested format and dimensions but omitted the memory-ownership dimension, so the wrapper could be reused with pointers belonging to a different (now-gone) backing; the fix adds that missing comparison and tracks ownership in vpx_image_owns_memory_.

Attack Path

  1. Open an encoder Web content creates a WebCodecs VideoEncoder configured for VP9 Profile 2 or Profile 3 (10-bit), routed to the software VpxVideoEncoder.
  2. Encode an external-memory frame Submit a frame whose planes let the encoder wrap external memory (needs_memory == false), so vpx_image_ points at that frame’s buffers.
  3. Encode a same-size frame needing allocation Submit a following frame with the same format and dimensions but requiring an encoder-owned buffer (needs_memory == true); the pre-fix check sees no change and reuses the stale wrapper.
  4. Trigger the stale access The encoder operates on vpx_image_ whose pointers refer to the earlier frame’s external memory that is no longer valid (inferred use-after-free); the shipped Vpx10BitVideoEncoderTest.EncodeDifferentMemoryTypes reproduces exactly this alternation.

Impact Assessment

An attacker running ordinary web content in a stock build can reach this path through the WebCodecs VideoEncoder API using VP9 Profile 2/3, with no special flag, extension, or policy required; the bug executes in the process hosting the software encoder (the renderer). The vendor advisory rates this high and classifies it as a use-after-free in WebCodecs; any read/write primitive or escalation beyond the stale-pointer reuse the patch addresses is (inferred) and not demonstrated by the diff.

Changed Functions

FunctionChangeNotes
H264VideoEncoderTest
media/video/software_video_encoder_test.cc
modified
Vpx10BitVideoEncoderTest
media/video/software_video_encoder_test.cc
modified
SVCVideoEncoderTest
media/video/software_video_encoder_test.cc
modified
ManualSVCVideoEncoderTest
media/video/software_video_encoder_test.cc
modified
LargeTimestampOverflowTest
media/video/software_video_encoder_test.cc
modified
TEST_P
media/video/software_video_encoder_test.cc
modified
if
media/video/vpx_video_encoder.cc
modified

Files Changed

  • media/video/software_video_encoder_test.cc
  • media/video/vpx_video_encoder.cc
  • media/video/vpx_video_encoder.h

Audit Directions

  • Cached wrapper reuse keyed on an incomplete signature
    audit any encoder/decoder that reuses a persistent buffer or image wrapper across frames; ensure the “has anything changed” test covers ownership/lifetime, not only format and dimensions.
  • External vs owned memory transitions
    look for code paths that can alternate between wrapping caller-supplied memory and self-allocated memory, and verify the transition forces a free/realloc rather than silently reusing pointers.
  • WebCodecs profile-specific branches
    review VP9 Profile 1/2/3 and other high-bit-depth paths in media/video encoders where format conversion and allocation decisions diverge from the common 8-bit case.
From 46bc46aeb6740ddee2c122ad1f5045b12b37a83a Mon Sep 17 00:00:00 2001
From: Eugene Zemtsov <eugene@chromium.org>
Date: Tue, 17 Mar 2026 18:48:13 -0700
Subject: [PATCH] media: Fix VpxVideoEncoder memory ownership bug

When encoding VP9 Profile 2 or 3, the encoder reuses its internal
image wrapper (`vpx_image_t`) between frames to save allocations.
However, it only checked if the format and dimensions changed.

If one frame provided external memory and the next frame required
the encoder to allocate its own buffer, the encoder would incorrectly
try to reuse the old external memory pointers.

This CL adds a check to ensure the image wrapper is recreated when
the memory ownership requirement changes between frames.

Bug: 491515787
Change-Id: If08d974edb10348a7fae18754788c0102e03c292
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7670921
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Commit-Queue: Eugene Zemtsov <eugene@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1600950}
---

diff --git a/media/video/software_video_encoder_test.cc b/media/video/software_video_encoder_test.cc
index 4ec61cd..3e879b8 100644
--- a/media/video/software_video_encoder_test.cc
+++ b/media/video/software_video_encoder_test.cc
@@ -316,6 +316,7 @@
 };
 
 class H264VideoEncoderTest : public SoftwareVideoEncoderTest {};
+class Vpx10BitVideoEncoderTest : public SoftwareVideoEncoderTest {};
 class SVCVideoEncoderTest : public SoftwareVideoEncoderTest {};
 class ManualSVCVideoEncoderTest : public SoftwareVideoEncoderTest {};
 class LargeTimestampOverflowTest : public SoftwareVideoEncoderTest {};
@@ -590,6 +591,46 @@
   EXPECT_EQ(total_decoded_frames, total_frames_count);
 }
 
+TEST_P(Vpx10BitVideoEncoderTest, EncodeDifferentMemoryTypes) {
+  VideoEncoder::Options options = CreateDefaultOptions();
+  options.frame_size = gfx::Size(2000, 2000);
+
+  encoder_->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
+                       /*output_cb=*/base::DoNothing(),
+                       ValidateStatusThenQuitCB());
+  RunUntilQuit();
+
+  VideoPixelFormat format1 = (profile_ == VP9PROFILE_PROFILE2)
+                                 ? PIXEL_FORMAT_YUV420P10
+                                 : PIXEL_FORMAT_YUV444P10;
+  VideoPixelFormat format2 =
+      (profile_ == VP9PROFILE_PROFILE2) ? PIXEL_FORMAT_I420 : PIXEL_FORMAT_I444;
+
+  // Encode a frame that doesn't need its own memory wrapper allocation.
+  auto frame1 = media::VideoFrame::CreateZeroInitializedFrame(
+      format1, options.frame_size, gfx::Rect(options.frame_size),
+      options.frame_size, base::Seconds(1));
+  encoder_->Encode(std::move(frame1), VideoEncoder::EncodeOptions(true),
+                   ValidateStatusThenQuitCB());
+  ASSERT_NO_FATAL_FAILURE(RunUntilQuit());
+
+  // Encode a frame that DOES need its own memory wrapper allocation.
+  auto frame2 = media::VideoFrame::CreateZeroInitializedFrame(
+      format2, options.frame_size, gfx::Rect(options.frame_size),
+      options.frame_size, base::Seconds(2));
+  encoder_->Encode(std::move(frame2), VideoEncoder::EncodeOptions(false),
+                   ValidateStatusThenQuitCB());
+  ASSERT_NO_FATAL_FAILURE(RunUntilQuit());
+
+  // Encode the first format again.
+  auto frame3 = media::VideoFrame::CreateZeroInitializedFrame(
+      format1, options.frame_size, gfx::Rect(options.frame_size),
+      options.frame_size, base::Seconds(3));
+  encoder_->Encode(std::move(frame3), VideoEncoder::EncodeOptions(false),
+                   ValidateStatusThenQuitCB());
+  ASSERT_NO_FATAL_FAILURE(RunUntilQuit());
+}
+
 TEST_P(SoftwareVideoEncoderTest, EncodeAndDecodeWithEnablingDrop) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(kWebCodecsVideoEncoderFrameDrop);
@@ -1527,6 +1568,15 @@
                          ::testing::ValuesIn(kVpxParams),
                          PrintTestParams);
 
+SwVideoTestParams kVpx10BitParams[] = {
+    {VideoCodec::kVP9, VP9PROFILE_PROFILE2, PIXEL_FORMAT_I420},
+    {VideoCodec::kVP9, VP9PROFILE_PROFILE3, PIXEL_FORMAT_I420}};
+
+INSTANTIATE_TEST_SUITE_P(Vpx10BitSpecific,
+                         Vpx10BitVideoEncoderTest,
+                         ::testing::ValuesIn(kVpx10BitParams),
+                         PrintTestParams);
+
 SwVideoTestParams kVpxSVCParams[] = {
     {VideoCodec::kVP9, VP9PROFILE_PROFILE0, PIXEL_FORMAT_I420, std::nullopt},
     {VideoCodec::kVP9, VP9PROFILE_PROFILE0, PIXEL_FORMAT_I420,
@@ -1603,6 +1653,7 @@
 #endif  // ENABLE_LIBAOM
 
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(H264VideoEncoderTest);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Vpx10BitVideoEncoderTest);
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SVCVideoEncoderTest);
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SoftwareVideoEncoderTest);
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ManualSVCVideoEncoderTest);
diff --git a/media/video/vpx_video_encoder.cc b/media/video/vpx_video_encoder.cc
index a661729e..6b0df913 100644
--- a/media/video/vpx_video_encoder.cc
+++ b/media/video/vpx_video_encoder.cc
@@ -919,13 +919,15 @@
                                                bool needs_memory) {
   const bool has_changed = vpx_image_.fmt != fmt ||
                            vpx_image_.d_w != codec_config_.g_w ||
-                           vpx_image_.d_h != codec_config_.g_h;
+                           vpx_image_.d_h != codec_config_.g_h ||
+                           vpx_image_owns_memory_ != needs_memory;
 
   if (!has_changed) {
     return;
   }
 
   vpx_img_free(&vpx_image_);
+  vpx_image_owns_memory_ = needs_memory;
   if (needs_memory) {
     CHECK(vpx_img_alloc(&vpx_image_, fmt, codec_config_.g_w, codec_config_.g_h,
                         /*align=*/1));
diff --git a/media/video/vpx_video_encoder.h b/media/video/vpx_video_encoder.h
index 099a3a2..de7ecc4 100644
--- a/media/video/vpx_video_encoder.h
+++ b/media/video/vpx_video_encoder.h
@@ -53,6 +53,8 @@
   vpx_codec_unique_ptr codec_;
   vpx_codec_enc_cfg_t codec_config_ = {};
   vpx_image_t vpx_image_ = {};
+  // True if `vpx_image_` was allocated with vpx_img_alloc().
+  bool vpx_image_owns_memory_ = false;
   gfx::Size originally_configured_size_;
   base::TimeDelta last_frame_timestamp_;
   gfx::ColorSpace last_frame_color_space_;
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/media/video/software_video_encoder_test.cc b/media/video/software_video_encoder_test.cc
index 4ec61cd..3e879b8 100644
--- a/media/video/software_video_encoder_test.cc
+++ b/media/video/software_video_encoder_test.cc
@@ -316,6 +316,7 @@
 };
 
 class H264VideoEncoderTest : public SoftwareVideoEncoderTest {};
+class Vpx10BitVideoEncoderTest : public SoftwareVideoEncoderTest {};
 class SVCVideoEncoderTest : public SoftwareVideoEncoderTest {};
 class ManualSVCVideoEncoderTest : public SoftwareVideoEncoderTest {};
 class LargeTimestampOverflowTest : public SoftwareVideoEncoderTest {};
@@ -590,6 +591,46 @@
   EXPECT_EQ(total_decoded_frames, total_frames_count);
 }
 
+TEST_P(Vpx10BitVideoEncoderTest, EncodeDifferentMemoryTypes) {
+  VideoEncoder::Options options = CreateDefaultOptions();
+  options.frame_size = gfx::Size(2000, 2000);
+
+  encoder_->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
+                       /*output_cb=*/base::DoNothing(),
+                       ValidateStatusThenQuitCB());
+  RunUntilQuit();
+
+  VideoPixelFormat format1 = (profile_ == VP9PROFILE_PROFILE2)
+                                 ? PIXEL_FORMAT_YUV420P10
+                                 : PIXEL_FORMAT_YUV444P10;
+  VideoPixelFormat format2 =
+      (profile_ == VP9PROFILE_PROFILE2) ? PIXEL_FORMAT_I420 : PIXEL_FORMAT_I444;
+
+  // Encode a frame that doesn't need its own memory wrapper allocation.
+  auto frame1 = media::VideoFrame::CreateZeroInitializedFrame(
+      format1, options.frame_size, gfx::Rect(options.frame_size),
+      options.frame_size, base::Seconds(1));
+  encoder_->Encode(std::move(frame1), VideoEncoder::EncodeOptions(true),
+                   ValidateStatusThenQuitCB());
+  ASSERT_NO_FATAL_FAILURE(RunUntilQuit());
+
+  // Encode a frame that DOES need its own memory wrapper allocation.
+  auto frame2 = media::VideoFrame::CreateZeroInitializedFrame(
+      format2, options.frame_size, gfx::Rect(options.frame_size),
+      options.frame_size, base::Seconds(2));
+  encoder_->Encode(std::move(frame2), VideoEncoder::EncodeOptions(false),
+                   ValidateStatusThenQuitCB());
+  ASSERT_NO_FATAL_FAILURE(RunUntilQuit());
+
+  // Encode the first format again.
+  auto frame3 = media::VideoFrame::CreateZeroInitializedFrame(
+      format1, options.frame_size, gfx::Rect(options.frame_size),
+      options.frame_size, base::Seconds(3));
+  encoder_->Encode(std::move(frame3), VideoEncoder::EncodeOptions(false),
+                   ValidateStatusThenQuitCB());
+  ASSERT_NO_FATAL_FAILURE(RunUntilQuit());
+}
+
 TEST_P(SoftwareVideoEncoderTest, EncodeAndDecodeWithEnablingDrop) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(kWebCodecsVideoEncoderFrameDrop);
@@ -1527,6 +1568,15 @@
                          ::testing::ValuesIn(kVpxParams),
                          PrintTestParams);
 
+SwVideoTestParams kVpx10BitParams[] = {
+    {VideoCodec::kVP9, VP9PROFILE_PROFILE2, PIXEL_FORMAT_I420},
+    {VideoCodec::kVP9, VP9PROFILE_PROFILE3, PIXEL_FORMAT_I420}};
+
+INSTANTIATE_TEST_SUITE_P(Vpx10BitSpecific,
+                         Vpx10BitVideoEncoderTest,
+                         ::testing::ValuesIn(kVpx10BitParams),
+                         PrintTestParams);
+
 SwVideoTestParams kVpxSVCParams[] = {
     {VideoCodec::kVP9, VP9PROFILE_PROFILE0, PIXEL_FORMAT_I420, std::nullopt},
     {VideoCodec::kVP9, VP9PROFILE_PROFILE0, PIXEL_FORMAT_I420,
@@ -1603,6 +1653,7 @@
 #endif  // ENABLE_LIBAOM
 
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(H264VideoEncoderTest);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Vpx10BitVideoEncoderTest);
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SVCVideoEncoderTest);
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SoftwareVideoEncoderTest);
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ManualSVCVideoEncoderTest);
Loading diff…

Original Bug Report

reported by sh...@gmail.com

Heap UAF write in libyuv Convert8To16Row_Any_AVX2 via crafted raw VideoFrame input

Note

This is the same issues as https://issues.chromium.org/issues/488516881, which got incorrectly closed. Its reproduced in https://clusterfuzz.com/testcase?key=5670022819020800, which you can see ==1==ERROR: AddressSanitizer: heap-use-after-free on address 0x773c022c6440 at pc 0x5789c2ff30ae bp 0x746bf8394430 sp 0x746bf8393bf0 in the log but somehow Clusterfuzz didn’t manage to recognize it.

VULNERABILITY DETAILS

A WebCodecs VideoEncoder can be driven into a heap-use-after-free with a write primitive in the renderer process. The bug appears to be a lifetime/ownership error in the raw-frame conversion pipeline used by VideoEncoder. Attacker-controlled raw VideoFrame inputs are converted through libyuv (8-bit -> 10-bit path), and stale/freed memory is subsequently written during conversion. The failing write occurs in Convert8To16Row_Any_AVX2 while processing frame buffers derived from JS-supplied frame metadata and payloads (format/layout/stride/coded size/transfer behavior).

Crash log:

==217383==ERROR: AddressSanitizer: heap-use-after-free on address 0x7ea546140980 at pc 0x56334a039cfe bp 0x7b613739cf70 sp 0x7b613739c730
WRITE of size 44 at 0x7ea546140980 thread T4 (ThreadPoolForeg)
[217383:217383:0301/014040.337243:WARNING:media/base/video_frame_converter.cc:81] EncoderStatus::10 Data: {"dst":"format:PIXEL_FORMAT_I420 storage_type:OWNED_MEMORY coded_size:214x176 visible_rect:0,0 214x175 natural_size:214x175 timestamp:2600274 color_space: {primaries:INVALID, transfer:INVALID, matrix:INVALID, range:INVALID} hdr_metadata: {}","src":"format:PIXEL_FORMAT_YUV444P10 storage_type:OWNED_MEMORY coded_size:216x175 visible_rect:0,0 216x175 natural_size:216x175 timestamp:2600274 color_space: {primaries:BT709, transfer:BT709, matrix:BT709, range:LIMITED} hdr_metadata: {}"}
    #0 0x56334a039cfd in __asan_memcpy (/mnt/lvm_data/chromium/src/out/asan_dbg_symbols/chrome+0xf35acfd) (BuildId: 3afd3fb7d064a1b6)
    #1 0x7f667bb54faa in Convert8To16Row_Any_AVX2 third_party/libyuv/source/row_any.cc:1636:1
    #2 0x7f667bb23860 in Convert8To16Plane third_party/libyuv/source/planar_functions.cc:244:5
    #3 0x7f667bb0d7b3 in I420ToI010 third_party/libyuv/source/convert_from.cc:106:3
    #4 0x7f667aec2181 in media::VpxVideoEncoder::Encode(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>) media/video/vpx_video_encoder.cc:662:7
    #5 0x7f667ae2003b in void base::internal::DecayedFunctorTraits<void (media::VideoEncoder::*)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), media::VideoEncoder*, scoped_refptr<media::VideoFrame>&&, media::VideoEncoder::EncodeOptions&&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>&&>::Invoke<void (media::VideoEncoder::*)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), media::VideoEncoder*, scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>>(void (media::VideoEncoder::*)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), media::VideoEncoder*&&, scoped_refptr<media::VideoFrame>&&, media::VideoEncoder::EncodeOptions&&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>&&) base/functional/bind_internal.h:740:12
    #6 0x7f667ae1fd74 in void base::internal::InvokeHelper<false, base::internal::FunctorTraits<void (media::VideoEncoder::*&&)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), media::VideoEncoder*, scoped_refptr<media::VideoFrame>&&, media::VideoEncoder::EncodeOptions&&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>&&>, void, 0ul, 1ul, 2ul, 3ul>::MakeItSo<void (media::VideoEncoder::*)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), std::__Cr::tuple<base::internal::UnretainedWrapper<media::VideoEncoder, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>>>(void (media::VideoEncoder::*&&)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), std::__Cr::tuple<base::internal::UnretainedWrapper<media::VideoEncoder, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>>&&) base/functional/bind_internal.h:932:12
    #7 0x7f667ae1fa47 in void base::internal::Invoker<base::internal::FunctorTraits<void (media::VideoEncoder::*&&)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), media::VideoEncoder*, scoped_refptr<media::VideoFrame>&&, media::VideoEncoder::EncodeOptions&&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>&&>, base::internal::BindState<true, true, false, void (media::VideoEncoder::*)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), base::internal::UnretainedWrapper<media::VideoEncoder, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>>, void ()>::RunImpl<void (media::VideoEncoder::*)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), std::__Cr::tuple<base::internal::UnretainedWrapper<media::VideoEncoder, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>>, 0ul, 1ul, 2ul, 3ul>(void (media::VideoEncoder::*&&)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), std::__Cr::tuple<base::internal::UnretainedWrapper<media::VideoEncoder, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>>&&, std::__Cr::integer_sequence<unsigned long, 0ul, 1ul, 2ul, 3ul>) base/functional/bind_internal.h:1069:14
    #8 0x7f667ae1f878 in base::internal::Invoker<base::internal::FunctorTraits<void (media::VideoEncoder::*&&)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), media::VideoEncoder*, scoped_refptr<media::VideoFrame>&&, media::VideoEncoder::EncodeOptions&&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>&&>, base::internal::BindState<true, true, false, void (media::VideoEncoder::*)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), base::internal::UnretainedWrapper<media::VideoEncoder, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>>, void ()>::RunOnce(base::internal::BindStateBase*) base/functional/bind_internal.h:982:12
    #9 0x7f66af5d3112 in base::OnceCallback<void ()>::Run() && base/functional/callback.h:155:12
    #10 0x7f66afaff12e in base::TaskAnnotator::RunTaskImpl(base::PendingTask&) base/task/common/task_annotator.cc:229:34
    #11 0x7f66afcec7d7 in void base::TaskAnnotator::RunTask<base::internal::TaskTracker::RunTaskImpl(base::internal::Task&, base::TaskTraits const&, base::internal::TaskSource*, base::internal::SequenceToken const&)::$_0>(perfetto::StaticString, base::PendingTask&, base::internal::TaskTracker::RunTaskImpl(base::internal::Task&, base::TaskTraits const&, base::internal::TaskSource*, base::internal::SequenceToken const&)::$_0&&) base/task/common/task_annotator.h:112:5
    #12 0x7f66afcec274 in base::internal::TaskTracker::RunTaskImpl(base::internal::Task&, base::TaskTraits const&, base::internal::TaskSource*, base::internal::SequenceToken const&) base/task/thread_pool/task_tracker.cc:691:19
    #13 0x7f66afcec428 in base::internal::TaskTracker::RunSkipOnShutdown(base::internal::Task&, base::TaskTraits const&, base::internal::TaskSource*, base::internal::SequenceToken const&) base/task/thread_pool/task_tracker.cc:676:3
    #14 0x7f66afcea93d in base::internal::TaskTracker::RunTaskWithShutdownBehavior(base::internal::Task&, base::TaskTraits const&, base::internal::TaskSource*, base::internal::SequenceToken const&) base/task/thread_pool/task_tracker.cc:706:7
    #15 0x7f66afce9a49 in base::internal::TaskTracker::RunTask(base::internal::Task, base::internal::TaskSource*, base::TaskTraits const&, base::ThreadType) base/task/thread_pool/task_tracker.cc:506:5
    #16 0x7f66afce8593 in base::internal::TaskTracker::RunAndPopNextTask(base::internal::RegisteredTaskSource) base/task/thread_pool/task_tracker.cc:394:5
    #17 0x7f66afd3b243 in base::internal::WorkerThread::RunWorker() base/task/thread_pool/worker_thread.cc:473:36
    #18 0x7f66afd3a5ab in base::internal::WorkerThread::RunPooledWorker() base/task/thread_pool/worker_thread.cc:359:3
    #19 0x7f66afd39e25 in base::internal::WorkerThread::ThreadMain() base/task/thread_pool/worker_thread.cc:339:7
    #20 0x7f66afeac06c in base::(anonymous namespace)::ThreadFunc(void*) base/threading/platform_thread_posix.cc:102:13
    #21 0x56334a039896 in asan_thread_start(void*) asan_interceptors.cpp

0x7ea546140980 is located 384 bytes inside of 123967-byte region [0x7ea546140800,0x7ea54615ec3f)
freed by thread T-1 here:
AddressSanitizer: CHECK failed: asan_descriptions.cpp:184 "((res.trace)) != (0)" (0x0, 0x0) (tid=217660)
    #0 0x56334a046d61 in __asan::CheckUnwind() asan_rtl.cpp
    #1 0x56334a061af2 in __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) sanitizer_termination.cpp
    #2 0x563349fa50a3 in __asan::HeapAddressDescription::Print() const asan_descriptions.cpp
    #3 0x563349fa8167 in __asan::ErrorGeneric::Print() asan_errors.cpp
    #4 0x56334a04211f in __asan::ScopedInErrorReport::~ScopedInErrorReport() asan_report.cpp
    #5 0x56334a0444ac in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) asan_report.cpp
    #6 0x56334a039d30 in __asan_memcpy (/mnt/lvm_data/chromium/src/out/asan_dbg_symbols/chrome+0xf35ad30) (BuildId: 3afd3fb7d064a1b6)
    #7 0x7f667bb54faa in Convert8To16Row_Any_AVX2 third_party/libyuv/source/row_any.cc:1636:1
    #8 0x7f667bb23860 in Convert8To16Plane third_party/libyuv/source/planar_functions.cc:244:5
    #9 0x7f667bb0d7b3 in I420ToI010 third_party/libyuv/source/convert_from.cc:106:3
    #10 0x7f667aec2181 in media::VpxVideoEncoder::Encode(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>) media/video/vpx_video_encoder.cc:662:7
    #11 0x7f667ae2003b in void base::internal::DecayedFunctorTraits<void (media::VideoEncoder::*)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), media::VideoEncoder*, scoped_refptr<media::VideoFrame>&&, media::VideoEncoder::EncodeOptions&&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>&&>::Invoke<void (media::VideoEncoder::*)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), media::VideoEncoder*, scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>>(void (media::VideoEncoder::*)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), media::VideoEncoder*&&, scoped_refptr<media::VideoFrame>&&, media::VideoEncoder::EncodeOptions&&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>&&) base/functional/bind_internal.h:740:12
    #12 0x7f667ae1fd74 in void base::internal::InvokeHelper<false, base::internal::FunctorTraits<void (media::VideoEncoder::*&&)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), media::VideoEncoder*, scoped_refptr<media::VideoFrame>&&, media::VideoEncoder::EncodeOptions&&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>&&>, void, 0ul, 1ul, 2ul, 3ul>::MakeItSo<void (media::VideoEncoder::*)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), std::__Cr::tuple<base::internal::UnretainedWrapper<media::VideoEncoder, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>>>(void (media::VideoEncoder::*&&)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), std::__Cr::tuple<base::internal::UnretainedWrapper<media::VideoEncoder, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>>&&) base/functional/bind_internal.h:932:12
    #13 0x7f667ae1fa47 in void base::internal::Invoker<base::internal::FunctorTraits<void (media::VideoEncoder::*&&)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), media::VideoEncoder*, scoped_refptr<media::VideoFrame>&&, media::VideoEncoder::EncodeOptions&&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>&&>, base::internal::BindState<true, true, false, void (media::VideoEncoder::*)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), base::internal::UnretainedWrapper<media::VideoEncoder, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>>, void ()>::RunImpl<void (media::VideoEncoder::*)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), std::__Cr::tuple<base::internal::UnretainedWrapper<media::VideoEncoder, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>>, 0ul, 1ul, 2ul, 3ul>(void (media::VideoEncoder::*&&)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), std::__Cr::tuple<base::internal::UnretainedWrapper<media::VideoEncoder, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>>&&, std::__Cr::integer_sequence<unsigned long, 0ul, 1ul, 2ul, 3ul>) base/functional/bind_internal.h:1069:14
    #14 0x7f667ae1f878 in base::internal::Invoker<base::internal::FunctorTraits<void (media::VideoEncoder::*&&)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), media::VideoEncoder*, scoped_refptr<media::VideoFrame>&&, media::VideoEncoder::EncodeOptions&&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>&&>, base::internal::BindState<true, true, false, void (media::VideoEncoder::*)(scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions const&, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>), base::internal::UnretainedWrapper<media::VideoEncoder, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, scoped_refptr<media::VideoFrame>, media::VideoEncoder::EncodeOptions, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>>, void ()>::RunOnce(base::internal::BindStateBase*) base/functional/bind_internal.h:982:12
    #15 0x7f66af5d3112 in base::OnceCallback<void ()>::Run() && base/functional/callback.h:155:12
    #16 0x7f66afaff12e in base::TaskAnnotator::RunTaskImpl(base::PendingTask&) base/task/common/task_annotator.cc:229:34
    #17 0x7f66afcec7d7 in void base::TaskAnnotator::RunTask<base::internal::TaskTracker::RunTaskImpl(base::internal::Task&, base::TaskTraits const&, base::internal::TaskSource*, base::internal::SequenceToken const&)::$_0>(perfetto::StaticString, base::PendingTask&, base::internal::TaskTracker::RunTaskImpl(base::internal::Task&, base::TaskTraits const&, base::internal::TaskSource*, base::internal::SequenceToken const&)::$_0&&) base/task/common/task_annotator.h:112:5
    #18 0x7f66afcec274 in base::internal::TaskTracker::RunTaskImpl(base::internal::Task&, base::TaskTraits const&, base::internal::TaskSource*, base::internal::SequenceToken const&) base/task/thread_pool/task_tracker.cc:691:19
    #19 0x7f66afcec428 in base::internal::TaskTracker::RunSkipOnShutdown(base::internal::Task&, base::TaskTraits const&, base::internal::TaskSource*, base::internal::SequenceToken const&) base/task/thread_pool/task_tracker.cc:676:3
    #20 0x7f66afcea93d in base::internal::TaskTracker::RunTaskWithShutdownBehavior(base::internal::Task&, base::TaskTraits const&, base::internal::TaskSource*, base::internal::SequenceToken const&) base/task/thread_pool/task_tracker.cc:706:7
    #21 0x7f66afce9a49 in base::internal::TaskTracker::RunTask(base::internal::Task, base::internal::TaskSource*, base::TaskTraits const&, base::ThreadType) base/task/thread_pool/task_tracker.cc:506:5
    #22 0x7f66afce8593 in base::internal::TaskTracker::RunAndPopNextTask(base::internal::RegisteredTaskSource) base/task/thread_pool/task_tracker.cc:394:5
    #23 0x7f66afd3b243 in base::internal::WorkerThread::RunWorker() base/task/thread_pool/worker_thread.cc:473:36
    #24 0x7f66afd3a5ab in base::internal::WorkerThread::RunPooledWorker() base/task/thread_pool/worker_thread.cc:359:3
    #25 0x7f66afd39e25 in base::internal::WorkerThread::ThreadMain() base/task/thread_pool/worker_thread.cc:339:7
    #26 0x7f66afeac06c in base::(anonymous namespace)::ThreadFunc(void*) base/threading/platform_thread_posix.cc:102:13
    #27 0x56334a039896 in asan_thread_start(void*) asan_interceptors.cpp
    #28 0x7f656e9d4ac2 in start_thread nptl/pthread_create.c:442:8
    #29 0x7f656ea668cf  misc/../sysdeps/unix/sysv/linux/x86_64/clone3.S:81


MiraclePtr Status: NOT PROTECTED
No raw_ptr<T> access to this region was detected prior to this crash.
This crash is still exploitable with MiraclePtr.
Refer to https://chromium.googlesource.com/chromium/src/+/main/base/memory/raw_ptr.md for details.

==100280==END OF ADDITIONAL INFO

VERSION

Chrome Version: 147.0.7703.0 (dev, ASan-instrumented Chromium build)

Operating System: Ubuntu 22.04.3 LTS (Jammy), x86_64, kernel 5.15.0-151-generic

REPRODUCTION CASE

PoC file:

  • highbd-fuzzer-1.html

Opening it with –autoplay-policy=no-user-gesture-required flag directly can trigger the bug. Note that this flag is not required for this vulnerability, just for making PoC leading to crash more reliably.

ASAN_OPTIONS=detect_odr_violation=0 chrome --headless file:///highbd-fuzzer-1.html --autoplay-policy=no-user-gesture-required

Also attached heap-spray-poc.html, which is a poc for controlled write, but it is less reliable.

Type of crash: Renderer (tab) process memory corruption

CREDIT INFORMATION

Reporter credit: heapracer (@heapracer)

View on issue tracker