CVE-2026-9111
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmodules/desktop_capture/linux/wayland/shared_screencast_stream.cc |
modified |
Files Changed
modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
Patch
From 90b22181ec922900e26de71463b8e0ed47a5c2dd Mon Sep 17 00:00:00 2001 From: mark a. foltz <[email protected]> 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 <[email protected]> Commit-Queue: Tomas Gunnarsson <[email protected]> Auto-Submit: Mark Foltz <[email protected]> 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); } }
Original Bug Report
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.ccthird_party/webrtc/modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.ccthird_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:
- Writer (PipeWire thread): The background
pipewire-main-loopthread invokesProcessBuffer(), which creates a newMouseCursorobject and assigns it tomouse_cursor_(line 777). This assignment is performed without holding any mutex. - Reader (Capture thread): The
desktopCaptureThread(an unsandboxed thread in the browser process) callsCaptureCursor(), which executesreturn 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.
- 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. - The Writer thread completes its assignment, which triggers the deletion of the old
MouseCursorobject. - The Reader thread completes its move, successfully constructing a new
unique_ptrthat 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,DesktopFrameWithCursorcallsAlphaBlend, which reads from the freedMouseCursor’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
DesktopAndCursorComposerstores the dangling pointer in its ownstd::unique_ptr<MouseCursor> cursor_. When a new cursor is received, or upon destruction,cursor_.reset()is called, which attempts todeletethe already-freed pointer a second time. BecauseMouseCursormanages aDesktopFramewith 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.)
- An attacker tricks a victim on Linux (Wayland/PipeWire) into visiting a malicious site.
- The site calls
navigator.mediaDevices.getDisplayMedia({ video: true }). - Once the user grants permission, the browser process begins capturing the screen using the vulnerable Wayland module.
- 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.