Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebRTC
DescriptionUse after free in WebRTC
ComponentWebRTC
Bug ClassUAF
Tracker504551032
Fix commit90b22181ec92 (src) +16/-5
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-19

Changed Functions

FunctionChangeNotes
if
modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
modified

Files Changed

  • modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
From 90b22181ec922900e26de71463b8e0ed47a5c2dd Mon Sep 17 00:00:00 2001
From: mark a. foltz <mfoltz@chromium.org>
Date: Mon, 20 Apr 2026 15:16:47 -0700
Subject: [PATCH] [Pipewire] Fix mouse cursor data race.

This addresses a potential data race when the mouse cursor is updated
on the Pipewire thread and read by the capture thread.

Bug: chromium:504551032
Change-Id: I1afb9febe8bb41ce62c63872e2cb5514e908dd38
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/465440
Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org>
Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org>
Auto-Submit: Mark Foltz <mfoltz@chromium.org>
Cr-Commit-Position: refs/heads/main@{#47502}
---

diff --git a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
index d611ca0..1aa8be5 100644
--- a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
+++ b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
@@ -128,8 +128,9 @@
   Mutex latest_frame_lock_ RTC_ACQUIRED_AFTER(queue_lock_);
   SharedDesktopFrame* latest_available_frame_
       RTC_GUARDED_BY(&latest_frame_lock_) = nullptr;
-  std::unique_ptr<MouseCursor> mouse_cursor_;
-  DesktopVector mouse_cursor_position_ = DesktopVector(-1, -1);
+  std::unique_ptr<MouseCursor> mouse_cursor_ RTC_GUARDED_BY(&latest_frame_lock_);
+  DesktopVector mouse_cursor_position_ RTC_GUARDED_BY(&latest_frame_lock_) =
+      DesktopVector(-1, -1);
 
   int64_t modifier_;
   std::unique_ptr<EglDmaBuf> egl_dmabuf_;
@@ -695,6 +696,7 @@
 }
 
 std::unique_ptr<MouseCursor> SharedScreenCastStreamPrivate::CaptureCursor() {
+  MutexLock latest_frame_lock(&latest_frame_lock_);
   if (!mouse_cursor_) {
     return nullptr;
   }
@@ -703,6 +705,7 @@
 }
 
 DesktopVector SharedScreenCastStreamPrivate::CaptureCursorPosition() {
+  MutexLock latest_frame_lock(&latest_frame_lock_);
   return mouse_cursor_position_;
 }
 
@@ -774,20 +777,28 @@
           mouse_frame->CopyPixelsFrom(
               bitmap_data, bitmap->stride,
               DesktopRect::MakeWH(bitmap->size.width, bitmap->size.height));
-          mouse_cursor_ = std::make_unique<MouseCursor>(
-              mouse_frame, DesktopVector(cursor->hotspot.x, cursor->hotspot.y));
+          {
+            MutexLock latest_frame_lock(&latest_frame_lock_);
+            mouse_cursor_ = std::make_unique<MouseCursor>(
+                mouse_frame,
+                DesktopVector(cursor->hotspot.x, cursor->hotspot.y));
+          }
 
           if (observer_) {
             observer_->OnCursorShapeChanged();
           }
         }
-        mouse_cursor_position_.set(cursor->position.x, cursor->position.y);
+        {
+          MutexLock latest_frame_lock(&latest_frame_lock_);
+          mouse_cursor_position_.set(cursor->position.x, cursor->position.y);
+        }
 
         if (observer_) {
           observer_->OnCursorPositionChanged();
         }
       } else {
         // Indicate an invalid cursor
+        MutexLock latest_frame_lock(&latest_frame_lock_);
         mouse_cursor_position_.set(-1, -1);
       }
     }
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential UAF and Double-Free in Wayland Screen Capture due to data race on MouseCursor

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 data race on the std::unique_ptr<MouseCursor> mouse_cursor_ member in SharedScreenCastStreamPrivate can occur between the PipeWire thread and the capture thread. This lack of synchronization allows a dangling pointer to be moved to the composer, leading to a potential Use-After-Free read (information leak) and Double-Free in the unsandboxed browser process.

Affected files:

  • third_party/webrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
  • third_party/webrtc/modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.cc
  • third_party/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc

Estimated timestamp from git blame: 2024-06-27

Root Cause

A thread safety issue exists in SharedScreenCastStreamPrivate within the WebRTC desktop capture module for Linux/Wayland (third_party/webrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc). The member mouse_cursor_ (a std::unique_ptr<MouseCursor>) is accessed and modified without synchronization from two different threads:

  1. Writer (PipeWire thread): The background pipewire-main-loop thread invokes ProcessBuffer(), which creates a new MouseCursor object and assigns it to mouse_cursor_ (line 777). This assignment is performed without holding any mutex.
  2. Reader (Capture thread): The desktopCaptureThread (an unsandboxed thread in the browser process) calls CaptureCursor(), which executes return std::move(mouse_cursor_); (line 702). This is also performed without synchronization.

Vulnerability Details

Because std::unique_ptr assignment and move operations are not atomic, concurrent execution leads to a data race on the internal raw pointer.

  1. The Reader thread executes std::move(mouse_cursor_) and reads the raw pointer just before the Writer thread completes its assignment of a new cursor.
  2. The Writer thread completes its assignment, which triggers the deletion of the old MouseCursor object.
  3. The Reader thread completes its move, successfully constructing a new unique_ptr that wraps the now-freed pointer.

This leads to two distinct impacts:

  • Use-After-Free (Information Leak): The dangling pointer is passed to DesktopAndCursorComposer::OnMouseCursor(). During frame composition, DesktopFrameWithCursor calls AlphaBlend, which reads from the freed MouseCursor’s image data and copies it into the captured video frame. This blends browser-process heap memory into the video stream sent back to the web page.
  • Double-Free (Potential RCE): The DesktopAndCursorComposer stores the dangling pointer in its own std::unique_ptr<MouseCursor> cursor_. When a new cursor is received, or upon destruction, cursor_.reset() is called, which attempts to delete the already-freed pointer a second time. Because MouseCursor manages a DesktopFrame with a virtual destructor, this provides a primitive for control-flow hijacking in the unsandboxed browser process.

Potential Attack Steps

(Note: These are suggested steps based on code analysis; a working PoC has not been executed.)

  1. An attacker tricks a victim on Linux (Wayland/PipeWire) into visiting a malicious site.
  2. The site calls navigator.mediaDevices.getDisplayMedia({ video: true }).
  3. Once the user grants permission, the browser process begins capturing the screen using the vulnerable Wayland module.
  4. The malicious page rapidly alters CSS cursor shapes or prompts the user to move the mouse rapidly while capturing frames at a high rate. This maximizes the execution frequency of both threads to win the race condition.

Suggested Fix

Access to mouse_cursor_ and mouse_cursor_position_ must be synchronized. Since queue_lock_ and latest_frame_lock_ already exist in SharedScreenCastStreamPrivate, one of these mutexes (or a new dedicated mutex) should be held during both the assignment in ProcessBuffer() and the read/move in CaptureCursor() and CaptureCursorPosition().

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