Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebRTC
DescriptionUse after free in WebRTC
ComponentWebRTC
Bug ClassUAF
Tracker499587071
Fix commit9dde36ebf937 (src) +239/-70
CISA KEVNot listed
Creditedboboliverfrancishoward@gmail.com
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
user_data_
modules/desktop_capture/linux/wayland/screencast_portal.cc
modified
if
modules/desktop_capture/linux/wayland/screencast_portal.cc
modified

Files Changed

  • modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc
  • modules/desktop_capture/linux/wayland/screen_capture_portal_interface.cc
  • modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h
  • modules/desktop_capture/linux/wayland/screencast_portal.cc
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

  1. Navigate to slack.com
  2. Join a huddle & share screen
  3. xdg-desktop-portal popup opens, choose one of the screens to share
  4. Confirm in slack you want to share that screen
  5. Repeat the above 1-10 times: The crash does not happen every time
  6. 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

View on issue tracker