mirror of
https://gitlab.freedesktop.org/xorg/xserver.git
synced 2026-06-08 01:08:23 +02:00
Add GLX extension protocol builders (proto/glx.py) and a regression test that reproduces the reversed length check in ChangeDrawableAttributes. The test creates a GLX context on the root visual, binds it with MakeCurrent (which auto-creates a GLXDrawable), then sends a ChangeDrawableAttributes request with length=3 (12 bytes) but numAttribs=2100. Without the fix, the reversed comparison operator (< instead of >) would let this undersized request pass validation, and DoChangeDrawableAttributes would iterate 2100 attribute pairs, reading far past the 12-byte request buffer. Assisted-by: Claude:claude-opus-4-6 Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2228>
168 lines
6.1 KiB
Python
168 lines
6.1 KiB
Python
# SPDX-License-Identifier: MIT
|
|
#
|
|
# Security tests for GLX extension vulnerabilities.
|
|
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from proto import glx
|
|
from xclient import Extension, X11Error
|
|
|
|
|
|
@pytest.fixture
|
|
def glx_xclient(xclient):
|
|
"""Provide an xclient with GLX extension available."""
|
|
ext = xclient.query_extension(Extension.GLX)
|
|
if not ext:
|
|
pytest.skip("GLX extension not available")
|
|
return xclient, ext.opcode
|
|
|
|
|
|
class TestGLXChangeDrawableAttributes:
|
|
"""Tests for GLX ChangeDrawableAttributes length validation."""
|
|
|
|
@pytest.mark.asan
|
|
def test_reversed_length_check_oob_read(self, xserver, glx_xclient):
|
|
"""
|
|
ZDI-CAN-30165: __glXDisp_ChangeDrawableAttributes used a
|
|
reversed comparison operator (<) instead of (>) when validating
|
|
the request length against numAttribs. The check tested whether
|
|
the computed request size was LESS THAN client->req_len, but
|
|
should have tested GREATER THAN. With the reversed operator, a
|
|
request claiming many more attribute pairs than actually present
|
|
passes validation.
|
|
|
|
DoChangeDrawableAttributes then iterates numAttribs attribute
|
|
pairs starting past the request header, reading beyond the
|
|
actual request data (OOB read). If a GLX_EVENT_MASK key is
|
|
found in the overread data, its value is written to
|
|
pGlxDraw->eventMask (OOB write).
|
|
|
|
Attack: Create a GLX context bound to the root window (which
|
|
auto-creates a GLXDrawable), then send ChangeDrawableAttributes
|
|
with length=3 (12 bytes) but numAttribs=2100. The computed
|
|
size 4203 > 3 would fail with the correct check, but 4203 < 3
|
|
is false, so the reversed check passes.
|
|
|
|
Fixed by changing < to > in both glxcmds.c and glxcmdsswap.c.
|
|
"""
|
|
xclient, opcode = glx_xclient
|
|
|
|
# Step 1: Create a direct context on the root visual.
|
|
ctx_id = xclient.alloc_id()
|
|
req = glx.CreateContextRequest(
|
|
opcode=opcode,
|
|
context_id=ctx_id,
|
|
visual=xclient.root_visual,
|
|
screen=0,
|
|
share_list=0,
|
|
is_direct=1,
|
|
)
|
|
xclient.send_request(req.to_bytes())
|
|
resp = xclient.recv_response(timeout=2.0)
|
|
if isinstance(resp, X11Error):
|
|
pytest.skip("GLX CreateContext failed (no GLX support for root visual)")
|
|
|
|
# Step 2: MakeCurrent to bind the context and auto-create
|
|
# a GLXDrawable for the root window.
|
|
req = glx.MakeCurrentRequest(
|
|
opcode=opcode,
|
|
drawable=xclient.root_window,
|
|
context_id=ctx_id,
|
|
old_context_tag=0,
|
|
)
|
|
xclient.send_request(req.to_bytes())
|
|
resp = xclient.recv_response(timeout=2.0)
|
|
if isinstance(resp, X11Error):
|
|
pytest.skip("GLX MakeCurrent failed")
|
|
|
|
# Step 3: ChangeDrawableAttributes with reversed length check.
|
|
# length=3 (12 bytes) but numAttribs=2100.
|
|
# Computed size: (12 + 2100*8) / 4 = 4203 words.
|
|
# Reversed check: 4203 < 3 → FALSE → passes!
|
|
# Correct check: 4203 > 3 → TRUE → BadLength.
|
|
req = glx.ChangeDrawableAttributesRequest(
|
|
opcode=opcode,
|
|
drawable=xclient.root_window,
|
|
num_attribs=0,
|
|
attribs=b"",
|
|
length_override=3,
|
|
num_attribs_override=2100,
|
|
)
|
|
xclient.send_request(req.to_bytes())
|
|
time.sleep(0.5)
|
|
|
|
assert xserver.is_alive, (
|
|
"Server crashed - GLX ChangeDrawableAttributes reversed "
|
|
"length check (ZDI-CAN-30165)"
|
|
)
|
|
|
|
|
|
class TestGLXChangeDrawableAttributesSwap:
|
|
"""Tests for GLX ChangeDrawableAttributes swap handler length validation."""
|
|
|
|
@pytest.mark.swapped_client
|
|
@pytest.mark.asan
|
|
def test_swap_oversized_request_rejected(self, xserver, xclient_swapped):
|
|
"""
|
|
__glXDispSwap_ChangeDrawableAttributes used a manual '>'
|
|
comparison instead of REQUEST_FIXED_SIZE. The manual check
|
|
only rejected undersized requests, silently accepting oversized
|
|
ones. REQUEST_FIXED_SIZE rejects both.
|
|
|
|
Send a request with correct numAttribs=0 but extra trailing
|
|
data (oversized). On a fixed server with REQUEST_FIXED_SIZE,
|
|
this is rejected with BadLength. On the old code with the
|
|
manual '>' check, the oversized request was accepted.
|
|
|
|
Fixed by replacing the manual check with REQUEST_FIXED_SIZE.
|
|
"""
|
|
conn = xclient_swapped
|
|
|
|
ext = conn.query_extension(Extension.GLX)
|
|
if not ext:
|
|
pytest.skip("GLX extension not available")
|
|
|
|
# Step 1: Create a direct context on the root visual.
|
|
ctx_id = conn.alloc_id()
|
|
req = glx.CreateContextRequest(
|
|
opcode=ext.opcode,
|
|
context_id=ctx_id,
|
|
visual=conn.root_visual,
|
|
screen=0,
|
|
share_list=0,
|
|
is_direct=1,
|
|
)
|
|
conn.send_request(req.to_bytes(">"))
|
|
resp = conn.recv_response(timeout=2.0)
|
|
if isinstance(resp, X11Error):
|
|
pytest.skip("GLX CreateContext failed")
|
|
|
|
# Step 2: MakeCurrent to create a GLXDrawable for root window.
|
|
req = glx.MakeCurrentRequest(
|
|
opcode=ext.opcode,
|
|
drawable=conn.root_window,
|
|
context_id=ctx_id,
|
|
old_context_tag=0,
|
|
)
|
|
conn.send_request(req.to_bytes(">"))
|
|
resp = conn.recv_response(timeout=2.0)
|
|
if isinstance(resp, X11Error):
|
|
pytest.skip("GLX MakeCurrent failed")
|
|
|
|
# Step 3: ChangeDrawableAttributes with numAttribs=0 but extra
|
|
# 8 bytes of trailing data. The correct length for numAttribs=0
|
|
# is 3 words (12 bytes). We send 5 words (20 bytes).
|
|
req = glx.ChangeDrawableAttributesRequest(
|
|
opcode=ext.opcode,
|
|
drawable=conn.root_window,
|
|
num_attribs=0,
|
|
attribs=b"\x00" * 8, # 8 extra bytes
|
|
)
|
|
conn.send_request(req.to_bytes(">"))
|
|
time.sleep(0.5)
|
|
|
|
assert xserver.is_alive, (
|
|
"Server crashed - GLX ChangeDrawableAttributes swap handler"
|
|
)
|