input_constrain_cursor() writes barrier hit and leave events into the
InputEventList buffer without checking how many slots remain.
The buffer is allocated by the DDX at GetMaximumEventsNum() (100) entries
and may contain one raw event already. A client can create more than 100
pointer barriers, pre-arm them as released via XIBarrierReleasePointer,
and then trigger a single pointer motion that crosses all barriers
simultaneously. The leave event loop iterates every hit barrier and
writes one event per barrier, overflowing the 100-slot buffer.
In an ideal world we'd change the InternalEvent* pointer to a struct
that contains allocated size, number of events and the pointer itself
but we're crossing public APIs here and that requires an ABI bump.
So we make do with cutting off the number of barrier events at 64 which
should be low enough to not trigger the issue. However, for the 100+
barrier case this means we only send BarierHit events, not BarrierRelease.
Potentially leaving a client stuck.
We mitigate that problem by simply refusing to create more than 32
barriers per screen (which on its own would also fix the issue, but hey,
'doppelt gemoppelt' as they say).
This vulnerability was discovered by:
Anonymous working with TrendAI Zero Day Initiative
ZDI-CAN-31938
CVE-2026-93519
Assisted-by: Claude:claude-opus-4-6
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 1f42cc1f00)
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)
Check CursorConfinedTo and CursorWarpedTo function pointers directly without
dereferencing them in 'if' conditions in events and xiwarppointer
Signed-off-by: Herman Semenoff <GermanAizek@yandex.ru>
input_constrain_cursor() writes pointer-barrier hit/leave events into the
caller-supplied events buffer and bumps *nevents. It dereferenced both
unconditionally (barrier_events->barrier_event = ev; *nevents += 1), so
calling it with a NULL events/nevents pair crashed on the first barrier.
Callers do not pass NULL today (the only NULL call, GetTouchEvents ->
positionSprite(), is hardcoded to Absolute mode where this function's
barrier paths are skipped), but the function is a straightforward
NULL-safety hazard. Guard event emission on a can_emit flag derived from
both pointers, while still performing the barrier clamping so cursor
coordinates are constrained correctly even when no events can be emitted.
Signed-off-by: Enrico Weigelt, metux IT consult <info@metux.net>
SDeviceChangedEvent left ValuatorClass.value and every ScrollClass-specific multi-byte field in server byte order. Swapped clients therefore received corrupt current values and scroll metadata.
Swap those fields and extend the protocol event conversion coverage to nonzero relative valuator values and both scroll directions and flags.
Signed-off-by: Lukáš Lipinský <18076-Mr-Tao@users.noreply.gitlab.freedesktop.org>
Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2291>
(cherry picked from commit cf91b97d6c)
(cherry picked from commit 7a7e9c66b17cd0d64d31fa19244f3e4eb162fed7)
(cherry picked from commit f9e486ad1d398fb8dcf48f88cd8923decd291c7c)
SwapScrollInfo converted the other multi-byte ScrollClass fields but left flags in server byte order. Swap it as well and decode the field in the swapped-client protocol test so nonzero flags exercise the wire representation.
Signed-off-by: Lukáš Lipinský <18076-Mr-Tao@users.noreply.gitlab.freedesktop.org>
Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2291>
(cherry picked from commit 8033c4d93e)
(cherry picked from commit 2d1d0b67dc2119507f52e3dae96827b8946d0eca)
(cherry picked from commit db6f28acaa7c7d2a049a92396e1bc35b6f759d8a)
Check that 'ev' is not NULL before attempting to deliver touch ownership
events in TouchPuntToNextOwner(), and add a defensive null check in
DeliverTouchEvents() to avoid potential NULL pointer dereferences
The failure check after allocating a new master device's touch class tested
'to->touch' (always true -- it was just calloc'd a few lines above and
already checked), not 'to->touch->touches' (the allocation that can
actually fail), and ran *after* the loop that indexes through it via
TouchInitTouchPoint() rather than before.
Trigger: reached via normal device-hierarchy-change processing
(ChangeMasterDeviceClasses -> DeepCopyDeviceClasses) whenever the
'touches' calloc() fails (memory pressure) while promoting a device's
touch class onto a master device that doesn't have one allocated yet.
Fix: check to->touch->touches before the loop, in the right order.
Found via a fleet-directed alloc-fail/UAF sweep of Xext/, not from a live
crash report.
Signed-off-by: Enrico Weigelt, metux IT consult <info@metux.net>
These helpers are always defined, even when compiling w/o PanoramiX
at all (symbol XINERAMA not defined), so we need fewer ifdef's.
Signed-off-by: Enrico Weigelt, metux IT consult <info@metux.net>
The per-mask loop read evmask->mask_len to compute the next mask's
length before checking that the fixed xXIEventMask header itself was
still within the request buffer - a request with num_masks >= 1 and
insufficient trailing data reads one xXIEventMask-header's worth past
the end of the request. The byte-swapped path just above already
performs the equivalent pre-check ('len < bytes_to_int32(sizeof
(xXIEventMask))') before touching evmask->mask_len; this brings the
non-swapped path in line with it.
Signed-off-by: Enrico Weigelt, metux IT consult <enrico.weigelt@vnc.biz>
No need to have an actual (const) variable for something that can easily
be a #define. And also moving it into the only source file actually
using it.
Signed-off-by: Enrico Weigelt, metux IT consult <info@metux.net>
Replace public min/max macros by private MIN/MAX and deprecate them.
The few drivers still using them can trivially define them on their own.
Adding a new private header for all kind of math-ish definitions - future
commits will move more stuff there.
Signed-off-by: Enrico Weigelt, metux IT consult <info@metux.net>
For safety, add extra parantheses on each used macro argument.
In most cases not strictly necessary, but better have some strict
and automatically enforcable policy here than wasting time on
judging individual cases.
Signed-off-by: Enrico Weigelt, metux IT consult <info@metux.net>
The handler deliberately continues when dixLookupDevice() returns BadAccess
(to emit class headers with the device state blanked out). However
dixLookupDevice() leaves *pDev == NULL on any non-Success return, so the
subsequent "v = dev->valuator" dereferenced NULL and crashed the server.
This is reachable when an XACE/SELinux hook denies the client's DixReadAccess
to the device. Bail out when dev is NULL instead of dereferencing it.
Fixes: 8502c06e19 ("xace: Fake return values on denials in input polling requests.")
Fixes: 5c03d13181 ("xace: add new hooks + access controls: XInput extension.")
Signed-off-by: Enrico Weigelt, metux IT consult <info@metux.net>
mask_len is already a byte count (min of the target mask size and
stuff->mask_len * 4), but it was passed to xi2mask_set_one_mask() as
"mask_len * 4", scaling by 4 a second time. With stuff->mask_len == 1 this
made the helper copy 5 bytes from the 4-byte request mask region, a 1-byte
over-read.
Pass mask_len directly, matching the sibling ProcXIGrabDevice().
Fixes: 59a6d7d478 ("Xi: don't overrun memory for grab masks.")
Signed-off-by: Enrico Weigelt, metux IT consult <info@metux.net>
The reply payload was allocated with the non-zeroing x_rpcbuf_reserve(), and
the CopySwapKbdFeedback/CopySwapPtrFeedback/CopySwapBellFeedback helpers
populate every field except the internal pad bytes of xKbdFeedbackState (1),
xPtrFeedbackState (2) and xBellFeedbackState (3). Those pad bytes were sent to
the client verbatim, disclosing uninitialized heap memory.
Use x_rpcbuf_reserve0() to zero the buffer, restoring the behaviour that the
earlier calloc()-based code provided before the x_rpcbuf conversion.
Fixes: 6413bd61d7 ("Xi: ProcXGetFeedbackControl(): use x_rpcbuf_t")
Signed-off-by: Enrico Weigelt, metux IT consult <info@metux.net>