Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package libXi for openSUSE:Factory checked in at 2026-09-28 10:34:54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/libXi (Old) and /work/SRC/openSUSE:Factory/.libXi.new.383539 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "libXi" Mon Sep 28 10:34:54 2026 rev:24 rq:1379999 version:1.8.3 Changes: -------- --- /work/SRC/openSUSE:Factory/libXi/libXi.changes 2026-05-20 15:23:40.740059879 +0200 +++ /work/SRC/openSUSE:Factory/.libXi.new.383539/libXi.changes 2026-09-28 10:34:57.798076500 +0200 @@ -1,0 +2,19 @@ +Wed Sep 23 16:16:29 UTC 2026 - Stefan Dirsch <[email protected]> + +- 0001-boo1281605_CVE-2026-93541_XQueryDeviceState-check-ValuatorClass-num_valuators-.patch + * Out-of-bounds read in libXi's XQueryDeviceState() (boo#1281605, CVE-2026-93541) +- 0002-boo1281606_CVE-2026-93542_size_classes-copy_classes-bound-XI2-class-lengths-to.patch + * Out-of-bounds read in libXi's XI2 class parsing via size_classes() and copy_classes() + (boo#1281606, CVE-2026-93542) +- 0003-boo1281608_CVE-2026-93543_size_classes-copy_classes-enforce-XI2-per-type-class.patch + * Out-of-bounds read in libXi's XI2 class parser (boo#1281608, CVE-2026-93543) +- 0004-boo1281609_CVE-2026-93544_XIQueryDevice-keep-padded-name-and-class-bytes-withi.patch + * Out-of-bounds read in libXi's XI2 XIQueryDevice reply parsing (boo#1281609, CVE-2026-93544) +- 0005-boo1281612_CVE-2026-93545_XListInputDevices-validate-device-name-lengths-again.patch + * Out-of-bounds read in libXi's XListInputDevices() (boo#1281612, CVE-2026-93545) +- 0006-boo1281615_CVE-2026-94281_XListInputDevices-validate-class-lengths-cumulativel.patch + * Out-of-bounds read in libXi's XListInputDevices() class parsing (boo#1281615, CVE-2026-94281) +- 0007-boo1281651_CVE-2026-94282_wireToEnterLeave-validate-buttons_len-against-the-re.patch + * Out-of-bounds read in libXi's XI2 enter/leave/focus cookie conversio (boo#1281651, CVE-2026-94282) + +------------------------------------------------------------------- New: ---- 0001-boo1281605_CVE-2026-93541_XQueryDeviceState-check-ValuatorClass-num_valuators-.patch 0002-boo1281606_CVE-2026-93542_size_classes-copy_classes-bound-XI2-class-lengths-to.patch 0003-boo1281608_CVE-2026-93543_size_classes-copy_classes-enforce-XI2-per-type-class.patch 0004-boo1281609_CVE-2026-93544_XIQueryDevice-keep-padded-name-and-class-bytes-withi.patch 0005-boo1281612_CVE-2026-93545_XListInputDevices-validate-device-name-lengths-again.patch 0006-boo1281615_CVE-2026-94281_XListInputDevices-validate-class-lengths-cumulativel.patch 0007-boo1281651_CVE-2026-94282_wireToEnterLeave-validate-buttons_len-against-the-re.patch ----------(New B)---------- New: - 0001-boo1281605_CVE-2026-93541_XQueryDeviceState-check-ValuatorClass-num_valuators-.patch * Out-of-bounds read in libXi's XQueryDeviceState() (boo#1281605, CVE-2026-93541) New: * Out-of-bounds read in libXi's XQueryDeviceState() (boo#1281605, CVE-2026-93541) - 0002-boo1281606_CVE-2026-93542_size_classes-copy_classes-bound-XI2-class-lengths-to.patch * Out-of-bounds read in libXi's XI2 class parsing via size_classes() and copy_classes() New: (boo#1281606, CVE-2026-93542) - 0003-boo1281608_CVE-2026-93543_size_classes-copy_classes-enforce-XI2-per-type-class.patch * Out-of-bounds read in libXi's XI2 class parser (boo#1281608, CVE-2026-93543) New: * Out-of-bounds read in libXi's XI2 class parser (boo#1281608, CVE-2026-93543) - 0004-boo1281609_CVE-2026-93544_XIQueryDevice-keep-padded-name-and-class-bytes-withi.patch * Out-of-bounds read in libXi's XI2 XIQueryDevice reply parsing (boo#1281609, CVE-2026-93544) New: * Out-of-bounds read in libXi's XI2 XIQueryDevice reply parsing (boo#1281609, CVE-2026-93544) - 0005-boo1281612_CVE-2026-93545_XListInputDevices-validate-device-name-lengths-again.patch * Out-of-bounds read in libXi's XListInputDevices() (boo#1281612, CVE-2026-93545) New: * Out-of-bounds read in libXi's XListInputDevices() (boo#1281612, CVE-2026-93545) - 0006-boo1281615_CVE-2026-94281_XListInputDevices-validate-class-lengths-cumulativel.patch * Out-of-bounds read in libXi's XListInputDevices() class parsing (boo#1281615, CVE-2026-94281) New: * Out-of-bounds read in libXi's XListInputDevices() class parsing (boo#1281615, CVE-2026-94281) - 0007-boo1281651_CVE-2026-94282_wireToEnterLeave-validate-buttons_len-against-the-re.patch * Out-of-bounds read in libXi's XI2 enter/leave/focus cookie conversio (boo#1281651, CVE-2026-94282) ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ libXi.spec ++++++ --- /var/tmp/diff_new_pack.foCBgo/_old 2026-09-28 10:34:58.343099335 +0200 +++ /var/tmp/diff_new_pack.foCBgo/_new 2026-09-28 10:34:58.344099377 +0200 @@ -29,6 +29,13 @@ #Git-Web: http://cgit.freedesktop.org/xorg/lib/libXi/ Source: http://xorg.freedesktop.org/releases/individual/lib/%{name}-%{version}.tar.xz Source1: baselibs.conf +Patch1: 0001-boo1281605_CVE-2026-93541_XQueryDeviceState-check-ValuatorClass-num_valuators-.patch +Patch2: 0002-boo1281606_CVE-2026-93542_size_classes-copy_classes-bound-XI2-class-lengths-to.patch +Patch3: 0003-boo1281608_CVE-2026-93543_size_classes-copy_classes-enforce-XI2-per-type-class.patch +Patch4: 0004-boo1281609_CVE-2026-93544_XIQueryDevice-keep-padded-name-and-class-bytes-withi.patch +Patch5: 0005-boo1281612_CVE-2026-93545_XListInputDevices-validate-device-name-lengths-again.patch +Patch6: 0006-boo1281615_CVE-2026-94281_XListInputDevices-validate-class-lengths-cumulativel.patch +Patch7: 0007-boo1281651_CVE-2026-94282_wireToEnterLeave-validate-buttons_len-against-the-re.patch BuildRoot: %{_tmppath}/%{name}-%{version}-build #git#BuildRequires: autoconf >= 2.60, automake, libtool BuildRequires: fdupes @@ -66,7 +73,7 @@ in %lname. %prep -%setup -q +%autosetup -p1 %build %configure --docdir=%_docdir/%name --disable-static ++++++ 0001-boo1281605_CVE-2026-93541_XQueryDeviceState-check-ValuatorClass-num_valuators-.patch ++++++ >From 7b6fffd13fd3914e0b39f3a4f131913da7f066e7 Mon Sep 17 00:00:00 2001 From: Olivier Fourdan <[email protected]> Date: Fri, 31 Jul 2026 15:05:27 +0200 Subject: [PATCH 1/7] XQueryDeviceState: check ValuatorClass num_valuators against class length The first pass only verified that the xValuatorState header fit in the reply. num_valuators was then used to size the destination and to copy valuator words from (v + 1) without ensuring those words fit in any->length. A forged QueryDeviceState reply with length equal to the size of xValuatorState and a large num_valuators could therefore read past the allocated reply buffer (denial of service). Reject ValuatorClass entries whose valuator array does not fit in the declared class length before sizing or copying. Found by AISLE in partnership with Red Hat AISLE-Report-PSIRTSUPT-13253 CVE-2026-93541 Reported-by: Aisle Research Signed-off-by: Olivier Fourdan <[email protected]> Assisted-by: AI --- src/XQueryDv.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/XQueryDv.c b/src/XQueryDv.c index 7ee2272..d8f29b1 100644 --- a/src/XQueryDv.c +++ b/src/XQueryDv.c @@ -116,10 +116,25 @@ XQueryDeviceState( case ValuatorClass: { xValuatorState *v = (xValuatorState *) any; + size_t nvals; + size_t needed; + + if (any->length < sizeof(xValuatorState)) + goto out; + if ((char *)any + sizeof(xValuatorState) > end) goto out; - size += (sizeof(XValuatorState) + - (v->num_valuators * sizeof(int))); + + nvals = v->num_valuators; + /* valuators follow the header; must fit in class length */ + if (nvals > (any->length - sizeof(xValuatorState)) / sizeof(CARD32)) + goto out; + + needed = sizeof(xValuatorState) + nvals * sizeof(CARD32); + if ((char *)any + needed > end) + goto out; + + size += sizeof(XValuatorState) + nvals * sizeof(int); } break; } @@ -165,18 +180,17 @@ XQueryDeviceState( xValuatorState *v = (xValuatorState *) any; XValuatorState *V = (XValuatorState *) Any; CARD32 *valuators = (CARD32 *) (v + 1); + size_t nvals = v->num_valuators; V->class = v->class; - V->length = sizeof(XValuatorState) + - v->num_valuators * sizeof(int); + V->length = sizeof(XValuatorState) + nvals * sizeof(int); V->num_valuators = v->num_valuators; V->mode = v->mode; Any = (XInputClass *) (V + 1); V->valuators = (int *)Any; - for (j = 0; j < (int)V->num_valuators; j++) + for (j = 0; j < (int)nvals; j++) *(V->valuators + j) = *valuators++; - Any = (XInputClass *) ((char *)Any + - V->num_valuators * sizeof(int)); + Any = (XInputClass *) ((char *)Any + nvals * sizeof(int)); } break; } -- 2.51.0 ++++++ 0002-boo1281606_CVE-2026-93542_size_classes-copy_classes-bound-XI2-class-lengths-to.patch ++++++ >From f499944ad595b9bd7e7571c810842244caf150aa Mon Sep 17 00:00:00 2001 From: Olivier Fourdan <[email protected]> Date: Fri, 31 Jul 2026 16:04:12 +0200 Subject: [PATCH 2/7] size_classes/copy_classes: bound XI2 class lengths to the received buffer Both size_classes() and copy_classes() walked server-provided class records using any_wire->length without checking that each class fits in the reply or event buffer. XIQueryDevice() had the reply end pointer available but did not pass it down; the same helpers are reused for XI_DeviceChanged conversion. A forged XIQueryDevice reply or truncated DeviceChanged event with an overlong class length could therefore read past the allocated buffer and crash the client. Pass an end pointer into size_classes and copy_classes, reject zero or overlong wire lengths before advancing, and propagate parse failures to the callers. Found by AISLE in partnership with Red Hat AISLE-Report-PSIRTSUPT-13600 CVE-2026-93542 Reported-by: Aisle Research Signed-off-by: Olivier Fourdan <[email protected]> Assisted-by: AI --- src/XExtInt.c | 50 +++++++++++++++++++++++++++++++++++++-------- src/XIQueryDevice.c | 24 +++++++++++++++++----- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/XExtInt.c b/src/XExtInt.c index cdf5579..6b60792 100644 --- a/src/XExtInt.c +++ b/src/XExtInt.c @@ -73,8 +73,8 @@ SOFTWARE. #define DONT_ENQUEUE False #define FP1616toDBL(x) ((x) * 1.0 / (1 << 16)) -int copy_classes(XIDeviceInfo *to, xXIAnyInfo* from, int *nclasses); -int size_classes(xXIAnyInfo* from, int nclasses); +int copy_classes(XIDeviceInfo *to, xXIAnyInfo* from, int *nclasses, char *end); +int size_classes(xXIAnyInfo* from, int nclasses, char *end); static XExtensionInfo *xinput_info; static const char *xinput_extension_name = INAME; @@ -1681,8 +1681,30 @@ wireToDeviceEvent(xXIDeviceEvent *in, XGenericEventCookie* cookie) return 1; } +static int +validate_class_header(char *ptr, char *end, xXIAnyInfo **any_out) +{ + xXIAnyInfo *any; + size_t remaining; + + if (ptr + sizeof(xXIAnyInfo) > end) + return -1; + + any = (xXIAnyInfo *) ptr; + if (any->length == 0) + return -1; + + remaining = (size_t) (end - ptr); + if ((size_t) any->length > remaining / 4) + return -1; + + *any_out = any; + + return 0; +} + _X_HIDDEN int -size_classes(xXIAnyInfo* from, int nclasses) +size_classes(xXIAnyInfo* from, int nclasses, char *end) { int len, i; xXIAnyInfo *any_wire; @@ -1694,7 +1716,10 @@ size_classes(xXIAnyInfo* from, int nclasses) for (i = 0; i < nclasses; i++) { int l = 0; - any_wire = (xXIAnyInfo*)ptr_wire; + + if (validate_class_header(ptr_wire, end, &any_wire) < 0) + return -1; + switch(any_wire->type) { case XIButtonClass: @@ -1735,7 +1760,7 @@ size_classes(xXIAnyInfo* from, int nclasses) * |______________________^ */ _X_HIDDEN int -copy_classes(XIDeviceInfo* to, xXIAnyInfo* from, int *nclasses) +copy_classes(XIDeviceInfo* to, xXIAnyInfo* from, int *nclasses, char *end) { XIAnyClassInfo *any_lib; xXIAnyInfo *any_wire; @@ -1757,7 +1782,9 @@ copy_classes(XIDeviceInfo* to, xXIAnyInfo* from, int *nclasses) for (i = 0; i < *nclasses; i++) { any_lib = (XIAnyClassInfo*)ptr_lib; - any_wire = (xXIAnyInfo*)ptr_wire; + + if (validate_class_header(ptr_wire, end, &any_wire) < 0) + return -1; switch(any_wire->type) { @@ -1913,8 +1940,11 @@ wireToDeviceChangedEvent(xXIDeviceChangedEvent *in, XGenericEventCookie *cookie) XIDeviceInfo info; int len; int nclasses = in->num_classes; + char *end = (char *)in + sizeof(xEvent) + in->length * 4; - len = size_classes((xXIAnyInfo*)&in[1], in->num_classes); + len = size_classes((xXIAnyInfo*)&in[1], in->num_classes, end); + if (len < 0) + return 0; cookie->data = out = malloc(sizeof(XIDeviceChangedEvent) + len); if (!out) @@ -1935,7 +1965,11 @@ wireToDeviceChangedEvent(xXIDeviceChangedEvent *in, XGenericEventCookie *cookie) info.classes = out->classes; - copy_classes(&info, (xXIAnyInfo*)&in[1], &nclasses); + if (copy_classes(&info, (xXIAnyInfo*)&in[1], &nclasses, end) < 0) { + free(out); + cookie->data = NULL; + return 0; + } out->num_classes = nclasses; return 1; diff --git a/src/XIQueryDevice.c b/src/XIQueryDevice.c index c89c4bc..c0f2ce1 100644 --- a/src/XIQueryDevice.c +++ b/src/XIQueryDevice.c @@ -34,8 +34,9 @@ #include <X11/extensions/extutil.h> #include "XIint.h" -extern int copy_classes(XIDeviceInfo* to, xXIAnyInfo* from, int *nclasses); -extern int size_classes(xXIAnyInfo* from, int nclasses); +extern int copy_classes(XIDeviceInfo* to, xXIAnyInfo* from, int *nclasses, + char *end); +extern int size_classes(xXIAnyInfo* from, int nclasses, char *end); XIDeviceInfo* XIQueryDevice(Display *dpy, int deviceid, int *ndevices_return) @@ -88,7 +89,8 @@ XIQueryDevice(Display *dpy, int deviceid, int *ndevices_return) for (i = 0; i < reply.num_devices; i++) { int nclasses; - size_t sz; + int sz; + int wire_len; XIDeviceInfo *lib = &info[i]; xXIDeviceInfo *wire = (xXIDeviceInfo*)ptr; @@ -113,14 +115,26 @@ XIQueryDevice(Display *dpy, int deviceid, int *ndevices_return) lib->name[wire->name_len] = '\0'; ptr += ((wire->name_len + 3)/4) * 4; - sz = size_classes((xXIAnyInfo*)ptr, nclasses); + sz = size_classes((xXIAnyInfo*)ptr, nclasses, end); + if (sz < 0) + { + Xfree(lib->name); + goto error_loop; + } lib->classes = Xmalloc(sz); if (lib->classes == NULL) { Xfree(lib->name); goto error_loop; } - ptr += copy_classes(lib, (xXIAnyInfo*)ptr, &nclasses); + wire_len = copy_classes(lib, (xXIAnyInfo*)ptr, &nclasses, end); + if (wire_len < 0) + { + Xfree(lib->name); + Xfree(lib->classes); + goto error_loop; + } + ptr += wire_len; /* We skip over unused classes */ lib->num_classes = nclasses; } -- 2.51.0 ++++++ 0003-boo1281608_CVE-2026-93543_size_classes-copy_classes-enforce-XI2-per-type-class.patch ++++++ >From e2089ab748828273f916bbffd4e65b506aa50fdc Mon Sep 17 00:00:00 2001 From: Olivier Fourdan <[email protected]> Date: Fri, 31 Jul 2026 16:20:56 +0200 Subject: [PATCH 3/7] size_classes/copy_classes: enforce XI2 per-type class payload sizes Bounding any_wire->length against the reply/event end pointer is not enough: a class length that fits in the buffer can still be shorter than the type-specific wire struct, and button/key counts can claim more bytes than that class contains. copy_classes() then reads past the payload when converting XI_DeviceChanged events (and when parsing XIQueryDevice replies). Validate per-type minimum sizes and that button/key payloads fit in the declared class length, and reject DeviceChanged allocations that would overflow size_t. Found by AISLE in partnership with Red Hat AISLE-Report-PSIRTSUPT-13717 CVE-2026-93543 Reported-by: Aisle Research Signed-off-by: Olivier Fourdan <[email protected]> Assisted-by: AI --- src/XExtInt.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/src/XExtInt.c b/src/XExtInt.c index 6b60792..2a6e7ac 100644 --- a/src/XExtInt.c +++ b/src/XExtInt.c @@ -1703,6 +1703,73 @@ validate_class_header(char *ptr, char *end, xXIAnyInfo **any_out) return 0; } +/* + * Reject class records whose type-specific header or payload cannot fit in + * the declared wire length (length is in 4-byte units). + */ +static int +validate_class_payload(xXIAnyInfo *any) +{ + size_t cls_len = (size_t) any->length * 4; + + switch (any->type) { + case XIButtonClass: + { + xXIButtonInfo *b = (xXIButtonInfo *) any; + size_t mask_size, labels_size, need; + + if (cls_len < sizeof(xXIButtonInfo)) + return -1; + + mask_size = ((size_t) b->num_buttons + 7) / 8; + mask_size = (mask_size + 3) / 4 * 4; + if (b->num_buttons > + (SIZE_MAX - sizeof(xXIButtonInfo) - mask_size) / 4) + return -1; + + labels_size = (size_t) b->num_buttons * 4; + need = sizeof(xXIButtonInfo) + mask_size + labels_size; + if (need > cls_len) + return -1; + + break; + } + case XIKeyClass: + { + xXIKeyInfo *k = (xXIKeyInfo *) any; + + if (cls_len < sizeof(xXIKeyInfo)) + return -1; + + /* copy_classes reads num_keycodes bytes from the wire */ + if ((size_t) k->num_keycodes > cls_len - sizeof(xXIKeyInfo)) + return -1; + + break; + } + case XIValuatorClass: + if (cls_len < sizeof(xXIValuatorInfo)) + return -1; + break; + case XIScrollClass: + if (cls_len < sizeof(xXIScrollInfo)) + return -1; + break; + case XITouchClass: + if (cls_len < sizeof(xXITouchInfo)) + return -1; + break; + case XIGestureClass: + if (cls_len < sizeof(xXIGestureInfo)) + return -1; + break; + default: + break; + } + + return 0; +} + _X_HIDDEN int size_classes(xXIAnyInfo* from, int nclasses, char *end) { @@ -1720,6 +1787,9 @@ size_classes(xXIAnyInfo* from, int nclasses, char *end) if (validate_class_header(ptr_wire, end, &any_wire) < 0) return -1; + if (validate_class_payload(any_wire) < 0) + return -1; + switch(any_wire->type) { case XIButtonClass: @@ -1786,6 +1856,9 @@ copy_classes(XIDeviceInfo* to, xXIAnyInfo* from, int *nclasses, char *end) if (validate_class_header(ptr_wire, end, &any_wire) < 0) return -1; + if (validate_class_payload(any_wire) < 0) + return -1; + switch(any_wire->type) { case XIButtonClass: @@ -1940,10 +2013,10 @@ wireToDeviceChangedEvent(xXIDeviceChangedEvent *in, XGenericEventCookie *cookie) XIDeviceInfo info; int len; int nclasses = in->num_classes; - char *end = (char *)in + sizeof(xEvent) + in->length * 4; + char *end = (char *)in + sizeof(xXIDeviceChangedEvent) + in->length * 4; len = size_classes((xXIAnyInfo*)&in[1], in->num_classes, end); - if (len < 0) + if (len < 0 || (size_t)len > SIZE_MAX - sizeof(XIDeviceChangedEvent)) return 0; cookie->data = out = malloc(sizeof(XIDeviceChangedEvent) + len); -- 2.51.0 ++++++ 0004-boo1281609_CVE-2026-93544_XIQueryDevice-keep-padded-name-and-class-bytes-withi.patch ++++++ >From a88a341135b79f6ed450f481e4a5d6ba502382af Mon Sep 17 00:00:00 2001 From: Olivier Fourdan <[email protected]> Date: Fri, 31 Jul 2026 16:27:02 +0200 Subject: [PATCH 4/7] XIQueryDevice: keep padded name and class bytes within the reply Class parsing helpers already reject overlong or inconsistent XI2 class records against the reply end pointer. Also require the padded device name and the bytes consumed by copy_classes() to stay inside that bound before advancing to the next device. A malformed XIQueryDevice reply with an inflated name_len (within the raw length check but exceeding the 4-byte-padded name field) could otherwise leave the parser misaligned for subsequent class data. Found by AISLE in partnership with Red Hat AISLE-Report-PSIRTSUPT-14043 CVE-2026-93544 Reported-by: Aisle Research Signed-off-by: Olivier Fourdan <[email protected]> Assisted-by: AI --- src/XIQueryDevice.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/XIQueryDevice.c b/src/XIQueryDevice.c index c0f2ce1..0da746e 100644 --- a/src/XIQueryDevice.c +++ b/src/XIQueryDevice.c @@ -93,6 +93,7 @@ XIQueryDevice(Display *dpy, int deviceid, int *ndevices_return) int wire_len; XIDeviceInfo *lib = &info[i]; xXIDeviceInfo *wire = (xXIDeviceInfo*)ptr; + size_t name_bytes; if (ptr + sizeof(xXIDeviceInfo) > end) goto error_loop; @@ -104,16 +105,18 @@ XIQueryDevice(Display *dpy, int deviceid, int *ndevices_return) nclasses = wire->num_classes; ptr += sizeof(xXIDeviceInfo); + name_bytes = ((size_t) wire->name_len + 3) / 4 * 4; - if (ptr + wire->name_len > end) + if (ptr + name_bytes > end) goto error_loop; lib->name = Xcalloc(wire->name_len + 1, 1); if (lib->name == NULL) goto error_loop; + strncpy(lib->name, ptr, wire->name_len); lib->name[wire->name_len] = '\0'; - ptr += ((wire->name_len + 3)/4) * 4; + ptr += name_bytes; sz = size_classes((xXIAnyInfo*)ptr, nclasses, end); if (sz < 0) @@ -128,7 +131,7 @@ XIQueryDevice(Display *dpy, int deviceid, int *ndevices_return) goto error_loop; } wire_len = copy_classes(lib, (xXIAnyInfo*)ptr, &nclasses, end); - if (wire_len < 0) + if (wire_len < 0 || ptr + wire_len > end) { Xfree(lib->name); Xfree(lib->classes); -- 2.51.0 ++++++ 0005-boo1281612_CVE-2026-93545_XListInputDevices-validate-device-name-lengths-again.patch ++++++ >From 234ce17d95c42d75f7f7fdb2bf7a24875451bc0a Mon Sep 17 00:00:00 2001 From: Olivier Fourdan <[email protected]> Date: Fri, 11 Sep 2026 16:37:48 +0200 Subject: [PATCH 5/7] XListInputDevices: validate device name lengths against reply buffer The name-parsing loops trusted the server-supplied length byte without checking that the advertised name actually fits within the received reply buffer. Additionally, the sizing loop used a wrong end pointer derived from the already-incremented list pointer plus the full reply length, which was too generous and could not catch overruns. A malicious server could return a ListInputDevices reply with a name length byte exceeding the remaining reply bytes, causing memcpy() to read past the buffer and crash the client. Check each name length against the actual reply buffer end (already computed as "end") in both the sizing and copy loops before accessing the name data. Found by AISLE in partnership with Red Hat AISLE-Report-PSIRTSUPT-14711 CVE-2026-93545 Reported-by: Aisle Research Signed-off-by: Olivier Fourdan <[email protected]> Assisted-by: AI --- src/XListDev.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/XListDev.c b/src/XListDev.c index db71e0e..6f0ce7b 100644 --- a/src/XListDev.c +++ b/src/XListDev.c @@ -229,12 +229,16 @@ XListInputDevices( size += s; } - Nptr = ((unsigned char *)list) + rlen; for (i = 0, nptr = (unsigned char *)any; i < ndevices; i++) { - if (nptr >= Nptr) + size_t name_len; + + if (nptr >= (unsigned char *)end) + goto out; + name_len = *nptr; + if (name_len > (size_t)((unsigned char *)end - nptr - 1)) goto out; - size += *nptr + 1; - nptr += (*nptr + 1); + size += name_len + 1; + nptr += name_len + 1; } clist = (XDeviceInfoPtr) Xmalloc(size); @@ -263,11 +267,18 @@ XListInputDevices( nptr = (unsigned char *)any; Nptr = (unsigned char *)Any; for (i = 0; i < ndevices; i++, clist++) { + size_t name_len; + + if (nptr >= (unsigned char *)end) + goto out; + name_len = *nptr; + if (name_len > (size_t)((unsigned char *)end - nptr - 1)) + goto out; clist->name = (char *)Nptr; - memcpy(Nptr, nptr + 1, *nptr); - Nptr += (*nptr); + memcpy(Nptr, nptr + 1, name_len); + Nptr += name_len; *Nptr++ = '\0'; - nptr += (*nptr + 1); + nptr += name_len + 1; } } -- 2.51.0 ++++++ 0006-boo1281615_CVE-2026-94281_XListInputDevices-validate-class-lengths-cumulativel.patch ++++++ >From 605f419d013153bf9e026cd100752ffbe930f3c1 Mon Sep 17 00:00:00 2001 From: Olivier Fourdan <[email protected]> Date: Fri, 11 Sep 2026 16:45:23 +0200 Subject: [PATCH 6/7] XListInputDevices: validate class lengths cumulatively in SizeClassInfo SizeClassInfo() checked each class record's length against the original remaining buffer size, but never decremented it after advancing past each record. A malicious server could supply class records whose individual lengths each passed the check but whose cumulative size exceeded the buffer, causing out-of-bounds reads in SizeClassInfo and ParseClassInfo. This is fixed by: - Decrementing len after each class record in SizeClassInfo - Validating minimum class-specific struct sizes before accessing fields - Rejecting unknown class types - Adding an end pointer and bounds checks to ParseClassInfo - Checking ParseClassInfo return value at the call site Found by AISLE in partnership with Red Hat AISLE-Report-PSIRTSUPT-14718 CVE-2026-94281 Reported-by: Aisle Research Signed-off-by: Olivier Fourdan <[email protected]> Assisted-by: AI --- src/XListDev.c | 45 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/src/XListDev.c b/src/XListDev.c index 6f0ce7b..7eb26d9 100644 --- a/src/XListDev.c +++ b/src/XListDev.c @@ -78,32 +78,47 @@ SizeClassInfo(xAnyClassPtr *any, size_t len, int num_classes, size_t *size) { int j; size_t sz = 0; + int clen; for (j = 0; j < num_classes; j++) { + if (len < sizeof(xAnyClassInfo)) + return 1; + clen = (*any)->length; + if (clen < (int)sizeof(xAnyClassInfo) || (size_t)clen > len) + return 1; + switch ((*any)->class) { case KeyClass: + if (clen < (int)sizeof(xKeyInfo)) + return 1; sz += pad_to_xid(sizeof(XKeyInfo)); break; case ButtonClass: + if (clen < (int)sizeof(xButtonInfo)) + return 1; sz += pad_to_xid(sizeof(XButtonInfo)); break; case ValuatorClass: { xValuatorInfoPtr v; + size_t need; - if (len < sizeof(v)) + if (clen < (int)sizeof(xValuatorInfo)) return 1; v = (xValuatorInfoPtr) *any; + need = sizeof(xValuatorInfo) + + ((size_t)v->num_axes * sizeof(xAxisInfo)); + if (need > (size_t)clen) + return 1; sz += pad_to_xid(sizeof(XValuatorInfo) + (v->num_axes * sizeof(XAxisInfo))); break; } default: - break; + return 1; } - if ((*any)->length > len) - return 1; - *any = (xAnyClassPtr) ((char *)(*any) + (*any)->length); + *any = (xAnyClassPtr) ((char *)(*any) + clen); + len -= (size_t)clen; } *size = sz; @@ -111,12 +126,20 @@ SizeClassInfo(xAnyClassPtr *any, size_t len, int num_classes, size_t *size) return 0; } -static void -ParseClassInfo(xAnyClassPtr *any, XAnyClassPtr *Any, int num_classes) +static int +ParseClassInfo(xAnyClassPtr *any, XAnyClassPtr *Any, int num_classes, + char *end) { int j; + int clen; for (j = 0; j < num_classes; j++) { + if ((char *)(*any) + sizeof(xAnyClassInfo) > end) + return 1; + clen = (*any)->length; + if (clen < (int)sizeof(xAnyClassInfo) || (char *)(*any) + clen > end) + return 1; + switch ((*any)->class) { case KeyClass: { @@ -165,11 +188,12 @@ ParseClassInfo(xAnyClassPtr *any, XAnyClassPtr *Any, int num_classes) break; } default: - break; + return 1; } - *any = (xAnyClassPtr) ((char *)(*any) + (*any)->length); + *any = (xAnyClassPtr) ((char *)(*any) + clen); *Any = (XAnyClassPtr) ((char *)(*Any) + (*Any)->length); } + return 0; } XDeviceInfo * @@ -260,7 +284,8 @@ XListInputDevices( clist->num_classes = list->num_classes; clist->inputclassinfo = Any; - ParseClassInfo(&any, &Any, (int)list->num_classes); + if (ParseClassInfo(&any, &Any, (int)list->num_classes, end)) + goto out; } clist = sclist; -- 2.51.0 ++++++ 0007-boo1281651_CVE-2026-94282_wireToEnterLeave-validate-buttons_len-against-the-re.patch ++++++ >From cecf160e9731fe01f3632f875f29ffcb598b052a Mon Sep 17 00:00:00 2001 From: Olivier Fourdan <[email protected]> Date: Fri, 11 Sep 2026 16:15:21 +0200 Subject: [PATCH 7/7] wireToEnterLeave: validate buttons_len against the received event size wireToEnterLeave() trusts the wire-supplied buttons_len field and copies buttons_len * 4 bytes from the event payload without checking that many bytes were actually received. A malicious X server can send an XI2 Enter, Leave, FocusIn or FocusOut event with a small ge.length but an oversized buttons_len, causing memcpy() to read past the end of the event buffer. Pass the total event size into wireToEnterLeave() and reject events where buttons_len * 4 exceeds the payload following the fixed-size xXIEnterEvent header. Found by AISLE in partnership with Red Hat AISLE-Report-PSIRTSUPT-15083 CVE-2026-94282 Reported-by: Aisle Research Signed-off-by: Olivier Fourdan <[email protected]> Assisted-by: AI --- src/XExtInt.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/XExtInt.c b/src/XExtInt.c index 2a6e7ac..9959efb 100644 --- a/src/XExtInt.c +++ b/src/XExtInt.c @@ -118,7 +118,8 @@ wireToHierarchyChangedEvent(xXIHierarchyEvent *in, XGenericEventCookie *cookie); static int wireToRawEvent(XExtDisplayInfo *info, xXIRawEvent *in, XGenericEventCookie *cookie); static int -wireToEnterLeave(xXIEnterEvent *in, XGenericEventCookie *cookie); +wireToEnterLeave(xXIEnterEvent *in, size_t event_bytes, + XGenericEventCookie *cookie); static int wireToPropertyEvent(xXIPropertyEvent *in, XGenericEventCookie *cookie); static int @@ -1042,7 +1043,9 @@ XInputWireToCookie( case XI_FocusIn: case XI_FocusOut: *cookie = *(XGenericEventCookie*)save; - if (!wireToEnterLeave((xXIEnterEvent*)event, cookie)) + if (!wireToEnterLeave((xXIEnterEvent*)event, + sizeof(xEvent) + ((size_t)ge->length * 4), + cookie)) { printf("XInputWireToCookie: CONVERSION FAILURE! evtype=%d\n", ge->evtype); @@ -2146,12 +2149,20 @@ wireToRawEvent(XExtDisplayInfo *info, xXIRawEvent *in, XGenericEventCookie *cook [event][modifiers][group][button] */ static int -wireToEnterLeave(xXIEnterEvent *in, XGenericEventCookie *cookie) +wireToEnterLeave(xXIEnterEvent *in, size_t event_bytes, + XGenericEventCookie *cookie) { - int len; + size_t mask_len, len; XIEnterEvent *out; - len = sizeof(XIEnterEvent) + in->buttons_len * 4; + mask_len = (size_t)in->buttons_len * 4; + + if (event_bytes < sizeof(*in) || mask_len > event_bytes - sizeof(*in)) + return 0; + if (mask_len > SIZE_MAX - sizeof(XIEnterEvent)) + return 0; + + len = sizeof(XIEnterEvent) + mask_len; cookie->data = out = malloc(len); if (!out) @@ -2189,8 +2200,8 @@ wireToEnterLeave(xXIEnterEvent *in, XGenericEventCookie *cookie) out->group.latched = in->group.latched_group; out->group.effective = in->group.effective_group; - out->buttons.mask_len = in->buttons_len * 4; - memcpy(out->buttons.mask, &in[1], out->buttons.mask_len); + out->buttons.mask_len = (int) mask_len; + memcpy(out->buttons.mask, &in[1], mask_len); return 1; } -- 2.51.0
