CVE-2026-87578
Overview
Background
- `CastMessagePortImpl`
- A cast_streaming class that wraps a
MessagePorttransport for Cast V2 messaging and notifies its owner through aClientinterface and anon_close_closure. - `MaybeClose`
- The method that tears the port down: it resets
message_port_, reports an error toclient_, and then runs theon_close_completion closure. - `base::WeakPtr` / `base::WeakPtrFactory`
- A Chromium smart-pointer pair that yields references which automatically become null once the pointed-to object is destroyed, so callers can test validity instead of dereferencing freed memory.
- Synchronous re-entrant destruction
- A pattern where invoking a callback (here
client_->OnError) can, before it returns, run code that deletes the very object that issued the call.
Root Cause Analysis
CastMessagePortImpl::MaybeClose calls client_->OnError(...) and then, on the following lines, reads and moves the instance member on_close_. As the commit message states, client_->OnError can synchronously destroy this, because the client’s error handler may tear down the object that owns this port. When that happens, the subsequent if (on_close_) test and std::move(on_close_).Run() operate on a CastMessagePortImpl that has already been freed, which is the use-after-free. The pre-patch code did anticipate that running on_close_ itself could delete this (hence the “Do not add any code after running the closure” comment), but it did not anticipate that the earlier client_->OnError call could delete this before on_close_ was ever touched.
The fix captures weak_this = weak_factory_.GetWeakPtr() at the top of the method and gates the member access on weak_this && weak_this->on_close_, so if OnError destroyed the object the closure block is skipped entirely.
this, with no revalidation of the object; the fix threads a WeakPtr through so the post-callback member access is guarded by an is-still-alive check.Attack Path
- Establish a Cast messaging session
An attacker on the Cast V2 channel drives the receiver-side
CastMessagePortImplinto a state where the port will be torn down. - Trigger `MaybeClose`
A socket error or close condition causes
MaybeCloseto run and invokeclient_->OnError(kCastV2CastSocketError). - Free the object inside the callback
The
client_’s error handler synchronously destroys the owning object and thusthis, whileMaybeCloseis still on the stack. - Dereference freed memory
Execution returns into
MaybeClose, which readson_close_and runs the moved-out closure on the now-freedCastMessagePortImpl, the use-after-free. - Exploit the freed allocation (inferred)
Controlling the contents of the reclaimed heap slot could turn the stale
on_close_access/Run()into a controlled call, but the diff does not demonstrate a concrete read/write or code-execution primitive.
Impact Assessment
CastMessagePortImpl in the process hosting the Cast receiver (the browser/utility-side cast_streaming stack), with the precondition of an active Cast V2 messaging session that reaches the error/close path. Beyond the memory-corruption primitive, escalation to controlled execution is (inferred) and not shown by the patch. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.3.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcomponents/cast_streaming/browser/cast_message_port_impl.cc |
modified |
Files Changed
components/cast_streaming/browser/cast_message_port_impl.hcomponents/cast_streaming/browser/cast_message_port_impl.cc
Audit Directions
- Post-callback member accessAudit any method that invokes a client/observer/delegate callback (e.g.
OnError,OnClose,OnMessage) and then touchesthisor its fields afterward; require aWeakPtrguard or a documented no-delete contract. - Multiple re-entrant frees in one methodWhere a function already guards one deletion point (like the
on_close_closure here), verify that every earlier call in the same method which can also freethisis likewise guarded, not just the last one. - Cast/message-port lifetime couplingReview other cast_streaming and
message_portteardown paths where the transport, theClient, and completion closures can destroy each other, and confirm ownership/teardown ordering prevents stale self-references.
Patch
From d19a0a41c2d8266a9e6ab06c1e0466b027208d99 Mon Sep 17 00:00:00 2001
From: Muyao Xu <muyaoxu@google.com>
Date: Fri, 29 May 2026 19:58:08 -0700
Subject: [PATCH] [cast_streaming] Fix UAF in CastMessagePortImpl
Calling `client_->OnError` in `MaybeClose` can synchronously destroy
`this`. This CL uses a WeakPtr to guard access to on_close_ to prevent
UAF.
Bug: b:517371367
Change-Id: I4f10df89ee27f00c659bda847ffdbf4e9b1b3948
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7882166
Commit-Queue: Jordan Bayles <jophba@chromium.org>
Reviewed-by: Jordan Bayles <jophba@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1638928}
---
diff --git a/components/cast_streaming/browser/cast_message_port_impl.cc b/components/cast_streaming/browser/cast_message_port_impl.cc
index 6568f23..903822d5d 100644
--- a/components/cast_streaming/browser/cast_message_port_impl.cc
+++ b/components/cast_streaming/browser/cast_message_port_impl.cc
@@ -97,6 +97,7 @@
CastMessagePortImpl::~CastMessagePortImpl() = default;
void CastMessagePortImpl::MaybeClose() {
+ base::WeakPtr<CastMessagePortImpl> weak_this = weak_factory_.GetWeakPtr();
if (message_port_) {
message_port_.reset();
}
@@ -104,10 +105,10 @@
client_->OnError(
openscreen::Error(openscreen::Error::Code::kCastV2CastSocketError));
}
- if (on_close_) {
+ if (weak_this && weak_this->on_close_) {
// |this| might be deleted as part of |on_close_| being run. Do not add any
// code after running the closure.
- std::move(on_close_).Run();
+ std::move(weak_this->on_close_).Run();
}
}
diff --git a/components/cast_streaming/browser/cast_message_port_impl.h b/components/cast_streaming/browser/cast_message_port_impl.h
index e70d09a..a78396eb 100644
--- a/components/cast_streaming/browser/cast_message_port_impl.h
+++ b/components/cast_streaming/browser/cast_message_port_impl.h
@@ -10,6 +10,7 @@
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
#include "components/cast/message_port/message_port.h"
#include "third_party/openscreen/src/cast/common/public/message_port.h"
@@ -60,6 +61,8 @@
raw_ptr<Client> client_ = nullptr;
std::unique_ptr<cast_api_bindings::MessagePort> message_port_;
base::OnceClosure on_close_;
+
+ base::WeakPtrFactory<CastMessagePortImpl> weak_factory_{this};
};
} // namespace cast_streaming
Original Bug Report
Potential Use-After-Free in CastMessagePortImpl::MaybeClose due to synchronous self-destruction
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 CastMessagePortImpl::MaybeClose in the browser process. Calling client_->OnError can trigger a synchronous destruction cascade that deletes the CastMessagePortImpl instance. The code subsequently continues to execute and evaluate the on_close_ callback from the freed memory of the deleted instance.
Affected files:
components/cast_streaming/browser/cast_message_port_impl.cccomponents/cast_streaming/browser/cast_message_port_impl.h
Estimated timestamp from git blame: 2020-12-04
Description
A potential Use-After-Free (UAF) vulnerability has been identified in CastMessagePortImpl::MaybeClose() within components/cast_streaming/browser/cast_message_port_impl.cc. Under specific conditions, invoking the error callback on client_ initiates a synchronous teardown sequence on the same call stack. This sequence results in the deletion of the CastMessagePortImpl instance. When the execution unwinds back to the caller frame, the method attempts to read and execute the in-object on_close_ closure member variable on the already-freed this pointer.
Because on_close_ is a base::OnceClosure (which wraps a reference-counted BindStateBase pointer) rather than a raw_ptr, MiraclePtr (BackupRefPtr) does not protect against or mitigate this memory safety violation.
Potential Step-by-Step Sequence
(Note: These are potential steps identified through static code analysis; we do not currently have a running proof-of-concept exploit.)
- During the initial connection handshake phase when
is_initialized_is stillfalse, a connection error or channel closure is encountered on theMessagePortchannel (for example, if a compromised Renderer process abruptly closes the Mojo IPC pipe of theMessagePort). - This triggers
CastMessagePortImpl::OnPipeError(), which synchronously callsCastMessagePortImpl::MaybeClose(). - Inside
MaybeClose(),client_->OnError()is called on line 104 withkCastV2CastSocketError. client_(aReceiverSessionMessengerinstance) synchronously executesReportError(), propagating the error to the registered Open Screen callback lambda, which synchronously callsReceiverSessionClient::OnError().- Because initialization is not yet complete (
is_initialized_ == false),OnError()synchronously callsEndSession(), which triggers a sequence of termination events:ReceiverSessionImpl::OnSessionEnded()StreamingReceiverSessionClient::OnStreamingSessionEnded()StreamingRuntimeApplication::OnError()StreamingRuntimeApplication::StopApplication()
- Inside
StopApplication(),receiver_session_client_.reset()is called. This triggers a synchronous destruction cascade of all owned components, includingStreamingReceiverSessionClient,ReceiverSessionImpl,CastStreamingSession, and eventuallyCastMessagePortImpl(thisis deallocated). - The execution flow unwinds back to
CastMessagePortImpl::MaybeClose()(line 107) and evaluatesif (on_close_). Sincethishas been deleted, it performs a Use-After-Free read and subsequently callsstd::move(on_close_).Run()on line 110, allowing a potential instruction pointer hijack in the unsandboxed Browser process.
Proposed Remediation
To prevent this vulnerability, use a base::WeakPtr to check for the instance’s liveness before accessing any member variables or running callbacks after client_->OnError() is called.
Add a base::WeakPtrFactory<CastMessagePortImpl> weak_factory_{this}; to CastMessagePortImpl and update MaybeClose to:
void CastMessagePortImpl::MaybeClose() {
base::WeakPtr<CastMessagePortImpl> weak_this = weak_factory_.GetWeakPtr();
if (message_port_) {
message_port_.reset();
}
if (client_) {
client_->OnError(
openscreen::Error(openscreen::Error::Code::kCastV2CastSocketError));
}
if (weak_this && weak_this->on_close_) {
std::move(weak_this->on_close_).Run();
}
}
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.