Files
cinder27/docs/SPTM-MIE-27.2.md
T

12 KiB
Raw Blame History

SPTM / MIE deep dive — iOS 27.2 (24B119) vs 27.0.1 (24A446)

This is the part that the kernelcache alone cannot answer. SPTM is a separate firmware image in the IPSW (Firmware/sptm.t8160.release.im4p), and it did change in 27.2. The earlier note that "SPTM is unchanged" was based only on the kernel's __DATA_SPTM segment and was wrong: that segment is identical, but the SPTM monitor itself is not.

Component inventory

Component 27.0.1 27.2 Change
sptm.t8160 1,261,600 B · SPTM-820.0.22 1,327,136 B · SPTM-820.40.24 +65,536 B
txm.iphoneos 557,088 B · Image4 374~7022 557,088 B · Image4 374~8637 build-only
exclavecore.t8160 33,259,754 B 33,489,130 B +229,376 B
kernel __DATA_SPTM 311,296 B · sha f4bc1e5e… 311,296 B · sha f4bc1e5e… identical
kernel MTE strings VM_KERN_MEMORY_MTAG, taggable pages same unchanged

SPTM segment sizes:

Segment 27.0.1 27.2
__TEXT_EXEC 425,984 425,984
__LATE_CONST 524,288 589,824 (+65,536)
__TEXT / __DATA / __BOOTDATA 98,304 / 65,536 / 98,304 same

The +64 KiB is entirely in __LATE_CONST; the executable text kept its size but its hash changed (347aa218… → 064c286f…), so functions were edited/relocated.

What 27.2 adds to SPTM

String diff (32 new, 7 removed) is dominated by IOMMU/DART and hibernation hardening — none of it is named in the 162-CVE advisory:

  • VIOLATION_T8110_DART_UNGANG_GAPF_RACE
  • %s: %s: iommu_read_refcnt (0x%hhx) > read_refcnt(0x%x) for FTE %p
  • %s: Failed to verify TLBI
  • %s: The GMMU TLBI verification register should be 8B-aligned
  • gmmu-tlbi-verification-reg / gmmu-tlbi-verification-bit
  • %s: UAT Dekker gate acquisition timed out after %llu ns
  • %s: UAT Dekker lock acquisition timed out after %llu ns
  • %s: dart %p (%s:%u): relaxed_rw_protections and allow_pte_remap are not supported together
  • %s: dart %p (%s:%u): Inconsistent unganged clock protection configuration
  • VIOLATION_NVME_ILLEGAL_HIBERNATION_QUEUE_LATCH
  • hib_header_copy->handoffPageCount < HIB_HANDOFF_PAGECOUNT_LIMIT
  • sptm_allow_vm_isa_internal_guests, sptm_is_iuou_or_iuos_device
  • SPTM-820.40.24|2026-10-03:14:50:35

The TLBI string has five code xrefs in 27.2, at functions starting near 0xfffffff0270c7aac, 0xfffffff0270c85f8, 0xfffffff0270c987c, 0xfffffff0270cacf4 and 0xfffffff027109940.

