Xi: validate modifier values in ProcXIPassiveUngrabDevice

ProcXIPassiveUngrabDevice() passes client-supplied modifier values
directly to DeletePassiveGrabFromList() without validating them.

DeletePassiveGrabFromList() may reach DeleteDetailFromMask() which uses
BITCLEAR(mask, modifier). The mask is allocated as 8 Mask words (256
bits). A modifier value > 255 causes BITCLEAR to index mask[modifier>>5]
past the allocation, resulting in a controlled single-bit-clear at an
attacker-chosen heap offset.

Add the same modifier validation that CheckGrabValues() uses: reject
modifiers that are not XIAnyModifier and have bits set outside
AllModifiersMask.

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

ZDI-CAN-32366

CVE-2026-93523

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2341>
(cherry picked from commit 37a1847a60)
This commit is contained in:
Peter Hutterer 2026-10-20 19:46:54 +02:00 • committed by Enrico Weigelt, metux IT consult
commit e81102be19

View file

@ -49,6 +49,10 @@
#include "swaprep.h"
#include "exglobals.h" /* BadDevice */
#define AllModifiersMask ( \
ShiftMask | LockMask | ControlMask | Mod1Mask | Mod2Mask | \
Mod3Mask | Mod4Mask | Mod5Mask )
int
ProcXIPassiveGrabDevice(ClientPtr client)
{
@ -294,6 +298,14 @@ ProcXIPassiveUngrabDevice(ClientPtr client)
if (rc != Success)
return rc;
modifiers = (uint32_t *) &stuff[1];
for (i = 0; i < stuff->num_modifiers; i++, modifiers++) {
if (*modifiers != XIAnyModifier && (*modifiers & ~AllModifiersMask)) {
client->errorValue = *modifiers;
return BadValue;
}
}
mod_dev = (InputDevIsFloating(dev)) ? dev : GetMaster(dev, MASTER_KEYBOARD);
tempGrab = AllocGrab(NULL);