mirror of
https://gitlab.freedesktop.org/xorg/xserver.git
synced 2026-06-07 11:08:21 +02:00
Add ListFonts, SetFontPath, and GetFontPath protocol builders to proto/x11.py and a regression test that reproduces the doListFontsAndAliases stack buffer overflow. The test creates a temporary font directory with a fonts.alias file containing an alias whose target name is 400 bytes -- exceeding the old XLFDMAXFONTNAMELEN of 256 but under libXfont2's MAXFONTNAMELEN of 1024. It prepends this directory to the font path via SetFontPath, then sends ListFonts matching the alias name. Without the fix, the server would copy the oversized resolved name into a 256-byte stack buffer, causing a stack buffer overflow. ZDI-CAN-30136 Assisted-by: Claude:claude-opus-4-6 Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2228>
92 lines
3.7 KiB
Python
92 lines
3.7 KiB
Python
# SPDX-License-Identifier: MIT
|
|
#
|
|
# Security tests for font alias handling vulnerabilities.
|
|
|
|
import os
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from proto import x11
|
|
from xclient import X11Reply
|
|
|
|
|
|
class TestFontAliasOverflow:
|
|
"""Tests for doListFontsAndAliases stack buffer overflow via long alias."""
|
|
|
|
@pytest.mark.asan
|
|
def test_list_fonts_long_alias_overflow(self, xserver, xclient, tmp_path):
|
|
"""
|
|
ZDI-CAN-30136: doListFontsAndAliases copies the resolved alias
|
|
target from libXfont2 into tmp_pattern[] and c->current.pattern[],
|
|
both sized XLFDMAXFONTNAMELEN. The server defined
|
|
XLFDMAXFONTNAMELEN as 256, but libXfont2 allows alias targets up
|
|
to MAXFONTNAMELEN (1024) bytes in fonts.alias files. A
|
|
fonts.alias with a target name between 257 and 1023 bytes caused
|
|
a stack buffer overflow when the alias was resolved via
|
|
ListFonts.
|
|
|
|
Attack:
|
|
1. Create a font directory with fonts.alias containing an alias
|
|
whose target name exceeds the old 256-byte buffer (but stays
|
|
under 1024 to pass libXfont2 validation).
|
|
2. SetFontPath to include this directory.
|
|
3. ListFonts with a pattern matching the alias name.
|
|
4. Server copies oversized resolved name into the undersized
|
|
stack and struct buffers -- stack buffer overflow.
|
|
|
|
Fixed by increasing XLFDMAXFONTNAMELEN to 1024 to match
|
|
libXfont2's MAXFONTNAMELEN.
|
|
"""
|
|
# The old XLFDMAXFONTNAMELEN was 256, now 1024
|
|
# MAXFONTNAMELEN in libXfont2 is 1024
|
|
# Use a target length > 256 but < 1024 to trigger the old bug.
|
|
# The overflow must be large enough to clobber the saved return
|
|
# address on the stack; 256 + 400 = 656 bytes overflows 400
|
|
# bytes past the tmp_pattern[256] buffer which reliably reaches
|
|
# the saved RIP and crashes the server.
|
|
target_len = 656
|
|
alias_name = "pwn"
|
|
|
|
# Step 1: Create evil font directory with long alias target
|
|
evil_dir = str(tmp_path / "evilfonts")
|
|
os.makedirs(evil_dir)
|
|
|
|
# fonts.dir (empty -- 0 fonts, required for FPE init)
|
|
with open(os.path.join(evil_dir, "fonts.dir"), "w") as f:
|
|
f.write("0\n")
|
|
|
|
# fonts.alias with oversized target name
|
|
# Use XLFD-like format starting with '-' so the FPE recognizes it
|
|
long_target = "-" + "A" * (target_len - 1)
|
|
with open(os.path.join(evil_dir, "fonts.alias"), "w") as f:
|
|
f.write(f"{alias_name} {long_target}\n")
|
|
|
|
# Step 2: Get current font path so we can restore it later
|
|
req = x11.GetFontPathRequest()
|
|
xclient.send_request(req.to_bytes())
|
|
resp = xclient.recv_response(timeout=5.0)
|
|
assert isinstance(resp, X11Reply), "GetFontPath failed"
|
|
original_paths = x11.GetFontPathReply.from_reply(resp.data).paths
|
|
|
|
# Step 3: Set font path to include evil directory first
|
|
new_paths = [evil_dir] + original_paths
|
|
req = x11.SetFontPathRequest(paths=new_paths)
|
|
xclient.send_request(req.to_bytes())
|
|
xclient.flush_responses(timeout=1.0)
|
|
|
|
# Step 4: ListFonts with pattern matching the alias name.
|
|
# This triggers doListFontsAndAliases which resolves the alias
|
|
# and copies the oversized target into the stack buffer.
|
|
req = x11.ListFontsRequest(pattern=alias_name, max_names=10)
|
|
xclient.send_request(req.to_bytes())
|
|
time.sleep(0.5)
|
|
|
|
assert xserver.is_alive, (
|
|
"Server crashed - font alias stack buffer overflow (ZDI-CAN-30136)"
|
|
)
|
|
|
|
# Step 5: Restore original font path
|
|
req = x11.SetFontPathRequest(paths=original_paths)
|
|
xclient.send_request(req.to_bytes())
|
|
xclient.flush_responses(timeout=1.0)
|