Bug 2545949 (CVE-2026-93536)

Summary: CVE-2026-93536 xorg-x11-server: xorg-x11-server: Denial of Service via use-after-free in gesture event handling
Product: [Other] Security Response Reporter: OSIDB Bzimport <bzimport>
Component: vulnerabilityAssignee: Product Security <prodsec-ir-bot>
Status: NEW --- QA Contact:
Severity: low Docs Contact:
Priority: low    
Version: unspecifiedCC: rhel-process-autobot, security-response-team, watson-tool-maintainers
Target Milestone: ---Keywords: Security
Target Release: ---   
Hardware: All   
OS: Linux   
Whiteboard:
Fixed In Version: Doc Type: ---
Doc Text:
A flaw was found in the X.Org Server. When a window is removed during an active gesture, the server fails to clean up references to the destroyed window in its gesture tracking data. A local attacker can exploit this flaw to cause a use-after-free condition—where the system accesses memory after it has been released—potentially leading to unauthorized information disclosure or a denial of service (DoS).
Story Points: ---
Clone Of: Environment:
Last Closed: Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:
Deadline: 2026-10-07   

Description OSIDB Bzimport 2026-10-05 15:45:17 UTC
GestureBuildSprite() copies raw WindowPtr values into the gesture sprite
trace via CopySprite() without any reference counting or lifetime
management. WindowGone() only repairs touch sprite traces when a window
is destroyed -- gesture sprite traces are never checked or cleaned. This
leaves stale WindowPtr references in gesture sprites.

When DeliverOneGestureEvent() later dereferences the stale WindowPtr via
DeepestSpriteWin(&gi->sprite)->drawable.id, it produces a use-after-free.
The freed 4 bytes (drawable.id) can be read back, potentially leaking
information about the contents of the freed memory region.

Fixed in: xorg-server-21.1.25 and xwayland-24.1.14

* Patch
From a4dd330114c58f760c141c84c7c01cc693181acf Mon Sep 17 00:00:00 2001
From: Peter Hutterer <peter.hutterer>
Date: Thu, 3 Sep 2026 14:57:31 +1000
Subject: [PATCH xserver] Xi: clean up gesture sprite traces in WindowGone

Gesture sprite traces which have the same 'fixed sprite trace
for the duration of the gesture' logic as touch sprite traces.
Both copy WindowPtrs into the trace without refcounting them.

If a device supports gestures but does not have a TouchClass,
the cleanup skipped over the gesture sprite cleanup, leaving
dangling pointers in place if a sprite trace is destroyed
during an active gesture (i.e. window is removed).

DeliverOneGestureEvent() then dereferences the stale pointer via
DeepestSpriteWin(&gi->sprite)->drawable.id, causing a use-after-free.

This vulnerability was discovered by:
  4nibhal working with TrendAI Zero Day Initiative

ZDI-CAN-32753

CVE-2026-93536

Assisted-by: Claude:claude-opus-4-6
#+begin_src diff
---
 Xi/exevents.c | 24 +++++++++++++++++-------
 1 file changed, 17 insertions(+), 7 deletions(-)

diff --git a/Xi/exevents.c b/Xi/exevents.c
index 2ee4fbc53c27..f17e519c2b09 100644
--- a/Xi/exevents.c
+++ b/Xi/exevents.c
@@ -2869,34 +2869,44 @@ InputClientGone(WindowPtr pWin, XID id)
             return Success;
         }
         prev = other;
     }
     FatalError("client not on device event list");
 }
 
 /**
- * Search for window in each touch trace for each device. Remove the window
- * and all its subwindows from the trace when found. The initial window
- * order is preserved.
+ * Search for window in each touch and gesture trace for each device.
+ * Truncate the trace when the window is found.
  ,*/
 void
 WindowGone(WindowPtr win)
 {
     DeviceIntPtr dev;
 
     for (dev = inputInfo.devices; dev; dev = dev->next) {
         TouchClassPtr t = dev->touch;
         int i;
 
-        if (!t)
-            continue;
+        if (t) {
+            for (i = 0; i < t->num_touches; i++) {
+                SpritePtr sprite = &t->touches[i].sprite;
+                int j;
 
-        for (i = 0; i < t->num_touches; i++) {
-            SpritePtr sprite = &t->touches[i].sprite;
+                for (j = 0; j < sprite->spriteTraceGood; j++) {
+                    if (sprite->spriteTrace[j] == win) {
+                        sprite->spriteTraceGood = j;
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (dev->gesture) {
+            SpritePtr sprite = &dev->gesture->gesture.sprite;
             int j;
 
             for (j = 0; j < sprite->spriteTraceGood; j++) {
                 if (sprite->spriteTrace[j] == win) {
                     sprite->spriteTraceGood = j;
                     break;
                 }
             }
-- 
2.55.0
#+end_src