Bug 2475267 (CVE-2026-94282) - CVE-2026-94282 libXi: OOB read in XI2 enter/leave/focus cookie conversion (`wireToEnterLeave`)
Summary: CVE-2026-94282 libXi: OOB read in XI2 enter/leave/focus cookie conversion (`w...
Keywords:
Status: NEW
Alias: CVE-2026-94282
Product: Security Response
Classification: Other
Component: vulnerability-draft
Version: unspecified
Hardware: All
OS: Linux
medium
medium
Target Milestone: ---
Assignee: Product Security
QA Contact:
URL:
Whiteboard:
Depends On:
Blocks:
TreeView+ depends on / blocked
 
Reported: 2026-05-11 22:06 UTC by OSIDB Bzimport
Modified: 2026-09-25 16:33 UTC (History)
3 users (show)

Fixed In Version:
Clone Of:
Environment:
Last Closed:
Embargoed:


Attachments (Terms of Use)

Description OSIDB Bzimport 2026-05-11 22:06:01 UTC
AI_ONLY_REPORT
package: libXi-1.8.2-4.1.hum1
------
Summary: OOB read in XI2 enter/leave/focus cookie conversion  
(`wireToEnterLeave`): malformed XI2 enter/leave/focus GenericEvents can  
cause a client-side out-of-bounds read when `buttons_len` exceeds the  
actual payload length.
Requirements to exploit: An attacker must control or compromise the X  
server or X11 transport endpoint used by a libXi-linked client, and the  
client must process XI2 `XI_Enter`, `XI_Leave`, `XI_FocusIn`, or  
`XI_FocusOut` events through the cookie conversion path.
Component affected: `libXi-1.8.2-4.1.hum1`, `src/XExtInt.c`,  
`XInputWireToCookie()` and `wireToEnterLeave()`
Version affected: `libXi-1.8.2-4.1.hum1`, when used by a client that  
accepts XI2 enter/leave/focus GenericEvents from a malicious or compromised  
X server
Patch available: no released package fix established; proposed patch  
included below
Version fixed: unknown
Upstream coordination: Not notified.
CVSS: CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H - 6.1 (MEDIUM)
AV:L - The attacker must control the X server endpoint the client is  
already connected to, rather than reach a network service exposed by the  
package itself.
AC:L - Crafting a GenericEvent with a short payload and oversized  
`buttons_len` is straightforward once the attacker controls that event  
stream.
PR:N - No privileges are required on the victim host beyond providing  
the malicious X server data.
UI:R - A user must run an affected client and connect it to the  
malicious or compromised X server.
S:U - The effect is confined to the client process that parses the event.
C:L - Adjacent in-process bytes may be copied past the received event  
buffer, creating limited disclosure potential, although disclosure was not  
directly demonstrated.
I:N - The issue does not provide a write primitive or direct data  
modification capability.
A:H - The malformed event can crash the client or otherwise disrupt  
event processing.
Impact: Moderate. This issue can cause client-side denial of service and  
may allow limited disclosure of adjacent in-process data, but exploitation  
depends on a malicious or compromised X server and a client taking the  
affected XI2 cookie conversion path. That makes it harder to exploit than  
Red Hat's Important or Critical categories, while still representing more  
than minimal security impact when reachable.
Embargo: no
Reason: This is a client-side parsing issue with a constrained threat  
model, no demonstrated code execution, and straightforward operational  
mitigation by avoiding untrusted X servers until a fix is available.
Acknowledgement: Aisle Research
Vulnerability Details: In `src/XExtInt.c`, `XInputWireToCookie()` routes  
`XI_Enter`, `XI_Leave`, `XI_FocusIn`, and `XI_FocusOut` to  
`wireToEnterLeave()` without passing validated wire-event size context.  
`wireToEnterLeave()` then trusts the wire-supplied `buttons_len` field when  
sizing the destination buffer and copying button-mask bytes:
```c
static int
wireToEnterLeave(xXIEnterEvent *in, XGenericEventCookie *cookie)
{
int len;
XIEnterEvent *out;
len = sizeof(XIEnterEvent) + in->buttons_len * 4;
...
out->buttons.mask_len = in->buttons_len * 4;
memcpy(out->buttons.mask, &in[1], out->buttons.mask_len);
return 1;
}
```
There is no validation on this path that `buttons_len * 4` fits within the  
received GenericEvent payload before the `memcpy()` occurs. A malformed  
`XI_Enter`, `XI_Leave`, `XI_FocusIn`, or `XI_FocusOut` event can therefore  
drive a client-side out-of-bounds read. The directly supported impact is  
application crash or event-processing failure; limited in-process memory  
disclosure is also plausible because bytes beyond the received event buffer  
may be copied into cookie data.
Steps to reproduce:
1. Build `libXi-1.8.2-4.1.hum1` and a minimal XI2 client with  
AddressSanitizer enabled that selects XI2 events and processes them via  
`XNextEvent()` and the cookie path.
2. Run the client with `DISPLAY` pointing to a controlled X server or  
protocol fuzzer that can emit crafted XI2 `GenericEvent` messages.
3. Emit an `XI_Enter` event (the same applies to `XI_Leave`, `XI_FocusIn`,  
and `XI_FocusOut`) with a small `ge.length` so the trailing payload is  
short, but with `buttons_len` set so `buttons_len * 4` exceeds the  
available payload bytes.
4. Observe an AddressSanitizer invalid read at `memcpy(out->buttons.mask,  
&in[1], out->buttons.mask_len)` in `wireToEnterLeave()` or an equivalent  
client crash.
Mitigation: Until this is fixed, avoid connecting libXi-linked XI2 clients  
to untrusted or compromised X servers. Where operationally possible,  
restrict applications to trusted local display servers and avoid exposing  
attacker-controlled X11 event streams to the affected cookie conversion  
path.
Proposed Fix: Pass the actual received event size into  
`wireToEnterLeave()`, validate the derived button-mask length against that  
size, and reject malformed events before allocation and copy.
```diff
diff --git a/libXi-1.8.2/src/XExtInt.c b/libXi-1.8.2/src/XExtInt.c
index 0000000..0000000 100644
— a/libXi-1.8.2/src/XExtInt.c
+++ b/libXi-1.8.2/src/XExtInt.c
@@ -55,6 +55,7 @@
#include <config.h>
#endif
+#include <limits.h>
#include <stdio.h>
#include <stdint.h>
#include <X11/extensions/XI.h>
@@ -118,7 +119,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);
@@ -1017,7 +1019,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);
@@ -2014,12 +2018,23 @@ wireToRawEvent(XExtDisplayInfo *info, xXIRawEvent  
*in, XGenericEventCookie *coo
    [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 * 4u;
+
+    if (mask_len > (size_t)INT_MAX)
+        return 0;
+    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)
@@ -2057,8 +2072,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;
}
```
------
This report was generated using AI technology. Always review AI-generated  
content prior to use


Note You need to log in before you can comment on or make changes to this bug.