Chrome · WebRTC
CVE-2026-11074
UAF in WebRTC
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
user_data_modules/desktop_capture/linux/wayland/screencast_portal.cc |
modified | |
ifmodules/desktop_capture/linux/wayland/screencast_portal.cc |
modified |
Files Changed
modules/desktop_capture/linux/wayland/base_capturer_pipewire.ccmodules/desktop_capture/linux/wayland/screen_capture_portal_interface.ccmodules/desktop_capture/linux/wayland/screen_capture_portal_interface.hmodules/desktop_capture/linux/wayland/screencast_portal.cc
Patch
From 9dde36ebf937da0ab92837932b253cc3d42c8dc2 Mon Sep 17 00:00:00 2001 From: Jan Grulich <[email protected]> Date: Thu, 16 Apr 2026 11:04:26 +0200 Subject: [PATCH] Fix use-after-free in ScreenCast and Camera portal D-Bus callbacks GDBus async callbacks fire on the GLib main thread with a raw pointer to the portal object. When the portal is destroyed on another thread, the callback accesses freed memory. Introduce PortalGuard, a ref-counted mutex-protected wrapper that outlives the portal. Callbacks lock the guard and check the portal pointer before use. Stop() locks the same mutex to null the pointer, blocking until any in-flight callback finishes. Utility functions now take scoped_refptr<PortalGuard> and manage refs internally. Bug: chromium:491979284 Bug: chromium:499587071 Change-Id: I80fe20c5c3b6509666554c7cc7454f09cab6c2e4 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/463800 Reviewed-by: Harald Alvestrand <[email protected]> Commit-Queue: Jan Grulich <[email protected]> Reviewed-by: Ilya Nikolaevskiy <[email protected]> Reviewed-by: Andreas Pehrson <[email protected]> Cr-Commit-Position: refs/heads/main@{#47444} --- diff --git a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc index 6cb394f..45537dc 100644 --- a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc +++ b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc @@ -75,6 +75,10 @@ } BaseCapturerPipeWire::~BaseCapturerPipeWire() { + // Destroy the portal first. Its destructor may block until in-flight + // GDBus callbacks finish, and those callbacks access other members + // (options_, callback_) through the notifier_ pointer. + portal_.reset(); options_.screencast_stream()->StopScreenCastStream(); } diff --git a/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.cc b/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.cc index 3ea47e4..3a71218 100644 --- a/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.cc +++ b/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.cc @@ -15,6 +15,7 @@ #include <cstdint> #include <string> +#include "modules/portal/portal_guard.h" #include "modules/portal/portal_request_response.h" #include "modules/portal/scoped_glib.h" #include "modules/portal/xdg_desktop_portal_utils.h" @@ -73,7 +74,8 @@ GVariant* parameters, GDBusConnection* connection, std::string& session_handle, - guint& session_closed_signal_id) { + guint& session_closed_signal_id, + scoped_refptr<PortalGuard> guard) { uint32_t portal_response = 2; Scoped<GVariant> response_data; g_variant_get(parameters, /*format_string=*/"(u@a{sv})", &portal_response, @@ -101,7 +103,8 @@ session_closed_signal_id = g_dbus_connection_signal_subscribe( connection, kDesktopBusName, kSessionInterfaceName, /*member=*/"Closed", session_handle.c_str(), /*arg0=*/nullptr, G_DBUS_SIGNAL_FLAGS_NONE, - session_close_signal_handler, this, /*user_data_free_func=*/nullptr); + session_close_signal_handler, guard->AddRefAndGet(), + portal_guard_release); } void ScreenCapturePortalInterface::OnStartRequestResult(GDBusProxy* proxy, diff --git a/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h b/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h index 06401f0..487a1e6 100644 --- a/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h +++ b/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h @@ -16,6 +16,8 @@ #include <string> +#include "api/scoped_refptr.h" +#include "modules/portal/portal_guard.h" #include "modules/portal/portal_request_response.h" #include "modules/portal/xdg_session_details.h" #include "rtc_base/system/rtc_export.h" @@ -65,7 +67,8 @@ GVariant* parameters, GDBusConnection* connection, std::string& session_handle, - guint& session_closed_signal_id); + guint& session_closed_signal_id, + scoped_refptr<PortalGuard> guard); // Handles the result of session start request. void OnStartRequestResult(GDBusProxy* proxy, GAsyncResult* result); }; diff --git a/modules/desktop_capture/linux/wayland/screencast_portal.cc b/modules/desktop_capture/linux/wayland/screencast_portal.cc index affab4d..e399d38 100644 --- a/modules/desktop_capture/linux/wayland/screencast_portal.cc +++ b/modules/desktop_capture/linux/wayland/screencast_portal.cc @@ -21,6 +21,7 @@ #include "modules/desktop_capture/desktop_capture_types.h" #include "modules/portal/pipewire_utils.h" +#include "modules/portal/portal_guard.h" #include "modules/portal/portal_request_response.h" #include "modules/portal/scoped_glib.h" #include "modules/portal/xdg_desktop_portal_utils.h" @@ -63,7 +64,6 @@ notifier, OnProxyRequested, OnSourcesRequestResponseSignal, - this, prefer_cursor_embedded) {} ScreenCastPortal::ScreenCastPortal( @@ -71,7 +71,6 @@ PortalNotifier* notifier, ProxyRequestResponseHandler proxy_request_response_handler, SourcesRequestResponseSignalHandler sources_request_response_signal_handler, - gpointer user_data, bool prefer_cursor_embedded) : notifier_(notifier), capture_source_type_(ToCaptureSourceType(type)), @@ -79,14 +78,26 @@ : CursorMode::kMetadata), proxy_request_response_handler_(proxy_request_response_handler), sources_request_response_signal_handler_( - sources_request_response_signal_handler), - user_data_(user_data) {} + sources_request_response_signal_handler) {} ScreenCastPortal::~ScreenCastPortal() { Stop(); } void ScreenCastPortal::Stop() { + // Cancel first so that any callback entering g_dbus_proxy_call_finish() + // after this point gets G_IO_ERROR_CANCELLED via GTask's check_cancellable. + if (cancellable_) + g_cancellable_cancel(cancellable_); + + // Lock the guard to wait for any in-flight callback on the GLib main + // thread that already passed the _finish() check and is currently using + // the portal. Once we acquire the lock, that callback has finished. + if (guard_) { + MutexLock lock(&guard_->mutex); + guard_->portal = nullptr; + } + UnsubscribeSignalHandlers(); TearDownSession(std::move(session_handle_), proxy_, cancellable_, connection_); @@ -135,8 +146,12 @@ void ScreenCastPortal::Start() { cancellable_ = g_cancellable_new(); + + guard_ = scoped_refptr<PortalGuard>(new PortalGuard()); + guard_->portal = this; + RequestSessionProxy(kScreenCastInterfaceName, proxy_request_response_handler_, - cancellable_, this); + cancellable_, guard_); } xdg_portal::SessionDetails ScreenCastPortal::GetSessionDetails() { @@ -145,8 +160,8 @@ void ScreenCastPortal::OnPortalDone(RequestResponse result) { notifier_->OnScreenCastRequestResult(result, pw_stream_node_id_, pw_fd_); - if (result != RequestResponse::kSuccess) { - Stop(); + if (result != RequestResponse::kSuccess && cancellable_) { + g_cancellable_cancel(cancellable_); } } @@ -154,7 +169,9 @@ void ScreenCastPortal::OnProxyRequested(GObject* gobject, GAsyncResult* result, gpointer user_data) { - static_cast<ScreenCastPortal*>(user_data)->RequestSessionUsingProxy(result); + ScopedPortalLock lock(user_data); + if (auto* that = static_cast<ScreenCastPortal*>(lock.portal())) + that->RequestSessionUsingProxy(result); } void ScreenCastPortal::RequestSession(GDBusProxy* proxy) { @@ -162,15 +179,16 @@ connection_ = g_dbus_proxy_get_connection(proxy_); SetupSessionRequestHandlers( "webrtc", OnSessionRequested, OnSessionRequestResponseSignal, connection_, - proxy_, cancellable_, portal_handle_, session_request_signal_id_, this); + proxy_, cancellable_, portal_handle_, session_request_signal_id_, guard_); } // static void ScreenCastPortal::OnSessionRequested(GDBusProxy* proxy, GAsyncResult* result,
Loading diff…
Original Bug Report
reported by [email protected]
Chromium crashes during xdg-desktop-portal screencast on Wayland KDE (Slack screen share)
Steps to reproduce the problem
- Navigate to slack.com
- Join a huddle & share screen
- xdg-desktop-portal popup opens, choose one of the screens to share
- Confirm in slack you want to share that screen
- Repeat the above 1-10 times: The crash does not happen every time
- See the shared screen rendered once in slack before chromium freezes then crashes
Problem Description
Chromium sometimes crashes when sharing a screen via xdg-desktop-portal in slack.
Additional Comments
Crash happens in Chromium & Google Chrome
Summary
Chromium crashes during xdg-desktop-portal screencast on Wayland KDE (Slack screen share)
Custom Questions
Crashed report ID:
1d90fc35909c51b9
How much crashed?
The whole browser
Is it a problem with a plugin?
No - It’s the browser itself
Additional Data
Category: Crashes
Chrome Channel: Stable
Regression: N/A
Has Chrome Feedback with description matching the bug title: https://listnr.corp.google.com/product/237/reports?searchText=Chromium%20crashes%20during%20xdg-desktop-portal%20screencast%20on%20Wayland%20KDE%20(Slack%20screen%20share)&filter=0&dateRange=30
- https://listnr.corp.google.com/product/237/reports?searchText=Chromium crashes during xdg-desktop-portal screencast on Wayland KDE (Slack screen share)&filter=0&dateRange=30
- https://listnr.corp.google.com/product/237/reports?searchText=Chromium%20crashes%20during%20xdg-desktop-portal%20screencast%20on%20Wayland%20KDE%20(Slack%20screen%20share
- https://listnr.corp.google.com/product/237/reports?searchText=Chromium%20crashes%20during%20xdg-desktop-portal%20screencast%20on%20Wayland%20KDE%20(Slack%20screen%20share)&filter=0&dateRange=30
References
On This Page