xserver/test/pyxtest/test_fb.py
Olivier Fourdan ebb1cf007f test/pyxtest: fix ruff I001/UP035 import ordering
Organize import blocks and import Iterator from collections.abc.

Assisted-by: AI
Signed-off-by: Olivier Fourdan <ofourdan@redhat.com>
Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2265>
2026-07-29 10:09:46 +02:00

175 lines
5.5 KiB
Python

# SPDX-License-Identifier: MIT
#
# Security tests for fb (software) font rendering vulnerabilities.
import os
import tempfile
import time
import pytest
from proto import pcf, x11
from xclient import X11Error
class TestFbGlyphBlt:
"""Tests for fbPolyGlyphBlt/fbImageGlyphBlt with malicious fonts.
The GLYPHWIDTHPIXELS and GLYPHHEIGHTPIXELS macros compute glyph
dimensions from signed INT16 fields. A crafted PCF font can
produce negative results (e.g. rightSideBearing < leftSideBearing
or descent negative enough to underflow ascent + descent).
Negative gWidth/gHeight passes the ``if (gWidth && gHeight)``
check (any non-zero value is truthy in C), then:
- fbGlyph8/16/32: ``while (height--)`` with negative height
wraps through all negative values before reaching zero,
running for ~2 billion iterations and writing out of bounds.
- memcpy-based paths: negative width cast to size_t wraps to
SIZE_MAX.
The fix changes the check to ``if (gWidth > 0 && gHeight > 0)``
to explicitly reject negative dimensions.
"""
@pytest.mark.asan
@pytest.mark.parametrize(
"max_lsb, max_rsb, max_ascent, max_descent, "
"glyph_lsb, glyph_rsb, glyph_ascent, glyph_descent",
[
pytest.param(
0,
8,
8,
0,
0,
8,
10,
-20,
id="negative_height",
),
pytest.param(
0,
8,
8,
0,
10,
0,
8,
0,
id="negative_width",
),
],
)
def test_negative_glyph_metrics(
self,
xserver,
xclient,
max_lsb,
max_rsb,
max_ascent,
max_descent,
glyph_lsb,
glyph_rsb,
glyph_ascent,
glyph_descent,
):
"""
Crafted PCF font with negative per-glyph dimensions exercising
the fb software rendering path (Xvfb, no glamor).
Without the fix, negative gWidth or gHeight passes the
``if (gWidth && gHeight)`` truthiness check in fbPolyGlyphBlt
and fbImageGlyphBlt, leading to integer wrapping in
downstream loops and out-of-bounds memory access.
"""
font_data, xlfd = pcf.build_evil_pcf(
max_lsb=max_lsb,
max_rsb=max_rsb,
max_ascent=max_ascent,
max_descent=max_descent,
glyph_lsb=glyph_lsb,
glyph_rsb=glyph_rsb,
glyph_ascent=glyph_ascent,
glyph_descent=glyph_descent,
char_code=0x21,
)
with tempfile.TemporaryDirectory(prefix="pyxtest-font-") as font_dir:
font_path = os.path.join(font_dir, "evil.pcf")
with open(font_path, "wb") as f:
f.write(font_data)
fonts_dir_path = os.path.join(font_dir, "fonts.dir")
with open(fonts_dir_path, "w") as f:
f.write("1\n")
f.write(f"evil.pcf {xlfd}\n")
xclient.send_request(x11.SetFontPathRequest(paths=[font_dir, "built-ins"]))
time.sleep(0.5)
for r in xclient.flush_responses(timeout=2.0):
if isinstance(r, X11Error):
pytest.skip(f"SetFontPath failed (error {r.error_code})")
fid = xclient.alloc_id()
xclient.send_request(x11.OpenFontRequest(fid=fid, name=xlfd))
time.sleep(0.5)
for r in xclient.flush_responses(timeout=2.0):
if isinstance(r, X11Error):
pytest.skip(f"OpenFont failed (error {r.error_code})")
pix_id = xclient.alloc_id()
xclient.send_request(
x11.CreatePixmapRequest(
pid=pix_id,
drawable=xclient.root_window,
width=64,
height=64,
depth=xclient.root_depth,
)
)
gc_id = xclient.alloc_id()
xclient.send_request(
x11.CreateGCRequest(
cid=gc_id,
drawable=pix_id,
values={x11.CreateGCRequest.GCFont: fid},
)
)
time.sleep(0.5)
for r in xclient.flush_responses(timeout=2.0):
if isinstance(r, X11Error):
pytest.skip(f"CreatePixmap/CreateGC failed (error {r.error_code})")
# PolyText8 exercises fbPolyGlyphBlt
xclient.send_request(
x11.PolyText8Request(
drawable=pix_id,
gc=gc_id,
x=10,
y=30,
string="!",
)
)
time.sleep(0.5)
assert xserver.is_alive, (
"Server crashed - fb glyph rendering with negative per-glyph metrics"
)
# ImageText8 exercises fbImageGlyphBlt
xclient.send_request(
x11.ImageText8Request(
drawable=pix_id,
gc=gc_id,
x=10,
y=30,
string="!",
)
)
time.sleep(0.5)
assert xserver.is_alive, (
"Server crashed - fb image glyph rendering with "
"negative per-glyph metrics"
)