Overview

Critical
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Chromecast
DescriptionUse after free in Chromecast
ComponentChromecast
Sub-componentChromecast
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
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.
Tracker516988476
Fix commit23cc22d65ef4 (chromium/src) +4/-1
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-27 (per vendor advisory)
Fix landed2026-07-14 (public git)
Disclosed2026-08-25
Patch gap42 days — fix public in source before the advisory

Background

`AudioDecoderSoftwareWrapper`
a Chromecast media class (in chromecast/media/common/audio_decoder_software_wrapper.cc/.h) that wraps a software CastAudioDecoder and forwards decode requests to it.
`base::Unretained(this)`
a base::Bind helper that captures a raw, non-owning this pointer with no lifetime guarantee, so the bound callback dereferences it unconditionally when run.
`base::WeakPtr` / `base::WeakPtrFactory`
a smart-pointer mechanism that invalidates all outstanding pointers when the referenced object is destroyed, causing a bound callback to become a safe no-op rather than executing on freed memory.
Pending asynchronous decode
the wrapper returns MediaPipelineBackend::kBufferPending after calling software_decoder_->Decode(...), indicating the decode has not completed and the registered completion callback (OnDecodedBuffer) will fire later.

Root Cause Analysis

The wrapper submits a buffer to the software decoder via software_decoder_->Decode(base::WrapRefCounted(buffer_base), base::BindOnce(&AudioDecoderSoftwareWrapper::OnDecodedBuffer, base::Unretained(this))) and then returns MediaPipelineBackend::kBufferPending, deferring completion to an asynchronous callback. Because the completion callback captured this with base::Unretained, the lifetime invariant that AudioDecoderSoftwareWrapper must outlive any pending decode was never enforced. If the AudioDecoderSoftwareWrapper instance is destroyed while a decode is still outstanding, the later invocation of OnDecodedBuffer dereferences the freed this, a classic use-after-free.

The fix adds a base::WeakPtrFactory<AudioDecoderSoftwareWrapper> weak_factory_{this} member (with the new #include "base/memory/weak_ptr.h") and replaces base::Unretained(this) with weak_factory_.GetWeakPtr(), so that if the object is gone when the decode completes the callback is silently dropped instead of running on a dangling pointer.

Key insight
The single mistake was binding an asynchronous completion callback with base::Unretained(this) on an object whose lifetime is not guaranteed to span the pending decode; switching that binding to weak_factory_.GetWeakPtr() makes the callback a no-op after destruction, closing the use-after-free.

Attack Path

  1. Trigger a pending decode Feed audio through the Chromecast media pipeline so that AudioDecoderSoftwareWrapper calls software_decoder_->Decode(...) and returns MediaPipelineBackend::kBufferPending, leaving an OnDecodedBuffer callback bound to base::Unretained(this) outstanding.
  2. Destroy the wrapper mid-decode Cause the AudioDecoderSoftwareWrapper to be torn down (e.g., pipeline reconfiguration, stream/track teardown, or error handling) before the asynchronous decode completes, freeing the object while the callback is still registered. (inferred — the diff shows the pending callback but not the specific teardown trigger.)
  3. Callback fires on freed memory When the software decoder finishes, it invokes the bound OnDecodedBuffer callback, dereferencing the freed this pointer, which is the use-after-free.
  4. Downstream escalation Reuse or grooming of the freed allocation could let an attacker control the dereferenced memory and steer execution. (inferred — the patch demonstrates only the dangling dereference, not any concrete read/write primitive or code execution.)

Impact Assessment

An attacker who can drive the Chromecast audio decode path can cause OnDecodedBuffer to run on a destroyed AudioDecoderSoftwareWrapper, yielding a use-after-free in the Chromecast media component; the advisory classifies this as a UAF in Chromecast with impact “Use after free in Chromecast.” The precondition is that the wrapper is destroyed while a decode submitted via CastAudioDecoder::Decode is still pending. The vendor advisory rates this critical severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.3. The advisory does not state exploited-in-the-wild status or a severity rating beyond the UAF classification.

Files Changed

  • chromecast/media/common/audio_decoder_software_wrapper.cc
  • chromecast/media/common/audio_decoder_software_wrapper.h

Audit Directions

  • `base::Unretained(this)` in async callbacks
    Audit every base::Bind/base::BindOnce that captures this with base::Unretained and is passed into an asynchronous or “pending”-returning API, since these are unsafe whenever the bound object can be destroyed before the callback runs.
  • Pending-completion patterns
    Scan Chromecast media classes that return MediaPipelineBackend::kBufferPending (or otherwise register deferred completion callbacks) and confirm the callback receiver’s lifetime is guaranteed by ownership or WeakPtr, not by an implicit assumption.
  • Missing `WeakPtrFactory` on callback-issuing classes
    Flag classes that hand out self-referencing callbacks to owned or subordinate objects (like a decoder they call Decode on) but declare no base::WeakPtrFactory, as they cannot invalidate outstanding callbacks at destruction.
From 23cc22d65ef44403a48a2218e64a584267437ff2 Mon Sep 17 00:00:00 2001
From: Simeon Anfinrud <sanfin@chromium.org>
Date: Tue, 14 Jul 2026 11:06:04 -0700
Subject: [PATCH] [chromecast] Use WeakPtr for AudioDecoderSoftwareWrapper decode callback

This resolves a potential Use-After-Free by replacing base::Unretained(this)
with a WeakPtr in the callback passed to CastAudioDecoder::Decode.
If AudioDecoderSoftwareWrapper is destroyed while a decode is pending,
the callback will safely be a no-op instead of executing on a deleted pointer.

BUG=516988476
TEST=Compiled and passed unit tests.

Change-Id: I7d973d2cc3a19465ae56b52be6d8223f17dc7c1b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8077607
Auto-Submit: Simeon Anfinrud <sanfin@chromium.org>
Reviewed-by: Shawn Quereshi <shawnq@google.com>
Commit-Queue: Shawn Quereshi <shawnq@google.com>
Cr-Commit-Position: refs/heads/main@{#1662004}
---
diff --git a/chromecast/media/common/audio_decoder_software_wrapper.cc b/chromecast/media/common/audio_decoder_software_wrapper.cc
index e1e9bd4..6fa24cb 100644
--- a/chromecast/media/common/audio_decoder_software_wrapper.cc
+++ b/chromecast/media/common/audio_decoder_software_wrapper.cc
@@ -70,7 +70,7 @@
   software_decoder_->Decode(
       base::WrapRefCounted(buffer_base),
       base::BindOnce(&AudioDecoderSoftwareWrapper::OnDecodedBuffer,
-                     base::Unretained(this)));
+                     weak_factory_.GetWeakPtr()));
   return MediaPipelineBackend::kBufferPending;
 }
 
