- New kern.ipc.lifecycle module: dead-name registration, dead-name send, notification receive, name reuse — single-threaded, safe on device - Device receipt (iPhone19,7, iOS 27.0.1, ad-hoc signed): request DEAD_NAME returns -300 (MIG_TYPE_ERROR); send -> MACH_SEND_INVALID_DEST; receive times out; 0/12,288 registrations armed in kern.ipc - macOS 27 sweep: 13 notification-type/right combinations, all -300 - kern.ipc now counts armed registrations and retracts the old 'survived' verdict when armed == 0; the request table is never armed - docs/KERNEL-RW-ASSESSMENT.md: 112507 marked closed for this app - app 2.2 build 5; 57 modules
157 lines
9.1 KiB
Markdown
157 lines
9.1 KiB
Markdown
# 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 | **Not reachable from the app.** Device receipt: 12,288 notification registrations, 0 armed, 12,288 refused with -300 (MIG_TYPE_ERROR). No request-table entry exists, so there is no stale index to consume. |
|
||
| 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 — reachability closed on the device (2026-10-31)
|
||
|
||
The staged lifecycle harness was run on the target device (iPhone19,7, iOS 27.0.1)
|
||
with an ad-hoc signed build, and the race module was re-run with an `armed` counter:
|
||
|
||
- `mach_port_request_notification(DEAD_NAME)` → **-300** (MIG_TYPE_ERROR), `prev = 0`.
|
||
- Send to the dead name → `MACH_SEND_INVALID_DEST`; receive → `MACH_RCV_TIMED_OUT`.
|
||
- `kern.ipc`: 12,288 registration attempts, **0 armed, 12,288 refused**.
|
||
- On macOS 27 the same call was swept over 13 combinations of notification type
|
||
(SEND_POSSIBLE / PORT_DESTROYED / NO_SENDERS / DEAD_NAME) and right layout
|
||
(receive-only, send+receive, send-only; notify port receive or send). Every
|
||
combination returned -300.
|
||
|
||
The kernel refuses to arm a request-table entry for this process. With no entry,
|
||
the stale-index consumers in `ipc_port.c` have nothing to consume: the race cannot
|
||
be driven from an unentitled third-party app, and the "survived" line from the old
|
||
harness was measuring port churn, not the bug. The published fix shape (NULL checks
|
||
on `ipr` / `table`) is consistent with a denial or null-dereference path, not with a
|
||
reclaimable object.
|
||
|
||
**Verdict: closed for this app.** The remaining hypothetical is a platform process
|
||
that is allowed to register notifications; that is outside Cinder27's reach.
|
||
|
||
### CVE-2026-112507 — the race itself
|
||
|
||
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**, and the one entry that had a theoretical UAF path (112507) is now closed
|
||
by device evidence: notification registration is refused, so the request table is
|
||
never armed. The maximum safe capability the lab 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.
|