mirror of
https://github.com/X11Libre/xserver.git
synced 2026-10-26 01:59:57 +00:00
dix: remove passive grabs referencing a device on removal
Passive grabs (GrabRec) store raw DeviceIntPtr values for both the
grabbed device and the modifier device without any lifetime management.
When a device is removed via XIRemoveMaster, CloseDevice() frees the
device struct, but leaves dangling pointers in grab->modifierDevice
for any grab that used this device.
Subsequent pointer events trigger CheckPassiveGrab() which dereferences
the dangling modifierDevice pointer for XI grabs (the CORE and XI2 paths
reassign it, but the XI path does not).
This vulnerability was discovered by:
Anonymous working with TrendAI Zero Day Initiative
ZDI-CAN-31832
CVE-2026-93516
Assisted-by: Claude:claude-opus-4-6
Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2341>
(cherry picked from commit be57263415)
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
parent
727e3952c5
commit
8304192c3c
3 changed files with 168 additions and 1 deletions
|
|
@ -1162,6 +1162,40 @@ CloseOneDevice(const DeviceIntPtr dev, DeviceIntPtr *listHead)
|
|||
return BadMatch;
|
||||
}
|
||||
|
||||
static Bool
|
||||
GrabReferencesDevice(void *value, XID id, void *cdata)
|
||||
{
|
||||
GrabPtr grab = value;
|
||||
DeviceIntPtr dev = cdata;
|
||||
|
||||
return grab->device == dev || grab->modifierDevice == dev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all passive grabs that reference the given device, either as
|
||||
* the grabbed device or as the modifier device. This must be called
|
||||
* before the device is freed to avoid dangling pointers in GrabRec.
|
||||
*/
|
||||
static void
|
||||
RemovePassiveGrabsForDevice(DeviceIntPtr dev)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < currentMaxClients; i++) {
|
||||
GrabPtr grab;
|
||||
|
||||
if (!clients[i])
|
||||
continue;
|
||||
|
||||
while ((grab = LookupClientResourceComplex(clients[i],
|
||||
X11_RESTYPE_PASSIVEGRAB,
|
||||
GrabReferencesDevice,
|
||||
dev))) {
|
||||
FreeResource(grab->resource, X11_RESTYPE_NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a device from the device list, closes it and thus frees all
|
||||
* resources.
|
||||
|
|
@ -1197,6 +1231,8 @@ RemoveDevice(DeviceIntPtr dev, BOOL sendevent)
|
|||
flags[dev->id] = XIDeviceDisabled;
|
||||
}
|
||||
|
||||
RemovePassiveGrabsForDevice(dev);
|
||||
|
||||
int flag = InputDevIsMaster(dev) ? XIMasterRemoved : XISlaveRemoved;
|
||||
|
||||
input_lock();
|
||||
|
|
|
|||
|
|
@ -6,18 +6,41 @@ import struct
|
|||
from dataclasses import dataclass
|
||||
|
||||
# XI (v1) minor opcodes
|
||||
XGrabDeviceButton = 17
|
||||
XChangeDeviceControl = 35
|
||||
XChangeDeviceProperty = 37
|
||||
XGetDeviceProperty = 39
|
||||
|
||||
# XI2 minor opcodes
|
||||
XIChangeCursor = 42
|
||||
XIChangeHierarchy = 43
|
||||
XIQueryVersion = 47
|
||||
XIQueryDevice = 48
|
||||
XIPassiveGrabDevice = 54
|
||||
XIPassiveUngrabDevice = 55
|
||||
XIChangeProperty = 57
|
||||
XIGetProperty = 59
|
||||
XIBarrierReleasePointer = 61
|
||||
|
||||
# XIChangeHierarchy change types
|
||||
XIAddMaster = 1
|
||||
XIRemoveMaster = 2
|
||||
|
||||
# XIRemoveMaster return modes
|
||||
XIAttachToMaster = 1
|
||||
XIFloating = 2
|
||||
|
||||
# XIQueryDevice special device IDs
|
||||
# XIAllDevices = 0 (already defined below)
|
||||
# XIAllMasterDevices = 1 (already defined below)
|
||||
|
||||
# XI device use types (from xXIDeviceInfo)
|
||||
XIMasterPointer = 1
|
||||
XIMasterKeyboard = 2
|
||||
XISlavePointer = 3
|
||||
XISlaveKeyboard = 4
|
||||
XIFloatingSlave = 5
|
||||
|
||||
XI2_MAJOR = 2
|
||||
XI2_MINOR = 4
|
||||
|
||||
|
|
@ -374,7 +397,6 @@ class DeviceResolutionCtl:
|
|||
return header + val_data
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class XIAddMasterInfo:
|
||||
"""XIAddMaster change body for XIChangeHierarchy.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,25 @@ from proto import x11, xfixes, xi, xtest
|
|||
from xclient import BadLength, BadValue, Extension, X11Error, X11Reply
|
||||
|
||||
|
||||
def find_device(
|
||||
resp, name_prefix: str, use: int | None = None, byte_order: str = "<"
|
||||
) -> int | None:
|
||||
"""Parse an XIQueryDevice reply to find a master device by name.
|
||||
|
||||
Returns the device ID or None if not found.
|
||||
"""
|
||||
if not isinstance(resp, X11Reply):
|
||||
return None
|
||||
|
||||
reply = xi.XIQueryDeviceReply.from_reply(resp.data, byte_order)
|
||||
|
||||
for device in reply.devices:
|
||||
if (use is None or device.use == use) and device.name.startswith(name_prefix):
|
||||
return device.deviceid
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def xi_xclient(xclient):
|
||||
"""Provide an xclient with XI2 initialized."""
|
||||
|
|
@ -170,6 +189,96 @@ class TestXIPassiveGrab:
|
|||
f"Expected BadValue ({x11.BadValue}), got error code {resp.error_code}"
|
||||
)
|
||||
|
||||
@pytest.mark.asan
|
||||
def test_passive_grab_modifier_device_uaf(self, xserver, xi_xclient):
|
||||
"""
|
||||
ZDI-CAN-31832: Use-after-free in CheckPassiveGrab via
|
||||
grab->modifierDevice after device removal.
|
||||
|
||||
XGrabDeviceButton creates a passive grab with grabtype XI. The grab
|
||||
stores a raw DeviceIntPtr in grab->modifierDevice without any reference
|
||||
counting or lifetime management.
|
||||
|
||||
When the modifier device (a master keyboard) is removed via
|
||||
XIChangeHierarchy(XIRemoveMaster), CloseDevice frees
|
||||
the device struct, but doesn't clear the now-dangling
|
||||
grab->modifierDevice pointer.
|
||||
|
||||
A subsequent pointer event routed to the grab window triggers
|
||||
CheckPassiveGrabsOnWindow -> CheckPassiveGrab, which
|
||||
dereferences the freed device via gdev = grab->modifierDevice
|
||||
then reads gdev->key.
|
||||
"""
|
||||
conn = xi_xclient
|
||||
opcode = conn.query_extension(Extension.XI).opcode
|
||||
|
||||
# 1. Create a window for the grab target
|
||||
wid = conn.create_window()
|
||||
req = x11.MapWindowRequest(window=wid)
|
||||
conn.send_request(req)
|
||||
conn.flush_responses(timeout=0.5)
|
||||
|
||||
# 2. Create a new master device pair "uaf"
|
||||
add_info = xi.XIAddMasterInfo(name="uaf", send_core=1, enable=1)
|
||||
req = xi.XIChangeHierarchyRequest(
|
||||
opcode=opcode,
|
||||
num_changes=1,
|
||||
changes_data=add_info.to_bytes("<"),
|
||||
)
|
||||
conn.send_request(req)
|
||||
conn.flush_responses(timeout=1.0)
|
||||
|
||||
req = xi.XIQueryDeviceRequest(opcode=opcode, deviceid=xi.XIAllDevices)
|
||||
conn.send_request(req)
|
||||
resp = conn.recv_response(timeout=2.0)
|
||||
kbd_id = find_device(resp, name_prefix="uaf", use=xi.XIMasterKeyboard)
|
||||
if kbd_id is None:
|
||||
pytest.fail("Could not find 'uaf' master keyboard device")
|
||||
|
||||
# 3. XGrabDeviceButton: passive grab on VCP with modifierDevice=kbd_id.
|
||||
# This creates a grabtype=XI grab with modifierDevice pointing
|
||||
# to the new master keyboard.
|
||||
req = xi.XGrabDeviceButtonRequest(
|
||||
opcode=opcode,
|
||||
grab_window=wid,
|
||||
grabbed_device=xi.VirtualCorePointer,
|
||||
modifier_device=kbd_id,
|
||||
button=0, # AnyButton
|
||||
modifiers=0x8000, # AnyModifier
|
||||
)
|
||||
conn.send_request(req)
|
||||
conn.flush_responses(timeout=0.5)
|
||||
|
||||
# 4. Remove the master device - this frees the device struct
|
||||
rem_info = xi.XIRemoveMasterInfo(
|
||||
deviceid=kbd_id,
|
||||
return_mode=xi.XIFloating,
|
||||
)
|
||||
req = xi.XIChangeHierarchyRequest(
|
||||
opcode=opcode,
|
||||
num_changes=1,
|
||||
changes_data=rem_info.to_bytes("<"),
|
||||
)
|
||||
conn.send_request(req)
|
||||
conn.flush_responses(timeout=1.0)
|
||||
|
||||
# 6. Warp the pointer into the grab window to trigger
|
||||
# CheckMotion -> ActivateEnterGrab -> CheckPassiveGrab -> UAF
|
||||
req = x11.WarpPointerRequest(
|
||||
dst_window=wid,
|
||||
dst_x=50,
|
||||
dst_y=50,
|
||||
)
|
||||
conn.send_request(req)
|
||||
conn.flush_responses(timeout=0.5)
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
assert xserver.is_alive, (
|
||||
"Server crashed - passive grab modifierDevice "
|
||||
"use-after-free on device removal (ZDI-CAN-31832)"
|
||||
)
|
||||
|
||||
|
||||
class TestXIChangeProperty:
|
||||
"""Tests for XIChangeProperty vulnerabilities."""
|
||||
|
|
|
|||
Loading…
Reference in a new issue