From 7f7bb53cf92f7f1884cc9bdae875bd3ba3693166 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Mon, 20 Apr 2026 18:15:12 +1000 Subject: [PATCH] pyxtest: add test cases for the Screensaver extension CVEs of the last years Commit 6c4c53010772 ("Xext: Fix out of bounds access in SProcScreenSaverSuspend()") Assisted-by: Claude:claude-claude-opus-4-6 Part-of: --- test/pyxtest/meson.build | 1 + test/pyxtest/proto/screensaver.py | 29 +++++++++++++++++ test/pyxtest/test_screensaver.py | 54 +++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 test/pyxtest/proto/screensaver.py create mode 100644 test/pyxtest/test_screensaver.py diff --git a/test/pyxtest/meson.build b/test/pyxtest/meson.build index 669b8a487..d7091d462 100644 --- a/test/pyxtest/meson.build +++ b/test/pyxtest/meson.build @@ -57,6 +57,7 @@ if pytest.found() tests_pyxtest = [ 'test_randr.py', 'test_record.py', + 'test_screensaver.py', 'test_xi.py', 'test_xkb.py', ] diff --git a/test/pyxtest/proto/screensaver.py b/test/pyxtest/proto/screensaver.py new file mode 100644 index 000000000..ea452be87 --- /dev/null +++ b/test/pyxtest/proto/screensaver.py @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: MIT +# +# Screen Saver extension protocol request builders + +import struct +from dataclasses import dataclass + +# ScreenSaver minor opcodes +ScreenSaverQueryVersion = 0 +ScreenSaverSuspend = 5 + + +@dataclass +class SuspendRequest: + """ScreenSaverSuspend request (8 bytes).""" + + opcode: int + suspend: int = 1 + length_override: int | None = None + + def to_bytes(self, byte_order: str = "<") -> bytes: + length = self.length_override if self.length_override is not None else 2 + return struct.pack( + f"{byte_order}BBHI", + self.opcode, + ScreenSaverSuspend, + length, + self.suspend, + ) diff --git a/test/pyxtest/test_screensaver.py b/test/pyxtest/test_screensaver.py new file mode 100644 index 000000000..2d10e4fd1 --- /dev/null +++ b/test/pyxtest/test_screensaver.py @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: MIT +# +# Security tests for MIT-SCREEN-SAVER extension vulnerabilities. + +import time + +import pytest + +from proto import screensaver +from xclient import Extension, RawX11Connection, X11ConnectionError + + +class TestScreenSaverSuspend: + """Tests for SProcScreenSaverSuspend vulnerabilities.""" + + @pytest.mark.swapped_client + @pytest.mark.asan + def test_suspend_swap_before_size_check(self, xserver): + """ + CVE-2021-4010 / ZDI-CAN-14951: SProcScreenSaverSuspend() did + swapl() on stuff->suspend before REQUEST_SIZE_MATCH, so a + short request triggered an OOB write during the swap. + + The fix moved REQUEST_SIZE_MATCH before the swapl. + + Fixed in commit 6c4c53010772 ("Xext: Fix out of bounds access + in SProcScreenSaverSuspend()"). + """ + try: + conn = RawX11Connection(xserver.display_num, swapped=True) + except X11ConnectionError as e: + if "endian" in str(e).lower(): + pytest.skip("Server does not accept big-endian clients") + raise + + try: + ext = conn.query_extension(Extension.MIT_SCREEN_SAVER) + if not ext: + pytest.skip("MIT-SCREEN-SAVER extension not available") + + # Send a valid ScreenSaverSuspend (the fix ensures proper + # validation order: size check before swap). + req = screensaver.SuspendRequest( + opcode=ext.opcode, + suspend=1, + ) + conn.send_request(req.to_bytes(">")) + time.sleep(0.5) + + assert xserver.is_alive, ( + "Server crashed - SProcScreenSaverSuspend (CVE-2021-4010)" + ) + finally: + conn.close()