mirror of
https://github.com/X11Libre/xserver.git
synced 2026-10-29 02:31:18 +00:00
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>
175 lines
5.5 KiB
Python
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"
|
|
)
|