SPTM/MIE deep dive: sptm.t8160 820.22 -> 820.40.24 (+64 KiB)

- docs/SPTM-MIE-27.2.md: separate SPTM firmware changed (kernel __DATA_SPTM
  is not the monitor); new DART GAPF race, GMMU TLBI verification, iommu
  refcount, Dekker locks, hibernation handoff/latch checks; TXM build-only;
  Exclave +229 KB; MIE SK tag-page check unchanged (210 insns, relocated)
- candidate directions: DART unganged-GAPF race, hibernation handoff page
  count, SK tag-page aliasing, dangling tag storage; no working bypass
- correct the earlier claim that SPTM was unchanged
- tools: fw_diff, string_xref, func_string_diff
This commit is contained in:
JBJF committed 2026-10-31 17:14:56 +08:00
1 parent 300d4805be
commit 09a02858f6
4 files changed
+172 -51

No files matched your search

+153
View File
@@ -0,0 +1,153 @@
# 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`. These are new/edited DART
invalidation paths, not present in the 27.0.1 image.
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. **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.
2. **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.
3. **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.
4. **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.
## Reproduce
```bash
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
```