diff --git a/chromecast/media/common/audio_decoder_software_wrapper.h b/chromecast/media/common/audio_decoder_software_wrapper.h
index acb1b65..e76a71b1 100644
--- a/chromecast/media/common/audio_decoder_software_wrapper.h
+++ b/chromecast/media/common/audio_decoder_software_wrapper.h
@@ -11,6 +11,7 @@
 #include <string>
 
 #include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
 #include "chromecast/media/api/cast_audio_decoder.h"
 #include "chromecast/public/media/media_pipeline_backend.h"
 
@@ -68,6 +69,8 @@
   AudioConfig output_config_;
   scoped_refptr<DecoderBufferBase> pending_pushed_buffer_;
   bool decoder_error_;
+
+  base::WeakPtrFactory<AudioDecoderSoftwareWrapper> weak_factory_{this};
 };
 
 }  // namespace media
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Use-After-Free in AudioDecoderSoftwareWrapper during Media Pipeline Revocation

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential Use-After-Free (UAF) vulnerability exists in Chromecast’s audio decoding pipeline during a pipeline revocation operation. When software-decoded buffers are freed via the destruction of AudioDecoderSoftwareWrapper, the underlying vendor MediaPipelineBackend may still hold and dereference a dangling raw pointer to the buffer. This can potentially lead to virtual method table hijacking and out-of-bounds execution in the unsandboxed Cast browser process.

Affected files:

  • chromecast/media/common/audio_decoder_software_wrapper.cc
  • chromecast/media/common/media_pipeline_backend_wrapper.cc
  • chromecast/media/common/audio_decoder_wrapper.cc

Estimated timestamp from git blame: 2018-11-28

Description

A potential Use-After-Free (UAF) memory corruption vulnerability has been identified in the Chromecast media pipeline. When falling back to software audio decoding via AudioDecoderSoftwareWrapper, the pipeline can destroy the software decoder wrapper and release the sole scoped_refptr holding the decoded buffer before stopping or destroying the underlying vendor MediaPipelineBackend. Under the kBufferPending contract, the vendor backend holds a raw pointer to this buffer, creating a window where a dangling raw pointer can be dereferenced.

