CVE-2025-13638
Overview
Background
- `WebMediaPlayerMS`
- the Blink media player that plays a
MediaStream(for example fromgetUserMedia, WebRTC or capture) inside anHTMLMediaElement. It holds aclient_pointer back to that element. - Oilpan conservative stack scanning
- Blink’s garbage collector treats pointers it finds on the native stack as roots. A garbage-collected object referenced only from a live stack slot is not reclaimed.
- Player lifetime coupling
- the player is owned by its media element. If the element is collected and finalized, the player (
this) is destroyed too. - `base::debug::Alias`
- a helper that stops the compiler from optimizing away a local variable, so a stack copy of a pointer stays in place for the stack scan.
Root Cause Analysis
Several WebMediaPlayerMS methods are invoked as callbacks from outside the element, from the audio renderer and compositor/frame-delivery paths. When those callbacks run, no stack frame necessarily holds a reference to the element, and the element may already be unreachable from script and eligible for garbage collection. The new comment says GC is prevented only when the call came from the element itself or HasPendingActivation() is true. Otherwise, a GC that runs during the callback can finalize the element, and that destroys this while the method is still running. Any later access to member state in the callback is then a use-after-free.
The patch adds a PREVENT_CLIENT_GC macro that copies client_ into a local client_copy_ and passes it to base::debug::Alias.
The fix places this macro at the top of four callback entry points, so conservative stack scanning keeps the element, and therefore the player, alive until the callback returns.
WebMediaPlayerMS assumed the owning media element would outlive the call, but nothing on the stack rooted it. The fix pins client_ on the stack for the duration of each such callback.Attack Path
- Create a MediaStream player
A page attaches a
MediaStreamto a media element, which creates aWebMediaPlayerMS. - Drop all references Script drops every reference to the element, so it is eligible for GC while the stream keeps delivering frames or audio.
- External callback fires
A frame or audio event invokes one of the affected callbacks:
OnAudioRenderErrorCallback,OnOpacityChanged, or the first-frame and transform-change handlers. - GC during the callback If garbage collection runs while the callback is executing, the element is finalized and the player is freed, so later member accesses in the callback use freed memory. The exact timing and any resulting memory-corruption primitive are (inferred).
Impact Assessment
WebMediaPlayerMS object in the sandboxed renderer process. Ordinary web content can reach it by creating MediaStream playback, but it depends on winning a race between a callback and a GC cycle. Some stream sources need a capture permission, though others such as canvas or WebRTC streams may not (inferred). The vendor rates it low severity, and the advisory does not report exploitation in the wild. Code execution in the renderer, or anything beyond it, is (inferred) and not shown by the patch.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/modules/mediastream/web_media_player_ms.cc |
modified |
Files Changed
third_party/blink/renderer/modules/mediastream/web_media_player_ms.cc
Audit Directions
- Other external entry pointsCheck the remaining
WebMediaPlayerMSmethods, and otherWebMediaPlayerimplementations such asWebMediaPlayerImpl, that are reached from compositor, audio or IPC callbacks without a stack reference to the element. - Non-GC objects owned by GC objectsLook for objects destroyed by an Oilpan finalizer that run callbacks which may allocate or trigger GC. Each such entry point needs the owner rooted, through a
Persistent, a stack reference, or pending-activity tracking. - `HasPendingActivity` coverageCheck that media elements report pending activity for every state in which external callbacks can still arrive, rather than relying on per-callback stack pins.
Patch
From 9696b698f7dc30fbef6a93790aafbd289538a803 Mon Sep 17 00:00:00 2001 From: Frank Liberato <[email protected]> Date: Mon, 27 Oct 2025 12:49:33 -0700 Subject: [PATCH] Prevent media element GC in callbacks in WebMediaPlayerMS Callbacks into WebMediaPlayerMS can happen, in general, when the element is eligible for GC. This CL adds a stack reference to the client (element) to prevent conservative GC from reclaiming the element during the callback. Bug: 448046109 Change-Id: I67e37bbc2b25ccd54d0d94e1857f07dd3e673418 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7000843 Reviewed-by: Dale Curtis <[email protected]> Commit-Queue: Frank Liberato <[email protected]> Cr-Commit-Position: refs/heads/main@{#1536174} --- diff --git a/third_party/blink/renderer/modules/mediastream/web_media_player_ms.cc b/third_party/blink/renderer/modules/mediastream/web_media_player_ms.cc index f0a7fc93..1b2ff56f 100644 --- a/third_party/blink/renderer/modules/mediastream/web_media_player_ms.cc +++ b/third_party/blink/renderer/modules/mediastream/web_media_player_ms.cc @@ -12,6 +12,7 @@ #include <string> #include <utility> +#include "base/debug/alias.h" #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/memory/raw_ptr.h" @@ -63,6 +64,19 @@ #include "third_party/blink/renderer/platform/wtf/cross_thread_copier_media.h" #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" +// Put this macro in a scope to prevent `client_` from being GC'd. +// This is important for any method that might be called from anywhere +// where GC of the element is not prevented. GC is prevented if the +// call into `this` came from the element itself (directly or indirectly, +// as long as the element's `this` is on the stack), or HasPendingActivation() +// returns true. In other cases, especially callbacks from the "outside +// world", one should PREVENT_CLIENT_GC to keep the element from being +// garbage collected. Failure to do this can cause `this` to be destroyed +// when the player is finalized. +#define PREVENT_CLIENT_GC \ + auto client_copy_ = client_; \ + base::debug::Alias(&client_copy_) + namespace blink { template <> @@ -456,6 +470,7 @@ void WebMediaPlayerMS::OnAudioRenderErrorCallback() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + PREVENT_CLIENT_GC; if (watch_time_reporter_) watch_time_reporter_->OnError(media::AUDIO_RENDERER_ERROR); @@ -1311,6 +1326,7 @@ bool is_opaque) { DVLOG(1) << __func__; DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + PREVENT_CLIENT_GC; has_first_frame_ = true; OnTransformChanged(video_transform); @@ -1329,6 +1345,7 @@ void WebMediaPlayerMS::OnOpacityChanged(bool is_opaque) { DVLOG(1) << __func__; DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + PREVENT_CLIENT_GC; opaque_ = is_opaque; if (!bridge_) { @@ -1345,6 +1362,7 @@ media::VideoTransformation video_transform) { DVLOG(1) << __func__; DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + PREVENT_CLIENT_GC; if (!bridge_) { // Keep the old |video_layer_| alive until SetCcLayer() is called with a new
Original Bug Report
Use-After-Free in WebMediaPlayerMS::OnFirstFrameReceived
Steps to reproduce the problem
- chrome.exe –js-flags="–stress-incremental-marking –expose-gc" –no-sandbox
Problem Description
In the MaybeCreateWatchTimeReporter function below, a call to GarbageCollector is possible, which causes Use-After-Free.
void WebMediaPlayerMS::OnFirstFrameReceived(
media::VideoTransformation video_transform,
bool is_opaque) {
DVLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
has_first_frame_ = true;
OnTransformChanged(video_transform);
OnOpacityChanged(is_opaque);
if (use_surface_layer_)
ActivateSurfaceLayerForVideo(video_transform);
SetReadyState(WebMediaPlayer::kReadyStateHaveMetadata);
SetReadyState(WebMediaPlayer::kReadyStateHaveEnoughData);
TriggerResize();
ResetCanvasCache();
MaybeCreateWatchTimeReporter();
The most annoying code in the code is the function below called from the WebMediaPlayerMS::SetReadyState function, and the ScheduleNamedEvent function can be avoided by satisfying the tracks_are_ready condition in the function below.
void HTMLMediaElement::SetReadyState(ReadyState state) {
'''
bool was_potentially_playing = PotentiallyPlaying();
ReadyState old_state = ready_state_;
ReadyState new_state = state;
bool tracks_are_ready = TextTracksAreReady();
'''
if (ready_state_ == kHaveEnoughData && old_state < kHaveEnoughData &&
tracks_are_ready) {
if (old_state <= kHaveCurrentData) {
ScheduleNamedEvent(event_type_names::kCanplay);
if (is_potentially_playing)
ScheduleNotifyPlaying();
}
if (autoplay_policy_->RequestAutoplayByAttribute()) {
paused_ = false;
SetShowPosterFlag(false);
GetCueTimeline().InvokeTimeMarchesOn();
ScheduleNamedEvent(event_type_names::kPlay);
ScheduleNotifyPlaying();
can_autoplay_ = false;
}
ScheduleNamedEvent(event_type_names::kCanplaythrough);
}
UpdatePlayState();
}
The exact location of the function that calls the GarbageCollector can be found through the AudioComponents or VideoComponents function call in the WebMediaPlayerMS::GetMediaStreamType function called by WebMediaPlayerMS::MaybeCreateWatchTimeReporter.
WebMediaPlayerMS::GetMediaStreamType() {
if (web_stream_.IsNull())
return std::nullopt;
// If either the first video or audio source is remote, the media stream is
// of remote source.
MediaStreamDescriptor& descriptor = *web_stream_;
MediaStreamSource* media_source = nullptr;
if (HasVideo()) {
auto video_components = descriptor.VideoComponents();
DCHECK_GT(video_components.size(), 0U);
media_source = video_components[0]->Source();
} else if (HasAudio()) {
auto audio_components = descriptor.AudioComponents();
DCHECK_GT(audio_components.size(), 0U);
media_source = audio_components[0]->Source();
}
If the trigger doesn’t proceed smoothly, try using native Chromium instead of ASAN Chromium. Depending on your computer’s specifications, there may still be areas where the task order and GC condition processing probability are low. We will improve this in the future and re-upload the proof-of-concept (PoC).
The Chromium commit that triggered this vulnerability is 06773e4fd40f4b8ad8a3c94a86ff14613b626c8e
Summary
Use-After-Free in WebMediaPlayerMS::OnFirstFrameReceived
Custom Questions
Type of crash:
Renderer
Crash state:
An action occurs that references a freed object member object.
Reporter credit:
sherkito
Additional Data
Category: Security
Chrome Channel: Not sure
Regression: N/A \