Medium watch ✔︎ verified CVSS 8.8 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in GPU
DescriptionUse after free in GPU
ComponentGPU
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable — factors set 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. Citations (Stop 462-493, weak_this 472, reset_cb_ 488-489, weak_factory_ 492, destructor 62) all match; diff adds the missing post-reset_cb_ liveness check. Double-free/RCE correctly marked inferred. Advisory labels GPU but code is renderer-side media client, as analysis states.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker497292072
Fix commitd193b6540a3a (chromium/src) +13/-9
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
CreditedGoogle
Reported2026-03-28 (per vendor advisory)
Fix landed2026-03-31 (public git)
Disclosed2026-05-12
Patch gap42 days — fix public in source before the advisory

Background

`MojoVideoDecoder`
the renderer-side media client that proxies decode requests over Mojo to a remote (GPU-process) video decoder.
`MojoVideoDecoder::Stop`
the handler invoked on Mojo disconnection that drains pending callbacks before dropping outstanding work.
On-stack `WeakPtr`
a base::WeakPtr<MojoVideoDecoder> captured locally so the method can detect whether a callback reentrantly destroyed this.
`weak_factory_.InvalidateWeakPtrs`
the call that invalidates all outstanding weak pointers to the object.

Root Cause Analysis

In the pre-fix MojoVideoDecoder::Stop (media/mojo/clients/mojo_video_decoder.cc:462-493), the method captures weak_this = weak_this_ at line 472, then runs init_cb_ followed by if (!weak_this) return; (lines 474-478), and runs each entry of pending_decodes_ with its own if (!weak_this) return; (lines 480-485). The reset callback, however, was executed at lines 488-489 (if (reset_cb_) std::move(reset_cb_).Run();) with no following liveness check before reaching weak_factory_.InvalidateWeakPtrs(); at line 492. The invariant is “after running any callback that may reentrantly destroy this, the method must not touch member state unless the on-stack WeakPtr confirms the object is still alive.” Because reset_cb_ can synchronously destroy the decoder, control could return to line 492 and invoke weak_factory_ on freed memory — a use-after-free.

The fix wraps the reset callback so it is immediately followed by if (!weak_this) { return; }, matching the pattern already used for init_cb_ and pending_decodes_; it also brackets the init_cb_ check inside the if (init_cb_) block. The change TIGHTENS the method by adding the previously missing post-callback liveness check.

Key insight
The reset callback was the one potentially-reentrant callback in Stop not guarded by the on-stack WeakPtr check, so if it destroyed the object the subsequent weak_factory_.InvalidateWeakPtrs() ran on freed memory; the fix adds the same if (!weak_this) return; guard after reset_cb_.

Attack Path

  1. Arm a pending reset A reset is in flight on the MojoVideoDecoder so that reset_cb_ is populated when Stop runs. (inferred)
  2. Drop the connection The remote end disconnects the Mojo pipe, invoking Stop as the disconnect handler (mojo_video_decoder.cc:462).
  3. Reentrant destruction Running std::move(reset_cb_).Run() at line 489 synchronously destroys this through the reset-completion chain. (inferred)
  4. Use after free With no guard after the reset callback pre-fix, control reaches weak_factory_.InvalidateWeakPtrs() at line 492 operating on the freed object.

Impact Assessment

An attacker able to drive a disconnect while a reset is pending could cause a use-after-free of the MojoVideoDecoder in the process that hosts the media client (the renderer per the reporter). The advisory rates this medium severity and not exploited in the wild. The reporter’s escalation to a double-free and remote code execution goes beyond what the shown code demonstrates and is (inferred).

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

Audit Directions

  • Post-callback liveness
    In any handler that captures an on-stack WeakPtr and runs multiple callbacks, confirm a !weak_this check follows every callback that could reentrantly destroy the object, not just some.
  • Disconnect handlers
    Treat Mojo disconnect/error handlers as reentrancy-prone and audit them for member access after running client-supplied callbacks.
  • Trailing member use
    Flag a final member-method call (e.g. weak_factory_.InvalidateWeakPtrs()) that executes after a reentrant callback with no intervening validity guard.
From d193b6540a3aa70d8154d0590a11c5782dca2edf Mon Sep 17 00:00:00 2001
From: Dale Curtis <[email protected]>
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 <[email protected]>
Auto-Submit: Dale Curtis <[email protected]>
Reviewed-by: Ted (Chromium) Meyer <[email protected]>
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 [email protected]

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