byxtest: add test cases for the RECORD extension CVEs of the last years

Commit 2902b78535 ("Fix XRecordRegisterClients() Integer underflow")

Assisted-by: Claude:claude-claude-opus-4-6
Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2187>
This commit is contained in:
Peter Hutterer 2026-04-20 18:14:38 +10:00 committed by Marge Bot
parent 845eb56df2
commit 33f3066ddb
3 changed files with 150 additions and 0 deletions

View file

@ -56,6 +56,7 @@ if pytest.found()
# This needs to be kept in sync with the test_foo.py files in the tree
tests_pyxtest = [
'test_randr.py',
'test_record.py',
'test_xi.py',
'test_xkb.py',
]

View file

@ -0,0 +1,82 @@
# SPDX-License-Identifier: MIT
#
# RECORD extension protocol request builders
import struct
from dataclasses import dataclass, field
# RECORD minor opcodes
RecordQueryVersion = 0
RecordCreateContext = 1
RecordRegisterClients = 2
RECORD_MAJOR = 1
RECORD_MINOR = 13
@dataclass
class QueryVersionRequest:
"""RecordQueryVersion request."""
opcode: int
major: int = RECORD_MAJOR
minor: int = RECORD_MINOR
def to_bytes(self, byte_order: str = "<") -> bytes:
return struct.pack(
f"{byte_order}BBHHH",
self.opcode,
RecordQueryVersion,
2,
self.major,
self.minor,
)
@dataclass
class CreateContextRequest:
"""RecordCreateContext request.
Header is 20 bytes, followed by nClients CARD32 client IDs,
then nRanges xRecordRange structs (32 bytes each).
"""
opcode: int
context_id: int
element_header: int = 0
client_ids: list[int] = field(default_factory=list)
ranges_data: bytes = b""
n_clients_override: int | None = None
n_ranges_override: int | None = None
def to_bytes(self, byte_order: str = "<") -> bytes:
n_clients = (
self.n_clients_override
if self.n_clients_override is not None
else len(self.client_ids)
)
n_ranges = (
self.n_ranges_override
if self.n_ranges_override is not None
else len(self.ranges_data) // 32
)
client_data = b""
for cid in self.client_ids:
client_data += struct.pack(f"{byte_order}I", cid)
total_bytes = 20 + len(client_data) + len(self.ranges_data)
pad_len = (4 - total_bytes % 4) % 4
total_bytes += pad_len
header = struct.pack(
f"{byte_order}BBH I B xxx I I",
self.opcode,
RecordCreateContext,
total_bytes // 4,
self.context_id,
self.element_header,
n_clients,
n_ranges,
)
return header + client_data + self.ranges_data + b"\x00" * pad_len

View file

@ -0,0 +1,67 @@
# SPDX-License-Identifier: MIT
#
# Security tests for RECORD extension vulnerabilities.
import time
import pytest
from proto import record
from xclient import Extension
@pytest.fixture
def record_xclient_swapped(xclient_swapped):
"""Provide a byte-swapped xclient with RECORD initialized."""
ext = xclient_swapped.query_extension(Extension.RECORD)
if not ext:
pytest.skip("RECORD extension not available")
req = record.QueryVersionRequest(opcode=ext.opcode)
xclient_swapped.send_request(req.to_bytes(">"))
xclient_swapped.recv_response(timeout=5.0)
return xclient_swapped, ext.opcode
class TestRecordCreateContext:
"""Tests for RECORD CreateContext/RegisterClients vulnerabilities."""
@pytest.mark.swapped_client
@pytest.mark.asan
def test_swap_create_register_integer_underflow(
self, xserver, record_xclient_swapped
):
"""
CVE-2020-14362 / ZDI-CAN-11574: SwapCreateRegister() used
stuff->length (attacker-controlled 16-bit wire field) instead
of client->req_len for bounds checking nClients.
With big requests or a carefully crafted length field, the
subtraction ``stuff->length - header_size`` can underflow,
allowing nClients to pass the check and causing OOB reads
during client ID swapping.
Fixed in commit 2902b78535ec ("Fix XRecordRegisterClients() Integer
underflow").
"""
conn, opcode = record_xclient_swapped
ctx_id = conn.alloc_id()
# Send CreateContext claiming many clients but with
# minimal actual data.
req = record.CreateContextRequest(
opcode=opcode,
context_id=ctx_id,
client_ids=[0], # One client ID
ranges_data=b"\x00" * 32, # One range (32 bytes)
n_clients_override=100, # Claim 100 clients
n_ranges_override=1,
)
conn.send_request(req.to_bytes(">"))
time.sleep(0.5)
assert xserver.is_alive, (
"Server crashed - integer underflow in SwapCreateRegister (CVE-2020-14362)"
)