From 165d2810a053fa1837a069f47fe3d3105b6d920f Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Tue, 5 May 2026 10:42:28 +1000 Subject: [PATCH] pyxtest: add test for present notify array byte-swap fix The test sends a PresentPixmap request with a notify entry from a byte-swapped client. Without the fix, the window ID in the notify is not swapped, causing dixLookupWindow to fail with BadWindow. With the fix, the window ID is correctly interpreted. See 925edb6c9e ("present: Fix missing byte swaps in sproc_present_pixmap()") Assisted-by: Claude:claude-claude-opus-4-6 Part-of: --- test/pyxtest/proto/present.py | 27 +++++++++++-- test/pyxtest/test_present.py | 75 +++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/test/pyxtest/proto/present.py b/test/pyxtest/proto/present.py index b2e1d888a..97fba73b2 100644 --- a/test/pyxtest/proto/present.py +++ b/test/pyxtest/proto/present.py @@ -70,6 +70,17 @@ class QueryCapabilitiesRequest: ) +@dataclass +class PresentNotify: + """xPresentNotify structure (8 bytes): window(4) + serial(4).""" + + window: int + serial: int = 0 + + def to_bytes(self, byte_order: str = "<") -> bytes: + return struct.pack(f"{byte_order}II", self.window, self.serial) + + @dataclass class PixmapRequest: """PresentPixmap request. @@ -93,10 +104,19 @@ class PixmapRequest: target_msc: int = 0 divisor: int = 0 remainder: int = 0 - # notifies follow but we omit them for testing + notifies: list[PresentNotify] | None = None def to_bytes(self, byte_order: str = "<") -> bytes: - return struct.pack( + notify_data = b"" + if self.notifies: + for n in self.notifies: + notify_data += n.to_bytes(byte_order) + + base_size = 72 # 18 words + total_size = base_size + len(notify_data) + length = total_size // 4 + + header = struct.pack( f"{byte_order}BBH" # header: opcode, sub-opcode, length f"III" # window, pixmap, serial f"II" # valid, update @@ -109,7 +129,7 @@ class PixmapRequest: f"Q", # remainder (CARD64) self.opcode, PresentPixmap, - 18, # 72 bytes = 18 words + length, self.window, self.pixmap, self.serial, @@ -125,6 +145,7 @@ class PixmapRequest: self.divisor, self.remainder, ) + return header + notify_data @dataclass diff --git a/test/pyxtest/test_present.py b/test/pyxtest/test_present.py index 2c274c11f..63fb35492 100644 --- a/test/pyxtest/test_present.py +++ b/test/pyxtest/test_present.py @@ -55,3 +55,78 @@ class TestPresentSelectInput: f"PresentSelectInput returned error(s): {errors} - " "eid not swapped → BadIDChoice" ) + + +class TestPresentNotify: + """Tests for PresentPixmap notify array byte-swap fix. + + Fix: present: Fix missing byte swaps in sproc_present_pixmap() + + The xPresentNotify array following the fixed header was not + byte-swapped at all. Each entry has window (CARD32) and serial + (CARD32) fields that need swapl(). Without swapping, a + byte-swapped client's window IDs are garbled, causing + dixLookupWindow to fail with BadWindow. + + Fixed in commit 925edb6c9e ("present: Fix missing byte swaps in + sproc_present_pixmap()"). + """ + + @pytest.mark.swapped_client + def test_present_pixmap_notifies_window_swapped(self, xserver, xclient_swapped): + """ + sproc_present_pixmap was missing byte swaps for the variable-length + xPresentNotify array. + + Send a PresentPixmap request with a notify entry whose window + field is a valid window created by this client. Without the swap, + the window ID is garbled and dixLookupWindow fails with BadWindow. + With the swap, the window ID is correctly interpreted. + + Fixed in commit 925edb6c9e ("present: Fix missing byte swaps in + sproc_present_pixmap()"). + """ + conn = xclient_swapped + + ext = conn.query_extension(Extension.PRESENT) + if not ext: + pytest.skip("Present extension not available") + + req = present.QueryVersionRequest(opcode=ext.opcode) + conn.send_request(req.to_bytes(">")) + conn.recv_response(timeout=5.0) + + win = conn.create_window() + pixmap = conn.create_pixmap() + + # The notify window is the same window as the main request window. + # With the fix, the window ID in the notify is correctly swapped + # and the lookup succeeds. Without the fix, the garbled ID causes + # BadWindow. + notify = present.PresentNotify(window=win, serial=1) + + req = present.PixmapRequest( + opcode=ext.opcode, + window=win, + pixmap=pixmap, + serial=0, + notifies=[notify], + ) + conn.send_request(req.to_bytes(">")) + responses = conn.flush_responses(timeout=1.0) + + assert xserver.is_alive, "Server crashed" + + # With the fix: either success (no error for void request) or + # a non-BadWindow error (e.g. BadMatch from the present + # implementation). The key point is no BadWindow (error 3). + # Without the fix: BadWindow because the notify's window ID + # was not byte-swapped. + bad_window_errors = [ + r for r in responses if isinstance(r, X11Error) and r.error_code == 3 + ] + assert len(bad_window_errors) == 0, ( + f"PresentPixmap returned BadWindow error(s): " + f"{bad_window_errors} - notify window IDs not " + "byte-swapped in sproc_present_pixmap" + )