Function-level alignment makes the change concrete. The first TLBI function (0xfffffff0270c7aac, 369 instructions) matches a 264-instruction function at 0xfffffff0270b7a90 in 27.0.1 (prefix similarity 0.90). The 27.2 version inserts a block that:

  1. reads DAIF and masks interrupts (msr DAIFSet, #0x7, dsb sy);
  2. reads CNTVCTSS_EL0 and CNTFRQ_EL0 and computes a deadline;
  3. loads a timeout constant (0x3d40 = 15680 units) and the TLBI verification register address;
  4. polls/validates the GMMU TLBI completion, and only then continues.

The 27.0.1 function issues the invalidation and proceeds without that wait. This is not a relocated instruction; it is an added completion check.

The DART violation names are not referenced by a direct adr/adrp+add — they are indexed through a violation table, so their exact raiser is a table lookup. The name itself is the evidence that 27.2 adds a check for the T8110 DART "ungang GAPF" race.

MIE / MTE surface

MIE is not a single binary. The relevant pieces are:

  • SPTM: manages tag pages and checks MTE tag page containing SK tags is mapped. The function is at 0xfffffff0270c6320 in 27.0.1 and 0xfffffff0270d7028 in 27.2; both are exactly 210 instructions, same prologue and same range-check shape (subs + ccmp against two globals). It was relocated, not rewritten.
  • Kernel: the MTE page allocator (VM_KERN_MEMORY_MTAG, taggable pages, MTE fill thread) has the same strings in both builds; no new MTE logic appears in the 62 new kernel strings.
  • Exclave core: carries the PMM tag machinery (pmmtag_l4_decode, Conclave MTE tag check fault, shared-memory tags), and grew by 229,376 B. That is a separate firmware update; no CVE in the advisory names it.

So the MIE policy surface in SPTM and XNU is unchanged in 27.2. The only MIE-adjacent code that changed is inside Exclave, which is a separate domain.

Related SPTM strings that were already present in 27.0.1 and remain:

  • %s: Dangling tag storage reference from previous mapping attempt
  • %s: Found SK-owned memory in non-wired memory
  • %s: dart %p (%s:%u): power down not permitted as SK still using the DART

The dangling-tag check is a real state machine: a previous tag-storage mapping attempt can leave a reference that SPTM detects later. It exists in both builds, so it is not a 27.2 fix; it is an audit target.

Why Titan does not transfer

Titan (iOS 16.1–17.4b3, A14–A17) is not an SPTM logic bug. It uses:

  1. AGX coprocessor code execution to overwrite the AGX power thread state;
  2. the AGX hibernation context to restore a attacker-supplied TTBR1_EL1 and a second ROP stack (microPPL bypass);
  3. a self-referencing page-table entry to map and write any physical page, defeating PPL/SPTM.

Apple patched the AGX thread-state PAC in 18.1b4 and the TTBR restore in 17.4b4. The 27.2 SPTM diff is not a return of that bug: it is DART/hibernation hardening inside the monitor. A 27.2-era bypass would need a new primitive that can either (a) make SPTM map a page table it does not protect, or (b) corrupt the tag/SK metadata that SPTM trusts. There is no such primitive in the diff.

Candidate bypass directions (hypotheses, not PoCs)

These are grounded in the 27.2 changes and the unchanged MIE checks. None is a working exploit; each needs a reachable primitive that the firmware diff does not supply.

  1. GMMU TLBI completion race (strongest static lead). 27.0.1 lacks the completion wait that 27.2 adds to the DART invalidation path. A DMA-capable device whose translation is invalidated can keep using a stale GMMU entry if the invalidation has not completed; the stale entry is a DMA read/write to a page that SPTM has already unmapped or repurposed. This is an IOMMU bypass, not a page-table permission bypass, but it defeats the same isolation goal. Reachability requires a userclient that owns DART mappings and can keep DMA in flight across an unmap — the next thing to test on hardware.
  2. DART unganged-GAPF race. 27.2 adds a violation for a race between unganging a DART and a page-table remap. On 27.0.1 the race can leave a stale DART translation if a mapping is unganged while a PTE is remapped. A stale IOMMU translation is a DMA read/write primitive to memory SPTM considers protected. Reachability: a userclient that owns DART mappings (IOGPU, display, NVMe, PCIe). This is the closest analogue to a "SPTM bypass", but it is an IOMMU bypass, not a page-table permission bypass.
  3. Hibernation handoff page count. 27.2 adds hib_header_copy->handoffPageCount < HIB_HANDOFF_PAGECOUNT_LIMIT and the NVMe hibernation queue latch violation. On 27.0.1 a corrupted hibernation header could make SPTM restore from a handoff page it did not size-check. Reachability: modifying the hibernation image or the NVMe queue, not an app.
  4. SK tag-page aliasing. The SPTM check only rejects a tag page whose physical address falls in the [start,end) range loaded from two globals. A mapping that aliases the same physical tag page at a different PA, or a remap that changes the range after the check, would let user-controlled tags cover SK memory. That is an MIE bypass: forge the tag for an SK page, then read/write it. Reachability: a kernel primitive that maps tag storage with a user-influenced PA; not present in the 27.2 diff.
  5. Dangling tag-storage reference. SPTM already detects a dangling reference from a previous mapping attempt. A race between unmap and remap of tag storage could leave the stale reference usable; the freed tag page could be reallocated as a user tag page. Same MIE impact as (3).

Verdict

  • SPTM did change: 820.0.22 → 820.40.24, +64 KiB, DART/TLBI/hibernation hardening. The kernel-side __DATA_SPTM being identical is not evidence that SPTM is unchanged.
  • No SPTM/MIE bypass is named in the 27.2 advisory, and no page-table permission check was removed or weakened in the diff. The changes add checks.
  • MIE policy in SPTM/XNU is unchanged; Exclave changed separately.
  • A working bypass was not produced. The candidate paths above need a reachable primitive (DART userclient, tag-storage mapping, or hibernation image control) and a device session; writing one blind would be guesswork.

Verification plan — GMMU TLBI completion race

The static delta is real, but exploitability depends on keeping a DART client's DMA in flight across an unmap. The test is hardware-only; it is written down here so the next session does not have to rediscover the shape.

Target: a disposable 27.0.1 device (vulnerable) and a 27.2 device (control).

Primitive needed: an IOGPU userclient plus IOSurface/metal command buffers. The kernel DART and IOGPU kexts are unchanged in 27.2 (IOGPU only gained a systemPagingOff() timeout diagnostic), so the user-reachable map/unmap path is the same on both builds; the fix lives inside SPTM.

Steps:

  1. Allocate a page-aligned IOSurface S, fill it with a canary, map it through IOGPU, and record its physical pages if the API exposes them.
  2. Submit a long-running GPU write into S (a compute shader that keeps writing a pattern for several milliseconds).
  3. While the GPU work is in flight, unmap/free S through IOGPU so the DART entry is invalidated. On 27.0.1 the invalidation is not completion-checked.
  4. Immediately allocate a fresh page P (user or kernel object) and fill it with a different canary. If the GPU's writes land in P, the GMMU entry survived the unmap: a stale DMA write primitive.
  5. Escalate only after step 4 is observed: reuse P as a kernel object with a pointer field and use the GPU write to corrupt it.

Success criterion: a write to P after S was unmapped, reproducible on 27.0.1 and absent on 27.2. A GPU timeout or a single crash is not success; the canary difference is.

Safety: run with the phone's panic log enabled, one stage at a time, and stop on the first unexpected kernel panic. The harness must journal the stage before the unmap (same pattern as Cinder27's GuardJournal).

Verification plan — MIE tag storage

The SPTM MTE tag page containing SK tags check is unchanged, so a bypass here would be a new bug, not a 27.2 regression. It needs a kernel primitive that maps tag storage with a caller-influenced physical address (or a dangling tag-storage reference). The 27.2 diff does not expose such a primitive. The audit order is:

  1. Find the kernel callers of the SPTM tag-storage map/unmap operations (the kernel MTE allocator, VM_KERN_MEMORY_MTAG).
  2. Check whether the PA is derived from a user-controlled IOSurface/descriptor.
  3. If yes, attempt an alias (map the same PA at a different address) and see whether the SPTM range check still rejects it.
  4. If the check is bypassed, forge the tag for an SK page and read/write it.

Until step 1 yields a user-influenced PA, this remains a hypothesis.

Reproduce

python3 tools/ipsw/remote_ipsw.py extract <url> sptm work/fw/27.2-sptm.im4p
python3 tools/ipsw/ipswlib.py kernel ...          # IM4P/LZFSE decode
python3 tools/string_xref.py work/fw/27.2-sptm.bin "Failed to verify TLBI"
python3 tools/fw_diff.py work/fw/27.0.1-sptm.bin work/fw/27.2-sptm.bin