From e8ec7ca022bd21077885a99f34c3a363aa88fe1f Mon Sep 17 00:00:00 2001 From: Mikhail Dmitrichenko Date: Tue, 14 Apr 2026 12:06:51 +0300 Subject: [PATCH 1/9] xkb: fix incorrect size check when growing doodads in a section In XkbAddGeomDoodad(), when adding a doodad to a specific section (section != NULL), there is a comparison between section->num_doodads and geom->sz_doodads instead of the section's own section->sz_doodads. The else branch (global geometry doodads) was already correct. Compare section->num_doodads against section->sz_doodads to prevent a potential out-of-bounds. Found by Linux Verification Center (linuxtesting.org) with SVACE. Signed-off-by: Mikhail Dmitrichenko (cherry picked from commit dd8b8cf49d326802c53b01835618a7e3765d91cb) Part-of: --- xkb/XKBGAlloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xkb/XKBGAlloc.c b/xkb/XKBGAlloc.c index f0cda24fe..9b71f6121 100644 --- a/xkb/XKBGAlloc.c +++ b/xkb/XKBGAlloc.c @@ -769,7 +769,7 @@ XkbAddGeomDoodad(XkbGeometryPtr geom, XkbSectionPtr section, Atom name) return doodad; } if (section) { - if ((section->num_doodads >= geom->sz_doodads) && + if ((section->num_doodads >= section->sz_doodads) && (_XkbAllocDoodads(section, 1) != Success)) { return NULL; } From b3e1ebc42be2a5732ed0ed9eeaffd1e17b391a86 Mon Sep 17 00:00:00 2001 From: Mikhail Dmitrichenko Date: Tue, 14 Apr 2026 13:22:35 +0300 Subject: [PATCH 2/9] xkb: fix potential buff overflow in XkbVModIndexText for XkbCFile format len calculation and strncpy limit were off by one when prefixing "vmod_" to the virtual modifier name. This could write the final NULL one byte past the allocated buffer from tbGetBuffer(). Use proper allocation len for prefix to avoid writing out-of-bounds. Found by Linux Verification Center (linuxtesting.org) with SVACE Signed-off-by: Mikhail Dmitrichenko (cherry picked from commit 5dfb435c1d864bf154369cb86d085d4159730378) Part-of: --- xkb/xkbtext.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xkb/xkbtext.c b/xkb/xkbtext.c index d6f688f5c..8fb61137f 100644 --- a/xkb/xkbtext.c +++ b/xkb/xkbtext.c @@ -138,11 +138,11 @@ XkbVModIndexText(XkbDescPtr xkb, unsigned ndx, unsigned format) len = strlen(tmp) + 1; if (format == XkbCFile) - len += 4; + len += 5; rtrn = tbGetBuffer(len); if (format == XkbCFile) { strcpy(rtrn, "vmod_"); - strncpy(&rtrn[5], tmp, len - 4); + strncpy(&rtrn[5], tmp, len - 5); } else strncpy(rtrn, tmp, len); From fe7cd76311ed3ee3b26d75320ab957f44be7833e Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 29 Apr 2026 05:59:12 +0000 Subject: [PATCH 3/9] Xi: add missing gesture grab type checks in ProcXIPassiveUngrabDevice ProcXIPassiveUngrabDevice was missing XIGrabtypeGesturePinchBegin and XIGrabtypeGestureSwipeBegin from its detail!=0 rejection check. The corresponding ProcXIPassiveGrabDevice function correctly includes these gesture types. Assisted-by: Claude:claude-claude-opus-4-6 (cherry picked from commit 90954812496770f4903a024cb610404a8dd882ad) Part-of: --- Xi/xipassivegrab.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Xi/xipassivegrab.c b/Xi/xipassivegrab.c index 896233bec..2f22e64f6 100644 --- a/Xi/xipassivegrab.c +++ b/Xi/xipassivegrab.c @@ -332,7 +332,9 @@ ProcXIPassiveUngrabDevice(ClientPtr client) if ((stuff->grab_type == XIGrabtypeEnter || stuff->grab_type == XIGrabtypeFocusIn || - stuff->grab_type == XIGrabtypeTouchBegin) && stuff->detail != 0) { + stuff->grab_type == XIGrabtypeTouchBegin || + stuff->grab_type == XIGrabtypeGesturePinchBegin || + stuff->grab_type == XIGrabtypeGestureSwipeBegin) && stuff->detail != 0) { client->errorValue = stuff->detail; return BadValue; } From d17cd09380b94a4e23fd0b772427c1a1c6be1e07 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Sat, 18 Apr 2026 07:34:51 +1000 Subject: [PATCH 4/9] xkb: Fix out-of-bounds array access in _CheckSetShapes() The primaryNdx and approxNdx fields in the shape wire description are attacker-controlled CARD8 values from the client request. They are used to index into the shape->outlines[] array, but were only checked against XkbNoShape (0xff) and never validated against the actual number of outlines (shapeWire->nOutlines). Assisted-by: Claude:claude-claude-opus-4-6 (cherry picked from commit 86a321ad98213957bbb56f295417b0939326718b) Part-of: --- xkb/xkb.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/xkb/xkb.c b/xkb/xkb.c index 0493210e8..f875a70c7 100644 --- a/xkb/xkb.c +++ b/xkb/xkb.c @@ -5561,10 +5561,22 @@ _CheckSetShapes(XkbGeometryPtr geom, ol->num_points = olWire->nPoints; olWire = (xkbOutlineWireDesc *)ptWire; } - if (shapeWire->primaryNdx != XkbNoShape) + if (shapeWire->primaryNdx != XkbNoShape) { + if (shapeWire->primaryNdx >= shapeWire->nOutlines) { + client->errorValue = _XkbErrCode3(0x08, shapeWire->primaryNdx, + shapeWire->nOutlines); + return BadValue; + } shape->primary = &shape->outlines[shapeWire->primaryNdx]; - if (shapeWire->approxNdx != XkbNoShape) + } + if (shapeWire->approxNdx != XkbNoShape) { + if (shapeWire->approxNdx >= shapeWire->nOutlines) { + client->errorValue = _XkbErrCode3(0x08, shapeWire->approxNdx, + shapeWire->nOutlines); + return BadValue; + } shape->approx = &shape->outlines[shapeWire->approxNdx]; + } shapeWire = (xkbShapeWireDesc *) olWire; } wire = (char *) shapeWire; From ce02d030202249b6df7ccca83aed5371deef4958 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Sat, 18 Apr 2026 07:35:15 +1000 Subject: [PATCH 5/9] xkb: Fix off-by-one in color index validation in _CheckSetGeom() The bounds checks for baseColorNdx and labelColorNdx in _CheckSetGeom() use '>' instead of '>=' when comparing against req->nColors. Since nColors is a count and valid indices are 0 to nColors-1, an index equal to nColors is one past the end of the array. Assisted-by: Claude:claude-claude-opus-4-6 (cherry picked from commit 6b6e8020b902e48e3330f9a54cd439a51988bc50) Part-of: --- xkb/xkb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xkb/xkb.c b/xkb/xkb.c index f875a70c7..107284cff 100644 --- a/xkb/xkb.c +++ b/xkb/xkb.c @@ -5626,12 +5626,12 @@ _CheckSetGeom(XkbGeometryPtr geom, xkbSetGeometryReq * req, ClientPtr client) client->errorValue = _XkbErrCode3(0x01, 2, req->nColors); return BadValue; } - if (req->baseColorNdx > req->nColors) { + if (req->baseColorNdx >= req->nColors) { client->errorValue = _XkbErrCode3(0x03, req->nColors, req->baseColorNdx); return BadMatch; } - if (req->labelColorNdx > req->nColors) { + if (req->labelColorNdx >= req->nColors) { client->errorValue = _XkbErrCode3(0x03, req->nColors, req->labelColorNdx); return BadMatch; From fee9a11d89ec6ac37f8901c98aa6faf255c91530 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Sat, 18 Apr 2026 07:35:53 +1000 Subject: [PATCH 6/9] xkb: Fix off-by-one and NULL dereferences in _CheckSetOverlay() Off-by-one in rowUnder validation: the bounds check uses '>' instead of '>=' when comparing rWire->rowUnder against section->num_rows. Since num_rows is a count and valid indices are 0 to num_rows-1, rowUnder == num_rows passes the check but is one past the valid range. XkbAddGeomOverlayRow() uses this as an array index, causing an out-of-bounds read on section->rows[]. And throw in two alloc checks while we're at it. Assisted-by: Claude:claude-claude-opus-4-6 (cherry picked from commit ed19312c4bda0a8f66b236348ffc553e5d8d2a09) Part-of: --- xkb/xkb.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/xkb/xkb.c b/xkb/xkb.c index 107284cff..fb1e45677 100644 --- a/xkb/xkb.c +++ b/xkb/xkb.c @@ -5357,6 +5357,8 @@ _CheckSetOverlay(char **wire_inout, xkbSetGeometryReq *req, } CHK_ATOM_ONLY(olWire->name); ol = XkbAddGeomOverlay(section, olWire->name, olWire->nRows); + if (!ol) + return BadAlloc; rWire = (xkbOverlayRowWireDesc *) &olWire[1]; for (r = 0; r < olWire->nRows; r++) { register int k; @@ -5366,12 +5368,14 @@ _CheckSetOverlay(char **wire_inout, xkbSetGeometryReq *req, if (!_XkbCheckRequestBounds(client, req, rWire, rWire + 1)) return BadLength; - if (rWire->rowUnder > section->num_rows) { + if (rWire->rowUnder >= section->num_rows) { client->errorValue = _XkbErrCode4(0x20, r, section->num_rows, rWire->rowUnder); return BadMatch; } row = XkbAddGeomOverlayRow(ol, rWire->rowUnder, rWire->nKeys); + if (!row) + return BadAlloc; kWire = (xkbOverlayKeyWireDesc *) &rWire[1]; for (k = 0; k < rWire->nKeys; k++, kWire++) { if (!_XkbCheckRequestBounds(client, req, kWire, kWire + 1)) From 801ef0f58870fa0c43084b2b0775c5258ffb8b94 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Sat, 18 Apr 2026 07:38:14 +1000 Subject: [PATCH 7/9] xkb: Add bounds check for action data in CheckKeyActions() CheckKeyActions() validates the per-key action count bytes individually but does not verify that the computed total action data region falls within the request buffer before advancing the wire pointer past it. After the loop, the function calculates the final wire position as wire + nActs * sizeof(XkbAnyAction), where nActs is the sum of per-key action counts read from the request. The upstream length validation in _XkbSetMapCheckLength() uses req->totalActs from the request header, not the computed nActs. If a crafted request provides a totalActs value that passes the length check but per-key action counts that sum to a different nActs, the wire pointer could advance past the actual request buffer. The subsequent SetKeyActions() function uses memcpy to read from this potentially out-of-bounds region, which could leak heap data or cause a crash. Assisted-by: Claude:claude-claude-opus-4-6 (cherry picked from commit a439a7340ad976983ef34eca4f537831b38e191f) Part-of: --- xkb/xkb.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xkb/xkb.c b/xkb/xkb.c index fb1e45677..c389448e4 100644 --- a/xkb/xkb.c +++ b/xkb/xkb.c @@ -1857,6 +1857,11 @@ CheckKeyActions(ClientPtr client, if (req->nKeyActs % 4) wire += 4 - (req->nKeyActs % 4); *wireRtrn = (CARD8 *) (((XkbAnyAction *) wire) + nActs); + if (nActs > 0 && + !_XkbCheckRequestBounds(client, req, wire, *wireRtrn)) { + *nActsRtrn = _XkbErrCode2(0x25, nActs); + return 0; + } *nActsRtrn = nActs; return 1; } From d4e3dfe3e91bffd58794e7ab693e5cd1ed949924 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 6 May 2026 11:45:15 +1000 Subject: [PATCH 8/9] present: actually return the created notifies present_create_notifies() creates an array of notifies but never returns them to the caller, despite them being passed individually to present_add_window_notify(). The caller proceeds with a NULL notifies array, eventually causing an OOB in present_vblank_notify() when vblank->notifies is NULL. Reported-by: Feng Ning, Innora Pte. Ltd. (cherry picked from commit f70cc16c6831c9faa14c1f2a8588c6efb6ede263) Part-of: --- present/present_notify.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/present/present_notify.c b/present/present_notify.c index 00b3b68bd..531b949b6 100644 --- a/present/present_notify.c +++ b/present/present_notify.c @@ -92,6 +92,8 @@ present_create_notifies(ClientPtr client, int num_notifies, xPresentNotify *x_no added++; } + + *p_notifies = notifies; return Success; bail: From c8bdb554268c152b8bcea71800c8cf189dc31409 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 17 Apr 2026 12:02:13 +1000 Subject: [PATCH 9/9] glx: reject negative size in FeedbackBuffer and SelectBuffer requests Assisted-by: Claude:claude-claude-opus-4-6 (cherry picked from commit 54860e6c7f513739adf225a7998004f230db81a0) Part-of: --- glx/single2.c | 8 ++++++++ glx/single2swap.c | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/glx/single2.c b/glx/single2.c index 36a01f0cb..948d00f1d 100644 --- a/glx/single2.c +++ b/glx/single2.c @@ -61,6 +61,10 @@ __glXDisp_FeedbackBuffer(__GLXclientState * cl, GLbyte * pc) pc += __GLX_SINGLE_HDR_SIZE; size = *(GLsizei *) (pc + 0); type = *(GLenum *) (pc + 4); + if (size < 0) { + cl->client->errorValue = size; + return BadValue; + } if (cx->feedbackBufSize < size) { cx->feedbackBuf = reallocarray(cx->feedbackBuf, (size_t) size, __GLX_SIZE_FLOAT32); @@ -91,6 +95,10 @@ __glXDisp_SelectBuffer(__GLXclientState * cl, GLbyte * pc) pc += __GLX_SINGLE_HDR_SIZE; size = *(GLsizei *) (pc + 0); + if (size < 0) { + cl->client->errorValue = size; + return BadValue; + } if (cx->selectBufSize < size) { cx->selectBuf = reallocarray(cx->selectBuf, (size_t) size, __GLX_SIZE_CARD32); diff --git a/glx/single2swap.c b/glx/single2swap.c index b140946ba..fdc093900 100644 --- a/glx/single2swap.c +++ b/glx/single2swap.c @@ -62,6 +62,10 @@ __glXDispSwap_FeedbackBuffer(__GLXclientState * cl, GLbyte * pc) __GLX_SWAP_INT(pc + 4); size = *(GLsizei *) (pc + 0); type = *(GLenum *) (pc + 4); + if (size < 0) { + cl->client->errorValue = size; + return BadValue; + } if (cx->feedbackBufSize < size) { cx->feedbackBuf = reallocarray(cx->feedbackBuf, (size_t) size, __GLX_SIZE_FLOAT32); @@ -96,6 +100,10 @@ __glXDispSwap_SelectBuffer(__GLXclientState * cl, GLbyte * pc) pc += __GLX_SINGLE_HDR_SIZE; __GLX_SWAP_INT(pc + 0); size = *(GLsizei *) (pc + 0); + if (size < 0) { + cl->client->errorValue = size; + return BadValue; + } if (cx->selectBufSize < size) { cx->selectBuf = reallocarray(cx->selectBuf, (size_t) size, __GLX_SIZE_CARD32);