Critical watch CVSS 8.8 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

Critical
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Chromoting
DescriptionUse after free in Chromoting
ComponentChromoting
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.
Tracker504587882
Fix commit6a81578f1c26 (chromium/src) +7/-2
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-04-20 (per vendor advisory)
Fix landed2026-04-21 (public git)
Disclosed2026-05-05
Patch gap14 days — fix public in source before the advisory

Background

`PipewireCaptureStream`
the Chrome Remote Desktop (Chromoting) Linux host class that captures screen frames via PipeWire/SharedScreenCastStream.
Member destruction order
C++ destroys a class’s members in the reverse of their declaration order, so a member declared later is destroyed earlier.
`CallbackProxy` / Observer
callback_proxy_ is a std::unique_ptr<CallbackProxy> installed as the stream_’s Observer, which the PipeWire capture thread calls back into to deliver frames.
`SharedScreenCastStream` (`stream_`)
a reference-counted WebRTC object that runs a background PipeWire thread and holds a raw Observer* back to callback_proxy_.

Root Cause Analysis

PipewireCaptureStream installs its callback_proxy_ as the Observer of stream_, and stream_’s background PipeWire thread invokes that observer to deliver captured frames. In the original header, callback_proxy_ was declared after stream_, so at destruction callback_proxy_ (declared later) was destroyed before stream_ — leaving stream_ and its still-running thread holding a dangling Observer* to the freed CallbackProxy, a use-after-free if the thread fired during teardown. The original destructor also did nothing to stop the stream or detach the observer before members began tearing down.

The fix reorders the members so callback_proxy_ is declared before stream_ (so stream_ is destroyed first), and the new destructor explicitly calls StopVideoCapture() — which joins the PipeWire thread — and stream_->SetObserver(nullptr) to clear the dangling Observer* before callback_proxy_ is destroyed, as the added comment states. Together these guarantee the capture thread is stopped and the observer detached while callback_proxy_ is still alive.

Key insight
The core mistake was a destruction-order hazard: callback_proxy_ was freed while stream_ and its live PipeWire thread still referenced it; the fix stops the thread and nulls the observer in the destructor and reorders the members so the stream is torn down before its proxy.

Attack Path

  1. Active capture session A Chromoting host has a running PipewireCaptureStream with callback_proxy_ installed as stream_’s observer and the PipeWire thread delivering frames.
  2. Stream teardown begins The PipewireCaptureStream is destroyed (session/stream ends); pre-patch, callback_proxy_ is destroyed first because it was declared after stream_, while the PipeWire thread is not yet joined.
  3. Thread touches freed proxy The still-running PipeWire thread invokes the now-freed CallbackProxy through stream_’s dangling Observer*, dereferencing freed memory (use-after-free).
  4. Downstream corruption (inferred) Whether the dangling callback can be steered into a controllable write or code execution is not shown by the diff and is (inferred).

Impact Assessment

An attacker able to influence the timing of stream teardown versus PipeWire frame delivery can trigger a use-after-free in the Chromoting (Chrome Remote Desktop) Linux host process. The vendor advisory rates this critical and describes it as a use after free in Chromoting; preconditions include the Linux PipeWire capture backend being in use and a race during PipewireCaptureStream destruction, and any escalation beyond the dangling dereference is (inferred).

Files Changed

  • remoting/host/linux/pipewire_capture_stream.cc
  • remoting/host/linux/pipewire_capture_stream.h

Audit Directions

  • Observer/callback outliving its target
    Audit classes that register a unique_ptr-owned member as an observer/callback on another member; verify the observed object is torn down (or detached) before the observer member.
  • Threaded resources freed without join
    Look for destructors that free objects referenced by a background thread without first stopping/joining that thread (e.g. missing Stop*() calls before member teardown).
  • Declaration-order-dependent lifetimes
    Review member declaration order in classes where one member holds a raw back-pointer to another, since reverse-order destruction can create transient dangling pointers.
From 6a81578f1c262475cd22c854c66dcbd7b689e47e Mon Sep 17 00:00:00 2001
From: Yuwei Huang <[email protected]>
Date: Tue, 21 Apr 2026 07:25:30 -0700
Subject: [PATCH] Fix UAF in PipewireCaptureStream due to member destruction order

Bug: 504587882
Change-Id: Id0839d82f626b0553e6d09cb56051f50d86798e5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7781064
Auto-Submit: Yuwei Huang <[email protected]>
Commit-Queue: Joe Downing <[email protected]>
Reviewed-by: Joe Downing <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1618163}
---

diff --git a/remoting/host/linux/pipewire_capture_stream.cc b/remoting/host/linux/pipewire_capture_stream.cc
index 70e0d48..bdacc0a 100644
--- a/remoting/host/linux/pipewire_capture_stream.cc
+++ b/remoting/host/linux/pipewire_capture_stream.cc
@@ -151,6 +151,11 @@
 
 PipewireCaptureStream::~PipewireCaptureStream() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // StopScreenCastStream() joins the PipeWire thread, and SetObserver(null)
