CVE-2026-8581
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmedia/mojo/clients/mojo_video_decoder.cc |
modified | |
formedia/mojo/clients/mojo_video_decoder.cc |
modified |
Files Changed
media/mojo/clients/mojo_video_decoder.cc
Patch
From d193b6540a3aa70d8154d0590a11c5782dca2edf Mon Sep 17 00:00:00 2001
From: Dale Curtis <dalecurtis@chromium.org>
Date: Tue, 31 Mar 2026 10:37:26 -0700
Subject: [PATCH] Check WeakPtr more in MojoVideoDecoder::Stop()
No known issues, but this unifies the WeakPtr checks across all
the possible callbacks which could trigger destruction.
Fixed: 497292072
Change-Id: I3b9cf94a0f6b3982ee7bf08282b43f680a3dd9c2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7712494
Commit-Queue: Dale Curtis <dalecurtis@chromium.org>
Auto-Submit: Dale Curtis <dalecurtis@chromium.org>
Reviewed-by: Ted (Chromium) Meyer <tmathmeyer@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1607962}
---
diff --git a/media/mojo/clients/mojo_video_decoder.cc b/media/mojo/clients/mojo_video_decoder.cc
index 611fec4..87d3d52c 100644
--- a/media/mojo/clients/mojo_video_decoder.cc
+++ b/media/mojo/clients/mojo_video_decoder.cc
@@ -467,26 +467,30 @@
// |init_cb_| is likely to reentrantly destruct |this|, so we check for that
// using an on-stack WeakPtr.
- // TODO(sandersd): Update the VideoDecoder API to be explicit about what
- // reentrancy is allowed, and therefore which callbacks must be posted.
- base::WeakPtr<MojoVideoDecoder> weak_this = weak_this_;
+ auto weak_this = weak_this_;
- if (init_cb_)
+ if (init_cb_) {
std::move(init_cb_).Run(DecoderStatus::Codes::kDisconnected);
-
- if (!weak_this)
- return;
+ if (!weak_this) {
+ return;
+ }
+ }
for (auto& pending_decode : pending_decodes_) {
// It would be ideal if we could get a reason for the interruption.
std::move(pending_decode.second).Run(DecoderStatus::Codes::kDisconnected);
- if (!weak_this)
+ if (!weak_this) {
return;
+ }
}
pending_decodes_.clear();
- if (reset_cb_)
+ if (reset_cb_) {
std::move(reset_cb_).Run();
+ if (!weak_this) {
+ return;
+ }
+ }
// Drop any outstanding callbacks.
weak_factory_.InvalidateWeakPtrs();
Original Bug Report
Potential Use-After-Free and Double Free in MojoVideoDecoder::Stop()
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A synchronous destruction chain triggered by a compromised GPU process disconnecting a Mojo pipe during a track change causes a Use-After-Free in MojoVideoDecoder::Stop(). Accessing weak_factory_ on the freed object subsequently causes a deterministic Double Free of a PartitionAlloc object, potentially allowing Remote Code Execution in the Renderer process.
Affected files:
media/mojo/clients/mojo_video_decoder.cc
Estimated timestamp from git blame: 2024-07-15
Description
A Use-After-Free (UAF) vulnerability exists in media::MojoVideoDecoder::Stop(), which acts as the Mojo disconnect handler for the mojom::VideoDecoder pipe. If the GPU process drops the connection while a reset is pending, a synchronous callback chain can destroy the MojoVideoDecoder instance while it is still executing Stop(). This UAF is cleanly transformed into a deterministic PartitionAlloc Double Free, offering a strong primitive for Remote Code Execution (RCE).
In MojoVideoDecoder::Stop(), the handler executes several callbacks. While the first two (init_cb_ and pending_decodes_) are followed by a local WeakPtr liveness check (if (!weak_this) return;), the execution of reset_cb_ lacks this check. If reset_cb_ destroys this, the subsequent call to weak_factory_.InvalidateWeakPtrs() executes on freed memory.
The Double Free Mechanism
- At the start of
Stop(), an on-stackWeakPtris created:base::WeakPtr<MojoVideoDecoder> weak_this = weak_this_;. This increments the reference count of theWeakReference::Flagto 2. Stop()callsstd::move(reset_cb_).Run(). During a track change, this propagates throughDecoderStream::OnDecoderReset,VideoRendererImpl::OnVideoDecoderStreamResetDone, andRendererImpl::CleanUpTrackChange.- This chain culminates in
RendererImpl::ReinitializeVideoRenderer, which synchronously assigns a newstd::unique_ptr<VideoDecoderStream>. The old stream is destroyed, which synchronously destroys theMojoVideoDecoder(this). - The
MojoVideoDecoderdestructor destroys itsweak_factory_. Thescoped_refptrholding theWeakReference::Flagdrops its refcount to 1. TheMojoVideoDecodermemory is then freed to PartitionAlloc. - Execution returns to
Stop(). The method callsweak_factory_.InvalidateWeakPtrs();on the freed object. - This function instantiates a new
Flagand uses thescoped_refptrassignment operator on the freed memory. It overwrites the oldFlagpointer and drops the oldFlag’s refcount from 1 to 0, causing the oldFlagto be deleted (First Free). Stop()returns. The on-stackweak_thisgoes out of scope, dropping the oldFlag’s refcount from 0 to -1, resulting in a Double Free of theFlagobject.
Note: MiraclePtr (BackupRefPtr) does not protect against this because the UAF occurs via the this pointer already loaded into the execution frame, and the Double Free occurs on a secondary Flag object.
Potential Attacker Steps
(Note: This analysis is based on code paths; a fully weaponized proof-of-concept has not been run by our tooling.)
- An attacker compromises the GPU process.
- The attacker hosts a malicious webpage that forces the victim’s Renderer to initiate a video track change (e.g., via MSE), flushing the video stream and generating a
ResetIPC to the GPU. - The compromised GPU process intentionally disconnects the
mojom::VideoDecoderpipe instead of responding to theResetIPC. - The Renderer executes the disconnect handler (
Stop()), triggering the synchronous destruction chain and subsequent Double Free of theWeakReference::Flag. - The attacker grooms the Renderer’s PartitionAlloc heap from the GPU process (e.g., using other Mojo interfaces) to overlap the double-freed memory, gaining an arbitrary read/write primitive and achieving RCE to bypass Site Isolation.
Suggested Fix
Add a liveness check immediately after executing reset_cb_ in MojoVideoDecoder::Stop(), matching the pattern used for the other callbacks in the same method:
if (reset_cb_)
std::move(reset_cb_).Run();
if (!weak_this)
return;
// Drop any outstanding callbacks.
weak_factory_.InvalidateWeakPtrs();
Evaluated with Chrome root at commit: 876d480da1f794d87813cfa2e6ff4fcf9771e939
Results 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.