Overview

High
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Receiver
DescriptionUse after free in Receiver
ComponentReceiver
Sub-componentCast_streaming
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.
Tracker517371367
Fix commitd19a0a41c2d8 (chromium/src) +6/-2
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-28 (per vendor advisory)
Fix landed2026-05-30 (public git)
Disclosed2026-09-08
Patch gap101 days — fix public in source before the advisory

Background

`CastMessagePortImpl`
A cast_streaming class that wraps a MessagePort transport for Cast V2 messaging and notifies its owner through a Client interface and an on_close_ closure.
`MaybeClose`
The method that tears the port down: it resets message_port_, reports an error to client_, and then runs the on_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.

Key insight
The method dereferenced its own members after invoking a client callback that can synchronously free 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

  1. Establish a Cast messaging session An attacker on the Cast V2 channel drives the receiver-side CastMessagePortImpl into a state where the port will be torn down.
  2. Trigger `MaybeClose` A socket error or close condition causes MaybeClose to run and invoke client_->OnError(kCastV2CastSocketError).
  3. Free the object inside the callback The client_’s error handler synchronously destroys the owning object and thus this, while MaybeClose is still on the stack.
  4. Dereference freed memory Execution returns into MaybeClose, which reads on_close_ and runs the moved-out closure on the now-freed CastMessagePortImpl, the use-after-free.
  5. 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

An attacker who can drive Cast streaming message-port teardown gains a use-after-free of a 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

FunctionChangeNotes
if
components/cast_streaming/browser/cast_message_port_impl.cc
modified

Files Changed

  • components/cast_streaming/browser/cast_message_port_impl.h
  • components/cast_streaming/browser/cast_message_port_impl.cc

Audit Directions

  • Post-callback member access
    Audit any method that invokes a client/observer/delegate callback (e.g. OnError, OnClose, OnMessage) and then touches this or its fields afterward; require a WeakPtr guard or a documented no-delete contract.
  • Multiple re-entrant frees in one method
    Where 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 free this is likewise guarded, not just the last one.
  • Cast/message-port lifetime coupling
    Review other cast_streaming and message_port teardown paths where the transport, the Client, and completion closures can destroy each other, and confirm ownership/teardown ordering prevents stale self-references.
From d19a0a41c2d8266a9e6ab06c1e0466b027208d99 Mon Sep 17 00:00:00 2001
From: Muyao Xu <[email protected]>
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 <[email protected]>
Reviewed-by: Jordan Bayles <[email protected]>
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
Loading diff…

Original Bug Report

reported by [email protected]

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.cc
  • components/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.)

  1. During the initial connection handshake phase when is_initialized_ is still false, a connection error or channel closure is encountered on the MessagePort channel (for example, if a compromised Renderer process abruptly closes the Mojo IPC pipe of the MessagePort).
  2. This triggers CastMessagePortImpl::OnPipeError(), which synchronously calls CastMessagePortImpl::MaybeClose().
  3. Inside MaybeClose(), client_->OnError() is called on line 104 with kCastV2CastSocketError.
  4. client_ (a ReceiverSessionMessenger instance) synchronously executes ReportError(), propagating the error to the registered Open Screen callback lambda, which synchronously calls ReceiverSessionClient::OnError().
  5. Because initialization is not yet complete (is_initialized_ == false), OnError() synchronously calls EndSession(), which triggers a sequence of termination events:
    • ReceiverSessionImpl::OnSessionEnded()
    • StreamingReceiverSessionClient::OnStreamingSessionEnded()
    • StreamingRuntimeApplication::OnError()
    • StreamingRuntimeApplication::StopApplication()
  6. Inside StopApplication(), receiver_session_client_.reset() is called. This triggers a synchronous destruction cascade of all owned components, including StreamingReceiverSessionClient, ReceiverSessionImpl, CastStreamingSession, and eventually CastMessagePortImpl (this is deallocated).
  7. The execution flow unwinds back to CastMessagePortImpl::MaybeClose() (line 107) and evaluates if (on_close_). Since this has been deleted, it performs a Use-After-Free read and subsequently calls std::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.

View on issue tracker