From bb13ee0410c3602edd716e88d1007aab263f91a5 Mon Sep 17 00:00:00 2001 From: Alan Coopersmith Date: Fri, 19 Jun 2026 14:52:00 -0700 Subject: [PATCH] test/pyxtest: add test for ProcXIChangeCursor with window None Currently crashes the X server Signed-off-by: Alan Coopersmith Part-of: --- test/pyxtest/proto/xi.py | 23 +++++++++++++++++++++++ test/pyxtest/test_xi.py | 27 ++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/test/pyxtest/proto/xi.py b/test/pyxtest/proto/xi.py index 820b6941e6..73d522cb93 100644 --- a/test/pyxtest/proto/xi.py +++ b/test/pyxtest/proto/xi.py @@ -11,6 +11,7 @@ XChangeDeviceProperty = 37 XGetDeviceProperty = 39 # XI2 minor opcodes +XIChangeCursor = 42 XIQueryVersion = 47 XIPassiveGrabDevice = 54 XIPassiveUngrabDevice = 55 @@ -75,6 +76,28 @@ class XIQueryVersionRequest: ) +@dataclass +class XIChangeCursorRequest: + """XIChangeCursor request.""" + + opcode: int + window: int + cursor: int + deviceid: int = XIAllMasterDevices + + def to_bytes(self, byte_order: str = "<") -> bytes: + return struct.pack( + f"{byte_order}BBH II HH", + self.opcode, + XIChangeCursor, + 4, + self.window, + self.cursor, + self.deviceid, + 0, # pad + ) + + @dataclass class XIPassiveGrabDeviceRequest: """XIPassiveGrabDevice request.""" diff --git a/test/pyxtest/test_xi.py b/test/pyxtest/test_xi.py index 042f6343b6..dcfff83826 100644 --- a/test/pyxtest/test_xi.py +++ b/test/pyxtest/test_xi.py @@ -7,7 +7,7 @@ import struct import pytest from proto import xi -from xclient import BadLength, BadValue, Extension, X11Error, X11Reply +from xclient import BadLength, BadWindow, BadValue, Extension, X11Error, X11Reply @pytest.fixture @@ -389,3 +389,28 @@ class TestXIChangeDeviceControl: "ChangeDeviceControl returned BadValue - " "resolution values not byte-swapped" ) + + +class TestXIChangeCursor: + def test_change_cursor_null_window(self, xserver, xi_xclient): + """ + XIChangeCursor dereferences pWin even if it's not set + """ + opcode = xi_xclient.query_extension(Extension.XI).opcode + req = xi.XIChangeCursorRequest( + opcode=opcode, + window=0, + cursor=0, + deviceid=xi.VirtualCorePointer, + ) + xi_xclient.send_request(req) + resp = xi_xclient.recv_response(timeout=5.0) + + assert xserver.is_alive, "Server crashed" + + # Without the fix: SegFault on a NULL WindowPtr + # With the fix: BadWindow + assert isinstance(resp, X11Error), f"Expected an error, got {resp}" + assert resp.error_code == BadWindow, ( + "ChangeCursor didn't return BadWindow for Window 0" + )