mirror of
https://gitlab.freedesktop.org/xorg/xserver.git
synced 2026-06-09 00:28:25 +02:00
Add screensaver protocol builders for SetAttributes, UnsetAttributes, and ForceScreenSaver, then add a regression test that reproduces the CreateSaverWindow use-after-free. The test sequence: 1. SetAttributes(root, 100x100, mask=0) - creates screen private with attr 2. ForceScreenSaver(Active) - creates the saver window 3. UnsetAttributes(root) - clears pPriv->attr to NULL 4. ForceScreenSaver(Active) - re-enters CreateSaverWindow Without the fix, step 4 triggers CheckScreenPrivate which finds all fields empty (attr=NULL, events=NULL, hasWindow=FALSE, installedMap=None), frees pPriv, and sets the screen private to NULL. The function then dereferences the freed pPriv->attr pointer, causing a use-after-free. Assisted-by: Claude:claude-opus-4-6 Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2228>
122 lines
4.2 KiB
Python
122 lines
4.2 KiB
Python
# SPDX-License-Identifier: MIT
|
|
#
|
|
# Security tests for MIT-SCREEN-SAVER extension vulnerabilities.
|
|
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from proto import screensaver, x11
|
|
from xclient import Extension
|
|
|
|
|
|
@pytest.fixture
|
|
def screensaver_xclient(xclient):
|
|
"""Provide an xclient with the MIT-SCREEN-SAVER extension queried."""
|
|
ext = xclient.query_extension(Extension.MIT_SCREEN_SAVER)
|
|
if not ext:
|
|
pytest.skip("MIT-SCREEN-SAVER extension not available")
|
|
return xclient, ext.opcode
|
|
|
|
|
|
class TestScreenSaverSuspend:
|
|
"""Tests for SProcScreenSaverSuspend vulnerabilities."""
|
|
|
|
@pytest.mark.swapped_client
|
|
@pytest.mark.asan
|
|
def test_suspend_swap_before_size_check(self, xserver, xclient_swapped):
|
|
"""
|
|
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()").
|
|
"""
|
|
conn = xclient_swapped
|
|
|
|
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)
|
|
time.sleep(0.5)
|
|
|
|
assert xserver.is_alive, (
|
|
"Server crashed - SProcScreenSaverSuspend (CVE-2021-4010)"
|
|
)
|
|
|
|
|
|
class TestCreateSaverWindow:
|
|
"""Tests for CreateSaverWindow use-after-free via CheckScreenPrivate."""
|
|
|
|
@pytest.mark.asan
|
|
def test_create_saver_window_uaf(self, xserver, screensaver_xclient):
|
|
"""
|
|
ZDI-CAN-30168: CreateSaverWindow stores pPriv in a local
|
|
variable at function entry. When an existing saver window is
|
|
being replaced, it sets pPriv->hasWindow = FALSE and calls
|
|
CheckScreenPrivate(). If pPriv->attr is NULL (cleared by a
|
|
prior UnsetAttributes), pPriv->events is NULL, and
|
|
pPriv->installedMap is None, CheckScreenPrivate frees pPriv
|
|
and sets the screen private to NULL. The function then
|
|
dereferences the freed pPriv->attr pointer on the next line.
|
|
|
|
Attack sequence:
|
|
1. SetAttributes (creates pPriv with pPriv->attr set)
|
|
2. ForceScreenSaver(Active) (creates saver window)
|
|
3. UnsetAttributes (sets pPriv->attr = NULL)
|
|
4. ForceScreenSaver(Active) (re-enters CreateSaverWindow → UAF)
|
|
|
|
Fixed by re-fetching pPriv from the screen private after
|
|
CheckScreenPrivate returns.
|
|
"""
|
|
conn, opcode = screensaver_xclient
|
|
|
|
# Step 1: SetAttributes(root, 100x100, mask=0)
|
|
# Creates pPriv with pPriv->attr set.
|
|
req = screensaver.SetAttributesRequest(
|
|
opcode=opcode,
|
|
drawable=conn.root_window,
|
|
width=100,
|
|
height=100,
|
|
mask=0,
|
|
)
|
|
conn.send_request(req.to_bytes())
|
|
conn.flush_responses(timeout=0.5)
|
|
|
|
# Step 2: ForceScreenSaver(Active)
|
|
# Activates the screen saver, creating the saver window.
|
|
req = x11.ForceScreenSaver(mode=x11.ScreenSaverActive)
|
|
conn.send_request(req.to_bytes())
|
|
conn.flush_responses(timeout=0.5)
|
|
|
|
time.sleep(0.2)
|
|
|
|
# Step 3: UnsetAttributes(root)
|
|
# Sets pPriv->attr = NULL but does not destroy the saver window.
|
|
req = screensaver.UnsetAttributesRequest(
|
|
opcode=opcode,
|
|
drawable=conn.root_window,
|
|
)
|
|
conn.send_request(req.to_bytes())
|
|
conn.flush_responses(timeout=0.5)
|
|
|
|
# Step 4: ForceScreenSaver(Active) again → triggers UAF.
|
|
# CreateSaverWindow: cleanup block frees pPriv via
|
|
# CheckScreenPrivate, then reads pPriv->attr from freed memory.
|
|
req = x11.ForceScreenSaver(mode=x11.ScreenSaverActive)
|
|
conn.send_request(req.to_bytes())
|
|
time.sleep(0.5)
|
|
|
|
assert xserver.is_alive, (
|
|
"Server crashed - CreateSaverWindow UAF (ZDI-CAN-30168)"
|
|
)
|