CVE fixes

CVE-2026-50256 (ZDI-CAN-30136)
CVE-2026-50257 (ZDI-CAN-30159)
CVE-2026-50258 (ZDI-CAN-30160)
CVE-2026-50259 (ZDI-CAN-30161)
CVE-2026-50260 (ZDI-CAN-30163)
CVE-2026-50261 (ZDI-CAN-30164)
CVE-2026-50262 (ZDI-CAN-30165)
CVE-2026-50263 (ZDI-CAN-30168)
CVE-2026-50264

Resolves: https://redhat.atlassian.net/browse/RHEL-182428
This commit is contained in:
Olivier Fourdan 2026-06-10 14:07:46 +02:00
parent 88d2c260c1
commit 558fc5ded3
10 changed files with 888 additions and 1 deletions

View File

@ -0,0 +1,122 @@
From 2b60d9c28c98f6d2c924c69c0dcbb2aa64b861eb Mon Sep 17 00:00:00 2001
From: Peter Hutterer <peter.hutterer@who-t.net>
Date: Mon, 20 Apr 2026 11:16:13 +1000
Subject: [PATCH xserver 1/9] sync: fix deletion of counters and fences
Both FreeCounter() and miSyncDestroyFence() iterate over the trigger list
and invoke the CounterDestroyed callback on each trigger.
The CounterDestroyed callback (e.g. SyncAwaitTriggerFired) may call
FreeResource/FreeAwait, which frees the SyncAwaitUnion containing all
SyncAwait structs in the same Await group.
When multiple conditions in a single Await reference the same sync
object (counter or fence), the first callback frees all SyncAwait
structs while subsequent trigger list nodes still reference them. On the
next iteration, reading ptl->next or ptl->pTrigger dereferences freed
memory, leading to a use-after-free.
We need separate fixes for separate issues here to fix this in one go
- use our null-terminated list macro to make sure our next pointer stays
valid (the code accessed ptl->next after freeing it)
- update the list head before deleting the trigger, eventually this ends
up being NULL anyway but meanwhile the list head is a valid list
during CounterDestroyed
- check if we actually do have a trigger before dereferencing the
callback
- Set all triggers to NULL if they are shared so we don't dereference
potentially freed memory
This vulnerability was discovered by:
Anonymous working with TrendAI Zero Day Initiative
ZDI-CAN-30159 (miSyncDestroyFence), ZDI-CAN-30163 (FreeCounter)
Assisted-by: Claude:claude-opus-4-6
(cherry picked from commit f5abfb61994471023d8c6470428c8e30c411cc0b)
Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2229>
---
Xext/sync.c | 32 +++++++++++++++++++++++++-------
miext/sync/misync.c | 12 ++++++++----
2 files changed, 33 insertions(+), 11 deletions(-)
diff --git a/Xext/sync.c b/Xext/sync.c
index fd2ceb042..0079e85ed 100644
--- a/Xext/sync.c
+++ b/Xext/sync.c
@@ -1148,9 +1148,12 @@ FreeCounter(void *env, XID id)
SyncTriggerList *ptl, *pnext;
/* tell all the counter's triggers that counter has been destroyed */
- for (ptl = pCounter->sync.pTriglist; ptl; ptl = pnext) {
- (*ptl->pTrigger->CounterDestroyed) (ptl->pTrigger);
- pnext = ptl->next;
+ nt_list_for_each_entry_safe(ptl, pnext, pCounter->sync.pTriglist, next) {
+ /* Remove it from the list first so CounterDestroyed
+ * callbacks have a valid list to iterate */
+ pCounter->sync.pTriglist = pnext;
+ if (ptl->pTrigger)
+ (*ptl->pTrigger->CounterDestroyed) (ptl->pTrigger);
free(ptl); /* destroy the trigger list as we go */
}
if (IsSystemCounter(pCounter)) {
@@ -1182,13 +1185,28 @@ FreeAwait(void *addr, XID id)
for (numwaits = pAwaitUnion->header.num_waitconditions; numwaits;
numwaits--, pAwait++) {
- /* If the counter is being destroyed, FreeCounter will delete
- * the trigger list itself, so don't do it here.
+ /* If the counter is being destroyed, FreeCounter/miSyncDestroyFence
+ * will delete the trigger list itself, so don't do it here.
+ * However, we must NULL out the pTrigger pointer in the trigger list
+ * node so the destroy loop knows not to dereference it - the backing
+ * SyncAwait memory is about to be freed below.
*/
SyncObject *pSync = pAwait->trigger.pSync;
- if (pSync && !pSync->beingDestroyed)
- SyncDeleteTriggerFromSyncObject(&pAwait->trigger);
+ if (pSync) {
+ if (!pSync->beingDestroyed) {
+ SyncDeleteTriggerFromSyncObject(&pAwait->trigger);
+ } else {
+ SyncTriggerList *ptl;
+
+ nt_list_for_each_entry(ptl, pSync->pTriglist, next) {
+ if (ptl->pTrigger == &pAwait->trigger) {
+ ptl->pTrigger = NULL;
+ break;
+ }
+ }
+ }
+ }
}
free(pAwaitUnion);
return Success;
diff --git a/miext/sync/misync.c b/miext/sync/misync.c
index 0931803f6..6a47d1cdd 100644
--- a/miext/sync/misync.c
+++ b/miext/sync/misync.c
@@ -115,10 +115,14 @@ miSyncDestroyFence(SyncFence * pFence)
SyncScreenPrivPtr pScreenPriv = SYNC_SCREEN_PRIV(pScreen);
SyncTriggerList *ptl, *pNext;
- /* tell all the fence's triggers that the counter has been destroyed */
- for (ptl = pFence->sync.pTriglist; ptl; ptl = pNext) {
- (*ptl->pTrigger->CounterDestroyed) (ptl->pTrigger);
- pNext = ptl->next;
+ /* tell all the fence's triggers that the fence has been destroyed.
+ * Update pTriglist before each callback and free so that FreeAwait
+ * sees a valid list head when scanning for triggers to NULL out.
+ */
+ nt_list_for_each_entry_safe(ptl, pNext, pFence->sync.pTriglist, next) {
+ pFence->sync.pTriglist = pNext;
+ if (ptl->pTrigger)
+ (*ptl->pTrigger->CounterDestroyed) (ptl->pTrigger);
free(ptl); /* destroy the trigger list as we go */
}
--
2.54.0

View File

@ -0,0 +1,71 @@
From 513d92540e8edba52a08f53c461e4e366bb8b385 Mon Sep 17 00:00:00 2001
From: Peter Hutterer <peter.hutterer@who-t.net>
Date: Mon, 20 Apr 2026 11:17:08 +1000
Subject: [PATCH xserver 2/9] sync: restart trigger list iteration in
SyncChangeCounter after TriggerFired
This is the equivalent check to miSyncTriggerFence() from
commit f19ab94ba9c8 ("miext/sync: Fix use-after-free in miSyncTriggerFence()")
When a trigger fires via SyncAwaitTriggerFired, the resulting
FreeResource/FreeAwait call invokes SyncDeleteTriggerFromSyncObject for
every trigger in the same Await group. This unlinks and frees the
corresponding trigger list nodes - potentially including the node pnext
points to.
Fix by restarting iteration from the list head after a trigger fires, since
TriggerFired may have arbitrarily mutated the list. Triggers that have fired
are removed from the list by FreeAwait, so restarting cannot cause infinite
loops.
This vulnerability was discovered by:
Anonymous working with TrendAI Zero Day Initiative
ZDI-CAN-30164
Assisted-by: Claude:claude-opus-4-6
(cherry picked from commit bdd7bf57af208b1ddf57d4683d67104443b44812)
Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2229>
---
Xext/sync.c | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/Xext/sync.c b/Xext/sync.c
index 0079e85ed..69a28ec14 100644
--- a/Xext/sync.c
+++ b/Xext/sync.c
@@ -718,8 +718,29 @@ SyncChangeCounter(SyncCounter * pCounter, int64_t newval)
/* run through triggers to see if any become true */
for (ptl = pCounter->sync.pTriglist; ptl; ptl = pnext) {
pnext = ptl->next;
- if ((*ptl->pTrigger->CheckTrigger) (ptl->pTrigger, oldval))
+ if ((*ptl->pTrigger->CheckTrigger) (ptl->pTrigger, oldval)) {
(*ptl->pTrigger->TriggerFired) (ptl->pTrigger);
+ /* TriggerFired may have called SyncDeleteTriggerFromSyncObject
+ * for sibling triggers in the same Await group, freeing their
+ * trigger list nodes - potentially including pnext. Verify
+ * pnext is still on the counter's trigger list; if not,
+ * restart from the list head.
+ *
+ * Unlike miSyncTriggerFence() we cannot use a do/while
+ * restart loop here: counter trigger lists may contain alarm
+ * triggers which are not removed after firing and would cause
+ * an infinite loop when delta is 0.
+ */
+ if (pnext) {
+ SyncTriggerList *tmp;
+ for (tmp = pCounter->sync.pTriglist; tmp; tmp = tmp->next) {
+ if (tmp == pnext)
+ break;
+ }
+ if (!tmp)
+ pnext = pCounter->sync.pTriglist;
+ }
+ }
}
if (IsSystemCounter(pCounter)) {
--
2.54.0

View File

@ -0,0 +1,50 @@
From 7841780e7b1d0e0f5f9bb98691eaffece19d06e1 Mon Sep 17 00:00:00 2001
From: Peter Hutterer <peter.hutterer@who-t.net>
Date: Mon, 20 Apr 2026 11:17:41 +1000
Subject: [PATCH xserver 3/9] xkb: reject key types with num_levels exceeding
XkbMaxShiftLevel
CheckKeyTypes validates incoming key type definitions from XkbSetMap
requests but does not enforce an upper bound on numLevels. A client can set
numLevels up to 255 on a non-canonical key type, which is stored in the
server's type table.
When ChangeKeyboardMapping later triggers XkbUpdateKeyTypesFromCore, the
function XkbKeyTypesForCoreSymbols computes groupsWidth from num_levels and
uses the XKB_OFFSET(g, l) = (g * groupsWidth) + l macro to index into
tsyms[], a stack-allocated buffer of XkbMaxSymsPerKey (252) entries. With
num_levels=255, groupsWidth=255, and indices reach up to 3*255+254 = 1019,
overflowing the 252-element stack buffer by 767 KeySym-sized entries.
Fix by rejecting numLevels values greater than XkbMaxShiftLevel (63) in
CheckKeyTypes, alongside the existing lower-bound check for numLevels < 1.
This vulnerability was discovered by:
Anonymous working with TrendAI Zero Day Initiative
ZDI-CAN-30160
Assisted-by: Claude:claude-opus-4-6
(cherry picked from commit 543e108516428fc8c3bea91d6563ad266f9a801e)
Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2229>
---
xkb/xkb.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/xkb/xkb.c b/xkb/xkb.c
index 2139da7ee..f190be5eb 100644
--- a/xkb/xkb.c
+++ b/xkb/xkb.c
@@ -1644,7 +1644,7 @@ CheckKeyTypes(ClientPtr client,
}
n = i + req->firstType;
width = wire->numLevels;
- if (width < 1) {
+ if (width < 1 || width > XkbMaxShiftLevel) {
*nMapsRtrn = _XkbErrCode3(0x04, n, width);
return 0;
}
--
2.54.0

View File

@ -0,0 +1,53 @@
From 50b6eeda460f0badea82f689442461fea7f7af2a Mon Sep 17 00:00:00 2001
From: Peter Hutterer <peter.hutterer@who-t.net>
Date: Mon, 20 Apr 2026 11:18:13 +1000
Subject: [PATCH xserver 4/9] xkb: clamp nMaps to mapWidths buffer size in
CheckKeyTypes
CheckKeyTypes computes nMaps = firstType + nTypes from client-controlled
request fields when XkbSetMapResizeTypes is set. This value is used to
index mapWidths[], a stack-allocated CARD8 array of XkbMaxLegalKeyCode + 1
(256) elements. No upper bound is enforced on nMaps.
An attacker can first send SetMap(firstType=0, nTypes=255, ResizeTypes) to
set the server's num_types to 255, then send SetMap(firstType=255,
nTypes=10, ResizeTypes). The firstType > num_types check passes because
255 > 255 is false (the check uses > rather than >=). nMaps is then
computed as 265, and the loop writes mapWidths[255..264], overflowing 9
bytes past the stack buffer into adjacent stack variables (symsPerKey[]).
Fix by rejecting requests where firstType + nTypes would exceed the
mapWidths buffer size (XkbMaxLegalKeyCode + 1).
This vulnerability was discovered by:
Anonymous working with TrendAI Zero Day Initiative
ZDI-CAN-30161
Assisted-by: Claude:claude-opus-4-6
(cherry picked from commit 867b59b33bee669cb412f1314e47c52eacf6e00b)
Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2229>
---
xkb/xkb.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/xkb/xkb.c b/xkb/xkb.c
index f190be5eb..f92ba9c3d 100644
--- a/xkb/xkb.c
+++ b/xkb/xkb.c
@@ -1617,6 +1617,11 @@ CheckKeyTypes(ClientPtr client,
*nMapsRtrn = _XkbErrCode4(0x02, req->firstType, req->nTypes, 4);
return 0;
}
+ if (nMaps > XkbMaxLegalKeyCode + 1) {
+ *nMapsRtrn = _XkbErrCode4(0x02, req->firstType, req->nTypes,
+ XkbMaxLegalKeyCode + 1);
+ return 0;
+ }
}
else if (req->present & XkbKeyTypesMask) {
nMaps = xkb->map->num_types;
--
2.54.0

View File

@ -0,0 +1,154 @@
From f7f8b663c7d19cfbd29c8ea16ca6475d6a5f0af0 Mon Sep 17 00:00:00 2001
From: Peter Hutterer <peter.hutterer@who-t.net>
Date: Mon, 20 Apr 2026 11:18:48 +1000
Subject: [PATCH xserver 5/9] glx: fix reversed length check in
ChangeDrawableAttributes
The request length validation in __glXDisp_ChangeDrawableAttributes and
__glXDispSwap_ChangeDrawableAttributes uses the wrong comparison direction.
The check tests whether the computed request size is LESS THAN
client->req_len, but should test whether it is GREATER THAN. With the
reversed operator, an undersized request (where numAttribs claims more
attribute pairs than the request actually contains) passes validation.
DoChangeDrawableAttributes then iterates numAttribs attribute pairs starting
from the end of the request header, reading past the actual request data
into adjacent memory. This is an out-of-bounds read that can also cause
an out-of-bounds write when a GLX_EVENT_MASK attribute key is found in the
overread data and its corresponding value is written to pGlxDraw->eventMask.
This patch effectively reverts commit 402b329c3aa8 ("glx: Work around
wrong request lengths sent by mesa"). This was fixed in mesa commit
4324d6fdfbba1 in 2011 (mesa 7.11).
Fixes: 402b329c3aa8 ("glx: Work around wrong request lengths sent by mesa")
This vulnerability was discovered by:
Anonymous working with TrendAI Zero Day Initiative
ZDI-CAN-30165
Assisted-by: Claude:claude-opus-4-6
(cherry picked from commit 6d459e4daf715bea8abdafa8fb130be2f8a1d145)
Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2229>
---
glx/glxcmds.c | 21 +++++----------------
glx/glxcmdsswap.c | 12 +++++-------
2 files changed, 10 insertions(+), 23 deletions(-)
diff --git a/glx/glxcmds.c b/glx/glxcmds.c
index 75e42823c..758308432 100644
--- a/glx/glxcmds.c
+++ b/glx/glxcmds.c
@@ -1122,8 +1122,7 @@ __glXDisp_GetFBConfigsSGIX(__GLXclientState * cl, GLbyte * pc)
ClientPtr client = cl->client;
xGLXGetFBConfigsSGIXReq *req = (xGLXGetFBConfigsSGIXReq *) pc;
- /* work around mesa bug, don't use REQUEST_SIZE_MATCH */
- REQUEST_AT_LEAST_SIZE(xGLXGetFBConfigsSGIXReq);
+ REQUEST_SIZE_MATCH(xGLXGetFBConfigsSGIXReq);
return DoGetFBConfigs(cl, req->screen);
}
@@ -1344,9 +1343,7 @@ __glXDisp_DestroyPixmap(__GLXclientState * cl, GLbyte * pc)
ClientPtr client = cl->client;
xGLXDestroyPixmapReq *req = (xGLXDestroyPixmapReq *) pc;
- /* should be REQUEST_SIZE_MATCH, but mesa's glXDestroyPixmap used to set
- * length to 3 instead of 2 */
- REQUEST_AT_LEAST_SIZE(xGLXDestroyPixmapReq);
+ REQUEST_SIZE_MATCH(xGLXDestroyPixmapReq);
return DoDestroyDrawable(cl, req->glxpixmap, GLX_DRAWABLE_PIXMAP);
}
@@ -1495,14 +1492,8 @@ __glXDisp_ChangeDrawableAttributes(__GLXclientState * cl, GLbyte * pc)
client->errorValue = req->numAttribs;
return BadValue;
}
-#if 0
- /* mesa sends an additional 8 bytes */
+
REQUEST_FIXED_SIZE(xGLXChangeDrawableAttributesReq, req->numAttribs << 3);
-#else
- if (((sizeof(xGLXChangeDrawableAttributesReq) +
- (req->numAttribs << 3)) >> 2) < client->req_len)
- return BadLength;
-#endif
return DoChangeDrawableAttributes(cl->client, req->drawable,
req->numAttribs, (CARD32 *) (req + 1));
@@ -1569,8 +1560,7 @@ __glXDisp_DestroyWindow(__GLXclientState * cl, GLbyte * pc)
ClientPtr client = cl->client;
xGLXDestroyWindowReq *req = (xGLXDestroyWindowReq *) pc;
- /* mesa's glXDestroyWindow used to set length to 3 instead of 2 */
- REQUEST_AT_LEAST_SIZE(xGLXDestroyWindowReq);
+ REQUEST_SIZE_MATCH(xGLXDestroyWindowReq);
return DoDestroyDrawable(cl, req->glxwindow, GLX_DRAWABLE_WINDOW);
}
@@ -1923,8 +1913,7 @@ __glXDisp_GetDrawableAttributes(__GLXclientState * cl, GLbyte * pc)
ClientPtr client = cl->client;
xGLXGetDrawableAttributesReq *req = (xGLXGetDrawableAttributesReq *) pc;
- /* this should be REQUEST_SIZE_MATCH, but mesa sends an additional 4 bytes */
- REQUEST_AT_LEAST_SIZE(xGLXGetDrawableAttributesReq);
+ REQUEST_SIZE_MATCH(xGLXGetDrawableAttributesReq);
return DoGetDrawableAttributes(cl, req->drawable);
}
diff --git a/glx/glxcmdsswap.c b/glx/glxcmdsswap.c
index 7d6674470..96382672a 100644
--- a/glx/glxcmdsswap.c
+++ b/glx/glxcmdsswap.c
@@ -235,7 +235,7 @@ __glXDispSwap_GetFBConfigsSGIX(__GLXclientState * cl, GLbyte * pc)
__GLX_DECLARE_SWAP_VARIABLES;
- REQUEST_AT_LEAST_SIZE(xGLXGetFBConfigsSGIXReq);
+ REQUEST_SIZE_MATCH(xGLXGetFBConfigsSGIXReq);
__GLX_SWAP_INT(&req->screen);
return __glXDisp_GetFBConfigsSGIX(cl, pc);
@@ -327,7 +327,7 @@ __glXDispSwap_DestroyPixmap(__GLXclientState * cl, GLbyte * pc)
__GLX_DECLARE_SWAP_VARIABLES;
- REQUEST_AT_LEAST_SIZE(xGLXDestroyGLXPixmapReq);
+ REQUEST_SIZE_MATCH(xGLXDestroyGLXPixmapReq);
__GLX_SWAP_SHORT(&req->length);
__GLX_SWAP_INT(&req->glxpixmap);
@@ -440,9 +440,7 @@ __glXDispSwap_ChangeDrawableAttributes(__GLXclientState * cl, GLbyte * pc)
client->errorValue = req->numAttribs;
return BadValue;
}
- if (((sizeof(xGLXChangeDrawableAttributesReq) +
- (req->numAttribs << 3)) >> 2) < client->req_len)
- return BadLength;
+ REQUEST_FIXED_SIZE(xGLXChangeDrawableAttributesReq, req->numAttribs << 3);
attribs = (CARD32 *) (req + 1);
__GLX_SWAP_INT_ARRAY(attribs, req->numAttribs << 1);
@@ -514,7 +512,7 @@ __glXDispSwap_DestroyWindow(__GLXclientState * cl, GLbyte * pc)
__GLX_DECLARE_SWAP_VARIABLES;
- REQUEST_AT_LEAST_SIZE(xGLXDestroyWindowReq);
+ REQUEST_SIZE_MATCH(xGLXDestroyWindowReq);
__GLX_SWAP_INT(&req->glxwindow);
@@ -723,7 +721,7 @@ __glXDispSwap_GetDrawableAttributes(__GLXclientState * cl, GLbyte * pc)
__GLX_DECLARE_SWAP_VARIABLES;
- REQUEST_AT_LEAST_SIZE(xGLXGetDrawableAttributesReq);
+ REQUEST_SIZE_MATCH(xGLXGetDrawableAttributesReq);
__GLX_SWAP_SHORT(&req->length);
__GLX_SWAP_INT(&req->drawable);
--
2.54.0

