Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in GPU
DescriptionUse after free in GPU
ComponentGPU
Bug ClassUAF
Tracker497292072
Fix commitd193b6540a3a (chromium/src) +13/-9
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-12

Changed Functions

FunctionChangeNotes
if
media/mojo/clients/mojo_video_decoder.cc
modified
for
media/mojo/clients/mojo_video_decoder.cc
modified

Files Changed

  • media/mojo/clients/mojo_video_decoder.cc
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();
Loading diff…

Original Bug Report

reported by rj...@google.com

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

  1. At the start of Stop(), an on-stack WeakPtr is created: base::WeakPtr<MojoVideoDecoder> weak_this = weak_this_;. This increments the reference count of the WeakReference::Flag to 2.
  2. Stop() calls std::move(reset_cb_).Run(). During a track change, this propagates through DecoderStream::OnDecoderReset, VideoRendererImpl::OnVideoDecoderStreamResetDone, and RendererImpl::CleanUpTrackChange.
  3. This chain culminates in RendererImpl::ReinitializeVideoRenderer, which synchronously assigns a new std::unique_ptr<VideoDecoderStream>. The old stream is destroyed, which synchronously destroys the MojoVideoDecoder (this).
  4. The MojoVideoDecoder destructor destroys its weak_factory_. The scoped_refptr holding the WeakReference::Flag drops its refcount to 1. The MojoVideoDecoder memory is then freed to PartitionAlloc.
  5. Execution returns to Stop(). The method calls weak_factory_.InvalidateWeakPtrs(); on the freed object.
  6. This function instantiates a new Flag and uses the scoped_refptr assignment operator on the freed memory. It overwrites the old Flag pointer and drops the old Flag’s refcount from 1 to 0, causing the old Flag to be deleted (First Free).
  7. Stop() returns. The on-stack weak_this goes out of scope, dropping the old Flag’s refcount from 0 to -1, resulting in a Double Free of the Flag object.

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.)

  1. An attacker compromises the GPU process.
  2. 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 Reset IPC to the GPU.
  3. The compromised GPU process intentionally disconnects the mojom::VideoDecoder pipe instead of responding to the Reset IPC.
  4. The Renderer executes the disconnect handler (Stop()), triggering the synchronous destruction chain and subsequent Double Free of the WeakReference::Flag.
  5. 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.

View on issue tracker