Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Media
DescriptionUse after free in Media
ComponentMedia
Bug ClassUAF
Tracker497627277
Fix commitd34bbedc1c10 (chromium/src) +1/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
media/mojo/services/oop_video_decoder_factory_process_service.cc
modified

Files Changed

  • media/mojo/services/oop_video_decoder_factory_process_service.cc
From d34bbedc1c10210e58783e3a7bb0cb4c171253be Mon Sep 17 00:00:00 2001
From: Kramer Ge <fangzhoug@chromium.org>
Date: Thu, 16 Apr 2026 08:08:39 -0700
Subject: [PATCH] [media//]Unregister gpu channel observer on service destruction

.. regardless of channel lost or not.

OOPVideoDecoderFactoryProcessService is freed on the main thread, and
GpuChannelHost sets channel lost and notifies observers on the io
thread without locking.

OOPVideoDecoderFactoryProcessService should be unregistered from the
observer list regardless to prevent UAF caused by dtor running between
setting channel lost set and notifying observers.

R=vasilyt

Fixed: 497627277
Change-Id: Ieae0de16d0f642947af65a2a4f078a65e5338f4e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7766309
Reviewed-by: Colin Blundell <blundell@chromium.org>
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
Commit-Queue: Kramer Ge <fangzhoug@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1615872}
---

diff --git a/media/mojo/services/oop_video_decoder_factory_process_service.cc b/media/mojo/services/oop_video_decoder_factory_process_service.cc
index ca62e6d..1c03598 100644
--- a/media/mojo/services/oop_video_decoder_factory_process_service.cc
+++ b/media/mojo/services/oop_video_decoder_factory_process_service.cc
@@ -22,7 +22,7 @@
 
 OOPVideoDecoderFactoryProcessService::~OOPVideoDecoderFactoryProcessService() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (shared_image_interface_ && !shared_image_interface_->IsLost()) {
+  if (shared_image_interface_) {
     shared_image_interface_->RemoveGpuChannelLostObserver(this);
   }
 }
Loading diff…

Original Bug Report

reported by rj...@google.com

Potential Use-After-Free in OOPVideoDecoderFactoryProcessService via TOCTOU race

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A Time-of-Check to Time-of-Use (TOCTOU) race condition exists between the utility main thread and the IO thread during GPU channel disconnection. This allows OOPVideoDecoderFactoryProcessService to be destroyed without unregistering itself as an observer, leaving a dangling raw pointer. Subsequent observer notification on the IO thread results in a Use-After-Free.

Affected files:

  • media/mojo/services/oop_video_decoder_factory_process_service.cc
  • gpu/ipc/client/gpu_channel_host.cc
  • gpu/ipc/client/gpu_channel_observer.h

Estimated timestamp from git blame: 2025-11-06

Technical Details

A cross-thread Time-of-Check to Time-of-Use (TOCTOU) vulnerability exists in the OOPVideoDecoderFactoryProcessService (typically used on Linux and ChromeOS when USE_LINUX_VIDEO_ACCELERATION is enabled). The service implements the gpu::GpuChannelLostObserver interface and registers itself to be notified of GPU channel loss using a raw pointer (this).

The vulnerability is located in the service’s destructor, which incorrectly uses a lock-free check to decide whether to unregister itself as an observer:

OOPVideoDecoderFactoryProcessService::~OOPVideoDecoderFactoryProcessService() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // TOCTOU Vulnerability: Lock-free atomic read of connection state.
  if (shared_image_interface_ && !shared_image_interface_->IsLost()) {
    shared_image_interface_->RemoveGpuChannelLostObserver(this);
  }
}

The IsLost() check performs a lock-free atomic load of the channel’s connection state (is_connected_) from the GpuChannelHost. Simultaneously, when the GPU process is disconnected, GpuChannelHost::ConnectionTracker::OnDisconnectedFromGpuProcess() is executed on the utility process’s IO thread. This method sets the atomic connection state to false and then calls NotifyGpuChannelLost():

void GpuChannelHost::ConnectionTracker::OnDisconnectedFromGpuProcess() {
  is_connected_.store(false); // Atomic store
  // RACE WINDOW
  NotifyGpuChannelLost(); // Acquires lock and iterates observer_list_
}

Potential Attack Steps

While we do not currently have a weaponized Proof of Concept, an attacker could potentially exploit this via the following sequence:

  1. Compromise Renderer: Gain initial code execution in a sandboxed renderer process.
  2. Trigger GPU Crash: Induce a GPU process crash or channel disconnection from the compromised renderer.
  3. Trigger Service Destruction: Simultaneously close the video stream/Mojo pipe connecting to the OOP Video Decoder utility process.
  4. Win the Race: The IO thread in the utility process executes is_connected_.store(false) but is preempted before calling NotifyGpuChannelLost().
  5. Bypass Unregistration: The utility main thread executes ~OOPVideoDecoderFactoryProcessService(). Seeing IsLost() == true, it skips observer removal and frees the object memory.
  6. UAF Execution: The IO thread resumes, iterates over the observer_list_ containing the dangling raw pointer, and calls observer->OnGpuChannelLost(). Since this is a virtual method call, a heap-groomed utility process could allow the attacker to hijack the vtable and achieve Remote Code Execution (RCE) within the utility process sandbox.

Suggested Fix

The root cause is relying on a lock-free state check to bypass cleanup of a raw pointer in a multi-threaded observer list.

  1. Safe Observer Pattern: Migrate observer_list_ in GpuChannelHost::ConnectionTracker to use base::CheckedObserver or base::WeakPtr<GpuChannelLostObserver>. This ensures dangling pointers safely nullify or crash deterministically.
  2. Unconditional Removal: Alternatively, refactor the locking mechanism in GpuChannelHost to allow RemoveGpuChannelLostObserver to be called unconditionally in the destructor without risking deadlocks, ensuring the pointer is properly purged before the memory is freed.

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