View File

@ -0,0 +1,74 @@
From 637343690922ccd44bedf4e524357b0593067fa2 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
(cherry picked from commit ecc634f1b2f7aa473d3a267eada98c4918bf9e05)
Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2229>
---
Xext/saver.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Xext/saver.c b/Xext/saver.c
index c27a66c80..f750ef4cd 100644
--- a/Xext/saver.c
+++ b/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);
}
}
--
2.54.0

View File

@ -0,0 +1,90 @@
From 574f2e975aa8f2942f33b7fa35a33d20f27cdc02 Mon Sep 17 00:00:00 2001
From: Peter Hutterer <peter.hutterer@who-t.net>
Date: Wed, 29 Apr 2026 05:40:33 +0000
Subject: [PATCH xserver 7/9] dix: increase XLFDMAXFONTNAMELEN to match
libXfont2's MAXFONTNAMELEN
XLFDMAXFONTNAMELEN was 256 bytes, but libXfont2 defines MAXFONTNAMELEN
as 1024 and allows font names and alias targets up to that length in
fonts.alias files.
doListFontsAndAliases copies the resolved alias target into a
stack-allocated tmp_pattern[XLFDMAXFONTNAMELEN] and then into
c->current.pattern[XLFDMAXFONTNAMELEN] (defined in LFWIstateRec).
doListFontsWithInfo has the same pattern, copying the resolved name into
c->current.pattern[]. With the old 256-byte limit, a fonts.alias entry
with a target name between 257 and 1023 bytes would overflow both
buffers.
An attacker can exploit this by:
1. Creating a font directory with a fonts.alias containing an alias
whose target name exceeds 256 bytes
2. Using SetFontPath to add the malicious directory
3. Calling ListFonts with the alias name to trigger alias resolution
4. The oversized resolved name overflows the 256-byte stack buffer
Increase XLFDMAXFONTNAMELEN from 256 to 1024 to match libXfont2's
MAXFONTNAMELEN, ensuring the server can handle any name the font library
produces.
This vulnerability was discovered by:
Anonymous working with TrendAI Zero Day Initiative
ZDI-CAN-30136
Assisted-by: Claude:claude-opus-4-6
(cherry picked from commit bb5158f962dc935e58ef8b4b5fcb31be201a6e07)
Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2229>
---
dix/dixfonts.c | 8 ++++++++
include/closestr.h | 7 ++++++-
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/dix/dixfonts.c b/dix/dixfonts.c
index 0ea8678bb..386c38686 100644
--- a/dix/dixfonts.c
+++ b/dix/dixfonts.c
@@ -671,6 +671,10 @@ doListFontsAndAliases(ClientPtr client, LFclosurePtr c)
* is BadFontName, indicating the alias resolution
* is complete.
*/
+ if (resolvedlen > XLFDMAXFONTNAMELEN) {
+ err = BadFontName;
+ goto ContBadFontName;
+ }
memmove(tmp_pattern, resolved, resolvedlen);
if (c->haveSaved) {
char *tmpname;
@@ -934,6 +938,10 @@ doListFontsWithInfo(ClientPtr client, LFWIclosurePtr c)
memmove(c->savedName, name, namelen + 1);
aliascount = 20;
}
+ if (namelen > XLFDMAXFONTNAMELEN) {
+ err = BadFontName;
+ goto ContBadFontName;
+ }
memmove(c->current.pattern, name, namelen);
c->current.patlen = namelen;
c->current.max_names = 1;
diff --git a/include/closestr.h b/include/closestr.h
index 60e6f09bc..7567ac6ea 100644
--- a/include/closestr.h
+++ b/include/closestr.h
@@ -57,7 +57,12 @@ typedef struct _OFclosure {
/* ListFontsWithInfo */
-#define XLFDMAXFONTNAMELEN 256
+/* libXfont2 allows font names/aliases up to MAXFONTNAMELEN (1024) bytes in
+ * fonts.alias files. The server's pattern buffers must be large enough to
+ * hold resolved alias targets returned by the font library.
+ * ZDI-CAN-30136
+ */
+#define XLFDMAXFONTNAMELEN 1024
typedef struct _LFWIstate {
char pattern[XLFDMAXFONTNAMELEN];
int patlen;
--
2.54.0

View File

@ -0,0 +1,109 @@
From 8cb12cc50d0b1592294ad46594731dc088b493f5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michel=20D=C3=A4nzer?= <mdaenzer@redhat.com>
Date: Wed, 13 May 2026 14:29:26 +0200
Subject: [PATCH xserver 8/9] dri2: Use booleans for (fake) front buffer
tracking in do_get_buffers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This works as intended — the (fake) front buffer needs to be added
only if the client didn't request it in the first place — even if the
client requests the same attachment multiple times. This ensures we
never try to access more than (count + 1) entries of the buffers array.
Fixes: ff6c7764c290 ("DRI2: Implement protocol for DRI2GetBuffersWithFormat")
Signed-off-by: Michel Dänzer <mdaenzer@redhat.com>
(cherry picked from commit b7aa65cc3bb11b792ce2a3f511ba9b863acb11c8)
Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2229>
---
hw/xfree86/dri2/dri2.c | 37 ++++++++++++++++++-------------------
1 file changed, 18 insertions(+), 19 deletions(-)
diff --git a/hw/xfree86/dri2/dri2.c b/hw/xfree86/dri2/dri2.c
index 6619e3aa7..fdf15d9a1 100644
--- a/hw/xfree86/dri2/dri2.c
+++ b/hw/xfree86/dri2/dri2.c
@@ -560,9 +560,10 @@ do_get_buffers(DrawablePtr pDraw, int *width, int *height,
DRI2DrawablePtr pPriv = DRI2GetDrawable(pDraw);
DRI2ScreenPtr ds;
DRI2BufferPtr *buffers;
- int need_real_front = 0;
- int need_fake_front = 0;
- int have_fake_front = 0;
+ Bool need_real_front = FALSE;
+ Bool have_real_front = FALSE;
+ Bool need_fake_front = FALSE;
+ Bool have_fake_front = FALSE;
int front_format = 0;
int dimensions_match;
int buffers_changed = 0;
@@ -595,34 +596,32 @@ do_get_buffers(DrawablePtr pDraw, int *width, int *height,
if (buffers[i] == NULL)
goto err_out;
- /* If the drawable is a window and the front-buffer is requested,
- * silently add the fake front-buffer to the list of requested
- * attachments. The counting logic in the loop accounts for the case
- * where the client requests both the fake and real front-buffer.
+ /* In certain cases the (fake) front buffer is always needed, so return
+ * it even if the client failed to request it.
+ * The logic in & after the loop accounts for the case where the client
+ * does request the (fake) front buffer, to avoid returning it multiple
+ * times.
*/
if (attachment == DRI2BufferBackLeft) {
- need_real_front++;
+ need_real_front = TRUE;
front_format = format;
}
if (attachment == DRI2BufferFrontLeft) {
- need_real_front--;
+ have_real_front = TRUE;
front_format = format;
- if (pDraw->type == DRAWABLE_WINDOW) {
- need_fake_front++;
- }
+ if (pDraw->type == DRAWABLE_WINDOW)
+ need_fake_front = TRUE;
}
if (pDraw->type == DRAWABLE_WINDOW) {
- if (attachment == DRI2BufferFakeFrontLeft) {
- need_fake_front--;
- have_fake_front = 1;
- }
+ if (attachment == DRI2BufferFakeFrontLeft)
+ have_fake_front = TRUE;
}
}
- if (need_real_front > 0) {
+ if (need_real_front && !have_real_front) {
if (allocate_or_reuse_buffer(pDraw, ds, pPriv, DRI2BufferFrontLeft,
front_format, dimensions_match,
&buffers[i]))
@@ -633,7 +632,7 @@ do_get_buffers(DrawablePtr pDraw, int *width, int *height,
i++;
}
- if (need_fake_front > 0) {
+ if (need_fake_front && !have_fake_front) {
if (allocate_or_reuse_buffer(pDraw, ds, pPriv, DRI2BufferFakeFrontLeft,
front_format, dimensions_match,
&buffers[i]))
@@ -643,7 +642,7 @@ do_get_buffers(DrawablePtr pDraw, int *width, int *height,
goto err_out;
i++;
- have_fake_front = 1;
+ have_fake_front = TRUE;
}
*out_count = i;
--
2.54.0

View File

@ -0,0 +1,140 @@
From e674fb65a6662d1951c9d9fba2df429a04b54881 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michel=20D=C3=A4nzer?= <mdaenzer@redhat.com>
Date: Fri, 15 May 2026 17:47:51 +0200
Subject: [PATCH xserver 9/9] dri2: Deduplicate attachments in do_get_buffer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
It was always the intention of the DRI2 protocol that there's at most
one instance of each attachment, and that's how it was implemented in
Mesa.
Since that wasn't enforced though, there might be other clients in the
wild which (e.g. accidentally) request the same attachment multiple
times. So starting to a raise a protocol error in this case now risks
breaking such clients.
Instead, just deduplicate the attachments using a bit-set.
This has a couple of desirable side effects:
* destroy_buffer cannot be called multiple times for the same
DRI2BufferPtr.
* The client cannot cause the server to allocate a buffers array with
more entries than there are attachments (currently 11).
Signed-off-by: Michel Dänzer <mdaenzer@redhat.com>
(cherry picked from commit 339c279514326134b0878fc23ce6e9520440ce7f)
Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2229>
---
hw/xfree86/dri2/dri2.c | 36 ++++++++++++++++++++++--------------
1 file changed, 22 insertions(+), 14 deletions(-)
diff --git a/hw/xfree86/dri2/dri2.c b/hw/xfree86/dri2/dri2.c
index fdf15d9a1..6b6a2b9ef 100644
--- a/hw/xfree86/dri2/dri2.c
+++ b/hw/xfree86/dri2/dri2.c
@@ -560,16 +560,16 @@ do_get_buffers(DrawablePtr pDraw, int *width, int *height,
DRI2DrawablePtr pPriv = DRI2GetDrawable(pDraw);
DRI2ScreenPtr ds;
DRI2BufferPtr *buffers;
+ unsigned attachments_bitset = 0;
Bool need_real_front = FALSE;
- Bool have_real_front = FALSE;
Bool need_fake_front = FALSE;
- Bool have_fake_front = FALSE;
int front_format = 0;
int dimensions_match;
int buffers_changed = 0;
int i;
- if (!pPriv) {
+ if (!pPriv ||
+ count > DRI2BufferHiz + 1) {
*width = pDraw->width;
*height = pDraw->height;
*out_count = 0;
@@ -581,7 +581,10 @@ do_get_buffers(DrawablePtr pDraw, int *width, int *height,
dimensions_match = (pDraw->width == pPriv->width)
&& (pDraw->height == pPriv->height);
- buffers = calloc((count + 1), sizeof(buffers[0]));
+ /* Since we deduplicate attachments in the buffers array, there cannot be
+ * more entries than there are attachments.
+ */
+ buffers = calloc((min(count, DRI2BufferHiz) + 1), sizeof(buffers[0]));
if (!buffers)
goto err_out;
@@ -589,6 +592,14 @@ do_get_buffers(DrawablePtr pDraw, int *width, int *height,
const unsigned attachment = *(attachments++);
const unsigned format = (has_format) ? *(attachments++) : 0;
+ if (attachment > DRI2BufferHiz)
+ goto err_out;
+
+ if (attachments_bitset & (1u << attachment))
+ continue;
+
+ attachments_bitset |= 1u << attachment;
+
if (allocate_or_reuse_buffer(pDraw, ds, pPriv, attachment,
format, dimensions_match, &buffers[i]))
buffers_changed = 1;
@@ -608,20 +619,15 @@ do_get_buffers(DrawablePtr pDraw, int *width, int *height,
}
if (attachment == DRI2BufferFrontLeft) {
- have_real_front = TRUE;
front_format = format;
if (pDraw->type == DRAWABLE_WINDOW)
need_fake_front = TRUE;
}
-
- if (pDraw->type == DRAWABLE_WINDOW) {
- if (attachment == DRI2BufferFakeFrontLeft)
- have_fake_front = TRUE;
- }
}
- if (need_real_front && !have_real_front) {
+ if (need_real_front &&
+ !(attachments_bitset & (1u << DRI2BufferFrontLeft))) {
if (allocate_or_reuse_buffer(pDraw, ds, pPriv, DRI2BufferFrontLeft,
front_format, dimensions_match,
&buffers[i]))
@@ -632,7 +638,8 @@ do_get_buffers(DrawablePtr pDraw, int *width, int *height,
i++;
}
- if (need_fake_front && !have_fake_front) {
+ if (need_fake_front &&
+ !(attachments_bitset & (1u << DRI2BufferFakeFrontLeft))) {
if (allocate_or_reuse_buffer(pDraw, ds, pPriv, DRI2BufferFakeFrontLeft,
front_format, dimensions_match,
&buffers[i]))
@@ -642,7 +649,7 @@ do_get_buffers(DrawablePtr pDraw, int *width, int *height,
goto err_out;
i++;
- have_fake_front = TRUE;
+ attachments_bitset |= 1u << DRI2BufferFakeFrontLeft;
}
*out_count = i;
@@ -654,7 +661,8 @@ do_get_buffers(DrawablePtr pDraw, int *width, int *height,
* contents of the real front-buffer. This ensures correct operation of
* applications that call glXWaitX before calling glDrawBuffer.
*/
- if (have_fake_front && buffers_changed) {
+ if (buffers_changed &&
+ (attachments_bitset & (1u << DRI2BufferFakeFrontLeft))) {
BoxRec box;
RegionRec region;
--
2.54.0

View File

@ -42,7 +42,7 @@
Summary: X.Org X11 X server
Name: xorg-x11-server
Version: 1.20.11
Release: 35%{?gitdate:.%{gitdate}}%{?dist}
Release: 36%{?gitdate:.%{gitdate}}%{?dist}
URL: http://www.x.org
License: MIT
@ -258,6 +258,24 @@ Patch10078: 0004-xkb-Fix-out-of-bounds-read-in-CheckModifierMap.patch
# CVE-2026-34003: XKB Buffer overflow in CheckKeyTypes()
Patch10079: 0005-xkb-Add-additional-bound-checking-in-CheckKeyTypes.patch
Patch10080: 0006-xkb-Add-more-_XkbCheckRequestBounds.patch
# ZDI-CAN-30159 - CVE-2026-50257 - XSYNC Use-After-Free in miSyncDestroyFence()
# ZDI-CAN-30163 - CVE-2026-50260 - XSYNC Use-After-Free in FreeCounter()
Patch10081: 0001-sync-fix-deletion-of-counters-and-fences.patch
# ZDI-CAN-30164 - CVE-2026-50261 - XSYNC Use-After-Free in SyncChangeCounter()
Patch10082: 0002-sync-restart-trigger-list-iteration-in-SyncChangeCou.patch
# ZDI-CAN-30160 - CVE-2026-50258 - XKB Key Types Stack-based Buffer Overflow
Patch10083: 0003-xkb-reject-key-types-with-num_levels-exceeding-XkbMa.patch
# ZDI-CAN-30161 - CVE-2026-50259 - XKB SetMap Request Stack-based Buffer Overflow
Patch10084: 0004-xkb-clamp-nMaps-to-mapWidths-buffer-size-in-CheckKey.patch
# ZDI-CAN-30165 - CVE-2026-50262 - GLX ChangeDrawableAttributes Out-Of-Bounds Read/Write
Patch10085: 0005-glx-fix-reversed-length-check-in-ChangeDrawableAttri.patch
# ZDI-CAN-30168 - CVE-2026-50263 - CreateSaverWindow Use-After-Free Information Disclosure
Patch10086: 0006-saver-re-fetch-screen-private-after-CheckScreenPriva.patch
# ZDI-CAN-30136 - CVE-2026-50256 - Font Alias Stack-based Buffer Overflow
Patch10087: 0007-dix-increase-XLFDMAXFONTNAMELEN-to-match-libXfont2-s.patch
# CVE-2026-50264 - DRI2 DRIGetBuffers/DRIGetBuffersWithFormat Out-Of-Bounds Write
Patch10088: 0008-dri2-Use-booleans-for-fake-front-buffer-tracking-in-.patch
Patch10089: 0009-dri2-Deduplicate-attachments-in-do_get_buffer.patch
BuildRequires: make
BuildRequires: systemtap-sdt-devel
@ -668,6 +686,12 @@ find %{inst_srcdir}/hw/xfree86 -name \*.c -delete
%changelog
* Wed Jun 10 2026 Olivier Fourdan <ofourdan@redhat.com> - 1.20.11-36
- CVE fix for: CVE-2026-50256, CVE-2026-50257, CVE-2026-50258,
CVE-2026-50259, CVE-2026-50260, CVE-2026-50261,
CVE-2026-50262, CVE-2026-50263, CVE-2026-50264
Resolves: https://redhat.atlassian.net/browse/RHEL-182428
* Tue May 19 2026 Michel Dänzer <mdaenzer@redhat.com> - 1.20.11-35
- dri2: Protect against dri2ClientPrivate assertion failures
Resolves: https://redhat.atlassian.net/browse/RHEL-169728