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 <grulja@gmail.com>
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 <hta@webrtc.org>
Commit-Queue: Jan Grulich <grulja@gmail.com>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Reviewed-by: Andreas Pehrson <apehrson@mozilla.com>
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 bo...@gmail.com
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