+  // clears the dangling Observer* before `callback_proxy_` is destroyed.
+  StopVideoCapture();
+  stream_->SetObserver(nullptr);
 }
 
 void PipewireCaptureStream::SetPipeWireStream(
diff --git a/remoting/host/linux/pipewire_capture_stream.h b/remoting/host/linux/pipewire_capture_stream.h
index d1c2468..32d7bbd 100644
--- a/remoting/host/linux/pipewire_capture_stream.h
+++ b/remoting/host/linux/pipewire_capture_stream.h
@@ -98,11 +98,11 @@
   webrtc::ScreenId screen_id_ GUARDED_BY_CONTEXT(sequence_checker_) = -1;
   base::WeakPtr<webrtc::DesktopCapturer::Callback> callback_
       GUARDED_BY_CONTEXT(sequence_checker_);
+  std::unique_ptr<CallbackProxy> callback_proxy_
+      GUARDED_BY_CONTEXT(sequence_checker_);
   webrtc::scoped_refptr<webrtc::SharedScreenCastStream> stream_
       GUARDED_BY_CONTEXT(sequence_checker_) =
           webrtc::SharedScreenCastStream::CreateDefault();
-  std::unique_ptr<CallbackProxy> callback_proxy_
-      GUARDED_BY_CONTEXT(sequence_checker_);
   base::ObserverList<CaptureStream::CursorObserver> cursor_observers_
       GUARDED_BY_CONTEXT(sequence_checker_);
   bool is_capturing_frame_ GUARDED_BY_CONTEXT(sequence_checker_) = false;
Loading diff…

Original Bug Report

reported by [email protected]

Potential Cross-thread UAF in PipewireCaptureStream due to member destruction order

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 Chrome Security team. 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 the Linux Chrome Remote Desktop host due to the class member declaration order in PipewireCaptureStream. During stream teardown, a background PipeWire thread can make virtual method calls on an already-freed CallbackProxy object. An authenticated remote attacker could trigger this via SetVideoLayout messages, potentially leading to arbitrary code execution in the unsandboxed host process.

Affected files:

  • remoting/host/linux/pipewire_capture_stream.h
  • remoting/host/linux/pipewire_capture_stream.cc
  • third_party/webrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc

Estimated timestamp from git blame: 2025-08-28

Summary

A race condition leading to a potential heap use-after-free (UAF) exists in the Linux Wayland Remoting Host’s PipewireCaptureStream. The vulnerability is caused by an incorrect member declaration order in the PipewireCaptureStream class, where callback_proxy_ is destroyed before stream_. Because the background PipeWire thread loop managed by stream_ is only stopped when stream_ is destroyed, there is a race window where the background thread can access the already-freed callback_proxy_ and perform virtual calls, leading to a UAF.

Root Cause Analysis

In remoting/host/linux/pipewire_capture_stream.h, the class members are declared as follows:

webrtc::scoped_refptr<webrtc::SharedScreenCastStream> stream_
    GUARDED_BY_CONTEXT(sequence_checker_) =
        webrtc::SharedScreenCastStream::CreateDefault();
std::unique_ptr<CallbackProxy> callback_proxy_
    GUARDED_BY_CONTEXT(sequence_checker_);

In C++, class members are destroyed in the reverse order of their declaration. Therefore, when PipewireCaptureStream is destroyed, the callback_proxy_ is freed first, followed by the release of the stream_ reference.

The PipewireCaptureStream constructor registers callback_proxy_ as an observer using a raw pointer: stream_->SetObserver(callback_proxy_.get());.

The SharedScreenCastStream (in third_party/webrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc) maintains a background pw_thread_loop. This thread continues to invoke methods on the raw observer_ pointer until it is explicitly stopped via pw_thread_loop_stop() in the SharedScreenCastStreamPrivate destructor.

During the destruction of PipewireCaptureStream:

  1. callback_proxy_ is destroyed and its heap memory is freed.
  2. The background PipeWire thread receives a frame and calls ProcessBuffer().
  3. ProcessBuffer() checks if (observer_) (which is true, as the raw pointer is never cleared) and calls observer_->OnDesktopFrameChanged().
  4. This triggers a virtual method call on the freed callback_proxy_ memory.
  5. stream_ is released and the thread loop is finally stopped.

Potential Exploitation Steps

Note: These steps outline a potential path; our tooling agent has not executed a working proof of concept to verify arbitrary code execution.

  1. An attacker establishes an authenticated Chrome Remote Desktop (CRD) connection to a Linux Wayland host.
  2. The attacker uses heap grooming techniques via other CRD messages to prepare for a UAF vtable hijack.
  3. The attacker sends a SetVideoLayout message configured to remove an existing virtual monitor. This message is processed by the host regardless of whether the OS lock screen is active.
  4. The host processes the message, calling GnomeCaptureStreamManager::RemoveVirtualStream, which requests the compositor to stop the stream.
  5. The asynchronous callback OnStreamStopped erases the stream info, triggering the PipewireCaptureStream destructor.
  6. During the destruction race window, the active PipeWire thread attempts to process an incoming frame and executes observer_->OnDesktopFrameChanged().
  7. If the attacker successfully reclaimed the freed callback_proxy_ memory, the virtual call dereferences the attacker-controlled vtable, resulting in arbitrary code execution (RCE).

Impact

This vulnerability provides a classic primitive for Remote Code Execution (RCE) via a virtual call UAF. Because the remoting_me2me_host process runs unsandboxed with the privileges of the local user, successful exploitation results in full account compromise, effectively bypassing the OS lock screen.

Suggested Fix

  1. Reorder Declarations: In remoting/host/linux/pipewire_capture_stream.h, reorder the member variables so that callback_proxy_ is declared before stream_. This ensures stream_ (and its background thread) is destroyed prior to callback_proxy_.
  2. Explicit Cleanup: Alternatively, implement an explicit cleanup step in PipewireCaptureStream::~PipewireCaptureStream() that calls stream_->SetObserver(nullptr) and/or stream_->StopScreenCastStream() before the class members are naturally destroyed.

Evaluated with Chrome root at commit: 7353d249d9cacf9c7218e1d7b8a39cf39c72d646


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.

View on issue tracker