SPTM M5 boot bring-up: findings 23–28 + the H1 GENTER probe - #1
Open
qaustria wants to merge 30 commits into
Open
SPTM M5 boot bring-up: findings 23–28 + the H1 GENTER probe#1qaustria wants to merge 30 commits into
qaustria wants to merge 30 commits into
Conversation
Recover the frame-type, violation-code, domain and dispatch-state tables directly from the SPTM payloads rather than transcribing them by hand, and pin the generated header to a named build. - analysis/sptm/extract-*.py produce the CSVs; gen-frame-types.py emits asm/sptm.h from them and --check re-verifies the header against its CSV - sptm-decode.py turns a raw SPTM violation into a named diagnosis; test-sptm-decode.py covers it - check-header-syntax.sh confirms the header parses and its compile-time guards hold Firmware drift is real and mid-table: 25B78 (26.1) and 25C56 (26.2) are byte-identical, but IDs renumber between 26.2 and 26.5 -- VIOLATION_ILLEGAL_DISPATCH_PERMISSION is 41 on 25B78 and 43 on 25F71. Hence the per-build CSVs and the --check gate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Measured against Apple's own kernelcache.release.mac17g (25B78, t8142). kmutil emits fourteen Image4 tags -- lowest-VA, entry point, then (offset,size) pairs for ro, rs, rx, bx, rw, le. Each pair is not the extent of one named segment: it runs from an anchor segment to the start of the next anchor, absorbing every segment in between. __TEXT ro anchor __TEXT_BOOT_EXEC bx anchor __DATA_SPTM rs anchor __PRELINK_INFO rw anchor __TEXT_EXEC rx anchor __LINKEDIT le anchor __PRELINK_TEXT, __DATA_CONST and __DATA are interior -- never named, never separately labelled. The partition is exact: ro closes on 0x18fc000 (= __DATA_SPTM's fileoff) and le ends at 0x7354000 (= the file length). Consequences: - check-bootkc.py gains five invariants: canonical VMA order, filesize == vmsize per segment, VMA and file contiguity, all six anchors present, anchors ascending in role order. 27/27 on Apple's reference. - Those invariants catch a real defect the previous 22 could not see. Our packed m1n1 orders segments __TEXT __TEXT_EXEC __DATA_CONST __DATA PYLD __TEXT_BOOT_EXEC __DATA_SPTM __LINKEDIT, which places read-only data inside the rx span and yields two negative-length spans. Four correct failures. - pack-bootkc.py now refuses to write an image whose final VMA order is wrong instead of emitting an incoherent one. docs/bootkc-packaging.md's old segment table was wrong twice over: it mislabelled the roles (__DATA_CONST as ro, __DATA as rs, __DATA_SPTM as an unnamed SPTM window) and its vmsizes came from a different kernelcache than the one it named. Replaced with today's measurement and a Correction note rather than silently swapping the numbers. README findings 20 and 21: the span partition with its movk evidence, and the rusch95/asahi_neo XNU-shim prior art -- link a shim into Apple's real kernelcache, let XNU bring SPTM up normally, intercept late. That removes m1n1 from the critical path and dissolves the iBoot-validation and SPTM-domain-1-registration coin flips. It does not solve the late intercept, the t8142 device tree, or drivers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Renumber the surviving patches and drop the ones the SPTM findings invalidated. 0004 now carries the XNU-shim-shaped boot handoff. Note that nothing under build/m1n1/ is touched, here or anywhere else in this tree. Upstream m1n1's AGENTS.md forbids AI/LLM contributions outright (https://asahilinux.org/slop/), so the m1n1 side stays a human-authored patch series applied by script; build/ is gitignored. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two bugs, both of which stopped the script dead on a perfectly good
installer.
1. The guard read CFBundleShortVersionString, which is the installer
app's own version sequence and not the macOS version it installs --
the 26.1 installer reports "21.1.01", so the script rejected it. Read
DTPlatformVersion for the marketing version and, authoritatively,
Assets.0.Build from the asset manifest inside SharedSupport.dmg. The
downloaded installer verifies as build 25B78, macOS 26.1, which is
what the pinned SPTM tables describe.
2. startosinstall on Tahoe gates --volume off when run from a booted
macOS. The flag is still compiled in -- the binary contains
"Usage: startosinstall --volume <target volume path>" -- but it is
omitted from the runtime usage text and passing it makes the tool
print usage and exit 0, installing nothing. Probe the usage text and,
when the flag is missing, fall back to the graphical installer, which
still ships its destination pane ("Select the disk where you want to
install macOS.", IATargetSelectItem in IA.bundle).
--eraseinstall is explicitly called out as not a substitute:
--preservecontainer spares the other volumes in the container, not
the system volume being erased.
Also gitignore fw3/ -- 156 MB of extracted Apple firmware payloads
(sptm.t8142.release.im4p, kernelcache.release.mac17g and friends) whose
redistribution status is unclear. They are reproducible from the IPSW.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
macOS republishes the ADT that iBoot hands XNU as the IODeviceTree
registry plane, so the real hardware description for this exact board is
readable without unpacking DeviceTree.<board>.im4p out of an IPSW and
without guessing which variant applies. scripts/dump-adt.sh captures it.
Measured, not assumed:
identity J704AP / Mac17,2, chip-id 0x8142, board-id 0x22,
arm-io,t8142, production-cert 1, debug-enabled 0
arm-io physical = reg_base + 0x2_1000_0000, derived from uart0
where the ADT gives both the raw reg and the translated
IODeviceMemory address
DRAM base 0x100_0000_0000, size 0x4_0000_0000 (16 GiB)
UART0 0x3_A520_0000, 16 KiB, uart-1,samsung, IRQ 0x4cf,
carries boot-console
AIC 0x3_8100_0000, 0x1cc000, #main-cpus 10
CPUs 6xE (reg 0x0-0x5) + 4xP (reg 0x100-0x103), apple,sawtooth
Two of these change the plan.
DRAM base sits at 1 TiB, far above the M1 generation's 0x8_0000_0000.
Early MMU bringup cannot assume a 32- or 36-bit physical window.
The interrupt controller reports compatible "aic,3". Linux's
irq-apple-aic binds apple,aic and apple,aic2 only; there is no aic3
support upstream and no public register documentation. AIC2 was already
a substantial rework of AIC1, so a third revision should be budgeted as
real reverse engineering rather than a compatible-string addition. It is
on the critical path -- unlike a GPU or display driver, nothing boots
without interrupts.
The one piece of good news is uart-1,samsung: the same core Apple has
shipped since the M1, already driven by both m1n1 and Linux. Serial is
the one bringup step where we inherit working code.
Privacy: the full dump carries the ECID, serial numbers, MAC addresses
and platform UUID, so adt-full.{txt,plist} are gitignored and stay
machine-private. dump-adt.sh scrubs those properties from the broken-out
node files and exits non-zero if any survive; only the scrubbed files
and the derived peripheral list are committed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The span tables in README.md and docs/bootkc-packaging.md carried numbers from a restore-staged/ path that does not exist on disk. The original figures were right; a previous "correction" broke them. Re-measured against the BootKC this machine actually boots, cited by sha256 rather than by path: 120,668,160 bytes, ro closing on __DATA_SPTM at 0x1808000, le ending exactly at 0x7314000. Paths go stale between reboots and installs. Hashes do not. Adds HANDOFF.md so another agent can pick this up cold: the hard constraints (no m1n1 edits, no credentials, primary volume group stays at Full Security), the state of the SPTM research, and the two things found today -- kernel.release.t8142 already ships on the machine, and the configure-boot validation experiment needs no KDK. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Apple never shipped a release KDK for 25B78, the build this M5 runs -- developer.apple.com has only the 26.1 betas. But kmutil's KDK requirement is a directory probe, not a signature check, and its os_log names what it probes for: a SystemVersion.plist reporting the right build, System/Library/Kernels, and System.kext's pseudo-kext binaries. All of that ships in macOS except the pseudo-kext executables, which is what a KDK actually adds. build-bootkc-kmutil.sh assembles a synthetic KDK from the system's own files in a shadow root and drives kmutil through -R. Nothing outside the output dir is written, /Library/Developer is untouched, no sudo. The result confirms findings 19 and 20 by construction rather than by inference. All fourteen kc* tags land exactly on the span partition, kclo is the lowest vmaddr, kcep is the base of __TEXT_BOOT_EXEC, and the spans tile the file with no gaps. __DATA_SPTM comes out at 0x54000 -- byte-identical to Apple's shipping BootKC, since the monitor fixes that region's shape. verify-kc-tags.py re-derives the spans from the load commands and diffs them against the emitted tags, so this is checked per-run, not trusted from a table. Caveat recorded in both docs: the pseudo-kext binaries are stand-ins, not real KPI stubs, so only explicit-only collections build. Nothing here has been booted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The invariant checker was derived entirely from static RE of Apple's shipping kernelcache, before we could build a BootKC at all. It now passes all 22 invariants on an artifact produced by Apple's own packager -- an independent check on the checker. Two invariants are upgraded by this. The entry point landing inside __TEXT_BOOT_EXEC was inferred from one image; it is now confirmed as what kmutil deliberately emits. And __DATA_SPTM being 0x54000 of file-resident zeros in both images shows the region is sized by the monitor's interface rather than by the payload. Also updates HANDOFF.md: the KDK thread is closed, with the dead ends recorded so the next agent does not retry --allow-missing-kdk, --sdk or --kdk. Real KPI symbol stubs are now the open sub-thread, and the 1TR validation experiment is the top-ranked task. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
t8142-hardware.md called AIC3 the biggest driver gap on the critical path and assumed a third AIC revision would have to be reversed from scratch. It doesn't. Apple ships com.apple.driver.AppleInterruptControllerV3 unstripped -- 544 symbols, including the seven getAIC*Offset methods that compute register addresses. Each has a device-tree-override path and a built-in fallback; the fallback constants are the map. Independently, this board's own ADT node names every offset and stride. The two sources agree on every value, so the map is doubly sourced rather than inferred: globals at 0x0/0x4/0xc/0x14, IACK at 0x40000, per-die timestamps at 0x1000 stride 0x10, and a per-die interrupt block at 0x10000 stride 0x4a00 holding the config array plus set/clear/monitor bitmaps at +0x4400/+0x4600/+0x4800. Also records that there are two AIC3 sub-layouts, selected by a flag the driver reads rather than assumes -- t8142 is the compact one. A driver that hardcodes it will break on whatever ships the extended variant. Honest about what is still missing: the IACK read-acknowledge protocol and its return encoding, the IPI registers behind SoftwareInterruptTrigger, and CPU affinity. Nothing here has been read from live hardware. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AppleInterruptControllerV3 ships unstripped with its panic format
strings intact, and the format strings name the fields. Reading
handleInterrupt, SoftwareInterruptTrigger, enableVector,
disableVectorHard and start gives:
- AIC_IACK is read-to-acknowledge, no separate EOI. The returned word
packs the IRQ number in bits 0..11, the vector type in bits 16..18
(1 = hardware interrupt, 0 = drained) and the die in bits 24..26.
Apple's own panic string prints the extracted type alongside the raw
word, which is what pins the field widths.
- SW_INT_SET at +0x4000 and SW_INT_CLR at +0x4200 within the die
block. These have no ADT property and no accessor method; they are
hardcoded in SoftwareInterruptTrigger, which is why the first pass
missed them.
- INT_MASK_SET / INT_MASK_CLR are write-1-to-act. enableVector writes
a bare 1<<bit, not a read-modify-write.
- AIC_CAP0 bits 0..15 = interrupt count, bits 24..27 = dies minus one.
AIC_MAXNUMIRQ bits 0..15 = interrupts per die, bits 24..27 = max
dies (start panics above 8). AIC_GLBCFG bit 0 is the global enable.
Correct the per-die layout: the config array is 0x4000 bytes, not
0x4400. SoftwareInterruptTrigger puts SW_INT_SET at die_base + 0x4000,
which caps it. That makes it 4096 u32 entries, one per interrupt --
matching both the 0x200-byte bitmaps (4096 bits) and the 12-bit IRQ
field in the IACK word. Three independent numbers agreeing on 4096 is
the strongest internal check in the document.
Also record that t8142 must report AIC_REV & 0xff < 0x0b: above that the
driver requires an aic-numdies device-tree property this board does not
have, and start would panic. That one is an inference from absence, and
is flagged as such.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
README gets finding 23 (the AIC3 recovery, including the generalisable part: check llvm-nm and strings before budgeting an Apple block as a hard RE job -- two pessimistic estimates in this repo have now been wrong the same way). Correct the findings preamble, which claimed everything came from the 25F71 IPSW; 18-23 come from the target machine's own 25B78 firmware and live device tree. HANDOFF gets a new section 4.4 for AIC3, an updated critical-path note in 4.2, and a rewritten open-thread 3 -- the IACK protocol is no longer the ask; confirming AIC_REV < 0x0b is. Also a warning against moving the driver row in the progress table just because a register map exists: the column measures code that runs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every table in this repo that quotes an address, a size or a register
offset was transcribed by hand from a Mach-O, an IM4P or a device-tree
dump. check-doc-tables.py re-derives all of them and fails on any
disagreement -- 63 checks across README.md, docs/bootkc-packaging.md,
docs/aic3.md and docs/t8142-hardware.md.
It exists because a doc in this repo once got "corrected" with figures
from a path that did not exist, and the wrong numbers survived a commit.
The rule from that was "cite the artifact by hash, not by path"; this
enforces it.
It found one on its first run: README abbreviated the kernelcache sha256
as d457335f...d3214d, but the last six digits are 3d214d. Fixed.
What it checks:
- the sha256 and byte count the docs cite are this file's
- every row of the segment table against LC_SEGMENT_64
- both span tables, in both documents, against the anchor partition,
including that the spans tile the file with no gaps
- every AIC3 register offset, stride and mask against node-aic.txt,
and that the per-die block offsets are extint-baseaddress plus the
documented layout
- the hardware doc's identity, DRAM, UART and AIC figures, and that
the arm-io translation really is IODeviceMemory minus reg
Verified negatively as well as positively: perturbing a span end and a
globals-table offset produces exactly the two corresponding failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
check-doc-tables.py is written and green; the stale known-bad m1n1.bootkc.macho build output is gone. Record both as done rather than leaving them on the open list for the next session to re-derive. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ran the section 5 experiment from 1TR on the target machine. coih on the SPTMTest volume group went from absent to E6554B97...D07F6764CF. The payload was byte-identical to the kernelcache that already boots that volume group, so the only variable under test was the install path. nsih did NOT change, which is the more interesting half: a custom boot object is additive, not a replacement. coih is a separate slot alongside the normal next-stage object. Also records the ioreg chosen-node properties that name which manifest actually booted, and is explicit that this proves installation and not execution. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The section 5 run proved a custom boot object installs, not that it runs -- the payload was byte-identical to nsih, so both outcomes look the same from userspace. patch-version-string.py stamps a 3-byte marker into the timezone field of XNU's version string. Inert text, fixed width, file size unchanged, 6 bytes differ in total. uname -v after a boot then answers the question directly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The results volume is readable from every boot, so a script that writes next to itself removes the camera from the loop entirely -- and gives exact hashes rather than a picture of them. Baseline from Macintosh HD is in the commit-adjacent report: uname -v reads PDT, boot-manifest-hash matches that volume group's nsih. Also surfaces IODeviceTree boot-object-manifests, which enumerates every Image4 object iBoot loaded (krnl, sptm, trxm, ibot, lpol, dtre); an extra entry there would be a second signal that a custom object was consumed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
uname -v on SPTMTest reads "M5L 2025". The 6 bytes we changed are running on the machine. The stronger evidence is the device tree: IODeviceTree boot-object-manifests lists krnl on Macintosh HD and, on SPTMTest with coih set, no krnl at all -- a fuos entry in its place, every other tag unchanged. So "CustomKC or fuOS Image4 Hash" is literal. A custom boot object is loaded through the fuOS path and substitutes for the kernel object, which is why nsih stays put. sptm and trxm still load. We are handed control in the kernel's slot underneath the monitor -- the position finding 21's shim assumes and that dispatch_table[0][0]'s 0x02 mask requires. Payload was still Apple's kernel with 6 bytes changed, so this does not yet prove an arbitrary Mach-O of ours boots. Progress moved 4% -> 8%, with row 2 at 40% and reasoning recorded so it does not get rounded up. Boot reports archived under analysis/boot-reports, UUIDs and hostname scrubbed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The obvious next milestone -- print to the UART -- does not work on this machine. uart0 is real and Linux drives it, but a MacBook has no serial header; the debug UART needs a DCSD-class USB-C cable we do not have. docs/observability.md records that, because it silently invalidates the standard bare-metal first step. What we do have is the panic channel: NVRAM panicmedic-telemetry plus DiagnosticReports survive a reboot, and Macintosh HD is always bootable, so a failed probe can be recovered and decoded with sptm-decode.py. That makes failure informative, so probes should be built to fail usefully rather than to emit output. build-probe.py emits flat binaries for configure-boot --raw, loaded at the real BootKC __TEXT anchor. probe-spin is four bytes of `b .`: it touches no memory and assumes nothing about MMU state, x0, or exception level, so it cannot fault for a reason we invented. A silent hang is therefore the success case. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Outcome was neither predicted branch: no hang and no panic artifact, but a repeating black -> Apple logo (~3s) -> reset cycle. Two NVRAM variables turn out to record it. panicmedic-telemetry's event counter went 1 -> 5, so the failures were counted by firmware. boot-breadcrumbs holds iBoot's own progress trace as four-CC tags. Important limit on reading that trace, recorded so the next session does not over-claim: the final cycle is the SUCCESSFUL Macintosh HD boot, and it is byte-identical in shape to the failed ones. Breadcrumbs stop at lpol/ibot and never mention krnl, fuos, sptm or trxm on any boot. So the absence of fuos there is NOT evidence our object failed to load -- the channel simply does not reach that far. Whether our four bytes got the PC is still open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
spin never faults, so "rejected before handoff" and "ran until a watchdog fired" produce the same observable: a reset a few seconds in. brk #0 faults on its first instruction, so the two hypotheses have to diverge -- identical behaviour means control never reached our bytes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
probe-spin produced an ambiguous boot loop rather than the hoped-for black-screen hang. It perturbs the boot path but does not distinguish "our code ran then reset" from "the object was refused before first instruction". Record it as finding 26 and document the brk discriminator in the observability notes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add stage-1tr-probes.sh (builds the probes onto SPTMTest and drops the go/show helpers) plus run-1tr-brk.sh and show-1tr-log.sh. run-1tr-brk verifies the probe is exactly brk #0 (000020d4) and that the target is the expected SPTMTest volume group before invoking kmutil configure-boot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extracted j704/j813/j815 iBoot Stage 2 from the remote IPSW (analysis/iboot/, gitignored) and found the boot logo goes through DCP surface creation (display_dcp_create_surface CS_ARGB8888 -> send_swap -> enable_normal_mode), not a static linear scanout. Records the finding and the surface-address recovery paths; next step is disassembling the create_surface call site. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Traced iBoot's DCP display function (create_surface call at 0x120030 -> bl 0x16d1e4). The ARGB8888 surface backing base is computed at runtime (helpers bl 0x11c9b0 / 0x0004dc / 0x000500 into x21, then add x4,x21,x25 per plane) -- no framebuffer address is baked into the binary. Rules out a hardcodable static base and collapses the plan onto reading boot_args->Video at probe runtime, which is gated on the boot_args pointer (x0) at entry. Adds dependency-free RE tooling: iboot-xref.py (ADRP/ADR/ADD string-xref finder, resolves to file offsets without the load base) and iboot-dis.py (address-correct region disassembler over llvm-mc). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…instruction The brk #0 probe produced the identical signature to spin (black -> Apple logo ~3s -> loop, same lpol/ibot breadcrumbs, no panic artifact). A retired brk would have faulted synchronously and left a recoverable artifact; none appeared. Per the pre-registered discriminator, control never reached our bytes: a raw 4-byte object at 0xfffffe0007004000 is refused/reset before first-instruction retirement. Combined with finding 25 (a structurally-valid marked kernelcache DID run via the same coih install), the acceptance gate is structural: the boot object must be a valid BootKC/Mach-O, not a raw blob. The --raw probe line is exhausted; the next probe carries our code inside a real BootKC. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ow-up)
Raw objects are refused before execution (finding 27); a valid BootKC runs
(finding 25). scripts/patch-bootkc-entry.py isolates the container as the one
variable: it overwrites only the LC_UNIXTHREAD entry instruction (base of
__TEXT_BOOT_EXEC) of the pristine kernelcache with brk/spin/nop, same size,
every other byte Apple's. All three pass check-bootkc.py's 22 invariants.
entry-brk is the decisive contrast: raw brk left no panic; the same brk as a
valid BootKC's entry should fault and leave one. entry-nop is the control
(should still boot). Staged on the SPTMTest volume and wired into m5.sh as
install entry-{nop,brk,spin}. Plan in analysis/boot-reports/entry-patch-experiment.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Installing the entry-nop kernelcache (coih 48FEA9EC -> 323B8DAE) and booting SPTMTest brought up macOS normally, with no panic artifact. The control holds: patching the first executed instruction and reinstalling does not break the boot, so any brk/spin difference is the instruction, not the patch pipeline. Also confirms replacing Apple's `bti c` landing pad with nop raises no Branch-Target fault here, so a later brk fault will be the brk itself. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…xecutes The controlled pair is decisive: entry-nop and entry-brk are byte-identical except the single entry instruction, yet nop boots to macOS and brk does not (hang, no recoverable panic — the trap is too early for XNU's logger). iBoot gates on the image hash (coih, valid for both) and does not interpret instruction semantics, so the only stage that can turn those 4 bytes into different behaviour is the CPU executing the entry instruction. Counterpart to finding 27: a raw object is refused before execution; a structurally valid BootKC is not. The structural gate appears crossed. Confidence high (rests on the control, not one artifact) but not yet a direct positive readout. Next: corroborate with entry-spin, and begin rung 4 (pack m1n1 as a BootKC), now justified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds scripts/patch-bootkc-genter.py, the H1 probe emitter. It detours a late XNU symbol (default IOPlatformExpert::start, keeping the pacibsp landing pad at +0) and lays a save-x0..x17+x30 / set-x0,x16 / GENTER / restore / replay- displaced / branch-back trampoline into the __TEXT_BOOT_EXEC cave. Default call is (0x9,0x0) _sptm_cputrace_is_mode_supported (x16=0x900000000) — a pure predicate that writes no memory and mutates no SPTM state. Because the cave lives inside the blessed BootKC (domain 1), the GENTER fires from domain-1 territory; boot-survival alone reads out accept-vs-fault (no framebuffer needed). Verified without pushing another untested 121 MB artifact blindly: - assembler round-trips to the exact 25-instr design (frame balanced, one GENTER, displaced replayed before the branch, branch-back to start+8); - recovered a byte-exact pristine KC by inverting full-hook-detour.kc's two edits (hash d457335f… — confirms the detour transform is byte-exact); - built full-hook-genter.kc: passes all 30 check-bootkc.py invariants; - controlled pair proven — byte-identical to the proven-booting hook-detour KC except inside the cave, so any hardware divergence is due solely to the GENTER. Fixes a size-probe bug (cave_vm=0 tripped the branch-range check) and corrects the design note's movz x16,#0x9,lsl#32 hex (0xd2c00130, not 0xd2a00130). Also lands the supporting scripts (patch-bootkc-hook.py, patch-bootkc-paint.py) and five boot-report analysis notes that were untracked. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tlbi wrappers
The Linux MMU-port scaffold modelled TLB invalidation as a GENTER call
(sptm_broadcast_tlbi_{all,asid,va}), and the scope doc budgeted ~100 LoC to
reroute arch/arm64/include/asm/tlbflush.h through it. That call does not exist.
Evidence from the real M5 target firmware (25B78/t8142 BootKC, build/bootkc):
XNU's own __TEXT_EXEC contains 25 native `tlbi` instructions (VMALLE1IS/OS,
VALE1IS, VAE1OS, RVAE1IS) against only 2 GENTERs; subsys 0 has no TLBI dispatch
entry; the sole coupling is VIOLATION_POSSIBLE_PENDING_TLBI, a pending-TLBI
obligation the OS discharges with the native TLBI+DSB it already issues after a
pte update. So the governed OS invalidates the TLB natively — Linux keeps stock
flush_tlb_* unchanged.
Changes:
- remove the three sptm_broadcast_tlbi_* decls/stubs from asm/sptm.h and
mm/sptm.c; replace with a documentation block
- correct docs/linux-mmu-port-scope.md (TLBI section, tlbflush.h row, risk antiapplefox#1)
- README: mark the broadcast_tlbi TODO resolved
- analysis/boot-reports/finding-30-tlbi-is-native.md: full evidence + repro
Net: the port gets smaller and more correct — a ~100 LoC item deleted, not done.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
The SPTM-era M5 (T8142) boot-bring-up research line: reverse-engineering how iBoot
and the Secure Page Table Monitor gate the BootKC on Apple Silicon, and building the
tooling + probes needed to get a non-XNU kernel accepted. 29 commits of findings,
tooling, and analysis notes, culminating in the H1 GENTER probe.
All hardware experiments run in 1TR on an isolated
SPTMTestvolume group at FullSecurity;
Macintosh HDis never touched. Build artifacts (the 121 MB.kcfiles)are gitignored — only source, scripts, and analysis notes are here.
Highlights
H1 GENTER probe (this branch's headline,
f591774)scripts/patch-bootkc-genter.pydetours a late XNU symbol (defaultIOPlatformExpert::start, keeping thepacibsplanding pad) and issues one rawSPTM
GENTERfrom inside the blessed BootKC cave — the concrete test of whetherour detour inherits XNU's domain-1 authority (the "domain-1 launchpad" hypothesis).
(0x9,0x0) _sptm_cputrace_is_mode_supported— a pure predicate thatwrites no memory and mutates no SPTM state.
25-instruction design; a byte-exact pristine KC was recovered by inverting the
detour edits (hash matched); the built KC passes all 30
check-bootkc.pyinvariants; and it is byte-identical to the proven-booting detour KC except inside
the cave, making it a clean controlled experiment. Readout is by boot-survival —
no framebuffer needed.
Boot-object findings (24–28) — established that a modified boot object executes on
the M5 (
coihis settable), that raw/unblessed objects are refused before our firstinstruction, and that our patched instruction actually executes (entry-nop boots,
entry-brk hangs). Probe tooling:
patch-bootkc-entry.py,patch-bootkc-hook.py,patch-bootkc-paint.py, and 1TR staging/collection scripts.BootKC + packaging — the six SPTM role-spans are contiguous spans (not per-segment
labels); collections build with
kmutilwithout an Apple KDK;check-bootkc.pyvalidates 30 structural invariants;
pack-bootkc.py/ frame-type tooling.Interrupt + firmware RE — recovered the T8142 AIC3 register map and IACK/IPI
protocol (finding 23); extracted SPTM firmware tables for 25B78/25C56/25F71 with a
decoder (
sptm-decode.py) and its tests.Testing
check-bootkc.py,test-sptm-decode.py,check-frame-types.py, andcheck-doc-tables.pypass. Hardware probes are documented inanalysis/boot-reports/with their readout method (boot-survival triangulation). The H1 probe is built and
desk-verified but not yet run on hardware.
🤖 Generated with Claude Code