CVE-2026-10926
Overview
Files Changed
components/cast_streaming/browser/control/remoting/rpc_demuxer_stream_handler.cccomponents/cast_streaming/browser/control/remoting/rpc_demuxer_stream_handler.h
Patch
From c349ab936de4209fb288f10933582057df1c0f4f Mon Sep 17 00:00:00 2001 From: Simeon Anfinrud <[email protected]> Date: Thu, 14 May 2026 13:49:11 -0700 Subject: [PATCH] [chromecast] Fix Potential UAF in Cast PlaybackCommandDispatcher Replace base::Unretained(this) with weak_factory_.GetWeakPtr() when binding delayed tasks. Bug: 500075522 Test: Compiled and passed unit tests. Change-Id: Ia8b65f419890567a0117e0fbf8a6ff5102c288f6 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7765499 Reviewed-by: Shawn Quereshi <[email protected]> Commit-Queue: Simeon Anfinrud <[email protected]> Cr-Commit-Position: refs/heads/main@{#1630756} --- diff --git a/components/cast_streaming/browser/control/remoting/rpc_demuxer_stream_handler.cc b/components/cast_streaming/browser/control/remoting/rpc_demuxer_stream_handler.cc index c6d6625..47a22fd5 100644 --- a/components/cast_streaming/browser/control/remoting/rpc_demuxer_stream_handler.cc +++ b/components/cast_streaming/browser/control/remoting/rpc_demuxer_stream_handler.cc @@ -198,6 +198,11 @@ RpcDemuxerStreamHandler::MessageProcessor::~MessageProcessor() = default; +void RpcDemuxerStreamHandler::MessageProcessor::ProcessMessage( + std::unique_ptr<openscreen::cast::RpcMessage> message) { + process_message_cb_.Run(remote_handle(), std::move(message)); +} + bool RpcDemuxerStreamHandler::MessageProcessor::OnRpcInitializeCallback( std::optional<media::AudioDecoderConfig> audio_config, std::optional<media::VideoDecoderConfig> video_config) { @@ -307,7 +312,8 @@ task_runner_->PostDelayedTask( FROM_HERE, - base::BindOnce(process_message_cb_, remote_handle(), std::move(message)), + base::BindOnce(&MessageProcessor::ProcessMessage, + weak_factory_.GetWeakPtr(), std::move(message)), remaining_time); last_request_time_ = now + remaining_time; diff --git a/components/cast_streaming/browser/control/remoting/rpc_demuxer_stream_handler.h b/components/cast_streaming/browser/control/remoting/rpc_demuxer_stream_handler.h index 6ee76e4..6fd2eef7a 100644 --- a/components/cast_streaming/browser/control/remoting/rpc_demuxer_stream_handler.h +++ b/components/cast_streaming/browser/control/remoting/rpc_demuxer_stream_handler.h @@ -97,6 +97,7 @@ Type type); ~MessageProcessor() override; + void ProcessMessage(std::unique_ptr<openscreen::cast::RpcMessage> message); bool OnRpcInitializeCallback( std::optional<media::AudioDecoderConfig> audio_config, std::optional<media::VideoDecoderConfig> video_config);
Original Bug Report
Potential Use-After-Free in Cast PlaybackCommandDispatcher via delayed task
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 without the security team.
Overview: A Use-After-Free (UAF) vulnerability exists in the Cast streaming component’s PlaybackCommandDispatcher due to an uncanceled delayed task. A callback bound with base::Unretained(this) can be posted with a delay and outlive its parent object. On platforms where BackupRefPtr is disabled at runtime, this can potentially be exploited for Remote Code Execution in the browser process.
Affected files:
components/cast_streaming/browser/control/playback_command_dispatcher.cccomponents/cast_streaming/browser/control/remoting/rpc_demuxer_stream_handler.cccomponents/cast_streaming/browser/control/playback_command_dispatcher.hcomponents/cast_streaming/browser/control/remoting/rpc_demuxer_stream_handler.hthird_party/openscreen/src/cast/streaming/public/protobuf_messenger.h
Estimated timestamp from git blame: 2022-05-12
Vulnerability Summary
A Use-After-Free (UAF) vulnerability exists in PlaybackCommandDispatcher within the Cast streaming component (components/cast_streaming/browser/control/playback_command_dispatcher.cc). The class binds a callback using base::Unretained(this) and passes it to an RpcDemuxerStreamHandler, which can schedule it as a delayed task. If the Cast session is terminated before the task executes, the dispatcher is destroyed without canceling the task, leading to a UAF.
Technical Details
In PlaybackCommandDispatcher::OnRemotingSessionNegotiated(), a callback to SendRemotingRpcMessageToRemote is created using base::Unretained(this):
demuxer_stream_handler_ = std::make_unique<remoting::RpcDemuxerStreamHandler>(
task_runner_, this,
// ...
base::BindRepeating(&PlaybackCommandDispatcher::SendRemotingRpcMessageToRemote,
base::Unretained(this)));
This callback is ultimately passed to MessageProcessor. If an invalid config is received on an RPC_DS_READUNTIL_CALLBACK message, MessageProcessor::OnRpcReadUntilCallback() invokes OnNoBuffersAvailable() to request a corrected configuration. To rate-limit these requests, OnNoBuffersAvailable() calculates a delay (remaining_time, up to 100ms) and posts the callback to the SequencedTaskRunner:
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(process_message_cb_, remote_handle(), std::move(message)),
remaining_time);
If the remote sender immediately closes the Cast control channel, the session teardown sequence is initiated. ReceiverSessionImpl::OnMojoDisconnect() calls CastStreamingSession::Stop(), which destroys the ReceiverSessionClient and, consequently, the PlaybackCommandDispatcher. The pending delayed task, however, is not canceled.
When the timer expires, the task executes SendRemotingRpcMessageToRemote() using a dangling this pointer. It checks if (!messenger_) and invokes messenger_->SendMessageToRemote(*message). Because messenger_ is a raw pointer and ProtobufMessenger::SendMessageToRemote utilizes an internal std::function callback, controlling the freed memory allows an attacker to hijack the instruction pointer when the std::function is executed.
Notably, on CastOS (Linux-based Chromecast), BackupRefPtr (MiraclePtr) is compiled in but explicitly disabled at runtime by default (enable_backup_ref_ptr_feature_flag = false). Consequently, base::Unretained acts like a standard raw pointer and does not quarantine the allocation, rendering the UAF fully exploitable.
Potential Reproduction Steps
Note: These are suggested theoretical steps; our tooling agent does not have the ability to run code or provide a functional PoC.
- An attacker on the local network initiates a Cast streaming session with a CastOS receiver device, triggering the creation of a
PlaybackCommandDispatcher. - The attacker negotiates a remoting session and acquires demuxer handles via an
RPC_ACQUIRE_DEMUXERnetwork message. - The attacker sends a valid network message to trigger an initial buffer request.
- Rapidly (within 100ms), the attacker sends an
RPC_DS_READUNTIL_CALLBACKmessage with an invalid configuration (e.g., an audio config for a video handle). This causesOnNoBuffersAvailable()to post a delayed task referencing thebase::Unretainedcallback. - The attacker immediately terminates the Cast control channel (e.g., via a TCP FIN packet), causing the browser process to destroy the
PlaybackCommandDispatcher. - The attacker quickly sprays the browser process heap (e.g., by rapidly opening new Cast connections and sending controlled payload data) to reclaim the memory previously occupied by the dispatcher. They set the offset corresponding to
messenger_to point to a fake, attacker-controlledProtobufMessengerobject. - After the ~100ms delay timer expires, the queued task executes. It dereferences the attacker-controlled
messenger_pointer and callsSendMessageToRemote(), which executes a hijackedstd::function, leading to Remote Code Execution in the unsandboxed browser process.
Suggested Fix
Update PlaybackCommandDispatcher to use a base::WeakPtr to bind its callbacks instead of base::Unretained(this).
Add a base::WeakPtrFactory<PlaybackCommandDispatcher> weak_factory_{this}; as the last member of the class, and bind the callback using:
base::BindRepeating(
&PlaybackCommandDispatcher::SendRemotingRpcMessageToRemote,
weak_factory_.GetWeakPtr())
This ensures that if the PlaybackCommandDispatcher is destroyed before the delayed task executes, the weak pointer will be invalidated, and the callback will safely become a no-op instead of operating on freed memory.
Evaluated with Chrome root at commit: f200f57a19490707ff8bc7aa5de3cbc443a3afad
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.