From 16c0c0ab92fdeccca9791209d90826f76159b134 Mon Sep 17 00:00:00 2001
From: Peter Hutterer <peter.hutterer@who-t.net>
Date: Mon, 20 Apr 2026 11:19:20 +1000
Subject: [PATCH xserver 6/9] saver: re-fetch screen private after
 CheckScreenPrivate in CreateSaverWindow
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

CreateSaverWindow stores pPriv (the ScreenSaverScreenPrivatePtr) in a local
variable via the SetupScreen macro at function entry. When an existing saver
window is being replaced, the function sets pPriv->hasWindow = FALSE and
calls CheckScreenPrivate(). If at this point pPriv->attr is NULL (cleared
by a prior UnsetAttributes call), pPriv->events is NULL, and
pPriv->installedMap is None, then CheckScreenPrivate determines the screen
private is unused, frees it, and sets the screen private pointer to NULL.

The function then continues to dereference the now-freed pPriv on the very
next line (pPriv->attr), resulting in a use-after-free. On glibc 2.34+,
the tcache key at offset 8 within the freed block makes pPriv->attr appear
non-NULL, causing the function to continue operating on garbage data and
eventually crash.

The attack sequence is:
  1. SetAttributes (creates pPriv with pPriv->attr set)
  2. ForceScreenSaver(Active) (creates saver window, pPriv->hasWindow=TRUE)
  3. UnsetAttributes (sets pPriv->attr = NULL)
  4. ForceScreenSaver(Active) (re-enters CreateSaverWindow → UAF)

Fix by re-fetching pPriv from the screen private after CheckScreenPrivate
returns, so the subsequent NULL check correctly detects the freed state.

ScreenSaverFreeAttr has the same pattern, force pPriv to NULL there too
even though it has no real effect.

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

ZDI-CAN-30168

Assisted-by: Claude:claude-opus-4-6
---
 Xext/saver.c | 5 +++++
 1 file changed, 5 insertions(+)

Index: xorg-server-21.1.21/Xext/saver.c
===================================================================
--- xorg-server-21.1.21.orig/Xext/saver.c
+++ xorg-server-21.1.21/Xext/saver.c
@@ -348,6 +348,9 @@ ScreenSaverFreeAttr(void *value, XID id)
         dixSaveScreens(serverClient, SCREEN_SAVER_FORCER, ScreenSaverActive);
     }
     CheckScreenPrivate(pScreen);
+    /* CheckScreenPrivate may have freed pPriv (same pattern as
+     * CreateSaverWindow fix for ZDI-CAN-30168). */
+    pPriv = NULL;
     return TRUE;
 }
 
@@ -479,6 +482,8 @@ CreateSaverWindow(ScreenPtr pScreen)
             UninstallSaverColormap(pScreen);
             pPriv->hasWindow = FALSE;
             CheckScreenPrivate(pScreen);
+            /* Re-fetch pPriv since CheckScreenPrivate may have freed it */
+            pPriv = GetScreenPrivate(pScreen);
         }
     }
 