Root Cause Analysis

  1. Software Audio Ingestion: When software audio decoding fallback is active, AudioDecoderSoftwareWrapper::OnDecodedBuffer stores the unique owning reference to the software-decoded PCM buffer in its member pending_pushed_buffer_ (scoped_refptr<DecoderBufferBase>) and passes a raw pointer of this buffer to the vendor-implemented MediaPipelineBackend::AudioDecoder via PushBuffer:

    // chromecast/media/common/audio_decoder_software_wrapper.cc
    pending_pushed_buffer_ = decoded;                                  // sole ref
    MediaPipelineBackend::BufferStatus buffer_status =
        backend_decoder_->PushBuffer(pending_pushed_buffer_.get());    // raw ptr to vendor
    

    If the vendor backend returns MediaPipelineBackend::kBufferPending, it is permitted to retain and asynchronously dereference the raw pointer until it notifies completion via OnPushBufferComplete() (see chromecast/public/media/cast_decoder_buffer.h specifications).

  2. Pipeline Revocation Sequence: When a revocation event occurs (for example, when a new video stream requests resource allocation in MediaPipelineBackendManager::BackendUseVideoDecoder), MediaPipelineBackendWrapper::Revoke() is executed:

    // chromecast/media/common/media_pipeline_backend_wrapper.cc
    void MediaPipelineBackendWrapper::Revoke() {
      if (!revoked_) {
        revoked_ = true;
        if (audio_decoder_)
          audio_decoder_->Revoke();   // Step 1: Immediately destroys active wrapper
        if (video_decoder_)
          video_decoder_->Revoke();
        backend_ = std::make_unique<RevokedMediaPipelineBackendWrapper>(
            content_type_, backend_->GetCurrentPts()); // Step 3: Destructs vendor backend
      }
    }
    
  3. Premature Buffer Destruction: When audio_decoder_->Revoke() runs, it immediately replaces the internal active decoder wrapper with a revoked dummy wrapper inside AudioDecoderWrapper::Revoke():

    // chromecast/media/common/audio_decoder_wrapper.cc
    void AudioDecoderWrapper::Revoke() {
      ...
      audio_decoder_ = std::make_unique<RevokedAudioDecoderWrapper>(...);
    }
    

    This unique_ptr reassignment triggers the immediate destruction of ActiveAudioDecoderWrapper and its member AudioDecoderSoftwareWrapper decoder_. This releases pending_pushed_buffer_, and since its reference count drops to 0, the decoded buffer is deallocated immediately.

  4. The Vulnerability Window: At this point, the real vendor backend (backend_) is still active and has not been stopped or destroyed. It is only destroyed later when the reassignment of backend_ occurs in Step 3 of MediaPipelineBackendWrapper::Revoke(). If the vendor’s destructor or background worker threads attempt to access or clean up the pending CastDecoderBuffer* pointer, they will dereference freed heap memory. Since CastDecoderBuffer is a polymorphic object with virtual methods (e.g., data(), data_size(), timestamp()), this can result in virtual method table hijacking.

Potential Attack Scenario

While we do not have a working dynamic proof of concept, a potential exploitation path is analyzed as follows:

  1. A compromised renderer initiates a CastRenderer with an audio configuration that forces a fallback to software decoding (AudioDecoderSoftwareWrapper).
  2. The stream pushes buffers such that the vendor backend returns kBufferPending in response to PushBuffer(), leaving the vendor holding a raw pointer to the software-decoded buffer.
  3. The renderer triggers a pipeline revocation event (such as starting a second video-only stream that forces reclamation of the video decoder resource).
  4. During Revoke(), AudioDecoderSoftwareWrapper is destroyed first, freeing the buffer while the vendor’s background threads or destructor are still active and holding the raw pointer.
  5. When the vendor backend accesses the dangling pointer, control flow is hijacked. Because the media service runs in the context of the unsandboxed Cast browser process, this would lead to a sandbox escape and Remote Code Execution.

Suggested Remediation

To address this vulnerability, ensure that the active audio decoder and its buffered resources are not destroyed before the vendor backend is notified or stopped.

One potential fix is to emulate VideoDecoderWrapper behavior: prevent AudioDecoderWrapper::Revoke() from immediately destroying the underlying ActiveAudioDecoderWrapper and releasing its buffers, and instead let them persist until the outer MediaPipelineBackendWrapper is destroyed. Alternatively, ensure Stop() or a teardown signal is explicitly dispatched to the vendor backend before releasing pending_pushed_buffer_ during the revocation flow.

Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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