Patch Comparison: Regular / Development / Jailbreak / Experimental
Application exception actions and X1 lowering (2026-11-06). SystemHook preserves the inherited X1
EXC_BAD_INSTRUCTIONaction when an application installs its own task exception ports. Other exception bits, non-X1 and remote-task requests are forwarded unchanged. This extends the existing spawn policy; no firmware patch, preset or system-library instruction is added. An app-scoped equivalent survived two cold starts, fold/unfold and background resume on an iOS 27.1 legacy-TXM guest after independent remap and Intents workarounds. Full SystemHook runtime deployment remains separate. See failure, policy and validation boundaries.
Legacy Duo TXM remap evidence (2026-11-06). A guest with Core Bundle and environment 2.15.2 still had the flashed 2.13.8 TXM receipt containing
precheck_admissionandtrustcache_bypass. A same-task fixed private 16 KiB main-executable remap returned success and RX/RX, whilevm_region_64found R/R and the subsequent call faulted. A bounded app-local RW-copy-then-RX workaround advanced execution. This does not test or contradict #831 on freshly restored guests. No patch or preset changes; see the deployment evidence and limits.
Watch audio budget runtime check (2026-10-31). A fresh Watch7,18 VM on 26.4 (23T240): native Resonate text-message alert completed with
BudgetNotAvailable = 0; explicit Playback-category AVAudioPlayer prepared, started and advanced on Speaker/PuffinOutput. HAL I/O ran on both paths. Untruncated live audiomxd captures show PID 110 throughout and none of the previous budget failures. The owner confirmed audible output. See budget runtime check.
Watch audio budget patch (2026-10-31). The
filesystem-exp-watch_audiopayload includes a MediaExperience budget patch:patchWatchAudioBudgetNOPs theCMSMDeviceState_UpdateDeviceClasstoken-mismatch branch in the arm64e cache so a watch VM takes MediaExperience's virtual-device budget exit, version-gated to watchOS 26.4 and reported as thefilesystem-exp-watch_audio.budget_device_classsub-record. Covered by an always-on synthetic discovery test plus fixture-gated real-image tests; its runtime effect on Playback media is confirmed on a 26.4 restore (host audibility confirmed; see the budget runtime check above). See watch audio.
Watch audio validated on a fresh restore (2026-10-31). A new VM restored from the latest bundle with the shipped
watchospayload produces audible system sounds (the charging chime and ringtones). A paired control restore withfilesystem-exp-watch_audioblocked produced no audio: the live HAL directory had no virtio sound plugin, AVAudioSession reported no output routes and zero rate/channels, and AVAudioPlayer of the built-in NanoRingtone3.caf failed to prepare or play. Together these attribute the speaker route to the payload. Playback-category media (music, text and notification tones) still depends on the MediaExperience power-budget fix. No code change. See watch audio.
Watch preset audio (2026-10-30). The standard
watchospreset now includes the validated watch audio payload, owned by the singlefilesystem-exp-watch_audiodeclaration. The separate experimental speaker preset has been removed. Local-speaker Playback sessions still fail MediaExperience's power budget, which this does not address. Existing guests need a restore; other presets and the remaining broad audio patches are unchanged. See watch audio.
HomeView distance simulation (2026-10-30, runtime validation pending).
dyld-exp-homeview_environment_serviceopens only BreadBoardServices' environment-service internal-install branch on the reviewed 27.2 userland. The preceding client entitlement check remains intact; no global internal build identity is changed. The reveal follows the native Platform getter's call and validates the service's separate rejection paths with Capstone. Writes use cache-page re-attestation and the existing undo log. The patch is off in every shipped preset and is admitted only for HomeView installs. The guest RPC uses the native environment request, near/increased detail and far/decreased detail. Native reset clears all environment overrides and requires explicit acknowledgement. Retail runtime probes confirmed the internal-build gate; no live distance override or patch deployment has been performed. See distance investigation. Offline validation passes against an APFS cache clone: unique reviewed gate, one instruction and one page hash changed, repeat is a no-op, and undo restores every cache file byte-identically. Finished bundle build/admission and 94 focused Xcode tests pass; the live Near/Far UI effect is still unverified. After user authorization, HomeView received this patch and guest environment2.15.0-local.b1ed0817. Native ABI validation was corrected to accept the protocol'sVv(oneway void) qualifier, with a focused regression test. The installed daemon now activates the service and fetches native detail as root. No distance override was written by the agent; the user will test Near/Far/reset.
HomeView guest-profile backend lookup (2026-10-30). The BreadBoard display exception clears its first assertion, but its native guest-profile fetch still fails with an NSError chain ending in sandbox restriction 159 for
com.apple.mobile.keybagd.UserManager.xpc. A mobile native probe with BreadBoard's relevant client entitlements reproduces it. Userprofilesd's donor sandbox lacks that lookup. Newsystem-userprofilesd-exp-keybag_lookupadds exactly that one exception to userprofilesd, preserving the native profile/persona APIs and authorizations; it never fabricates a profile. Selected only inhomeview, gated to 27.2, reversible from executable backup. Native setup bypass verification passed independently (NeedsSetup=false,CompletedInitialRun=true, withSetupDoneuntouched). Validated on the clean HomeView VM: native guest-profile retrieval succeeds, BreadBoard stays up, and Setup.app renders. Touch navigation works from the bypassed dashboard into World Clock and its city chooser.
HomeView native shell display startup (2026-10-30). The clean guest reaches vphoned and backboardd, but BreadBoard traps in
BKSDisplayServicesStart+436, at the nonzero display-service query status assertion, before drawing its UI. Its donor entitlements usetemporary-sandboxand lackcom.apple.backboard.display.servicesand the backboard HID/render lookups already repaired for Campo on the 26.4 kernel. Newsystem-breadboard-exp-mach_lookupreuses that scoped exception merger, preserves the rest of BreadBoard's entitlements, and keeps an executable backup for deselection. On only inhomeview, gated to 27.2; native setup still stays on. Validated: the display-service assertion is gone. Native startup also needs the userprofilesd lookup repair above. No new kernel or instruction patch.
HomeView bringup (2026-10-30). Adds the opt-in
system-homeview-exp-setup_bypass, off in every shipped preset, including the newhomeviewpreset. The existing mobile setup helper runs in an isolated HomeView mode, journals onlycom.apple.purplebuddy/ForceNoBuddy, verifies the native SetupAssistant gates, and restores the exact original key on deselection. No completion, activation or HomeKit state is fabricated.devicetree-cfw-homeview_touchenables DriverKit for the VM's USB HID touch screen on J490/J491 installed trees only. The existingdevicetree-cfw-tv_app_architecturealso copies thehomedonor's app architecture.system-tv-exp-ota_userlandis reused for HomeView's own arm64e full OTA over the HD restore base; Apple TV remote/setup adapters remain off. No new instruction replacement or kernel patch. Restore, native setup, bypass enable/disable, framebuffer and touch are now tested; see HomeView bringup.
Camera session teardown (2026-10-29). Source-only change in libcamfix. Session tracking uses
objc_storeWeakOrNiland a strong weak-load before weak-table insertion. It skips sessions already in deallocation. No firmware patch or preset changes. A small camera app reproduces the original abort on iOS 27.0.1 (24A446). See reproduction and crash stack.
Watch speaker audio (2026-10-28).
filesystem-exp-watch_audio, selected only bywatchos, installs the watchOS-retargeted virtio sound HAL plugin before the system image is sealed; native product 4017 for Watch7,18 selects the existing HALSPP tunings and PuffinOutput publishes the speaker port. The installer also rewrites the eightspeaker_*chainTypes fromclhstodfltinLibrary/Audio/Tunings/AID*/VAD/graph_configurations_halspp.plistthrough the confined file-edit path, retaining all graphs, AU tunings, volume commands and microphone entries. Native product selection and the graph rewrite alone were not enough (the generic playback handler still declined the route), so the payload also applies three playback-only VirtualAudio bypasses:patchSpeakerProtectionGate(handler: .playback),patchVolumeModePrecondition, and the fault-logged inactive-protection terminating call, each matched by source/log/mask or walker/epilogue control-flow anchors and each one four bytes. Mute, play-and-record, the other walker exceptions, raw tunings and microphone patches stay disabled. Verified on WatchAudioGraphTest: live executable text differs from stock only in the three intended instructions (twelve bytes total) and graph data only in the eight speaker chainTypes. Ringtone playback selects Speaker / PuffinOutput, starts HAL I/O, survives repeated stop/start, and keeps audiomxd at PID 110; the owner confirmed audible output after unmuting the Mac. Local-speaker Playback sessions still fail MediaExperience's power budget, which this does not address, and other session categories and long-duration playback are not covered. All 103 focused tests and full bundle build/admission passed. See watch audio.
Apple TV RootHide bootstrap and environment hooks (2026-10-26). No new declaration or instruction patch. An arm64e Apple TV now admits
system-launchdaemons-boot-environmentandsystem-launchd-boot-jetsam_panic_guard_bypass(FirmwareGuestPatchResolution.tvArm64eGuestPatches); both stay blocked intvosandtvos-4kand on the plain-arm64 HD. On TV the environment islaunchdhook-vphone.dylibandSystemHook-vphone.dylibalone, theirLC_BUILD_VERSIONrewritten to tvOS (MachOBuildPlatform) and re-signed at install, with/vh; turning it off removes them. The launchd patch is unchanged: ontv4k's tvOS 26.6 arm64e launchd it rewrotetbnz w0, #0, 0xdac4at0xDA68tob 0xdac4(anchorjetsam property category) and/vhwent in asLC_LOAD_WEAK_DYLIB. With both on,tv4kbooted to the home screen with SystemHook in every process and the vphoned RootHide root asVPHONE_JB_ROOT; with both off again launchd came back from its backup and the libraries and/vhwere removed. vphoned installs Irisin'sappletvos-arm64epackage as the RootHide bootstrap on any tvOS guest and refuses rootless there; both were validated live ontv4k. See tvOS support.
Apple TV remote and native Setup (2026-10-24). The current
tvos-4kpreset selectssystem-setupbackground-exp-pineboard_lookupon 27.2 and blockssystem-tv-exp-legacy_setup. The environment update restores the original CompanionServices flags from their backup, returning to the new Setup app. Live debugger inspection localized Select's failure: Hello's native focus target exists but is deferred; a directional press commits it, after which Select executes the ordinary Hello callback and opens Language.system-setup-exp-deferred_focusinstalls a tvOS dylib into Setup via/tf, preserving its original entitlements and a reversible backup. Its adapter uses native TV button-focus policy (0) only for an active key Setup window whose WaffleIron system has default deferral (3) and no explicit override. It observes scene/key/focus updates, preserves other overrides, and uses UIKit's own preferred-focus machinery and eligibility checks. Earlier focus-initialization prototypes expired after the native 60-second idle timer. A live policy-0 test retained focus past 80 seconds and Select entered the native country picker. The adapter changes no pairing, drawing, ScenePhase, window suppression or setup-completion state. This patch is selected only bytvos-4kon 27.2; legacy setup remains off. Installed cold-boot testing passes the first Select after more than 65 seconds idle at Hello, without directional priming or a debugger. Completed AX/button taps now survive panel focus loss; pending holds are cancelled and neither may cross a guest reconnection. The actual AX-button/queue harness covers this live-discovered delivery failure. See the input investigation. Remote clicks, holds, Home/Settings navigation and Control Center have passed live testing.Native Setup then reached manual activation and failed with -71136. The matching tvOS mobileactivationd retains the existing
should_hactivatelocal-state path, but the TV preset had excluded it. The new declarationsystem-mobileactivationd-exp-tv_activationreuses that unchanged patcher with an exact 27.2 applicability gate, selected only bytvos-4kand admitted only for arm64e Apple TV. The installer uses one activation declaration per device, retaining the existing backup/revert path. Installed native Manual setup now advances past activation to room choice and Terms & Conditions. Cold boot ofa4b9ffc4exposed a packaging failure: the final Setup bundle seal discarded the entitlements preserved bypatchMachO, causing native TCC Bluetooth usage-description crashes. Both applying the focus adapter and restoring its backup now preserve original entitlements through bundle sealing. Corrected bundleda14d7a4retains the full pristine entitlement set byte-identically and passes the idle/AX input test. After user-confirmed Terms, native Setup completes to Home with its own SetupDone/TVBuddy completion flags true and TVSetupVersion=15. The original VM is also updated, with its prior setup and data preserved. Navigation, Control Center, Siri search, Power sleep and TV wake pass; audio effects without active media remain unverified. A final read-only daemon correction uses TV version/completion policy instead of the iOS setup version; 376 logic checks pass. Its bundle9ffd2d6eis installed on both machines. Final cold boots report TV setup and current versions 15, pending=false, and no Setup process. The clone returns directly to Home; the original remains running with its data intact. Both receipts have zero pending changes and legacy setup off.
The iPhone17,3 presents its board (2026-10-25).
VPhoneGuestDevice.presentsBoardno longer excludes the iPhone17,3; only the XS Max keeps the shared tree. An iPhone17,3 guest now gets the separate installed tree and thedevicetree-cfw-ipad_*presentation fromDeviceTree.d47ap.im4p, as every other iPhone does, and skipspreboot-exp-devicetree_identity. No patch is new.ipad_identityis on instandard, so astandardiPhone17,3 guest now reportsiPhone17,3instead ofiPhone99,11. The catalog's release list keeps its iPhone17,3 check explicit (VPhoneFirmwareCatalog.pairings), so its pairings do not change. Checked on a newiPhone17,3 27.0.1 (24A446)guest, cloudOS 26.4 (23E5207q), plainstandard: the restore tree split offDeviceTree.vphone600ap.guest.im4p; the receipt records theipad_*patches andpreboot-cfw-devicetree_board_audio;device.inforeportsiPhone17,3at 430x932 @3x; Settings > About shows Model Name iPhone 16 and an Action Button row; the Camera app shows the .5/1x/2 lenses and the test pattern; with Location Services on,location.setreads back a fresh simulated fix andbluetoothd/locationdkeep their boot PIDs (no #438). Crash reports held only the documented restore-reboot panic andduetexpertddatavault crashes, plus oneNANDTaskSchedulerPAC kill in its Core Data failure logging (no ANS NAND on a VM), not on a device tree path.
Account prompt hooks are selectable (2026-10-25).
system-preferences-cfw-followup_groups(libprefsfix.dylib, Settings' CoreFollowUp groups) andsystem-apps-cfw-signin_sheets(libsigninfix.dylib, in-app sign-in sheets) were installed with the guest environment and could not be turned off. Both are now declarations, on instandardandexperimental, blocked in the TV and HomePod presets, which already block the environment.cfw installandcfw update-environmentinstall or remove each library per the plan, a VM with no plan keeps both, and the live environment sync no longer reinstalls one that was turned off. SystemHook skips a missing library, so removal takes effect on the next launch of Settings or the app. No binary or loader changes. Account daemons stay running either way; update and account daemons are disabled only by vphoned's service profile (service trimming).
CSSC kernel compatibility experiment (2026-10-25).
kernel-boot-cssc_emulationtargets iOS 27 with cloudOS 26.4. It reuses the kernel's undefined-instruction copyin, PAC-safe interrupt helpers and signed-state check/sign ABI. Only known EL0 scalar CSSC words are evaluated; unknown faults retain native triage. The single baseline-A64 payload occupies persistent kernel text; no userland/cache CSSC trampolines or writable code pages are introduced. Existing X1 PAuth/CPA lowering remains unchanged. Selected X1 boot patch failures abort installation, and an unresolved CSSC kernel handler aborts kernel preparation. TV and HomePod presets exclude this operation. SeeResearch/Guest/iphone_duo_bringup.mdfor the reveal procedure and signed-state handling.
HomePod local app installation (2026-10-23).
system-installd-exp-homepod_adhocis selected only byhomepod-minion HomePod 27. It loads a dedicated TVOS arm64e build of the existing MIS signature-option and MobileInstallation ad-hoc adapters into installd via/hm. Native MIS still validates the signature and produces its metadata; the verifier'sallowAdhocSigningswitch accepts the missing certificate. No embedded-profile bypass, device identity, profile scope or spawn hooks are compiled into this library. Opt-out restores installd's original backup before removing the alias and library. HomePod lacks misagent, so the test app uses the research tvOS SDK and a local signature with no profile. SeeResearch/Guest/xcode_install_signature_gate.mdfor validation. TempPod passes real Xcode installation, headless execution, source breakpoint, stepping, variable inspection and Objective-C expression evaluation. Live opt-out restores installd and launchd byte-identically to their original backups and removes the owned aliases/marker/libraries; the default preset has been restored. Full bundle admission, 77 selected Swift tests and the persona ABI/policy harness pass.
HomePod Xcode installation persona (2026-10-23). The existing
system-installcoordination_proxy-cfw-persona_lookupnow has a HomePod 27 deployment, selected byhomepod-mini. TempPod reproduced the iPhone's Protobox denial of the installation proxy's UserManager lookup. HomePod loads a dedicated TVOS arm64elibvphoneinstallpersona.dylibin launchd via the short/vpalias. It reuses the existing root-owned marker, exact service/path/signing-identity and kernel-validated audit-token checks. Other sandbox calls retain their arguments and native result. No spawn, jetsam or iPhone environment hooks are included. Opt-out restores launchd from its original backup before removing the marker, alias and library. Xcode installation, process launch and LLDB stack inspection pass; seeResearch/Guest/ios27_xcode_persona_lookup.md.
HomePod cryptex DDI staging (2026-10-23).
system-cryptexd-exp-homepod_stagingis a selectable HomePod 27 guest patch, enabled byhomepod-miniand excluded from the iOS/Apple TV presets. It injects the TVOS arm64elibvphonecryptexstaging.dylibinto cryptexd through a load command, preserving the daemon's entitlements and an original.bak. Deselecting restores the daemon before removing the library. Only failedF_SETPROTECTIONCLASScalls returningEPERMbecomeENOTSUP, the daemon's existing unsupported-filesystem fallback. Other fcntl calls tail-forward their complete ABI arguments. Apple ticket/signature/trust-cache/graft checks remain on the native path. DDI persistence and nonce persistence use Apple's until-reboot mode. The research SEP's digest-storage failure is tolerated only for error 104 in the cryptex POSIX domain and an info plist naming Apple's DDI,/System/Developer, and DeveloperModeRequired true; the preceding native signature lock is unchanged. This does not enable the broad iPhone environment or its display hooks. Validation and deployment results are recorded inResearch/Guest/developer_disk_images.md. TempPod confirmed the staging errno, then exposed an authenticated APFSclasmismatch (tvOS ticket0xf4, research kernel0xf2). The independentpreboot-homepod-exp-cryptex_classrepair sets or adds only the concrete uint32/product/cryptex1-product-classto0xf4, which the kernel's native Image4 runtime reads. It preserves equality verification and global product-id; Preboot backup composition makes it reversible. No kernel instruction is patched. Reveal/validation procedure is in the same research document. TempPod now reportsconnectedwith a compatible, usable 27B48 tvOS cryptex DDI. Device/process/app/file queries and Xcode Device Hub DDI readiness pass. HomePod has no display, so Device Hub live preview is unsupported as expected; Xcode app installation, launch and LLDB stack inspection now pass. The initial UIKit template asserts when it requests a display-backed window scene; application UI support is separate from DDI readiness. Live opt-out restores cryptexd and the whole personalized DeviceTree to their pre-patch bytes, removing the injected library. Full bundle admission, 76 selected Swift tests, and the scoped native adapter harness pass.
vphoned on iOS 18 (2026-10-23): the daemon strongly imports Span types from
@rpath/libswiftCompatibilitySpan.dylib, but the guest payload omitted the back-deployment library. The bundle now carries the signed iPhoneOS library, and the existing vphoned installation steps (both restore-image injection and full/environment-only CFW install) copy it to/usr/lib/vphone/libswiftCompatibilitySpan.dylib. The iOS daemon gains that fallback rpath after/usr/lib/swift, preserving the system implementation on iOS 26+. The restore-image trust-cache scan includes the added file. Bundle admission checks its signature/platform and the daemon's search path. No new patch, preset, instruction replacement, or version change is needed; TV/watch daemon payloads are unchanged. This fixes a confirmed missing dependency; an iOS 18 guest boot still needs runtime validation.
HomePod integration with upstream main (2026-10-23). Merged upstream
d14231a4into the HomePod branch, retaining upstream's 2.12 compatibility contract, Apple Watch, research-board and sealed restore image workflows alongside HomePod's donor inference, OTA userland and audio routing. Thehomepod-minishipped preset now names the research-board set and blocks the new manual Frida relaxations and iOS Bluetooth-controller patch, matching its built-in definition. HomePod's original global product token, 48 kHz audio-only routing and Sound Glow policies are unchanged. Validation: full VPhone bundle (including admission checks) and Launchpad builds passed; 683 CoreKit, 170 selected patch/pipeline and 105 archive tests passed. The CoreKit throughput threshold needed one isolated retry after failing during the full parallel run. Two research-board firmware comparison tests skipped without their optional fixtures. HomePod display, setup, welcome, Siri and Sound Glow native harnesses and Launchpad machine/cache, compatibility, template and control harnesses passed. No bundle was installed and no live VM behavior was retested for this merge.
HomePod 27.2 OTA and native Sound Glow (2026-10-23, live verified). The 27.0 donor lacks the artwork/color extraction and native media animation cases introduced in 27.2.
system-homepod-exp-ota_userlandstages the full Apple HomePod mini OTA over the same device's 27.0 IPSW, retaining its restore identity and cloudOS boot chain, as the Apple TV OTA pairing does.system-homepod-exp-sound_glowinjects the existing guest adapter into NowPlayingCap and generates/usr/share/vphone/HomePodSoundGlow.plist. Its narrow nativeAccessorySettingProvider.mediaUIgetter selects value 1; Apple's artwork service, color extractor and CAPAnimationEffect.media remain responsible for rendering. Opt-out restores the original app and removes the policy. Both declarations are HomePod-only, require 27.2 or later, and are selected by default only byhomepod-mini. Original product identity and existing audio/Siri behavior are retained. Local validation and native evidence are recorded in Sound Glow. Live 27.2 boot and clear Music/AirPlay audio are verified. Initial playback exposed an unresolved own-device endpoint in the native artwork request. A scoped request adapter selects MediaRemote's local destination only for that own-device UID; native request/response and artwork processing remain intact. Remote requests and opt-out pass through. The routing correction, artwork processing succeed live in 24K120. Purple and yellow album artwork produce distinct native animated colors; pause fades to idle and resume works. A follow-up to the existing Siri speech patch uses a mixable, ducking auxiliary session with notify-on-deactivation after the live interruption test made Music abandon the receiver. The owner confirmed full speech while Music stayed on HomePod; native frames handed off to Siri and returned to colored music. Successful installation now records the actual mounted HomePod SystemVersion after disk commit, preserving cloudOS and variant. Preparing an OTA alone does not promote the host label; full-install command/creator variant recording retains HomePod identity. Restore-info tests cover this merge. Opt-out removes the policy and injected load command and restores white native playback. Re-enable plus cold boot restores audible music and colored native animation, confirmed by the owner and changing valid RGB reports. Final live bundle:2.11.0-local.8e60e757; original 27.0 VM and default bundle preserved.
HomePod Siri speech policy (2026-10-23, live speech verified). New optional
system-homepod-exp-siri_speechgenerates/usr/share/vphone/HomePodSiriSpeech.plistwithEnabled=true, restricted to 27.x HomePod guests and selected by default only inhomepod-mini. Installation requires vphoned, the top-cap adapter and virtio sound; welcome additionally requires this speech policy. Opt-out removes only the selected policy. The native branch supports an output-only auxiliary AVAudioSession ID carried on a normal type 13 SVXSystemEvent, allowing SoundBoard's existing Siri lifecycle and completion to handle speech without entering physical recording setup. The candidate repair is scoped to the existing SoundBoard adapter; global product identity, audio route and microphone capability flags remain intact. LivesiriSay:speaks the full exact welcome sentence. SiriHeadlessService reports synthesis success, sirittsd records playback start/finish, SoundBoard returns to idle and deactivates the auxiliary session. Colorful native frames advance during speech and return to black afterward. See welcome and speech policy.
HomePod welcome policy (2026-10-23, live sequence verified). New optional
system-homepod-exp-welcomeis restricted to 27.x and admitted only for HomePod guests. Onlyhomepod-miniselects it by default. The guest installer checks that vphoned, the HomePod top cap, local setup completion and virtio sound and native Siri speech are selected, then generates/usr/share/vphone/HomePodWelcome.plistwithEnabled=true. Opt-out removes only that policy; once-only Data state remains owned by the guest runtime. The coordinator runs in SoundBoard's existing injected HomePod adapter, using native tone completion, a coordinated Identify activity and Siri finalization after boot readiness. A durable guest-identity claim prevents replay across restarts and updates while allowing each template clone its own welcome. The owner confirms tone, exact speech and both animations on automatic boot. Logs establish TonePlaying, ToneFinished, SpeechRequested and SpeechFinalized. Live inspection caught Identify remaining active: cleanup must call the coordinatedsetLocalSessionActive:NO, which invokes the subclass cleanup; callingendLocalSessionActivityalone does not relinquish ownership. The regression mock now preserves that native distinction. Final installed bundle2.11.0-local.3f1d474epasses the complete sequence on a fresh-identity clone, returning to an empty activity stack. The original claim/status stay byte identical after update, cold boot and SoundBoard restart with no replay. Direct Siri speech, native animated frames and ordinary tone 1 still pass afterward, including the owner's audible and return-to-idle confirmation. See welcome policy and native behavior.
HomePod welcome pre-live ABI audit (2026-10-23). Direct 24J361 donor-cache metadata establishes UInt32 tone playback (
SBUITonePlaybackManager play:completion:,v28@0:8I16@?20). The candidate's initial UInt64 mock and guard agreed with each other but would reject the real guest method. Both are corrected, with an independent donor-signature fixture and rejection coverage for the old shape. Native Siri state/selector analysis also confirms a presenting-state activity path separate from microphone audio activation; live animation, metering and audibility remain pending.
HomePod audio-only identity correction (2026-10-22, live validation). An A/B boot of the same installed bundle, changing only the validated donor product token back to its prior virtual value, restores the native red replay; restoring the donor token blacks the same fixture. The donor identity enables HomePodDisplayService's physical gamma path, which requires absent brightness hardware. Audio now selects native B520 route 4 through VirtualAudio's own
ProductIDOverride, stored by vphoned and set in-process by the HAL plugin. The boot-tree HomePod capability patch retains the original VM product token.preboot-homepod-exp-audio_productis now blocked in every preset; its existing inverse restores previously installed guests from the shared Preboot backup. The old repair remains declared for explicit research and reversible migration. No display workaround or new instruction patch. All 88 focused Xcode firmware tests, four audio transport harnesses, the unchanged display adapter harness, the full bundle build and strict signature verification pass. Installed2.11.0-local.40cd1d45restores the exact original Preboot tree; MobileGestalt retains the virtual product enum while VirtualAudio uses route 4. The owner confirms clear tones. LED replay and native identify output work across two cold boots; the second playback run has zero starvation/drops in all cycles.
HomePod audio port (2026-10-22, live routing validation). New
preboot-homepod-exp-audio_productrestores the donor's exact 20-byte/product/product-idin Preboot through the existing reversible repair composition; new HomePod boot trees receive it too. MobileGestalt then resolves HomePod mini's native VirtualAudio product 4 instead of the VM's unknown 0.homepod-miniselects the shared virtio-snd HAL driver, retargeted/resealed for tvOS, publishing its output as the nativePuffinOutputendpoint. It does not apply the iPhone 8010 override or VirtualAudio instruction/tuning patches. Other presets keep the new identity repair off and TV excludes the driver. First live run proves native product 4, B520 tuning selection and driver load; routing requires the HomePod-only raw UInt32mode/glob/0custom property. The driver advertises this throughcust, retains the nativedefa/raww/tphn/measmode and reports changes. Corrected build61d08ba5routes toPuffinOutput; six playback cycles have zero starvation/drop counters. Final buildb284734aplays ordinary-session test tones on the Mac's selected Studio Display speakers; the owner confirms clear tones. Live silence traced to B520 speaker DSP buffer overruns at 44.1 kHz. HomePod output now advertises only its 48 kHz wire rate; forcing a 48 kHz session removes the errors and produces nonzero PCM in a scoped host tap. iPhone/iPad retain their alternate-rate support. No new instruction patch. See native evidence and validation.
HomePod setup completion and controls (2026-10-22, live validation). New
system-homepod-exp-setup_bypassis HomePod-only, restricted to 27.x, and selected only byhomepod-mini. It installs the System marker/usr/share/vphone/HomePodSetup.plist. A short-lived mobile helper journalsSetupDoneandSetupFinishedAllStepsincom.apple.purplebuddy, synchronizes them, and calls the donor'sBYSetupAssistantPrepareLaunchSentinelbefore checking the native needs-setup and initial-run APIs. The sentinel is a per-user POSIX semaphore, not a filesystem flag. Removing the marker restores exactly the original two keys, including absent values; the inverse survives until native restoration succeeds. The helper remains installed for rollback even if vphoned is deselected. This completes local setup state; it does not provision an Apple Account or HomeKit home. Live late-start correction: an already-running HPSetup retains assistant/voice-trigger assertions even after SoundBoard receives completion and runs post-setup initialization. Native preparation publishes fresh completion; after verification, the helper replays the donor's exported notification only for an existing sentinel and gracefully stops only mobile's exact/Applications/HPSetup.app/HPSetup, rechecking identity immediately before SIGTERM. It observes disappearance before reporting success; failures retain the inverse for retry. No SIGKILL, arbitrary process verb or new entitlement. Opt-out does not terminate processes. No new instruction patch. vphoned also exposes all 29SBSDebugInterfacemethods through a fixed, ABI-checked catalog usingcom.apple.debuginfo.soundboard; the HomePod-only Controls section includes native animation/phase choices and typed button and LED replay adapters. The host top cap now uses runtime-compiled Metal 4 optics with a macOS 15 Metal compatibility path. GPU tests and 35 Xcode catalog and selection tests pass. Final bundle2.11.0-local.26745ac8passed live late completion (one native init), fresh/repeated boot, and native Metal display presentation/reconnection. Opt-out restored absent keys and setup behavior. See implementation, live evidence, and service boundaries.
HomePod setup completion on 26.5 (2026-10-26). The gate of
system-homepod-exp-setup_bypassis now 26.5 or later (.atLeast(26, 5)instead of.major(27)); the helper is unchanged. Underhomepod-minion the 26.5 kernelcache mix (homepod-std, HomePod Software 26.5, 23L471), an environment update reported the patch "on in the current selection, newer than this VM's patch plan" and staged the marker. On the next boot the helper journalled both keys as absent, found all four SetupAssistant exports, and reportedNeedsSetup=false,CompletedInitialRun=true,Succeeded=truewith a freshlastPrepareLaunchSentinel; HPSetup ended. Setting the two keys with vphoned'ssetup.skipalone, as before, skips that sentinel, so SoundBoard never runs its setup-gated initialization. No new instruction patch.
HomePod AirPlay on a VM (2026-10-27). New
system-homepod-exp-airplay_vmis HomePod-only, restricted to 26.5, and selected only byhomepod-mini. It installs the System marker/usr/share/vphone/HomePodAirPlay.plist; the existing setup helper then sets AirPlay's own VM override,useWiFiAndAWDLOnVirtualMachine = 1in mobile'scom.apple.airplay, through CFPreferences. Without itAPSIsVirtualMachine()is true and airplayd forces discovery mode none ("customer VM build"), so the HomePod has no local endpoint and a Mac's Music fails with -15016. The helper journals the original value the same way as the setup keys (absent included) and, only when the value changed, sends SIGTERM to mobile's exactairplayd,tvairplaydandmediaremotedso they reread it. Opt-out restores the journal at the next boot. Live onhomepod-stdwith bundle2.13.5-local.47579029after removing the manually written pref: the status reportedChanged=true,Restarted=3, the journal recorded the key as absent, and the Mac's Music played to the HomePod (selected, active, playing). Playback also needs the gPTP recipe change in the HomePod kernelcache section. No new instruction patch. See Guest/homepod_airplay.md.
HomePod top cap (2026-10-22).
system-homepod-exp-top_capinstalls/usr/share/vphone/HomePodDisplay.plist, extracted from the selected board's/arm-io/dockchannel-rtp/rtp-transport/displaynode. It preserves the donor'sMDXCoords,MDYCoords,MDRingCoords,MDBitDepthandMDHWVersionpayloads for vphoned's virtual MiniDisplay HID device. Onlyhomepod-miniselects it by default; the installer rejects other guest families and missing or invalid donor metadata. Both full installation and environment updates stage it through confined descriptors; deselecting removes this added configuration and its receipt entry. It also installslibvphonehomepoddisplay.dylibonly into SoundBoard through anLC_LOAD_WEAK_DYLIBin its backed-up executable, preserving its entitlements and resealing its app bundle. launchd stripsDYLD_*environment variables from this platform process, so both the standalone daemon plist and launchd cache are cleaned of the earlier environment route while preserving their other keys and inserted libraries.IOHIDUserDevicereservesIOClass; the shim adapts MiniDisplay's discovery callback and class-identity query only for the exact virtual display ID. On a failed submission it can replace that virtual handle after vphoned restarts, retrying the same native frame and throttling device enumeration to once per second. Other devices retain their original failure behavior. LED reports still traverse the native HID device. Deselecting restores the original SoundBoard executable from.bak, reseals the app, and deletes the library. The VM retains its virtual hardware tree. No instruction patch is added.
HomePod mini (2026-10-22).
AudioAccessory5,1/ B520 uses its own HomePod Software 27.0 (24J361) monolithic arm64e TVOS IPSW with the cloudOS 26.4 boot chain. The dedicatedhomepod-minipreset retains 27 compatibility and vphoned, blocks the Apple TV OTA swap, HD TPRO workaround and iPhone GPU/audio/environment hooks. Newdevicetree-cfw-homepod_productcopies concretehorseman,siri-huband app architecture capabilities from B520's donor tree only into its installed identity. No new kernel instruction patch. See HomePod mini support for evidence, validation status and limitations.
VirtualAudio device-state null gate (2026-10-24):
system-virtualaudio-cfw-device_state_null_gate(guest system set, iOS 27, on instandard, blocked intvosandtvos-4k; verbcfw patch-virtualaudio-device-state-null-gate, riding the shared staged copy with the other four VirtualAudio verbs) makes VirtualAudio_Device's two device-state updates wait for the DeviceStateManager singleton instead of dereferencing it when it does not exist. Live evidence on the iPhone Duo guest: deferred initialization is dispatched at plugin init but on a machine with no haptic hardware it exits without constructing the manager — no "Ending Deferred Initialization" is ever logged, no thread is parked inside it at crash time — so TheInstance() returns null forever and a route push that arrives once the gates answer true reads it: audiomxd dies, anywhere from ~10 s to ~130 s into its life. The patch redirects each update's gate-check call to TheInstance, so thecbz w0, <skip>the function already carries now tests the singleton: no manager, the update is skipped through the function's own path; manager present, the fall-through re-calls TheInstance and the whole update runs untouched, as designed. The plugin header gates the patch to arm64e.x1 (cpusubtype 12, iOS 27.1's iPhone19,4): the 26.6.2 and 27.0 plugins are plain arm64e and are left byte-identical. Two earlier variants were replaced after live runs taught better: a…-device_state_guardthat permanently skipped both updates (stopped the crash, but wins by discarding the channel a foldable's audio graph learns the posture from) and a…-device_state_early_publishthat left the deferred-init actuator wait at once (a no-op on this guest — the wait loop is never reached, because the manager's construction needs haptics hardware a VM does not have). See the full section below.
CIBarcode standalone X1 lowering (2026-10-29, local validation):
system-cibarcode-cfw-arm64e_x1applies the existing static X1 planner to/System/Library/CoreImage/CIBarcode.cifilter/CIBarcode, outside the shared cache. It is gated to iOS 27.1, enabled instandard, and blocked in the TV and HomePod presets. The measured 24A94425 image UUID is807AC7DC-7FE2-3E60-BA1B-0E879FF70012. After whole-cache lowering, an app reached its normal main run loop but faulted on a utility queue in this image at relative address 5960 withEXC_BAD_INSTRUCTION/0xDAC1A7FE, called by+[CIFilter(CIFilterRegistryPrivate) filterWithName:setDefaults:]. This offset is evidence only; the patch discovers reachable instructions through the existing planner, not a fixed offset. The installer stages from.bak, re-attests/signs and seals the filter bundle, and restores the backup when deselected. On the pulled stock image, the planner lowered 425 instructions, re-attested 19 slots, and left zero unvisited candidates; the recorded fault site was lowered and a second pass was byte-identical. The X1 and catalogue suites passed (39 tests). An environment update on an existing M3 Max Duo preserved a byte-identical original.bakand recorded the new patch in its Guest receipt. The previously crashing app reached its home screen in both open and closed postures without a new CIBarcode crash. App-specific environment-risk checks remain active; login and transactions were not tested. The build used local Xcode 26 compatibility adjustments outside this patch. A full multi-firmware golden corpus was not available.
ARM64E_X1 whole-cache lowering (2026-10-23):
dyld-cfw-arm64e_x1_cache(guest system set, iOS 27, on instandard, blocked intvosandtvos-4k; verbcfw patch-arm64e-x1-cache) lowers the X1 instructions of every shared-cache image outsidedyld-boot-arm64e_x1_bootstrap's 38-image closure, with the same planner and replacement table, and re-attests each modified page. Without it every other image faults into launchd's single-threaded on-demand handler once per site per process; on iPhone19,4 27.1 (24A94425) that left launchd at 685 CPU-seconds 18 minutes after boot and FrontBoard killing SwiftUI-heavy extensions at the 10-second scene watchdog. On that cache it writes 24,606,083 instructions in 212,573 page runs across 4,508 images, skips none, and leaves 250,443 unvisited candidates to the on-demand handler; a full pass takes about 95 s on an M5 Pro. The planner's speedups (parallel images, thread-local locale, word-keyed decode cache, verified PC-relative moves, bitmap sets) were each checked to produce byte-identical caches, including the bootstrap patch's output against HEAD. The 27.1 VirtualAudio reveal now acceptsPACIBSPPC/RETABSPPCprologues and returns; the audiomxd crash those four patches triggered on 27.1 is root-caused as the pre-init DeviceStateManager read and fixed bysystem-virtualaudio-cfw-device_state_null_gateabove. See Duo bring-up.
ARM64E_X1 undo as words (2026-11-03, issue #789): the two X1 cache patches (
dyld-boot-arm64e_x1_bootstrap,dyld-cfw-arm64e_x1_cache) no longer keep their undo as page runs in.vphone-dsc-undo.json. They record each changed instruction's address and original word in.vphone-dsc-undo.wordsbeside it; the cache bytes they write are unchanged. On 24A94425 the whole-cache step went from a 24.5 GB to a 3.2 GB peak, the undo data from 4.4 GB to 298 MB, and each later cache verb from about 20 GB to 10 MB. A log written by 2.15.0 or earlier is converted once by the hiddencfw patch-dsc-undo-wordsverb before the cache patches run. See Undo words.
Foldable VM sub-type (2026-10-23):
preboot-cfw-devicetree_foldable_vm_subtype(guest system set, Preboot device-tree repair, on instandard, blocked intvosandtvos-4k, not applicable to boards without an entry; verbcfw patch-dt-foldable-vm-subtype) rewrites the installed tree's roottarget-sub-typefromV68APto Apple'sV68VM2025duringcfw installandcfw update-environment, never during restore. SpringBoardFoundation then takes its VM path for the fold pose (restore at launch, persist after transitions). vphoned's newdisplay.posturedrives the pose through the test-automation override (com.apple.springboard.testautomationadded toVPhoneDaemon.entitlements). See Duo bring-up.
Foldable product identity and virtual hinge (2026-10-24): the pose override above reaches SpringBoard only; CoreMotion's angle manager — and through it CoreDevice's
hingeAnglecapability and stream — reads a virtual hinge HID service instead, which a single-display guest never had.preboot-exp-devicetree_foldable_product_id(guest system set, Preboot device-tree repair, off in every preset after live measurement, foldable boards only; verbcfw patch-dt-foldable-product-id) replaces the installed tree's/product/product-idwith the foldable board's own 20-byte token, taken from the donorDeviceTree.v68ap.im4pin the VM's FirmwareOriginals (never a synthesized value). Measured on duotest-m5: with the Duo token and one display SpringBoard crashed on its first CA commit — root-caused to aCASecureIndicatorLayerInvalidNameexception ("Invalid indicator name Camera", the Duo camera-indicator lookup that one panel cannot satisfy) — whichsystem-springboard-cfw-fold_guardbelow now swallows; the token stays off in every preset because the visual fold still needs the second panel (Apple's Duo simulator registers two displays in its device type): with the guard the guest boots under the Duo identity, the hinge chain runs end to end, but a measured book sweep (100°) leaves the screen pixels unchanged — no letterbox, split, or seam — so the patch remains research-only until a dual-display guest exists.system-backboardd-cfw-hinge(guest system set, on instandard, foldable guests only) installslibvphonehinge.dylib, which SystemHook loads into backboardd: it publishes aHIDVirtualEventServicewith usage page0xFF0C, usage0x21and reports angles built with HID.framework'sIOHIDEventCreateHingeAngleEvent(allocator, timeStamp, angleDegrees, mechanicalAngleDegrees, velocityDegreesPerSecond, options)— the signature verified against macOS 27.0.1's HID.framework by reading the created event's own field description back, and checked inmake test-hinge. vphoned'sdisplay.posturefollows Apple's sweep model (suiatool's positions closed / book ~100° / open, per-direction duration, continuous and fade profiles, instant swap), publishing the sweep beside the pose override through the same preference-and-notification transport the accelerometer uses; every dispatch also publishes the newest sample (VPhoneHingeLatest) for the CMAngleManager stand-in inlibdevicehubfix(v6) that answers DeviceHub's hinge stream in dtdeviceinfod — CoreMotion's own machinery is never started there, which the gate-only v4 (MotionThread assert), v5's first build (dispatch_asyncon the bridge's NSOperationQueue,EXC_ARM_PAC_FAIL) and v5's liveness test (kill EPERM on root's backboardd read as dead,hingeAngle: false) each crashed or closed the chain on. See Duo bring-up.
Duo panel nodes, product identity on, and CoreMotion's VM relay (2026-10-30): measured against Apple's iPhone Duo simulator (
Research/Guest/Evidence/duo-sim-baseline/BASELINE.md), the dual-display guest still reported one display to MobileGestalt, no CoreMotion pose managers, and a 90° book angle from the test-automation override.
preboot-cfw-devicetree_foldable_displays(new; guest system set, Preboot device-tree repair, on instandard, blocked intvos,tvos-4kandhomepod-mini, foldable guests only; verbcfw patch-dt-foldable-displays) copies the board tree's/productchildren that carrysurface-type—display0(cover) anddisplay1(inner) on V68AP — into the guest's/product, first and in board order, withAAPL,phandlerenumbered above the tree's highest. libMobileGestalt reads them live fromIODeviceTree:/product:MGGetLogicalDeviceDisplayCountcounts the children withsurface-type(1 → 2) andDisplayExtendedPropertiestakes each one'ssurface-type,display-scale,display-rotation,display-corner-radius,chrome-identifierandframebuffer-identifier(nil → two entries). QuartzCore gives the cover's hardware display its entry at construction;libvphonefolddisplaynow gives the inner virtual display the entry with surface type 2 and a 460 ppi physical size (kCAVirtualDisplayPhysical*). The cover's radii stay Apple's tree value [5, 5, 59, 59]; the simulator's device type says [8, 8, 59, 59].preboot-exp-devicetree_foldable_product_idis renamedpreboot-cfw-devicetree_foldable_product_idand is on instandard(renamedPatchesmigrates VM records). CoreMotion'sCLPlatformInfo::queryEmbeddedSystemHardwarematches the V68 token throughMGIsDeviceOfType; without it the guest is a generic virtual device with zero motion capability bits, so+[CMAngleManager isAvailable],CMDeviceStateManagerandCMMagicPoseManagerwere NO and locationd started noCLDeviceStateRelay. The single-display crash loop that kept it off does not apply to the dual-display guest (measured on duobase-01: SpringBoard stable across boots, posture cycles and rotations).system-backboardd-cfw-hinge'slibvphonehinge.dylibno longer publishes its own0xFF0C/0x21angle service (CoreMotion drops its phase-0 events). It publishes the VM host controls locationd's relay subscribes to — primary usage0xFF61/0x5B— and dispatches vendor-defined events whose data is anIOCFSerialized dictionary of at most 256 bytes:{source: hinge-slider-control, type: range, value: <integer degrees>}and{source: orientation-picker-control, type: enum, value: portrait|landscape-left|landscape-right|faceup|facedown}. The relay publishesCoreMotion_DeviceStateRelay_Crane(0xFF0C/0x21) and_DeviceState(0xFF0C/0x22), the services the simulator shows, and SpringBoard'sSBFDevicePoseProviderreads the angle and magic pose through CoreMotion: book is 99.4° (simulator 100°), not the override's 90°. Stationary values are re-sent every 5 s because the relay reseeds 0°/portrait when locationd restarts. Checked inmake test-hinge(serialization round trip, size, vendor event).- vphoned's
display.postureanddisplay.rotationdrive the host controls and skip the test-automation override when CoreMotion has the capability (vp_core_motion_hinge); opening from closed wakes the screen first, because the pose provider publishes a moving pose only on display frames.libdevicehubfix's CMAngleManager stand-in steps aside when CoreMotion's own manager is available.
Inner display point scale (2026-10-31, issue #784):
system-springboard-cfw-fold_display'slibvphonefolddisplaynow has backboardd send-[CADisplay setDisplayProperties:]withpointScale3 for the inner virtual display as soon as the render server answers. A virtual display is created at point scale 1 and only SpringBoard's adapter answered 3, so a process outside SpringBoard (XCTest under WebDriverAgent, vphoned) saw the inner panel at 1x or 2x. The server takes the scale only from a caller withcom.apple.QuartzCore.displayable-context: backboardd's request took, SpringBoard's changed nothing. No patch was added or changed; seeResearch/Guest/iphone_duo_bringup.md, "the inner display's point scale, for every process".
Inner display type (2026-11-02, PR #788 follow-up): the same library has backboardd clear the inner virtual display's type field (bits 10–12 of the server display's flags, read from
-[CAWindowServerDisplay displayType]'s instructions) just before that point-scale publish, so every process started later readsCADisplay.displayType0, as the real Duo's and the simulator's inner panel do. With type 3 (virtual), FrontBoardServices built a not-always-connected identity in testmanagerd and turned the mode bynativeOrientation − currentOrientation, so XCTest (WebDriverAgent) captured the landscape inner display into a cut-off landscape buffer. The render server's own Server keeps its type-3 setup. No patch was added or changed; seeResearch/Guest/iphone_duo_bringup.md, "the inner display's type, and XCTest's screenshot of it".
Client-dispatched hinge values (2026-11-02, issue #784):
system-backboardd-cfw-hinge'slibvphonehinge.dylibnow, on iPhone19,4 only, detours IOKit's exported_io_hideventsystem_dispatch_eventin backboardd from its constructor, with libmisfix's detour (MISFix/MISFixDetour.c, built into the hinge). An event that is DeviceHub's hinge slider (host-control vendor event0xFF61/0x5B, its IOKit dictionary naming providercom.apple.Virtualization.VirtualMachines,hinge-slider-control,range, value 0–180) never reaches the server, which would refuse a client withoutcom.apple.private.hid.client.event-dispatch: the detour frees its buffer, answers success, and the hinge dispatches the event unchanged on the hinge's own host-control service, where locationd's relay and vphoned follow it as they follow DeviceHub's. This is what WebDriverAgent'sPOST /wda/device/hingeAnglesends, from a runner without the entitlement.MISFixDetouralso now refuses to relocate the PAuth_LR prologues (PACIASPPC,PACIBSPPC,PACNBIASPPC,PACNBIBSPPC), which sign with their own address; the X1-lowered cache a guest runs on a host without PAuth_LR has the SP-only forms there. Checked inmake test-hinge; measured on sbdisplay-duo (27.1): WDA POST 120, 0 and 180 movefold_angleand WDA's CoreMotion reading to each, and DeviceHub's own buttons still fold the guest. No firmware patch was added or changed; seeResearch/Guest/iphone_duo_bringup.md, "WebDriverAgent's hinge POST".
The hinge in locationd, and VirtualAudio on 27.2 (2026-11-05, issue #821):
system-backboardd-cfw-hinge'slibvphonehinge.dylibis now also loaded by SystemHook into locationd, where, on iPhone19,4 and only when the relay has 27.2's interpolator (CMDeviceStateRelayManageransweringeffectiveAngleandinterpolator,CMAngleInterpolatoransweringinterpolateToAngle:duration:), it replaces-[CMDeviceStateRelayManager _feedAngleUpdate:]: a value equal toeffectiveAngle, which 27.2 skips ("Already at angle; skipping interpolation"), goes tointerpolateToAngle:duration:0, one event at that angle; any other value takes the original. backboardd's stationary resend drops from 5 s to 2 s. Together they hand a CoreMotion client started while the hinge rests (WebDriverAgent's hinge GET) the angle within 2 s, as 27.1's relay did within 5 s. 27.1's relay has neither selector and is left alone. vphoned's one-time restart of the boot-time locationd (PR Lakr233/vphone-cli#826) is removed. Insystem-virtualaudio-cfw-mute_set_throw, the throw block may now be an exception built by a constructor call on the allocated object (27.2'sstd::system_error) as well as a CAException stored into in place; on 24B91020 the site is 0x10462c → epilogue 0x104314, and the five VirtualAudio patches, which share one staged copy and had all been left off, apply again. SeeResearch/Guest/iphone_duo_bringup.md, "a hinge client started later", andResearch/Guest/virtualaudio_speaker_route_throws.md.
SpringBoard fold-guard (2026-10-25):
system-springboard-cfw-fold_guard(guest system set, on instandard, blocked intvos,tvos-4kandhomepod-mini, foldable guests only, iOS 27+; installs/usr/lib/libvphonefoldguard.dylib) is the companion that makes the Duo product token bootable at all. With the token active and one panel, SpringBoard dies during its first CA transaction commit: the Duo branches build a secure status indicator for the camera, and-[CASecureIndicatorLayer _copyRenderLayer:layerFlags:commitFlags:]raisesCASecureIndicatorLayerInvalidName("Invalid indicator name Camera") — an NSException on the main thread, SIGABRT, twice per boot (measured on duotest-m5, iOS 27.1 24A94425; crash reports in the guest's osanalytics DiagnosticReports). The guard — SystemHook loads it into SpringBoard only — method-swizzles that one QuartzCore method with an@try/@catchthat logs (first five exceptions to/var/mobile/Library/Caches/vphone-foldguard.log) and returns nil, so the commit proceeds without the camera indicator. Measured: with the guard, a boot that crash-looped twice before reaches the home screen and stays up; the guard log shows the install line and exactly the two swallowed exceptions. The camera status dot is the casualty, and the casualty is now fully explained: the guest's QuartzCore statically accepts the name ("Camera" sits beside "Microphone" and "MicrophoneAccessibility" in the static table), but the validator first consults a runtime registry the window server fills per display, the virtual panel registers no indicator slots, and the registry rejects every name — measured by aliasing Camera→Microphone, which still raised. Filling that registry is backboardd's job (theCAWindowServerDisplayregistration surface), the same missing display-topology family as the fold layout; no client-side repair exists, so the lean catch stands. See Duo bring-up.
iPhone Duo bring-up (2026-10-23, first boot fails):
iPhone19,4/ V68AP, iOS 27.1 (24A94425), is registered with one outer display and paired with cloudOS 26.4 (23E5207q). Its device tree reportsarm64e.x1. This exercises the existing standard iOS 27 / ARM64E_X1 patch selection on a new userland; it introduces no new instruction patch or preset. Restore and CFW install completed, but setup and a windowed boot panic with PID 1 SIGILL. Installed dyld still uses CSSC instructions that this Mac lacks and current X1 lowering does not cover; exact fault-site attribution remains unverified. Four optional VirtualAudio patches also failed. Neither successful boot nor dual-display/hinge support is established. See inputs, evidence and boot results.
Experimental full OTA System update (2026-10-20):
system-exp-ota_userlandis off in Standard and the TV presets. For a stopped iPhone/iPad guest with a snapshot and prepared target firmware, it replaces System from a matching full OTA and reapplies the target Guest patches from a fresh receipt state. Data/User volumes and SEPStorage are retained. The same private disk clone receives the target kernelcache and TXM payloads under their existing IMG4 manifest; this reuses the existing declared TXM patches, adding no instruction patch or fixed offset. The whole System/kernel/TXM transaction aborts on compatibility patch failure. This is host-driven payload installation; Apple's UpdateBrain protocol and application migration are not implied. See the implementation and validation record.
OTA removed-app state (2026-10-25): capture preserves the guest's removed system-app IDs, and complete/commit/verify include LaunchServices placeholders in their absence checks. Reconciliation retains the original removal backups and independent app data. No instruction patch or preset changes; see the removed-app regression. 2026-10-21 correction: full updates also stage, validate and install
payloadv2/links.txt. The 24A446 iPad OTA omits 24,835 duplicate paths from its numbered chunks. Missing J410 media resources made audiomxd trap in CFRetain(NULL), blocking migration through liveactivitiesd and locationd. No new binary patch is used; the installer restores the OTA's own hard links through confined parent descriptors and checks the resulting inode. Corrected migration and the reference-machine clone subsequently booted with retained data. The clone's protected app reopened its existing data offline at 4/3/2 GB with service profile none. Separate explicit compatibility steps preserved a legacy re-signed app container and synchronized 38 retained Apple app programs from the target IPSW; these are not new binary patches. 2026-10-21 orchestration:fw otanow captures guest policies, takes one disk/SEP/NVRAM checkpoint with separate host configuration/firmware backups, prepares a pinned target, verifies real target migration, restores Auto-Lock through Settings with effective-policy readback, and reapplies the saved service profile. The installer stages target removable-app programs on System;ota.apps.applyupdates matching, already-installed Apple app programs at their original container paths and quarantines old serialized placeholders. Legacy third-party container signing is a preflight refusal, never an implicit repair. This adds no instruction patches. Full application and performance equivalence remain outside this result; see the runbook for current verification scope.
iPhone19,2 / ARM64E_X1 compatibility (validated 2026-10-17): the tested pairing is iPhone 18 Pro 27.0.1 (24A446), V63AP, with cloudOS 26.4 (23E5207q). Cache discovery selects
dyld_shared_cache_arm64e_x1and its matching subcaches; ordinary arm64e caches remain on their existing path. MachOKit 0.54.1 supplies the X1 cache format support. Thestandardpreset selects the following boot-essential patches when their iOS 27 applicability matches; non-X1 Mach-O inputs are unchanged.Kernel reveal. The analyzed image is
kernelcache.research.vphone600, XNU12377.100.591.502.1, UUID784F88D3-2AF5-3D4A-AB00-365607646E44. Its symbol JSON is absent from this checkout, so theml_grade_binaryandexec_mach_imgactnames are XNU and control-flow correlations rather than recovered symbols. The measured subtype comparisons are0xfffffe000933ceb4,0xfffffe00091bad94and0xfffffe00091bb088. Runtime patching does not embed those addresses: it finds the unique subtype-2 grading tail, follows real BL callers to the IMGPF_NOJOP/EBADARCH gate, then finds the shared-region gate and its subtype-2 success assignment. All three must resolve uniquely. Each site branches to an allocated cave containingcmp Wn,#12; ccmp Wn,#2,#6,ne; b resume, preserving subtype 2 and making subtype 12 take the same authenticated paths.Instruction path.
VPhoneArm64eX1Loweringconverts the supported PAuth_LR return/authentication forms to their SP-only forms and scalar ADDPT/SUBPT/MADDPT/MSUBPT to existing arithmetic encodings. This removes PC binding and CPA overflow poisoning; it provides instruction compatibility, not hardware-equivalent X1 security. Before installing the exception service, launchd forks an isolated child that executesPACIBSPPCandADDPT. Native success leaves compatibility disabled. A trap or probe failure installs the service and propagates its port through spawn before child execution. Supported faults suspend the target, validate the faulting word, create a private writable copy of that process's page, write one lowered instruction, restore RX, flush the instruction cache and retry the same PC. PID 1 uses the per-thread fallback because it hosts the service; other tasks usetask_suspendto close the thread-creation race. Unknown or unsupported instructions are forwarded to the captured kernel exception destination.Restore seal. For iOS 27 with cloudOS 26.4, the restore patch resolves the manifest-selected
SystemVolumeisysroot-hash payload first. Only hash type0x106(sha3_256_4k) needs a changed restore tool. For that image, the patch resolves the manifest-selected ramdisk, locatesapfs_sealvolume's options template from its unique getopt xref and decoded copy sequence, changes hash type1(sha256) to0x106, and re-attests the modified CodeDirectory page. The ordered trust-cache pass replaces exactly that executable's cdhash, preserves the two metadata bytes and restores v1 sort order. The 27.0.1 (24A446) IPSWs for iPhone17,1–5 and iPhone18,1–5 all have root-hash type1in both SystemVolume entries; iPhone19,2 has0x106. SHA-256 images leave both cloudOS restore files pristine. The logical plan and template key keep the user's selection stable;PatchPlan.plistrecords the patch as not applicable and omits it from the effective plan and restore receipt. Unknown hash types or malformed metadata abort before patching; discovery, signature coverage or uniqueness failures also abort the SHA3 patch.Validation and limits. On an M5 Max guest, the unsupported-feature path completed a clean offline restore, logged
Sealing System Volume, and the first disk boot logged successful on-disk root-hash authentication. Settings, Safari and Calculator launched; the instruction probe completed 20,000 calls without a repeat fault or file change, the unknown-opcode case retained SIGILL, and the task-suspension fix passed 240 concurrent vphoned snapshot requests across ten restarts. Focused Swift/C tests, strict arm64e compilation and analysis, the signer suite, the complete Debug bundle build and bundle admission pass. The native fast path still requires validation on a host whose virtual CPU exposes both probed X1 features. The tvOS presets block all seven iPhone-specific X1 and restore patches, including for a tvOS 27 userland.tvOS 26.4 support (2026-10-15). AppleTV5,3 / J42d uses a monolithic arm64 TVOS userland with the cloudOS 26.4 vphone600ap kernel.
devicetree-cfw-tv_app_architecturecopies the donor's concrete app architecture.system-dyld-exp-tv_tproreversibly disables the two hardware-TPRO parameter lookups in standalone and cached dyld, preserving native page-in linking.kernel-exp-tv_tpro_pagerschanges the native tunable's four-byte default through validated name/descriptor/startup-registration discovery. Both TPRO operations are opt-in, off in Standard. The persistent kernel fix allowed a normal four-CPU boot into TVSetup and Settings, with arrow/Return/Escape input. Activation remains Failed; audio and third-party apps are unverified. Diagnostic helpers and unused approaches are excluded from this PR; a normal boot and Escape navigation were confirmed after their removal. TV dyld installation requires a private APFS disk clone and validates its original standalone backup and cache undo before modifying an existing patch. Paired errors abort the install before disk replacement; rollback retires the undo record only after cache re-attestation and standalone restoration succeed. Details and evidence: tvOS support.tvOS follow-up (2026-10-15). Apple TV guests run vphoned. tvOS dyld refused the iOS-platform daemon (
wrong platform to load into process), so the bundle also shipsvphoned-tvos, the same arm64 binary withLC_BUILD_VERSIONplatform tvOS, chosen by guest family at install and launch.system-vphoned-boot-installandsystem-launchd_cache_loader-boot-unsecure_cache_gateare admitted for TV; the cache-gate patcher NOPs the same branch in the plain-arm64 loader unchanged.system-dyld-exp-tv_tproandkernel-exp-tv_tpro_pagersnow apply to every tvOS 26 userland on cloudOS 26.4 rather than 26.4 alone: the tunable is the cloudOS kernel's, and tvOS 26.6 (23L773) needed and booted with both. The newtvospreset is Standard with both on and the iOS display, camera, audio, sensor, GPU and shared-cache patches off. vphoned's consumer0x0C/0x40is the TV button (press type 17): Home on a press, Control Center when held.Apple TV 4K (2026-10-15). New research patch
system-tv-exp-ota_userland(manual-only; on only in thetvos-4kpreset) empties the restored System volume andaa extracts the AppleTV14,1 OTA'spayloadv2chunks staged in<vm>/OTAUserland, over an Apple TV HD 26.6 restore with the J255 identity. On an arm64e TV guest the installer also admitssystem-extensions-boot-gpu_bundleand rewrites the paravirtual GPU driver's and compiler plugin'sLC_BUILD_VERSIONto tvOS (MachOBuildPlatform) before re-signing; the guest then renders through the paravirtual GPU at 2160p.fw prepare --device AppleTV14,1stages the OTA natively (system AppleArchive: AEA decryption, segmented archive) and thetvos-4kpreset istvoswith the GPU bundle and the userland swap on and both TV TPRO patches off. See tvOS support.
Apple TV 4K, 4th generation (2026-10-23).
AppleTV18,1/J265APreusestvos-4k, the Apple TV HD 26.6 restore base, and cloudOS 26.4 beta (23E5207q). Its only catalog release is the supplied Pallas tvOS 27.2 beta OTA (24K120; asset alias24K6120shares the archive). OTA manifest validation refuses other OS versions for this board. Patch discovery preserves the recorded J265 identity over the HD manifest, and Launchpad selects the 4K preset. The real OTA staged its J265 tree and all 42 chunks;fw patchprocessed all 11 components with 239 patches, and the selected cache patchers passed dry runs against its userland. Launchpad and the bundle move to series 2.13 so earlier Launchpad builds cannot choose the HD preset for the new device. Restore and full CFW installation completed on 2026-10-24. The guest daemon reports AppleTV18,1 / 27.2.0; rendering initially stalled in SetupBackground and then reached the animated 4K Hello screen after repairing its service lookup. Select did not advance the new Setup scene; no Hello-handler entry appeared in a live log capture. Live validation exposed a missing sandbox lookup in the new SetupBackground app: a native probe with its original three entitlements took 15.049 seconds to initialize PBSDisplayManager; adding onlycom.apple.security.exception.mach-lookup.global-name = [com.apple.PineBoardServices]reduced that to 0.005 seconds. The 10-second scene watchdog kills the original before initialization returns. New declarationsystem-setupbackground-exp-pineboard_lookuptargets that app's entitlements, applies only to 27.2, and remains opt-in; the installer also requires an Apple TV identity. It preserves all existing entitlements and backs up the executable for reversal throughcfw update-environment. The supportedtvos-4kpreset instead selectssystem-tv-exp-legacy_setupfor 27.2:TVSetupV2.Enabled = falseandTVSetupV2HW.Enabled = falseinSystem/Library/FeatureFlags/Domain/CompanionServices.plist. PineBoard then selects the native TVSetup retained in this OTA. A cold-boot experiment reached its 4K Language screen; consumer HID and native keyboard Down/Up moved focus to Simplified Chinese and restored English. This does not complete setup or select a language. Both feature dictionaries are required; metadata, unrelated flags and encoding are preserved, and a.baksupports reversal through the normal guest patch selection. All 59 focused tests passed. The installed final bundle2.13.0-local.aaf2152ccold-boots to Language on two cold boots (vphoned in six and five seconds) with the temporary override removed and no crash reports. Its System.bakmatches the original plist and SetupBackground is restored byte-identically. Native Return advances to Region; Escape returns to Language, without completing setup. No instruction patch or new preset is introduced. The installer promotes the installed TV OTA's SystemVersion after committing its private disk clone, fixing the host's stale 26.6 restore-base label. See bring-up notes.
tvOS 27 on the Apple TV 4K (2026-10-16). No new patch. The 4K's tvOS 27.0 (24J361) OTA goes over the HD's 26.6 IPSW, and the plan resolves for the OTA's version.
system-tv-exp-ota_userlandnow applies from 26.0 on (it was gated on 26, so a 27 plan dropped it and the HD userland booted and panicked). Thetvos-4kpreset and arm64e TV admission add the iOS 27 shared-cache patchesdyld-boot-iomfb_force_kern,dyld-boot-lockdown_mode,dyld-boot-lsd_embedded_reg,dyld-boot-maxslideanddyld-boot-xpc_lwcr, all gated on 27. Validated ontv27-4k: TVSetup at 2160p in the host window, input, GPU time, vphoned. See tvOS support.
iOS 27 disabled services (2026-10-13):
system-jetsamproperties-cfw-disable_allowedremoves everyDisableAllowed = falsefromSystem/Library/LaunchDaemons/com.apple.jetsamproperties.*.plist. The guest system set selects it for iOS 27 only;cfw installand environment updates back each file up and restore it when deselected. iOS 27 marks 19 daemons this way, and launchd loads a marked job whatever its disabled override says (force-enabledinlaunchctl print). launchd reads the file the device tree'starget-typenames, so every guest but the iPhone17,3 was affected: the trimmed service profile left five jobs running and a slimmed template's setup boot failed its check, and the OTA block could not stopmobile.softwareupdated. No executable is patched. On an iPad16,1 24A446 guest the patch took 19 marks out of each of J410 and J411; after it no job was force-enabled, none of the profile's 144 was loaded, and a slimmed template built from nothing passed its setup boot. See evidence and verification.
iOS 27 locationd startup (2026-10-08):
system-locationd-cfw-disable_auto_cohortdisables onlyCLAutoCohortinSystem/Library/FeatureFlags/Domain/CoreLocation.plist. The guest system set selects it for iOS 27 only;cfw installand environment updates back up the original plist and restore it when deselected. Independent location silos avoid the startup queue hangs/PAC failures seen with automatic cohorts on the cloudOS 26.4 research kernel. No executable or PAC instruction is patched. Onloc27-iphone(24A435), 20 locationd restarts and 5 cold boots answered before watchdog recovery; restoring the original flag brought back repeated launches and a watchdog kill. A fresh iPad16,1 24A446 guest reproduced the same PAC failure with stock cohorts; with this change, another 20 restarts and 5 cold boots passed, with no new crash reports or watchdog actions. See evidence and verification.
Patch sets and presets (2026-09-28): every patch is now declared, and selection happens before any byte is written. The declarations live in nine bundled patch sets under
VPhoneExecutable/VPhoneCommand/FirmwarePatcher/PatchSets/(bootchain,kernel.base,kernel.cfw,kernel.hypervisor,kernel.frida,devicetree,guest.system,guest.display,guest.identity), listed byFirmwarePatchSetCatalog. A declaration's identifier is the record identifier the patcher already emits, or the common prefix when one patch writes several sites — sojb.kcall10is one selectable patch covering its four records, andsandbox_extcovers everysandbox_ext_<index>. 122 patches are declared in total.vphone-cli fw patchesprints them;--jsonis what the Launchpad patch editor reads.Two presets ship, prewritten, in
VPhone.bundle/Contents/Resources/patches_presets/. Both name all nine sets and differ only in their selection:standard(the default, and what a VM gets unless--presetsays otherwise) blocks the four Frida relaxations, the threehv_vmm_presentconcealment patches, and the iPhone17,3 identity rewrites;extendedblocks nothing.iPad guests (2026-10-01; every current iPad 2026-10-02): five DeviceTree patches, on in
standard, exist only for a VM whose userland comes from an iPad restore IPSW (iPad mini A17 Pro, iPad A16, iPad Air M3 11/13, iPad Pro M4 and M5 11/13 — seeVPhoneGuestDevice.known). Since 2026-10-02 their values are read atfw patchtime from the board's ownDeviceTree.<board>.im4pin the restore tree rather than a table, so an M-series board keeps Stage Manager (nodisable-chamois) and the iPad (A16) has nomedusa-overlay-app-capability:devicetree-cfw-ipad_artwork,devicetree-cfw-ipad_product,devicetree-cfw-ipad_buttons,devicetree-cfw-ipad_identityand (2026-10-02)devicetree-cfw-ipad_audio, which takes the board's/product/audionode. (/product/haptics, which no iPad tree has, is not one of them: since 2026-10-03devicetree-cfw-product_haptics_noderemoves it from every guest's tree, iPhone and iPad alike — see "Why the haptics node goes" below.) They are written to a second device tree,Firmware/all_flash/DeviceTree.vphone600ap.guest.im4p, whichfw preparecopies from the vphone600 one and the hybrid manifest names asDeviceTree;RestoreDeviceTreekeeps pointing at the original, so restore still boots theiPhone99,11identityrestored_externalchecks. On that copy the iPhone-shaped base properties (artwork_device_subtype,island_notch_location, camera offsets) and the-exp-identity rewrites are not applied; the iPad edits set the rootmodel/target-type/target-sub-type/compatibleto iPad16,1 / J410 / J410AP /J410AP, VPHONE600AP, AppleVirtualPlatformARM,/productartwork-device-idiomtopad(subtype 2266, scale 2), fill thesyscfgplaceholders J410AP carries with its values, add the iPad multitasking capabilities, and remove the phone-only placeholders and the ringer switch. GPU, framebuffer and boot properties stay vphone600's.cfw installskipspreboot-exp-devicetree_identityon an iPad guest. Values were copied fromDeviceTree.j410ap.im4piniPad16,1,iPad16,2_26.6.2_23G90_Restore.ipsw.A fifth,
llb-cfw-display_scale, fixes the screen scale. UIScreen's scale is MobileGestalt'smain-screen-scale, and libMobileGestalt — the only reader of the property in the 26.6.2 userland — takes it fromIODeviceTree:/chosen/display-scale. LLB (single-stage boot) writes that property as((v_depth >> 16) & 0xff) + 1from the boot video word, which the paravirtual display always reports as 3x, whatever the panel's size or thepixelsPerInchVZ is given (264 and 326 were tried). At 3x the iPad mini's 1488x2266 panel is 496x755 points and iPadOS lays its home screen out over itself. The patch, anchored on the ADRP+ADD of thedisplay-scaleliteral and the followingubfx wN, wM, #16, #8 ; add wN, wN, #1 ; str wN, [x0], replaces theaddwithmov wN, #<artwork scale>(ARM64Encoder.encodeMovzW). Found atLLB.vresearch101.RELEASE0xB9CC in cloudOS 26.4 (23E5207q). Editing the MobileGestalt cache's cached screen struct (oBbtJ8x+s1q0OkaiocPuog,{1488, 2266, 326, 3.0f}) does not change the live scale. Emitted only for an iPad guest.See
Documents/en/firmware/ipados.md.Other iPhone models (2026-10-05, issue #576): the four
devicetree-cfw-ipad_*presentation patches anddevicetree-cfw-ipad_audionow apply to every guest device except the iPhone17,3 (VPhoneGuestDevice.presentsBoard): the iPhone 16 Plus, 16 Pro, 16 Pro Max and 16e (iPhone17,4/1/2/5) and the iPhone 17, Air, 17 Pro, 17 Pro Max and 17e (iPhone18,3/4/1/2/5). No patch is new and the IDs keep theiripad_names, which presets list. Such a guest gets the separate installed tree, the board's identity,/productand audio node, and skipspreboot-exp-devicetree_identity, as an iPad does. The phone properties (island-notch-location,ui-reachability,oled-display, volume-button geometry,watch-companion,carplay-2) take the board's values instead of being removed.llb-cfw-display_scaleis emitted only when the guest's scale is not 3x, so it stays iPad-only. Checked on iPhone18,1 (V53AP) 26.6.2 (23G90) with cloudOS 26.4 (23E5207q): installed treemodeliPhone18,1, artwork subtype 2622 at 3x,/product/audioacoustic ID 8021 from the board; the guest booted to the home screen at 402x874 points. SeeDocuments/en/firmware/iphone-models.md.Only the camera remains of EXP by default (2026-09-28).
standardis now the JB baseline plus the virtual camera. Off by default, besides the concealment below:watchdogd.hv_vmm_cache(moved intoFirmwarePatchSetCatalog.hypervisorConcealmentPatches, and no longerbootEssential— watchdogd only panics once the OID is renamed), the eight DeviceTree identity rewrites (devicetree.target_sub_type,compatible_secondary,product.fdr_product_type,product.sub_product_type,product.unique_model,product.gestalt_variants_rename,arm_io.device_type,arm_io.soc_generation), and the post-restore Preboot DeviceTree rewrite, now declared aspreboot_devicetree.identity(.prebootDeviceTree, Guest Identity set). Until now that rewrite was undeclared andcfw installran it on every VM; it now asks the plan first, and a VM with no plan still gets it. The last two groups areFirmwarePatchSetCatalog.experimentalIdentityPatches. Still on: the four board presentation properties, camera offsets, the camera / FaceTime / audio / IOPM / SMC / ISP nodes, andcamera_dsc. Camera support reads/product/camerathrough MobileGestalt and does not depend on the identity rewrites. Issue #438 (no location with EXP) is the reason. Thelibvlocation.dylibapp hook that worked around it is removed, andlocation.*is back on IcliKit's locationd simulation with read-back. The Preboot DeviceTree comes from restore, so a VM restored with the identity on keeps it until it is restored again.
hv_vmm_presentconcealment is opt-in as of 2026-09-28, and it is not a preference.kernelcache_exp.hv_vmm(the kernel OID rename plus the kernel-internal cstring mangle) andhv_vmm_dsc(the shared-cache mangle the JB system installer runs) were made default-on by commit 228326d; on a freshly restored 26.4 guest they are a brick.bluetoothdon 23E246 caches itssysctlbyname("kern.hv_vmm_present")answer in adispatch_once, gets ENOENT and caches 0, and its chip-selection singleton then picks a transport fromMGIsDeviceOneOfType— nothing matches a virtual iPhone, the singleton stays NULL, and it faults and crash-loops until launchd throttles it.locationdblocks on a synchronous call to the throttled Bluetooth XPC service, thecom.apple.locationd.migratordatamigrator plugin hangs, and SpringBoard waits on migration: black screen, no panic. The addresses and the full chain are inResearch/Patches/hv_vmm_present_usermode_xrefs.md(B.3 correction).Neither half is useful alone — the rename without the cache mangle breaks the graphics and ML paths, the mangle without the rename does nothing — so they are declared as a pair,
FirmwarePatchSetCatalog.hypervisorConcealmentPatches, and a catalogue test refuses a shipped preset that enables one without the other. Both lostbootEssential, which they never were: a shipped preset that drops a boot-essential patch warns on every run. (The other former EXP patches stayed on in this first change; the note above turns everything but the camera off.) A VM records only the boxes its owner changed, in<vm>/PatchSelection.plist, andfw patchwrites what it resolved to<vm>/PatchPlan.plistforcfw installto reuse.Patch sets also load from outside the bundle. A
.vphonepatchsetis a macOS loadable bundle whoseContents/Resources/Manifest.plistis read before any of its code is mapped, and whose executable exports one symbol,vphone_patch_set_principal, returning aVPhonePatchSetPrincipalthat hands the pipeline oneBufferedPatcherper component the plan enabled. NotNSPrincipalClass: PatchKit is built for library evolution, so a subclass of a PatchKit class is registered with the ObjC runtime only when its metadata is first realized, andBundle.principalClasstherefore resolves to the wrong class entirely.VPhoneExecutable/VPhoneCommand/VPhonePatchSetExampleis the template and is what the loader tests load.An external set can patch the boot chain only, and reaches a run through a preset in
~/.vphone/patches_presets/naming it by identifier and path — the manifest at that path has to declare that identifier.vphone-cli patchset importcopies a set into~/.vphone/patchsetsand ad hoc signs it, which is what makes a bundle straight out of Xcode loadable at all: the linker signs the Mach-O but seals no resources, socodesign --verifyrejects it until onecodesign --force --sign -pass over the bundle fixes it. The signature is re-verified from disk at every load. Rootcfw installloads no external set, so nothing here is on a privileged path.Version gates replaced three flags. The patches
--frida,--force-exc-guardand--force-dsc-maxslidecontrolled now carry a structuredVPhonePatchApplicability:kernel-boot-thread_guard_violationis pinned toiOSBase: .major(18)(whose runningboardd trips a flavor-10 Mach port guard and crash-loops the UI),dyld-boot-maxslideto.major(27), and the twokernel-exp-frida_*patches tocloudOS: .atLeast(26, 4). A preset can turn a patch off, and a VM can turn one on that its preset leaves off, but neither can widen a version gate — so forcing the guard or the slide onto a 26.x base is no longer possible. That capability was opt-in for third-party RASP SDKs and was never required to boot.Two of the three flags are gone from the surface as well:
--force-exc-guardno longer exists, and--fridasurvives only on thepatch-componentdeveloper subcommand, not onvm createorfw patch.--force-dsc-maxslideis gone too (2026-09-30): it was removed fromvm create,restore,cfw install,VPhoneVirtualMachineCreateOptions, the Launchpad new-machine sheet, the Launchpad control verbs and the helper'sinstallCustomFirmwareXPC signature. On a 27 base it never reached the patcher at all: the installer passed--forceonly in a branch below the27.and26.0/18.cases, which the plan had already closed. A helper built before this change has a different hash and shows as outdated, so Launchpad reinstalls it before it is called with the new signature.The
iosBaseIs18/iosBaseIs27booleans are gone from the pipeline too: it now carries a parsedVPhoneVersion(major, minor, patch). Two things still branch on the release rather than on a selection, because they change which shapes a patch looks for rather than whether it runs: the 18.x skywalk-netagent boot-arg, andKernelJailbreakPatcher.applyIOS27.Byte-identity check (2026-09-28): the golden corpus was re-run over cloudOS 26.4 with iOS 26.1, 26.4 and 27.0 bases.
--preset standardreproduced the pre-patch-set output for all three, digest for digest, and--preset extendedreproduced the old--fridacase exactly. No run emitted thedeclared by no patch setwarning, so every record the boot chain emits is covered by a declaration. The one case that no longer exists is--force-exc-guardon a 26.1 base, for the reason above.
Current product scope (September 2026): the tables below preserve the historical patch comparison. The public runtime now exposes only JB. Guest package managers, Procursus, and first-boot package setup are outside this repository. The native Swift JB install retains the base system patches, launchd jetsam guard, debugserver entitlement edit, iOS 27 Campo entitlement edit, GPU driver, mandatory vphoned, and the small vphone launchd hook described below. The JB firmware pipeline also runs the former EXP DeviceTree identity/camera patches. The JB system installer runs the former EXP DSC camera patch, watchdogd patch, and post-restore Preboot DeviceTree rewrite. The former EXP kernel OID rename and its DSC half are the exception: declared, but off in
standardsince 2026-09-28 for the reason in the note above.SPOOF_BUILDremains opt-in and updates the rootfs and installed SystemOS SystemVersion.plist copies, plus the Preboot Cryptex copy when present. These are install-time integrations; successful patch dry-runs do not establish guest boot or Camera.app behavior. Old variant rows are research history, not available install modes.
Integration check (2026-09-25): A clean
/tmprestore tree assembled from cloudOS 26.4 (23E5207q) and iPhone17,3 iOS 27.0 (24A435) completed the publicfw patchcommand with 191 records: 7 former EXP kernel records and 23 DeviceTree records. The unchanged BuildManifest retained its input SHA-256. Against the existing iOS 27 VM's read-only system image,patch-hv-vmm-dsc --dry-runfound 29 writable cstrings and 15 blacklist entries;patch-camera-dsc --dry-runfound six methods. A copy of that VM's watchdogd was patched, passedcodesign -v, and was unchanged on a second run. The Preboot DT rewrite succeeded on a copy and was likewise unchanged on a second run. The original VM disk was not modified or booted.
Camera hooks and environment update (2026-09-25; not yet verified in a guest):
cfw installalso placeslibvcamcaptured.dylibandlibcamfix.dylibin/usr/lib. SystemHook treats/usr/libexec/cameracapturedas an injection target and loads/usr/lib/libvcamcaptured.dylibthere, and loads/usr/lib/libcamfix.dylibinto app processes that already have AVFoundation loaded. Neither needs ElleKit or a bootstrap: both hooks install their own Objective-C method replacements. A missing library is skipped silently; other load failures are logged tovphone-systemhook.log. A running guest receives changed copies of all four/usr/liblibraries through the vphoned environment update (Research/vphoned_http_api.md). Validation:processes.listshowscameracaptured,vphone-systemhook.logrecordscamera-hook=... result=loadedfor its PID, andvcamcaptured.logshows the hook installing its source.
Camera hook: front lookups and data-output frames (2026-10-10): no new patch and no new library;
libcamfix.dylibchanges behaviour only. It answers front-camera lookups with the single vcam device and delivers data-output frames at the session preset's size, orientation and pixel format with device exposure values. Details and validation:Guest/virtual_camera_transport.md.
Camera hook: the virtual camera on iOS 27 (2026-10-22): no new patch and no new library;
libvcamcaptured.dylibandlibcamfix.dylibchange behaviour only. iOS 27 has no_sSourceList, solibvcamcapturedadds the synthetic backing to theFigCaptureSourceBackingsProviderits microphone hook builds, whichFigCaptureSourceManagerpublishes from.libcamfixgives Camera.app's-capturePhotoWithSettings:delegate:the full callback sequence it waits for before saving, and starts the preview scan for a client that skips-startRunning. Camera.app's shutter saves photos again on 27 (issue #707) and on 26.6.2, which shoots the same way. Camera.app video recording followed (see the next note). Details and validation:Guest/virtual_camera_transport.md.
Camera hook: Camera.app video recording (2026-10-22): no new patch and no new library;
libcamfix.dylibchanges behaviour only. For a vcam-boundAVCaptureMovieFileOutputit writes the shm frames (and, when the session has a microphone input, an AVAudioEngine microphone track) to the requested movie with AVAssetWriter and sends the recording delegate its start and finish callbacks, so Camera.app's VIDEO mode saves movies on 27.0 and 26.6.2. On 26.x it also setsCAMCaptureCapabilities._videoSupported, which comes fromAVGQVideoCameraCapabilityand is NO on a guest, so the VIDEO tab appears. Details and validation:Guest/virtual_camera_transport.md.
3D gyroscope core (2026-10-08; guest acceptance pending):
system-backboardd-cfw-gyroscope, declared in the Guest System set and selected by both presets on iOS 18+, installs/usr/lib/libvphonegyro.dylib. SystemHook loads it into backboardd through the same no-bootstrap daemon route as the camera hook. It publishes a virtual HID gyro (page0xff00, usage9); vphoned'smotion.gyroscope.set/get/clearsends an atomic three-axis rad/s configuration through mobile's backboardd preferences and a Darwin notification. There is no system executable, kernel or DSC byte change. Disabling the patch removes the added library; live environment sync cannot reinstall an absent selected library. A provider/library update needs a reboot, without silently restarting backboardd. The VM display app's Features → Motion Sensors → 3D Gyroscope panel sends valid three-axis edits immediately, with a simulation checkbox that retains values and a reset that zeros all axes.motion.gyroscope.setaccepts an optional Booleanenabled(default true); the panel checksmotion_gyroscope_togglesupport. Writes serialize and coalesce, preserve the latest values across old acknowledgements, and retry pending state after reconnect. HID enumeration and dispatch are observable separately from CoreMotion app acceptance; the platform capability gate and optional IMU match still require guest proof. Reveal evidence and manual acceptance:Research/Guest/virtual_gyroscope.md.
Gravity accelerometer (2026-10-20; iPadOS 26.6.2 guest verified):
system-backboardd-cfw-accelerometerin Guest System, selected by both presets on iOS 18+, installs/usr/lib/libvphoneaccelerometer.dylib. SystemHook loads it into backboardd and SpringBoard. A built-in HID accelerometer (page0xff00, usage3) supplies stationary gravity in g, with a monotonic timestamp and sequence.display.rotationpublishes the matching physical pose through mobile preferences and a Darwin notification before its existing orientation request. The provider keeps that pose across SpringBoard relaunches; it streams while HID clients request a report interval and emits one sample on enumeration/configuration changes. Raw HID alone was verified insufficient on iPadOS 26.6.2: it enumerated but never gained a subscription, and the language-change wedge remained. The provider also classifies its stationary HID sample and posts it on BKOrientationManager's native queue, replacing the missing hardware classification stage. SpringBoard recomputes delivery and deferral after setup seeds the actual backlight state. No kernel, DSC or system executable bytes change. Disabling the patch removes its library; live environment synchronization never reinstalls an absent copy. Replacing the library requires a reboot, without restarting backboardd during environment installation. HID enumeration/dispatch alone do not establish SpringBoard recovery; native sink delivery and interface readback are separately observable. The isolatedgyro-regressclone passed gravity-only four-direction rotation, rotation lock/unlock and same-pose language-change relaunch on2.10.1-local.a8c22dce, without a recovery respring/lock cycle. Evidence and regression procedure:Research/Guest/springboard_orientation_stuck.md.
Device attitude (2026-10-08; guest acceptance pending):
system-apps-cfw-attitudein Guest System is selected by both presets on iOS 18+. It adds/usr/lib/libvphoneattitude.dylib, loaded by SystemHook into apps, with no system executable, kernel or DSC byte change. Public CMMotionManager methods supply a stationary XArbitraryZVertical pose through polling/callbacks. vphoned persists degrees and publishes one packed Darwin notify state; apps receive radians, quaternion, matrix and matching gravity. Disabling restores the native path; removing the library and relaunching apps reverts injection. Live sync cannot reinstall an absent selected library. Features → Motion Sensors → Device Attitude provides live angle editing, a simulation checkbox and zero reset. Native host subscription tests and guest cross-compilation passed; manual guest acceptance remains. Contract and validation:Research/Guest/virtual_attitude.md.
Device attitude respring fix (2026-10-09; guest acceptance pending): Two iOS 27.0.1 SpringBoard reports (19:41:21 and 19:42:51) show a Core Motion thread
SIGSEGVat0x10, while the main thread calls native stop throughVPhoneAttitudeSessionduring UIKit motion-effect subscription. The app-path loader had admitted SpringBoard; a fresh simulated session also stopped native updates without a preceding native start. SystemHook and the attitude dylib now exclude/System/Library/services and SpringBoard copies; native stop runs only for a previously forwarded native subscription.system-apps-cfw-attitudekeeps its identifier, selection and iOS 18+ gate; no kernel, DSC or system executable patch is added. The new sensorless lifecycle regression fails before the stop guard and passes after it; sample, real host hook and injection-scope tests also pass. All 51 catalogue/ model tests in six suites passed, and the full VPhone Debug bundle built, passed admission and strict deep signature verification. Updating the guest environment and rebooting is required for deployment verification.
Current launchd hook (2026-09-25; isolated VM verification):
cfw installnow placeslaunchdhook-vphone.dyliband a diagnosticSystemHook-vphone.dylibin/usr/lib, links/vhto the launchd hook, inserts a weak/vhload command for the launchd hook afterpatch-launchd-jetsam, and re-signs launchd. Until 2026-09-29 the hook also extended launchd'sPathsandLaunchDaemonscache values with the bootstrap'sLibrary/LaunchDaemons, under distinct/System/Library/LaunchDaemons/vphone.*.plistkeys (the tested iOS 26.6.2 cache loader ignored/var/jb/...and/Library/...keys). Those jobs did not match the plist path a package script boots out, so the hook no longer touchesxpc_dictionary_get_value: vphoned loads the bootstrap's daemons after boot, as RootHide'sjbctl startupdoes (Research/roothide_loader_links.md). The old binary inzqxwce/vphone-cli-storageat2ef6b06uses the real/var/jbkey andMSHookFunctionto interceptxpc_dictionary_get_value; it also requires/cores/systemhook.dyliband/cores/libellekit.dylibat startup. The bootstrap root is/var/jbor the one RootHide root vphoned installs,.jbroot-000114514191980C; spawns look for it until it appears. The hook also tries to remove PID 1's existing jetsam limit and suppresses future fatal task-limit assignments for PID 1. The launchd hook interposes PID 1'sposix_spawnwithout loading ElleKit, so process injection remains available before a package manager installs it. It addsDYLD_INSERT_LIBRARIES=/usr/lib/SystemHook-vphone.dylibtoxpcproxy, directly spawned bootstrap programs, and app executables under the system or application bundle paths.SystemHook-vphone.dylibinterposesposix_spawnpinsidexpcproxyto carry that environment into the final executable. Both stages preserve an existingDYLD_INSERT_LIBRARIES, avoid duplicate insertion, and honorDISABLE_TWEAKS,_SafeMode, and_MSSafeModein the target environment. PID 1 logs child PID, executable path, and spawn status under/var/mobile/Library/Caches. SystemHook logs there when permitted and falls back to the app's ownLibrary/Cachesunder its sandboxed home directory. SystemHook now carries the selected physical bootstrap path inVPHONE_JB_ROOTand loads that root'susr/lib/TweakLoader.dylibfor App and bootstrap executables when it exists. ElleKit owns individual tweak selection and loading.xpcproxyonly propagates the hook; unrelated system daemons do not load TweakLoader. Injected App and bootstrap processes also propagate to targetedposix_spawn,posix_spawnp, andexecvecalls. RootHide sandbox behavior and real ElleKit tweak loading still require runtime validation. On the rootless iOS 26.6.2 clone,xpcproxycalledposix_spawnpfor the package probe, and the final daemon reportedDYLD_INSERT_LIBRARIES=/usr/lib/SystemHook-vphone.dylibandsystemhook_loaded=1, including after its timed restart. Hooking only PID 1 reachedxpcproxybut did not reach the final daemon; theposix_spawnpbridge closes that observed gap. WithDISABLE_TWEAKS=1in that same daemon plist, the final process instead reportedDYLD_INSERT_LIBRARIES=<absent>andsystemhook_loaded=0; the bridge loggeddecision=disabled. This chain was also tested with a RootHide-shaped.jbroot-<16 hex>fixture on the cloned VM: launchd imported its plist, translated/usr/bin/...to the physical randomized root, loaded ElleKit from that root, and the final daemon reportedsystemhook_loaded=1both at load and after its timed restart. This validates path handling and injection through the randomized root, not a complete RootHide bootstrap or a real tweak package. Apps use a separate direct launchd spawn path on this iOS 26.6.2 VM. On the rootless clone, launchd logged the Calculator app spawn with PID 454 and injection enabled; the app's ownLibrary/Caches/vphone-systemhook.logrecorded the same PID and Calculator executable path. The app reached the foreground and rendered normally. In the subsequent chain-load test, a signed diagnosticTweakLoader.dylibunder the rootless bootstrap ran its constructor in the final daemon and in Calculator PID 403. Calculator's sandboxed container recorded the physical bootstrap path and a successfuldlopen; the App stayed frontmost. Three separate RunAtLoad daemon probes withDISABLE_TWEAKS=1,_SafeMode=1, and_MSSafeMode=1each reported noDYLD_INSERT_LIBRARIESandsystemhook_loaded=0after reboot. This diagnostic library proves the loading path, not real ElleKit or a tweak package. On 2026-09-25, the launchd and SystemHook spawn bridges gained a shared RootHide loader-link helper. Before a bootstrap executable is spawned, it creates or validates the.jbrootlink beside that executable, accepting/varand/private/varspellings of the same root. On an isolated clone ofvphone-27.0-cloudos-26.4, Xrash and Irisin App launches each created a missing link and reached the foreground. A dynamically loaded LaunchDaemon did not traverse the observed interposers; it still needed its link before service load. vphoned's initial fixed-directory link seeding remains in place. A bootstrap CLI probe then spawned itself with explicit stripped environments. Itsposix_spawn,posix_spawnp(absolute program path), andexecvechildren all reportedsystemhook_loaded=1,VPHONE_JB_ROOT=/private/var/jb, and the expected DYLD insertion. The first attempt exposed an alias gap: a/var/jb/...path was not recognized as the physical/private/var/jb/...root; both forms are now accepted. The previous central-log-only probe could not observe sandboxed apps even when SystemHook was loaded. The clonedvphone-launchdhook-lab-26.6.2booted with the weak dylib and retained a healthy vphoned API. Rootless and RootHide probes, each tested after reboot, were imported and spawned by launchd. The RootHide probe's plist named/usr/bin/vphone-hook-daemon-probe, which does not exist outside its randomized root; successful spawn confirms the in-memory physical-path translation. A read-onlyMEMORYSTATUS_CMD_GET_MEMLIMIT_PROPERTIESprobe returned active/inactive-1/-1for PID 1 with this hook. With/vhremoved but the existingpatch-launchd-jetsamleft in place, the same probe returned50/50MB with fatal attributes. These tests used a cloned VM disk and separate host API port; the original VM was not modified. On 2026-09-25, the new interposed spawn hook was installed on the existingvphone-launchdhook-lab-26.6.2rootless VM and read back byte-for-byte. After a cold boot, launchd importedwiki.qaq.ighostvtd.plist; the service and itswiki.qaq.ighostvt.serviceMach endpoint were running. The daemon'sDISABLE_TWEAKS=1kept TweakLoader out of that process. Launchd logged a successful Calculator spawn; Calculator reached the foreground and its own sandbox log recordedSystemHook-vphone.dyliband a successful load of the installed ElleKitTweakLoader.dylib. This checks the injection chain, but does not prove that a particular tweak took effect.
scripts/patchers/*.pyno longer exists. The tables below cite those filenames throughout, because that is where each patch was first written and where its on-device validation notes were recorded. Every one is now a Swift patcher reached throughvphone-cli cfw <verb>— read a citation as "the patch this became", and recover the Python itself from git history at78cbeeaif you need to run it. The port is documented in the three "Swift port status" sections further down.
EXP is a JB superset. Everything in the baseline tables below that is
Yfor JB is alsoYfor EXP. The columns are kept at three variants to avoid noise — the only place EXP and JB diverge is the Experimental additions below, all of which are EXP-only (JB and the other variants are deliberately unaffected). The EXP-only items, taken together:
- Kernel —
KernelEXPPatcherruns thehv_vmm_presentsysctl OID rename plus kernel-internal caller cstring/sandbox-profile-token mangle (formerly wired into JB Group B as JB-26 — moved out).- DeviceTree at fw_patch time — 8 identity-rewrite property patches (Tier 1b + 1c) flipping userland-visible identity surfaces toward D47AP / iPhone17,3.
- DSC user-mode — byte-5 cstring mangle of
kern.hv_vmm_presentwith a sign-in blacklist + per-page slot re-attestation (cfw_patch_hv_vmm_dsc.py), companion to the kernel rename.- watchdogd (EXP-JB-3.5) — surgical 2-instruction patch + slot re-attest; forces the cached "am I a VM?" byte to
1so watchdogd's clean-exit branch runs.- Post-restore DT rewrite (EXP-JB-6) — host-side rewrite of
devicetree.img4on the ramdisk's mounted rootfs for the three restore-fatal identity properties (rootmodel,target-type,compatible[0]) that broke restore when applied at fw_patch time.- SystemVersion.plist
ProductBuildVersion(EXP-JB-7, opt-in) — gated onSPOOF_BUILD=<id>. Rewrites the build identifier in the rootfs and cryptex copies ofSystemVersion.plist.- Camera.app accessibility — at fw_patch time: the
/product/cameranode, two/productcam-offset rewrites (Tier B), three new/productchild nodesfacetime/audio/iopm(Tier C), and five minimal/arm-iostubsisp/ispRtb/smc/iop-smc-nub/smc-ext-chargercarrying camera-front, camera-rear and camera-driver (Tier F). At install time: the 5+[_NUStyleTransfer*Processor processWithInputs:...]DSC short-circuits in NeutrinoCore plus a 1-instruction+[AVCaptureDevice authorizationStatusForMediaType:]rewrite in AVFCapture that returnsAuthorizedfor any media type (Stage 0 of the vcam stack — auth gate only; downstream cameracaptured/vcamd plumbing for actual frame delivery is open work). Together these make Camera.app's icon show on the home screen and in Spotlight, the viewfinder render, and arbitrary apps stop bailing on the camera permission check. The NeutrinoCore patch stops the viewfinder's CIImageProcessorKernel chain from asserting on a nil descriptor when ANE detection comes back NO on the VM.
Boot Chain Patches
Restore environment (iOS 27 + cloudOS 26.4)
The patch is gated to an iOS 27 base paired with cloudOS 26.4. Both paths are
resolved from BuildManifest.plist; discovery requires one semantic match and
the trust-cache update requires exactly one old cdhash, so an unknown restore
environment fails before publishing a partially patched tree.
AVPBooter
New machines boot OpenAVB instead of AVPBooter, and OpenAVB checks no image
digest: fw patch skips this component on them (the patch stays selected and
planned, which is what the machine behaves as). Machines created before keep
Apple's ROM and this patch. See Firmware/openavb_boot_rom.md.
iBSS
iBEC
LLB
TXM
Executable remapping: rows 13–16 form
txm-cfw-executable_remap, selected by standard without an iOS/cloudOS version gate. Unique semantic matches in pristine TXM are still required; missing or ambiguous shapes stop patching. Row 1 then uses the hash-flags alternative so cache misses remain real. Runtime reports on iOS 26.6.2 and iOS 27.1 each execute 28/28 main/framework original-code remaps with actual RX mappings; the tester also reports normal startup and App loading with the replacement. The iOS 27 forced type-10 pre-check patch is removed; native pre-check code is unchanged, and iOS 26/27 produce identical output from the pinned TXM. Host tests reproduce and resolve the historical0x12491/0x32202admission failures using this patch; the retained CMS restriction can still reject. Other OS pairs and policy contexts remain unverified. Native association and final-validation code is unchanged; ordinary-library Team ID matching is deliberately relaxed. The runtime validation executes 28/28 original-code remaps, but also executes modified framework code that stock iOS rejects. This is research compatibility, not full stock-policy alignment. See TXM executable remap for discovery, exact changes, regression tests, evidence and remaining limits.
Former pre-check admission:
txm-boot-precheck_admissionbypassed the selector-24 policy walk and fixed an iOS 27.0.1 first-boot init SIGKILL, but forced all admitted signatures to type 10. It is replaced by native classification above. The replacement has iOS 27.1 remap results and tester-reported startup success; the historical 27.0.1 pairing has not been retested. Historical evidence:Research/Firmware/txm_selector24_cms_gate.md.
Kernelcache
Base Patches (All Variants)
† Always required on iOS 18 bases (18.6.2's runningboardd/SpringBoard trip GUARD_TYPE_MACH_PORT "flavor 10", crash-looping the UI — the VM won't boot without it there). On other bases this is opt-in, not always-on: some third-party apps shipping crash-reporting/RASP SDKs (Bugly, Crashlytics, KSCrash, ...) call task_swap_exception_ports(), which the research kernel can enforce as a fatal GUARD_TYPE_MACH_PORT/KOBJECT_REPLY_PORT_SEMANTICS violation (see upstream issue #291, reproduced and confirmed via crash logs against a kernelcache.research.vphone600 (26.1/23B85) build) — but it's not required for the VM itself to boot on 26.x, so it stays opt-in rather than always-on for regular/jb/exp. applyExcGuard in FirmwarePipeline/KernelPatcher is iosBaseIs18 || forceExcGuard; pass --force-exc-guard to patch-firmware (or FORCE_EXC_GUARD=1 to the relevant make fw_patch* target) to enable it on a base that needs it. iosBaseIs18 (from iPhone-BuildManifest.plist's ProductVersion) also still gates the unrelated 18.x skywalk-netagent boot-arg workaround. Superseded: the --force-exc-guard / FORCE_EXC_GUARD=1 opt-in and the iosBaseIs18 boolean no longer exist. The patch is declared with iOSBase: .major(18), and no preset or VM selection can widen that gate, so it cannot be applied on 26.x.
JB-Only Kernel Methods (Reference List)
⚠ iOS-27 hard-gate (2026-07-20). Every patch below whose Purpose cites "iOS 27" / "27.0 on the 26.4 kernel" is hard-gated to a 27.x base via
KernelJBPatcher.applyIOS27— set byFirmwarePipelinefrom the iPhone baseProductVersion(mirroring theapplyExcGuard/iOS-18 mechanism at patch 27 above; standalonepatch-componentdefaults it true, override with--target-os). Gated set: JB-02b, JB-02d, JB-09 (theops[124]add, plus on 27 theops[267]removal — handed to JB-29; the other 201..316 hooks stay), JB-10b, JB-26, JB-27, JB-28, JB-29. On an 18.x/26.x base none of them run. This supersedes the per-row "No-op-in-effect for version-matched userlands" notes: those describe the effect if applied, but JB-27 is NOT a no-op on 26.x — itscmp 0x588→0x6e0retarget makes the 26.x userclient reject its own native 0x588 SwapEnd → dead display (the 26.5 regression that motivated the hard-gate). Verified 2026-07-20 (JB-29 addendum 2026-07-23): a 26.5 base produces a byte-identical kernelcache to pre-branchmain(patch-component --component kernel-jb --target-os 26.5vs main's output —cmpclean, 83 records each; re-confirmed unchanged after JB-29 bysha256, sinceops[267]stays blanket-neutered on 26.5), and a 27.0 base emits 95 records on thec0ecdb4bdeployment kernel: the 11 gated records above plus JB-29's 2 (jb.fpfs_scoped_open.{ops_retarget,cave}), withsandbox_ext_267suppressed (JB-29 ownsops[267]on 27 → the FileProvider-scoped trampoline instead of the blanket allow-stub).
Frida Stalker support is opt-in (
--frida). JB-23b and JB-25c run only when firmware patching is invoked with--frida(vphone-cli vm create … --frida,vphone-cli fw patch <vm> -V jb --frida,patch-firmware … --frida,patch-component --component kernel-jb --frida, ormake fw_patch_jb FRIDA=1), gated byKernelJBPatcher.applyFrida(set byFirmwarePipelinefromenableFrida). Baseline JB/EXP output is byte-identical when off (26.4 emits 83 records without--frida, 87 with — the 4 being JB-23b's 2 thread_set_state setters and JB-25c's 2 vm_map_delete gates). 2026-10-22: the Frida set also carries JB-33 (kernel-exp-frida_tpro_off, spawn) and JB-34 (kernel-exp-frida_syscall_filter_off, syscall-filtered daemons), ported from mowisec/frida_patches_srd;standardblocks all four. 2026-11-03: JB-37 (kernel-exp-frida_fork_inherit_invalid_code) joins the set for vp-fuzzer's forkserver: the fork child of a parent holding Apple'scom.apple.private.cs.debuggerkeeps the debug-region association of the code pages the parent rewrote; every preset butexperimentalblocks it with the other four. Frida itself is installed through the existing extra-debs mechanism: on a--fridacreate the orchestrator setsVPHONE_FRIDA=1,fetch_debs.shresolves the latestfrida_<ver>_iphoneos-arm64.deb(==re.frida.server: noDepends, rootless/var/jblayout) from the Frida GitHub releases into the debs cache,cfw_install_{jb,exp}.shstage it, and the first-boot "5b/8 INSTALL EXTRA DEBS" stepdpkg -i's it — no APT source, marker, or dependency resolution.
EXP-Only Kernel Methods (Reference List)
Runs in KernelEXPPatcher.findAll() (chained after KernelPatcher +
KernelJBPatcher for the .exp variant only — JB and other variants
do NOT execute these).
CFW Installation Patches
Binary Patches Applied Over SSH Ramdisk
Swift port status — the eight DSC patchers (2026-09-23)
The rows above describe the patches, which have not changed. What is new is that
each now has an in-process Swift implementation on the validated DSC foundation
(DSCChunkSet, DSCCodeSignature, DSCLocalSymbolTable, DSCSymbolResolver).
Every one was proven on the real 24A435 arm64e cache — ipsws/ref_extract/dsc_pristine,
79 chunks + .symbols = 80 files, 6.7 GB — by running the Python on one cp -c
clone and the Swift on another and comparing all 80 files. Every cfw.py call
site is now a vphone-cli cfw verb — see "the CFW verbs" below.
Each port was checked by a second agent that rebuilt the evidence rather than
reading it: own drivers linked against the module, whole-cache byte comparison,
and for lsdreg a differential sweep of both gate-finders over 33,000 function
addresses (49 gates each, zero verdict differences).
Two defects that review found and this branch fixed:
DSCCameraPatcherwrote each site as it classified it and re-attested once at the end, so a throw in the second family left the first family's five writes on disk with stale signature slots — half-patched and SIGKILL-on-page-in undercodeSigningMonitor == 2.applynow plans every family before writing any of them, so a failure writes nothing. Pinned byDSCCameraPatcherTests.failureWritesNothing, which fails and namesdyld_shared_cache_arm64e.15if the old shape is restored.DSCLockdownModePatcher.disassembleBlockdid not stop at an undecodable word.ARM64Disassemblersetscs.skipData = trueand the reference's_cs(cfw_asm.py:14) does not, so where the Python's stream ends this one walked past the data to its 60-instruction ceiling. On a userland with an inline literal before the gate that inverts the failure mode: the Python stops the install loudly, this would decode into unrelated code and could NOP a branch there and re-attest that page. It now breaks oninsn.id == 0, asDSCLSDEmbeddedRegPatcher.disassembleFunctionalready did.
Known, not fixed (all LOW, none changes a byte on this cache):
DSCIOMFBSwapEndPatcher— on the already-correct path it clears recorded writes and re-attests nothing, so the returnedDSCReattestationhas empty arrays andisFullyAttestedis vacuously true. The Python re-hashes the page and compares. Unreachable through today's shell graph; a footgun for the Swift driver that is meant to replace it.DSCXPCLWCRPatcher— a dry run returnsstatus == .patched, siteCount == 3although nothing was written;DSCIOMFBSwapEndPatchertakes the opposite convention (0). A driver summing these for a preview gets a wrong total.DSCLSDEmbeddedRegPatcher— record text sayscbz w0, 0x186eea048where the captured Python reference sayscbz w0, #0x186eea048. libcapstone-spm (Capstone 6) omits the#the Python patchers' Capstone 5.0.7 emitted. Bytes are identical; a field-level diff againstreference_patches/will flag it.DSCHVVMMPatcher.findStringSites(inMachO:)skips a string section whose declared extent overruns EOF, where the Python truncates and scans what is there — a malformed binary reads as a clean no-op. No caller in the repo today.DSCMaxSlidePatcher— thesharedRegionSize >= mapped spancorroboration couples the patch toDSCChunkSet's file-exclusion policy, so a stray cache-shaped file in the chunks directory turns a working patch into a hard abort. Fail-closed, and left alone deliberately.
Swift port status — the six independent Mach-O patchers (2026-09-23)
Rows 1, 2, 3, 5 and 13 above, plus the EXP watchdogd patch, now have Swift
implementations under Sources/FirmwarePatcher/CustomFirmware/ExecutablePatches/. The patches did not
change. Each was run against its Python on two cp -c clones of the real 24A435
binary in ipsws/ref_extract/macho_pristine/, both with re-attestation off (what
the Python emits) and on (against cfw_macho_codesign.reattest_modified_offsets).
Every port is idempotent, and three of the Pythons are not. A second Python
run over its own output walks past the first patch and changes something else:
cache_loader NOPs the cbnz x0 @0xC84 log-file guard, jetsam rewrites the
b.ne @0xFAB0 in the same return block, and seputil exits 1. The shell never
hit this because it always copies from the .bak first. The Swift reports
already patched and writes nothing, which is what the in-place call site in
CryptexFilesystemPatcherGuestPayload needs.
Wired in. CryptexFilesystemPatcherGuestPayload calls
CFWCacheLoaderPatcher and CFWMobileactivationd in process. Every shell call
site (cfw_install*.sh, cfw-kit, patch_{camera,hv_vmm}_userland.sh) now runs
vphone-cli cfw <verb> — see the next section.
A defect that review found and this branch fixed: CFWCacheLoaderPatcher
trapped on every patch with logging on, which is the default. The before/after
window starts two instructions ahead of the gate, and it converted that negative
delta with UInt64(Int), which traps even under -O. Every test passed
log: nil, so none reached it. Now covered by loggingPathDoesNotTrap.
A second defect, found when the CLI verbs landed and fixed here: a Mach-O
whose load-command table runs past EOF — a 64-byte truncation is enough — made
all six patchers die with SIGTRAP (BinaryBuffer.swift:9 precondition, exit
133), not a catchable error. MachOParser walked the table checking only that
each command's 8-byte header fit, then read fields up to +0x38 inside it.
MachOParser.forEachLoadCommand now bounds every command by cmdsize and by
the file, and each branch checks its own minimum command size; a truncated file
yields no segments and the patcher throws invalidFormat like any other bad
input.
Known, not fixed (all LOW, none changes a byte on these binaries):
CFWCacheLoaderPatcher.flagSetterOnResultaccepts a flag-setting instruction withx0/w0in any operand, including as the destination.CFWMobileactivationd's ObjC cross-check is scoped to the selector, not the class: it takes the first relative-method-list entry with that name.CFWJetsamPatcher's.alreadyPatchedis silent. If a later firmware emits an unconditionalbinto the return block ahead of the real gate, it would read as already patched.CFWWatchdogdTests.swiftconverts a VA to a file offset by subtracting the arm64 image base as a constant, in a test only.
The CFW verbs, and the end of scripts/patchers (2026-09-23)
The fourteen ports above had no caller outside the two in-process call sites, so
the installers still ran cfw.py. They no longer do. Every patch is a
vphone-cli cfw <verb> with the Python's exact verb name, positional arguments
and flags, so the shell diff is a prefix swap:
26 call sites across cfw_install{,_dev,_jb,_exp,_host}.sh,
patch_{camera,hv_vmm}_userland.sh and cfw-kit, whose cfw_py seam became
cfw_cli. Each verb was proven through the built binary against its Python on
cp -c clones of the real 24A435 references: the six Mach-O verbs byte-identical
(cmp + matching sha256, both exit 0), the eight DSC verbs diff -rq clean over
all 80 cache files on first run, second run and dry run.
Parity evidence is now frozen, not re-measured. The CFW test suites used to
spawn .venv/bin/python3 scripts/patchers/… and compare live. They now compare
against constants recorded from that same Python at commit 78cbeea, each with a
provenance comment naming the command and the input digest, and each suite checks
the fixture is the one the goldens were taken over — so a different firmware fails
on that line instead of looking like a patcher bug. Two structural fixes were
needed for the freeze to mean anything: the re-attester parity test was reading a
build product, whose bytes change every build, and the device-tree golden is a
digest map keyed by input digest, because that input exists in two cloudOS IPSWs.
The build product in question was .build/release/vphone-letmein, picked as the
nearest real ad-hoc-signed thin arm64 Mach-O with a non-page-aligned codeLimit.
The tests asserting a frozen digest moved off it first, onto the real 24A435
seputil; the rest of CFWMachOTests — structural cases that only needed some
signed Mach-O — kept pointing at the build product, and that stopped being viable
when vphone-letmein was deleted from the tree (see
Research/Host/host_binary_split.md). All of CFWMachOTests now takes its fixture
the way the sibling CFW parity suites do: macho_pristine/seputil, resolved
through VPHONE_MACHO_PRISTINE with ipsws/ref_extract/macho_pristine as the
default, failing rather than skipping when it is absent, since a skipped test
reads like a passing one. VPHONE_MACHO_FIXTURE_OPTIONAL=1 turns that back into
a skip for a machine that cannot carry the extracted IPSW.
The lesson generalises past this one binary: a test fixture must never be
something the build produces, whether or not the assertion is a frozen digest.
It was luck, not design, that the old fixture had the properties these tests
exercise — a non-page-aligned codeLimit and zero padding behind the load
commands — and a fixture that is rebuilt is also a fixture that can vanish.
Re-deriving any of these constants means re-extracting the reference Python from
git history — scripts/patchers/ is gone from the working tree.
One Python bug the port deliberately does not reproduce. A write that
straddles a code-signature page boundary — eight bytes at 0x280FFFC..0x2810004,
which is what cfw_patch_camera_dsc.py emits when its six entry points land on
both sides of the 2563/2564 line — re-attested only the page containing the
address. Page 2564 kept its original slot hash (7ccd65fa…) while its bytes
hashed to dfe38096…, and the guest dies on the first demand-page-in of it.
DSCCodeSignature.reattestRecordedWrites covers every dirtied page;
DSCFoundationTests.pageStraddlingWriteAttestsEveryDirtiedPage pins both the fix
and the reference's value, so the divergence stays on the record.
One verb is semantically, not byte, identical. cfw inject-daemons writes a
plist, and the two XML writers disagree on one thing: Python's plistlib
serialises an integral double as <real>1.0</real> where Foundation emits
<real>1</real>. Both parse back to the same value, and matching the Python
byte for byte would mean reimplementing plistlib's writer — which is why the
plan set the bar for plists at semantic equivalence (type, order, key set,
Data bytes) rather than bytes. Recorded here so "byte-identical" is not read
as covering the plist verbs.
Two behavioural differences kept on purpose, both on the already-patched path and neither changing a byte:
patch-camera-dscover an already-patched cache exits 0 where the Python raised and exited 1.cfw_install_exp.sh:126tests that exit status and its failure branch prints "likely build-version mismatch" — a wrong diagnosis for a re-install. Exit 0 with "chunks patched" is the true one.- The three idempotence divergences in the section above stand: the Swift recognises its own output where the Python double-applies or exits 1.
Final submodule removed: Scripts/Repos/InsertDylib was retained only for
a byte-parity test after CustomFirmwareInjectDylib took over the injection.
That test is replaced with two checked-in Objective-C source fixtures: a
hello world executable and a swizzle dylib. The test compiles them, checks the
plain output, injects the dylib with the Swift patcher, re-signs the executable,
and checks that it prints world hello. This exercises the actual dyld load
and swizzle without an external test reference or an optional skip.
P1 dropped capstone, keystone-engine and pyimg4 from the dependency
list; P2 then deleted the list. requirements.txt, scripts/setup_venv.sh,
scripts/setup_venv_linux.sh, the make setup_venv target and
scripts/pymobiledevice3_bridge.py are all gone — the restore backend was the
last program holding any of them up, and it is Sources/VPhoneRestore over
vendored libirecovery and idevicerestore now
(Research/Restore/native_restore_architecture.md). No Python is tracked in this repository
and nothing resolves a python3 at runtime.
Installed Components
kern.hv_vmm_present user-mode patcher (EXP only)
Companion to the EXP kernel patcher (KernelEXPPatcher.patchHvVmmRename).
Mangles byte 5 of every kern.hv_vmm_present cstring inside DSC dylibs
EXCEPT those in DONT_PATCH_INSTALL_NAMES (sign-in / device-likeness
consumers, ~15 entries). Patched dylibs query the renamed OID and get the
truthful 1 (graphics + accel passthrough); blacklisted dylibs keep the
original cstring, hit ENOENT on the renamed kernel, and defensively cache 0
("not running on a VM") for sign-in / device-attestation surfaces.
Source-of-truth research: Research/Patches/hv_vmm_present_usermode_xrefs.md.
JB and other variants are NOT affected by this patcher.
Patch shape (every site) — cstring mangle:
Byte 5, not byte 0. The mangle has to keep the kern. top-level
namespace intact or the kernel's name-to-MIB resolver routes the call to
nothing at all. Byte 0 would give Xern.hv_vmm_present, and Xern is not
a registered top-level sysctl namespace, so it could never resolve — see
the comment at scripts/patchers/cfw_patch_hv_vmm.py:40-47.
Which way the lie runs. This is the part that reads backwards if you
have the pre-blacklist-flip design in mind. The companion kernel patch
(KernelEXPPatcher.patchHvVmmRename) renames the OID itself, so after
both halves are installed:
- a mangled dylib asks for
kern.Xv_vmm_present, which resolves to the renamed OID and returns the truthful1— that is what keeps graphics and accel passthrough working; - a blacklisted dylib keeps
kern.hv_vmm_present, which now hitsENOENT. Its post-call check (cbnz w0, skiporcmp w0,#0 ; b.ne skip) takes the skip-cache path, the cached "is_vmm" byte stays at BSS-zero, and that dylib believes it is not in a VM.
So mangling is what tells a dylib the truth, and not mangling is what makes it lie. The blacklist is therefore the list of consumers that must be deceived (sign-in, device likeness), not the list of ones to fix.
We don't modify executable code at all — only one byte of read-only string data. The kernel call still happens, so any sysctl-tracing infrastructure can still see activity.
Idempotent: the patcher scans for MANGLED_NEEDLE first
(cfw_patch_hv_vmm_dsc.py:133) and each site re-checks byte 5 before
writing, so a re-run does no work.
DSC-side patches — driven by an explicit blacklist
(DONT_PATCH_INSTALL_NAMES in scripts/patchers/cfw_patch_hv_vmm_dsc.py,
~15 entries) applied to chunks under
SystemOS/System/Library/Caches/com.apple.dyld/. Every dylib that is not
named there gets mangled. Add a line to the blacklist to stop patching
that dylib on the next install — useful for bisecting which consumer is
responsible for an observable change.
The table below lists dylibs that ARE mangled, i.e. ones absent from the blacklist. It used to be introduced as a
PATCH_INSTALL_NAMESwhitelist; that name no longer exists in the module.
Standalone-binary patches (6 files, applied to the device rootfs over SSH)
Explicitly NOT patched (compute / accel — patching here turns off VM fast-paths that exist so the lib doesn't try to touch real silicon ANE / AGX / hardware codecs):
Wiring
scripts/patchers/cfw_patch_hv_vmm.py— standalone cstring patcher (used for the on-device files): finds the"kern.hv_vmm_present\0"cstring in the Mach-O's __cstring section and rewrites its first byte ('k'→'X').scripts/patchers/cfw_dsc_chunks.py— chunked-DSC byte-level helper (DSCChunks(chunks_dir)): vmaddr↔chunk-fileoff mapping, cstring scan over executable mappings, byte read/write at a vmaddr, and Mach-O header walk-back to resolve a vmaddr to the dylib install name (LC_ID_DYLIB).scripts/patchers/cfw_patch_hv_vmm_dsc.py— DSC-native orchestrator. No externalipswdependency. For every"kern.hv_vmm_present\0"occurrence in any executable mapping, walks back to the containing dylib's Mach-O header, readsLC_ID_DYLIB, and — unless the install name is in theDONT_PATCH_INSTALL_NAMESblacklist — rewrites byte 5 of the cstring throughDSCChunks.write_at_vma. Pure Python. Blacklist-based by design so an operator can add an entry to take one dylib out of the patch set and bisect.— gone. The standalone-Mach-O subcommand and its backing patcher (scripts/patchers/cfw.py patch-hv-vmm <binary>cfw_patch_hv_vmm_rootfs.py) were removed with item 8 in the blacklist-flip redesign; the 6 on-device files no longer need an SSH-time patch.scripts/patch_hv_vmm_userland.sh:65-73documents why the wrapper's standalone branch has no honest substitute.scripts/patchers/cfw.py patch-hv-vmm-dsc <chunks_dir>— DSC subcommand (used while the SystemOS Cryptex DMG is still mounted on the host, before the device copy).scripts/patch_hv_vmm_userland.sh— thin wrapper used by the install scripts.scripts/cfw_install_exp.sh— EXP install script. Pre-step before invokingcfw_install.sh: decrypts the SysOS Cryptex into the cache locationcfw_install.shalready uses, mounts it, applies the DSC patch, unmounts. The unmodifiedcfw_install.shthen sees the cached (already-patched) DMG. Standalone watchdogd is patched later via SSH at step[EXP-JB-3.5].scripts/cfw_install_jb.shandscripts/cfw_install_dev.sh— unchanged from pre-experimental baseline. Neither runs the DSC patcher.
kern.hv_vmm_present kernel patcher — Part A + Part B (EXP only)
The KernelEXPPatchHvVmmRename Swift patcher (in KernelEXPPatcher.findAll(),
chained after KernelPatcher and KernelJBPatcher for the .exp variant only)
renames the sysctl OID and rewrites every kernel-internal occurrence of the
old name so kexts continue to find it under the new name. Two parts. JB and
other variants do NOT run this patcher.
Part A — OID name rename. Finds the OID's oid_name cstring as
the NUL-delimited bytes \0hv_vmm_present\0 (exactly one match
required in the kernelcache; on iPhone17,3 / iOS 26.1 this lives at
file offset 0x964e0 inside com.apple.kernel). Flips byte 0 of the
cstring 'h' (0x68) → 'X' (0x58). After the patch, the kernel's
sysctl_register_oid keeps the OID's MIB and value (1) intact but
the name resolver returns ENOENT for kern.hv_vmm_present and
returns 1 for kern.Xv_vmm_present.
Part B — kernel-internal caller mangle. After Part A, any
kernel-side sysctlbyname("kern.hv_vmm_present", …) call gets
ENOENT and falls into the caller's "not in a VM" branch — which on
the bring-up build caused AMFI to panic with AMFI: No PMGR? (ConfigurationSettings.cpp:388) during ramdisk boot. Part B mangles
every kernel-internal occurrence of the kern.hv_vmm_present name
so callers continue to find the renamed OID. The mangle flips byte
5 of the inner cstring ('h' after kern.) → 'X', producing
kern.Xv_vmm_present.
Two byte-aligned forms are searched, both anchored at the
kern.hv_vmm_present substring:
On iPhone17,3 / iOS 26.1 / 23B85 the universe is 5 occurrences (verified by raw substring scan over the kernelcache buffer):
Part B emits one patch record per match (5 total, plus Part A's 1)
under patch IDs kernelcache_exp.hv_vmm_internal_caller_mangle and
kernelcache_exp.hv_vmm_oid_rename. Idempotent: a re-run detects
already-mangled bytes (kern.Xv_vmm_present instead of
kern.hv_vmm_present) and reports the patch as already applied.
Note on the sandbox-profile occurrence. This was missed by the
original Part B because its needle required NUL on both sides. The
sandbox-profile blob stores OID names as TLV-framed tokens where the
trailing byte is \x0f (sandbox EOT) rather than a NUL. Without the
second needle, sandboxed callers that interpret the profile's
kern.hv_vmm_present-matching rule would still match against the
OLD name, while the OID itself has been renamed — so the rule's
ALLOW/DENY/audit action would never fire. With the second needle,
the rule's name token is rewritten to kern.Xv_vmm_present and
sandboxed callers that hit the renamed OID match the (rewritten)
rule as intended. Covered occurrence verified on
iPhone17,3 / iOS 26.1 / 23B85 at file offset 0xa6618b.
watchdogd surgical hv_vmm_present cache patch (EXP only)
Why a dedicated patch. After the EXP kernel-side OID rename
(KernelEXPPatchHvVmmRename), sysctlbyname("kern.hv_vmm_present", ...)
returns ENOENT on this image. /usr/libexec/watchdogd caches that
answer at startup. On ENOENT the cached byte stays at its BSS-zero
default (0) and the downstream cbz w0, ... at the IOWatchdog-lookup
site (+0x58e0) takes a branch into a _os_crash wrapper that does
brk #1. launchd's _PanicOnCrash → PanicOnConsecutiveCrash = true
flag in com.apple.watchdogd.plist escalates the SIGTRAP to a kernel
panic. The cstring-mangle approach used elsewhere doesn't apply here
because we want this binary to behave as if the sysctl returned 1,
not as if it returned ENOENT.
Patch shape. Two-instruction surgical edit at every site in watchdogd that has the canonical caching shape:
Net effect: the cached byte is forced to 1 regardless of the sysctl
result, and watchdogd's pre-existing "detected virtual machine
environment, exiting..." clean-exit branch runs instead of the trap
path. Two functions in watchdogd match this shape on
iPhone17,3 / iOS 26.1; both are patched.
Code signing. The byte edit invalidates the SHA-256 slot hashes
for the 4 KiB pages containing the modifications in watchdogd's own
CS_CodeDirectory. The patcher recomputes those slot hashes in place
via cfw_macho_codesign.reattest_modified_offsets (4 KiB page size
read from the CD, correct tail-slot length, all present CDs). The
resulting CD mutation also changes the cdHash, but the existing JB
kernel patch patch_amfi_cdhash_in_trustcache accepts any cdHash, so
AMFI's trust-cache check still passes at execve. The patcher does NOT
re-sign with ldid — preserving the original Apple-issued code-signing
identifier (com.apple.watchdogd) is required for launchd's boot-task
identity validation; an earlier attempt to re-sign other rootfs
binaries with ldid_sign tripped this check on mobile_obliterator.
Wiring.
scripts/patchers/cfw_macho_codesign.py— standalone-Mach-O page-hash re-attestation (parallel tocfw_dsc_codesign.pybut parsesLC_CODE_SIGNATUREdirectly, uses page size from the CD header, handles short tail slot, updates every present CD).scripts/patchers/cfw_patch_watchdogd.py— capstone-anchored pattern matcher + Keystone-assembled 2-insn patch + slot reattest. Idempotent.scripts/patchers/cfw.py patch-watchdogd <binary>— CLI subcommand.scripts/patch_hv_vmm_userland.sh watchdogd <binary>— thin shim used by the install script.scripts/cfw_install_exp.sh— invokes the patcher at step[EXP-JB-3.5]on the live/mnt1/usr/libexec/watchdogd(scp-down, patch, scp-up, chmod 0755). JB and DEV install scripts do NOT run this step.
DeviceTree /product/camera node addition at fw_patch time (EXP only)
DeviceTreePatcher carries an experimentalNodeAdditions list with one
entry — a /device-tree/product/camera child node. Applied only when
includeIdentityPatches is true (i.e. variant .exp); other variants
leave the product subtree unchanged.
Why this is necessary: libMobileGestalt.dylib resolves
MGGetBoolAnswer("still-camera") (cstring at vmaddr 0x1b1c5fedd) via a
chain that reads IODeviceTree:/product/camera (cstring at vmaddr
0x1b1c5832a, 65 ADRP+ADD xrefs in the same dylib's __text). The
canonical iPhone17,3 D47AP DT carries this node with 64 capability
properties; the vphone600AP DT ships zero camera, audio,
facetime, or back-camera references under /product. Without
the node, SpringBoard's SBAppTags = ["still-camera"] filter hides
Camera.app's icon and refuses to launch the bundle.
The node alone is not enough: the SBAppTags resolver also chains
through /product direct properties (assistant, dictation,
compatible-device-fallback, chrome-identifier, …), most of which
ship as 12-byte 'syscfg/XXXX' cstring placeholders on vphone600 and
read back as NO. Tier 1d (below) rewrites those.
Idempotent: re-running against an already-patched DT detects the existing child and skips.
Camera DSC patch (EXP only)
apply_all_camera_patches in scripts/patchers/cfw_patch_camera_dsc.py
runs two patch families. Symbols are resolved per-build via
ipsw dyld symaddr against the cryptex's dyld_shared_cache_arm64e
header.
Wired into cfw_install_exp.sh immediately after the hv_vmm DSC step,
inside the same hdiutil attach block (one mount/unmount per install).
Page-hash re-attestation keeps the cryptex's CodeDirectory slots
consistent with the modified pages so amfid / TXM accepts the DSC at
next boot.
DeviceTree identity properties at fw_patch time (EXP only)
DeviceTreePatcher carries two property-patch lists: basePropertyPatches
(4 entries — serial-number, home-button-type, artwork-device-subtype,
island-notch-location) applied for every variant, and
identityPropertyPatches (8 entries) applied only when includeIdentityPatches
is true, which FirmwarePipeline sets exactly when variant == .exp.
The 8 EXP-only identity properties (no restore-fatal ones — those go through EXP-JB-6 post-restore):
Root model and root target-type are deliberately NOT in this list —
both have been empirically shown to break restore. Those edits run
post-restore as EXP-JB-6.
Bulk /product direct-property completion (rewriting the ~30
'syscfg/XXXX' placeholders to D47AP integer/string values) was
attempted to make MGGetBoolAnswer("still-camera") answer YES via the
DT path alone. It broke screen rendering on the VM (the display
pipeline / framebuffer pulls one or more /product capability props
during init and chooses a render path the VM can't service). Reverted.
A narrower set targeted at the Camera-icon resolver chain (Tier B + C
- F, below) does work without breaking display.
DeviceTree Camera-icon completion (Tier B + C + F, EXP only)
Two PropertyPatch entries in identityPropertyPatches, three
AddChildNodePatch entries appended to experimentalNodeAdditions
for /product/* children, and five more for /arm-io/* stubs.
Empirically: this is the set that makes Camera.app's icon visible on
the home screen and in Spotlight without breaking screen rendering.
Tier B — /product cam-offset rewrites. vphone600 ships these
as 12-byte 'syscfg/{fcof,rcof}' cstring placeholders. d47ap carries
20-byte little-endian geometry blobs. Consumed by Camera.app / ARKit
/ FaceTime for image-centering math.
Tier C — new /product/* child nodes. d47ap carries three
sibling nodes to /product/camera. vphone600 has none of them.
All property values copied byte-for-byte from
ipsws/iPhone17,3_26.5_23F77_Restore_extracted/Firmware/all_flash/DeviceTree.d47ap.im4p.
Tier F — /arm-io/* minimal camera-flag stubs. d47ap carries
/arm-io/isp (65 props), /arm-io/ispRtb (53 props), and a deep
/arm-io/smc/iop-smc-nub/smc-ext-charger chain (3 levels of node).
vphone600 has none of these paths. We add minimal stub nodes carrying
ONLY the camera-* properties and the mandatory auto-name,
deliberately omitting compatible/device_type/reg/interrupts,
so no IOKit kext finds a matching compatible= and tries to probe
non-existent ISP / SMC hardware.
Dependency order: the patcher walks experimentalNodeAdditions in
array order against the in-memory tree, so each entry that resolves
to a parent added by an earlier entry resolves correctly.
Idempotent: re-running the patcher against an already-modified DT
detects the existing child by name and skips. The DT IM4P that ships
on subsequent boots is signed by Apple but the existing iBSS/iBEC/LLB
image4_validate_property_callback bypass accepts arbitrary payloads.
Post-restore DT identity rewrite (EXP-JB-6, EXP only)
After the restore daemon's BuildManifest identity check has passed,
cfw_install_exp.sh step [EXP-JB-6] scp's devicetree.img4 down from the
mounted rootfs (/mnt5/<boot-hash>/usr/standalone/firmware/), runs
scripts/patchers/cfw_patch_post_restore_dt.py, and scp's the rewritten
img4 back. The Python patcher unwraps the IM4P via pyimg4, parses the
DT flat-binary, rewrites three restore-fatal root properties, and repacks
preserving the IMG4's original IM4M ticket. The iBSS/iBEC/LLB
image4_validate_property_callback bypass (existing JB patch) accepts
the modified payload at next boot.
Idempotent. Skipped if already-rewritten DT is detected.
SystemVersion.plist ProductBuildVersion rewrite (EXP-JB-7, EXP only, opt-in)
Gated on the SPOOF_BUILD env var. When cfw_install_exp.sh is invoked
with e.g. SPOOF_BUILD=23F77, step [EXP-JB-7] runs
scripts/patchers/cfw_patch_build_version.py (plistlib-based,
format-preserving) on both the rootfs and cryptex copies of
SystemVersion.plist to rewrite the ProductBuildVersion key to the
specified id. Without SPOOF_BUILD, the step is skipped and the build
version stays at the original IPSW value.
kern.osversion is unaffected — that comes from a kernel global
initialized from boot args, not from this plist. Userland MG cache
picks up the new build identifier on first boot after the gestalt
cache rebuild.
CFW Installer Flow Matrix (Script-Level)
Summary
Ramdisk Variant Matrix
Cross-Version Dynamic Snapshot
Swift Migration Notes (2026-03-10)
- Swift
FirmwarePatchernow matches the Python reference patch output across all checked components:avpbooter1/1ibss4/4ibec7/7llb13/13txm1/1txm_dev12/12kernelcache28/28ibss_jb1/1kernelcache_jb84/84
- JB parity fixes completed in Swift:
- C23
vnode_getattrresolution now follows the Python backward BL scan and resolves0x00CD44F8. - C22 syscallmask cave encodings were corrected and centralized in
ARM64Constants.swift. - Task-conversion matcher masks and kernel-text scan range were corrected, restoring the patch at
0x00B0C400. jbDecodeBranchTarget()now correctly decodescbz/cbnz, restoring the real_bsd_initrootauth gate at0x00F7798C.- IOUC MACF matching now uses Python-equivalent disassembly semantics for the aggregator shape, restoring the deny-to-allow patch at
0x01260644.
- C23
- C24
kcall10cave instruction bytes were re-verified against macOSclang/as; no Swift byte changes were needed. - The Swift pipeline is now directly invokable from the product binary:
vphone-cli patch-firmware --vm-directory <dir> --variant {regular|dev|jb}vphone-cli patch-component --component {txm|kernel-base} --input <file> --output <raw>is available for non-firmware tooling that still needs a single patched payload during ramdisk packaging- default loader now preserves IM4P containers via
IM4PHandler - DeviceTree patching now uses the real Swift
DeviceTreePatcherin the pipeline - project
make fw_patch,make fw_patch_dev, andmake fw_patch_jbtargets now invoke this Swift pipeline via the unsigned debugvphone-clibuild, while the signed release build remains reserved for VM boot/DFU paths - on 2026-03-11, the legacy Python firmware patcher entrypoints and patch modules were temporarily restored from pre-removal history for parity/debug work.
- after byte-for-byte parity was revalidated against Python on
26.1and26.3forregular,dev, andjb, those legacy firmware-patcher Python sources and transient comparison/export helpers were removed again so the repo keeps Swift as the single firmware-patching implementation.
- Swift pipeline follow-up fixes completed after CLI bring-up:
findFile()now supports glob patterns such asAVPBooter*.bininstead of treating them as literal paths.- JB variant sequencing now runs base iBSS/kernel patchers first, then the JB extension patchers.
- Sequential pipeline application now merges each patcher's
PatchRecordwrites onto the shared output buffer while keeping later patcher searches anchored to the original payload, matching the standalone Swift/Python validation model. apply()now reuses an already-populatedpatchesarray instead of re-runningfindAll(), sopatch-firmware/patch-componentno longer double-scan or double-print the same component diagnostics on a single invocation.- unaligned integer reads across the firmware patcher now go through a shared safe
Data.loadLE(...)helper, fixing the JB IM4P crash (Swift/UnsafeRawPointer.swift:449misaligned raw pointer load). TXMPatchernow preserves pristine Python parity by preferring the legacy trustcache binary-search site when present, and only falls back to the selector24 hash-flags call chain (ldr x1, [x20,#0x38]->add x2, sp, #4->bl->ldp x0, x1, [x20,#0x30]->add x2, sp, #8->bl) when rerunning on a VM tree that already carries the dev/JB selector24 early-return patch.scripts/fw_prepare.shnow deletes stale sibling*Restore*directories in the working VM directory before patching continues, so a freshmake fw_prepare && make fw_patchcannot accidentally select an older prepared firmware tree (for example26.1) when a newer one (for example26.3) was just generated.- 26.0 and 26.0.1 GUI bring-up now patches installed DSC
IOMobileFramebufferonly whenProductVersionstarts with26.0:_kern_SwapEndpasses a 0x560-byte state to the 26.1 PCC userclient instead of the 26.0/26.0.1 0x548-byte state. JB host installs were validated on17,3_26.0_23A341,17,3_26.0.1_23A355, and unchanged17,3_26.1_23B85.
- IM4P/output parity fixes completed after synthetic full-pipeline comparison:
IM4PHandler.save()no longer forces a generic LZFSE re-encode.- Swift now rebuilds IM4Ps in the same effective shape as the Python patch flow and only preserves trailing
PAYPmetadata forTXM(trxm) andkernelcache(krnl). IBootPatcherserial labels now match Python casing exactly (Loaded iBSS,Loaded iBEC,Loaded LLB).DeviceTreePatchernow serializes the full patched flat tree, matching Pythondtree.py, instead of relying on in-place property writes alone.
- Synthetic CLI dry-run status on 2026-03-10 using IM4P-backed inputs under
ipsws/patch_refactor_input:- regular: 58 patch records
- dev: 69 patch records
- jb: 154 patch records
- Full synthetic Python-vs-Swift pipeline comparison status on 2026-03-10 using
scripts/compare_swift_python_pipeline.py:- regular: all 7 component payloads match
- dev: all 7 component payloads match
- jb: all 7 component payloads match
- Real prepared-firmware Python-vs-Swift pipeline comparison status on 2026-03-10 using
vm/aftermake fw_prepare:- historical note: the now-removed
scripts/compare_swift_python_pipeline.pycloned only the prepared*Restore*tree plusAVPBooter*.bin,AVPSEPBooter*.bin, andconfig.plist, avoidingNo space left on devicefailures from copyingDisk.imgaftermake vm_new. - regular: all 7 component payloads match
- dev: all 7 component payloads match
- jb: all 7 component payloads match
- historical note: the now-removed
- Runtime validation blocker observed on 2026-03-10:
NON_INTERACTIVE=1 SKIP_PROJECT_SETUP=1 make setup_machine JB=1reaches the Swift patch stage and reports[patch-firmware] applied 154 patches for jb, then fails when the flow transitions intomake boot_dfu.make boot_dfuoriginally failed at launch-policy time with exit137/ signal9because the releasevphone-clicould not launch on this host.amfidontwas then validated on-host:- it can attach to
/usr/libexec/amfid - the initial path allow rule failed because
AMFIPathValidatorreports URL-encoded paths (/Volumes/My%20Shared%20Files/...) - rerunning
amfidontwith the encoded project path and the release-binary CDHash allows the signed releasevphone-clito launch - this workflow was packaged as
make amfidont_allow_vphone/scripts/start_amfidont_for_vphone.sh superseded 2026-09-23.amfidontis gone, and so is the path/CDHash allowlist it provided — on macOS 26 amfid carriescom.apple.developer.hardened-process, which gates the debugger operations a per-validation decision needs.vphone-letmeinreplaces it with a global switch held open only for the length of one launch, andvphone-cli(now unentitled, so it always starts) drives that itself. The URL-encoding detail above no longer applies: nothing matches on paths any more.- That correction was itself wrong, and is reversed (2026-09-23, later the same day).
amfidontis the supported route again andvphone-letmeinis deleted. Two things were measured:vphone-letmeinopened its window by writing amfid's__TEXT. Where the host enforces code signing system-wide the kernel validates that page on the next fault, finds no signature, and kills amfid —EXC_BAD_ACCESS/SIGKILL(Code Signature Invalid), termination namespaceCODESIGNING, indicator "Invalid Page" — and the guest isSIGKILLed too because amfid never answered. Measured twice on macOS 27.0 (26A428) arm64e: the patch lands, the read-back verifies, and the child still dies. The gate issysctl vm.cs_system_enforcement, which reads 1 there and is read-only.- The claim that a per-validation decision is impossible was reasoning from that tool's own position.
amfidontdrives amfid through LLDB, anddebugserverholds the Apple-private debugger entitlements; arm64 breakpoints live in the CPU's debug registers, so no page is ever dirtied. That is why it works under enforcement and why it can scope to one binary.amfidontis an allowlist, not a global switch — the "global switch" wording belonged tovphone-letmeinand must not be carried onto it.
- The URL-encoding detail above is therefore live again for
--path, and is sidestepped entirely by allowing the CDHash instead, which is what the project documents. Allowvphone-vm, nevervphone-cli: the entry point carries no entitlements and amfid never objects to it.- install (measured on this machine, 2026-09-23):
xcrun python3 -m pip install --user amfidont→~/Library/Python/3.9/bin/amfidont. Apple's/usr/bin/python3is 3.9 and the tool re-execs Xcode's python3, so Xcode is required; Homebrew's python refuses under PEP 668. - run:
sudo amfidont daemon --cdhash "$(codesign -dv --verbose=4 <path>/vphone-vm 2>&1 | sed -n 's/^CDHash=//p' | head -1)" --spoof-apple --verbose. With it running,vphone-vm --helpexits 0 instead of beingSIGKILLed, and amfid stays alive. - it is not a dependency of this repo. Nothing here installs, spawns or supervises it, no Makefile target wraps it, and no Python is added to the tree for it —
vphone-clionly detects the refusal and prints the command.
- install (measured on this machine, 2026-09-23):
- it can attach to
- With launch policy bypassed,
make boot_dfuadvances into VM setup, emitsvm/udid-prediction.txt, and then fails withVZErrorDomain Code=2 "Virtualization is not available on this hardware." VPhoneAppDelegatestartup failure handling was tightened so these fatal boot/DFU startup errors now exit non-zero;make boot_dfunow reportsmake: *** [boot_dfu] Error 1for the nested-virtualization failure instead of incorrectly returning success.- The host itself is a nested Apple VM (
Model Name: Apple Virtual Machine 1,kern.hv_vmm_present=1), so the remaining blocker is lack of nested Virtualization.framework availability rather than firmware patching or AMFI bypass. boot_binary_checknow uses strict host preflight and fails earlier on this class of host withmake: *** [boot_binary_check] Error 3, avoiding a wasted VM-start attempt once the nested-virtualization condition is already known.- Added
make boot_host_preflight/scripts/boot_host_preflight.shto capture this state in one command:- model:
Apple Virtual Machine 1 kern.hv_vmm_present:1- SIP: disabled
allow-research-guests: disabled- current
kern.bootargs: empty - next-boot
nvram boot-args:amfi_get_out_of_my_way=1 -v(staged on 2026-03-10; requires reboot before it affects launch policy) spctl --status: assessments enabledspctl --assessrejects the signed release binary- unsigned debug
vphone-cli --help: exit0 - signed release
vphone-cli --help: exit137 - freshly signed debug control binary
--help: exit137
- model:
Automation Notes (2026-03-06)
scripts/setup_machine.shnon-interactive flow fix: renamed local variablestatustoboot_statein first-boot log wait and boot-analysis wait helpers to avoid zshstatusread-only special parameter collision.scripts/setup_machine.shnon-interactive first-boot wait fix: replaced(( waited++ ))with(( ++waited ))inmonitor_boot_log_untilto avoidset -eabort when arithmetic expression evaluates to0.scripts/jb_patch_autotest.shloop fix for sweep stability underset -e: replaced((idx++))with(( ++idx )).scripts/jb_patch_autotest.shzsh compatibility fix: renamed per-case result variablestatustocase_statusto avoidstatusread-only special parameter collision.scripts/jb_patch_autotest.shselection logic update:- default run now excludes methods listed in
KernelJBPatcher._DEV_SINGLE_WORKING_METHODS(pending-only sweep). - set
JB_AUTOTEST_INCLUDE_WORKING=1to include already-working methods and run the full list.
- default run now excludes methods listed in
- Sweep run record:
setup_logs/jb_patch_tests_20260306_114417(2026-03-06): aborted at[1/20]withread-only variable: statusinjb_patch_autotest.sh.setup_logs/jb_patch_tests_20260306_115027(2026-03-06): rerun afterstatusfix, pending-only mode (Total methods: 19).
- Final run result from
jb_patch_tests_20260306_115027at2026-03-06 13:17:- Finished: 19/19 (
PASS=15,FAIL=4, all failsrc=2). - Failing methods at that time:
patch_bsd_init_auth,patch_io_secure_bsd_root,patch_vm_fault_enter_prepare,patch_cred_label_update_execve. - 2026-03-06 follow-up:
patch_io_secure_bsd_rootfailure is now attributed to a wrong-site patch inAppleARMPE::callPlatformFunction("SecureRoot"gate at0xFFFFFE000836E1F0), not the intended"SecureRootName"deny-return path. The code was retargeted the same day to0xFFFFFE000836E464and re-enabled for the next restore/boot check. - 2026-03-06 follow-up:
patch_bsd_init_authwas retargeted after confirming the old matcher was hitting unrelated code; keep disabled in default schedule until a fresh clean-baseline boot test passes. - Final case:
[19/19] patch_syscallmask_apply_to_proc(PASS). - 2026-03-06 re-analysis: that historical
PASSis now treated as a false positive for functionality, because the recorded bytes landed at0xfffffe00093ae6e4/0xfffffe00093ae6e8inside_profile_syscallmask_destroyunderflow handling, not in_proc_apply_syscall_masks. - 2026-03-06 code update:
scripts/patchers/kernel_jb_patch_syscallmask.pywas rebuilt to target the real syscallmask apply wrapper structurally and now dry-runs onPCC-CloudOS-26.1-23B85 kernelcache.research.vphone600with 3 writes:0x02395530,0x023955E8, and cave0x00AB1720. User-side boot validation succeeded the same day.
- Finished: 19/19 (
- 2026-03-06 follow-up:
patch_kcall10was rebuilt from the old ABI-unsafe pseudo-10-arg design into an ABI-correctsysent[439]cave. Focused dry-run onPCC-CloudOS-26.1-23B85 kernelcache.research.vphone600now emits 4 writes: cave0x00AB1720,sy_call0x0073E180,sy_arg_munge320x0073E188, and metadata0x0073E190; the method was re-enabled in_GROUP_C_METHODS.- Observed failure symptom in current failing set: first boot panic before command injection (or boot process early exit).
- Post-run schedule change (per user request):
- commented out failing methods from default
KernelJBPatcher._PATCH_METHODSschedule inscripts/patchers/kernel_jb.py:patch_bsd_init_authpatch_io_secure_bsd_rootpatch_vm_fault_enter_preparepatch_cred_label_update_execve
- commented out failing methods from default
- 2026-03-06 re-research note for
patch_cred_label_update_execve:- old entry-time early-return strategy was identified as boot-unsafe because it skipped AMFI exec-time
csflagsand entitlement propagation entirely. - implementation was reworked to a success-tail trampoline that preserves normal AMFI processing and only clears restrictive
csflagsbits on the success path. - default JB schedule still keeps the method disabled until the reworked strategy is boot-validated.
- old entry-time early-return strategy was identified as boot-unsafe because it skipped AMFI exec-time
- Manual DEV+single (
setup_machine+PATCH=<method>) working set now includes:patch_amfi_cdhash_in_trustcachepatch_amfi_execve_kill_pathpatch_task_conversion_eval_internalpatch_sandbox_hooks_extendedpatch_post_validation_additional
- 2026-03-07 host-side note:
- reviewed private Virtualization.framework display APIs against the recorder pipeline in
Sources/VPhoneVirtualMachineKit/HostDevices/VPhoneScreenRecorder.swift. - replaced the old AppKit-first recorder path with a private-display-only implementation built around hidden
VZGraphicsDisplay._takeScreenshotWithCompletionHandler:capture. - added still screenshot actions that can copy the captured image to the pasteboard or save a PNG to disk using the same private capture path.
make buildis used as the sanity check path; live VM validation is still needed to confirm the exact screenshot object type returned on macOS 15.
- reviewed private Virtualization.framework display APIs against the recorder pipeline in
- 2026-03-15 tooling source sync update:
- removed ad-hoc
git clonesource fetching fromScripts/setup_tools.shandscripts/setup_libimobiledevice.sh. - added pinned git-submodule sources under
scripts/repos/for:trustcache,insert_dylib,libplist,libimobiledevice-glue,libusbmuxd,libtatsu,libimobiledevice,libirecovery,idevicerestore. - setup scripts now initialize required submodules via
git submodule update --init --recursive <path>and stage build copies under local tool build directories.
- removed ad-hoc
- 2026-06-15 cloudOS 26.5 (23F77) JB retargeting — P0 (sudo/setuid):
- JB-04
patch_hook_cred_label_update_execve(P0, sudo/setuid) — FIXED. Root cause:findVfsContextCurrentByShape()pinned a 5-word prologue ending inldr x1, [x0, #0x3E0]; the uthread offset drifted to#0x3F0on 26.5 (0x3E8on macOS 26.5.1 KDK), so the exact match returned 0 hits. Fix: resolvevfs_context_currentgenerically — symbol first, else the stable 4-word prologue prefix (pacibsp; stp x29,x30,[sp,#-0x10]!; mov x29,sp; mrs x0,tpidr_el1) followed by anyldr x1,[x0,#imm](imm left unpinned); uniqueness still required. Reveal: on the decompressed kernelcache the prefix matches 5 sites, exactly one followed by anldr x1,[x0,#imm]→vfs_context_current@ va0x8D7F39C(foff0x1D7B39C). Validated viamake test_jb_patches: bothjb.hook_cred_label.{ops_retarget,c23_cave}emit. - Symbol oracle for the above: macOS 26.5.1 KDK (
KDK_26.5.1_25F80) —kernel.release.vmapple+Sandbox.kext/AMFI.kext(arm64e) carry full nlist symbol tables for the XNU/Sandbox functions stripped from the vphone600 cache. - Remaining 26.5 JB failures (8) still open:
task_conversion_eval(inlined),proc_security_policy+proc_pidinfo(shared_proc_infoswitch refactored,cmp #0x21bound gone),io_secure_bsd_root(iOS-only, absent from KDK),mac_mount,spawn_validate_persona(iOS-only),vm_map_protect,kcall10/sysent.
- JB-04
- 2026-06-16 cloudOS 26.5 (23F77) JB retargeting — the 8 remaining P1 failures, all FIXED.
Ground truth: IDA (idasql) on the decompressed
kernelcache.research.vphone600, symbolicated via the macOS KDK oracle; XNU source cross-check. Validation:make test_jb_patches→ every supported cloudOS kernel applies with 0[-]failures (84 patches each). All anchors are version-independent (semantic/Capstone/call-graph), no pinned offsets/indices.- JB-11
proc_security_policy+ JB-12proc_pidinfo(shared root cause). Thesub wN,wM,#1 ; cmp wN,#0x21switch anchor matched TWO sites on 26.5; the naive first-match grabbed the wrong one (decodeWakeReason, lower address). Replaced the wholefindProcInfoAnchorwith two source-backed finders inKernelJBPatcherBase.swift:findProcSecurityPolicy()locates the unique function that loadsPRIV_GLOBAL_PROC_INFO(1002 =0x3EA, a stablebsd/sys/priv.hABI value) intow1ahead ofpriv_check_cred→_proc_security_policy@ va0x927E330(stub entrymov x0,#0; ret);findProcInfoInternal()= its sole caller viablIndex→_proc_info_internal@0x927B38C(proc_pidinfo is now inlined there). proc_pidinfo NOPs the uniqueldr x0,[x0,#0x18]; cbz x0; bl; cbz/cbnz wN; mov w0,#0x16(EINVAL); sub wN,wM,#1guard pair →0x927BDA8/0x927BDB0. - JB-08
task_conversion_eval_internal. Inlined; recovered via the unique"…pineapple on pizza…"panic-string function (task_get_special_port_from_user). The 26.1 matcher failed only because the compare operands swapped (cmp x0,x9vscmp x9,x0). RewrotecollectTaskConversionCandidatesto accept the kernel_task-vs-{X0,X1} compare in EITHER operand order. Unique hitcmp x0,x9 → cmp xzr,xzr@ va0x8D087A8. - JB-??
io_secure_bsd_root.AppleARMPE::callPlatformFunction(refs both"SecureRoot"+"SecureRootName"). The match-bit compare-context moved >0xA0 back (sync code inserted), breaking the old lookback. Re-anchored on the uniquecsel Wd,wzr,Wn,<cond>whoseWnis built askIOReturnNotPrivileged(movk Wn,#0xE000,lsl#16— IOKit error high half).csel w22,wzr,w9,ne → mov w22,#0@ va0x7B30E10. Dropped the pinned[x19,#0x11A]field offset. - JB-??
mac_mount. Wrapper still uniquely identified by the twin gates amongmount_commoncallers (__mac_mount@0x8EC04F0). Site 1 (tbnz wFlags,#5 → mov w?,#1preboot reject) unchanged → NOP @0x8EC06FC. Site 2 folded on 26.5:add x?,#0x70 ; ldrb w8,[x?,#1] ; tbz w8,#6→ldrb w8,[x16,#0x71] ; tbnz w8,#6. Re-anchoredfindStateGateon theldrb wN,[x,#imm] ; tbz/tbnz wN,#6pair (the#6role bit is the stable semantic) and clear the loaded reg →mov x8,xzr@0x8EC072C. - JB-??
spawn_validate_persona@0x91C0D4C(reached from the spawn entitlement wrapper, intact). The trailingmov x?,#0 ; ldr x?,[x?,#0x490] ; casacorroboration lowered differently on 26.5; re-anchoredmatchPersonaHelperon the dual sibling rejectldr [base,#8];cbz / ldr [base,#0xc];cbz(same base + same deny target + denymov w?,#1), preceded by the[_,#0x18]sibling guard. NOP both cbz →0x91C0DF8/0x91C0E00. - JB-25
vm_map_protect@0x8DCA0A8. The 26.1mov #6;bics;b.ne;tbnz#22;and #~Xblock was recompiled; the per-entry apply path now narrows protection with a runtime W^X mask register beforepmap_protect_options(lsr wT,wFlags,#7 ; and w3,wT,wMask,mov wMask,#5). Widening the mask#5 → #7makes the AND a pass-through so the requested protection (incl. the stripped bit) reaches the pmap — strictly permissive (prot&7 ⊇ prot&5).mov w27,#5 → mov w27,#7@0x8DCA30C. - JB-??
kcall10/ sysent.findNosys()matched an unrelated tinymov w0,#0x4e; retstub; the real_nosysis a large handler the sysent rows actually point to (112/558 entries). RewrotefindSysentTable()to find the table STRUCTURALLY (no_nosysdependency): the longest run of valid 24-bytesysentrows (chained auth-rebasesy_callinto __TEXT_EXEC + sanesy_return_type/sy_narg/sy_arg_bytes). Base @ foff0x7693B0(558 rows);sysent[439](SYS_kas_info) @ foff0x76BCD8; cave + 3 entry writes emit.
- JB-11
The cryptex merge stops shelling out (2026-09-23)
Eleven subprocess call sites in CryptexFilesystemPatcher* were doing work this
process can do directly. Two of them changed what lands on the volume, so they are
recorded here rather than only in the commit.
tar → VPhoneArchive. The last three runProcess("/usr/bin/tar", …) calls in
the package are gone: cfw_input.tar.zst into the scratch directory, iosbinpack64.tar
and the former AppleParavirtGPUMetalIOGPUFamily.tar onto the mounted volume. Those three used
VPhoneArchiveExtractor with the .ontoGuestVolume preset, so --zstd was no longer
passed (the filter is detected, and libzstd is static in the xcframework).
- Behaviour change:
.ontoGuestVolumeincludes--no-overwrite-dir, which the two guest-volumetarcalls did not pass — they passed only--preserve-permissions. Directories that iosbinpack64 and the GPU bundle shared with the system volume kept their own mode, owner and mtime instead of taking the archive's. That is whatcfw_install.shhad always passed GNU tar (--preserve-permissions --no-overwrite-dir); the Swift port dropped the second flag, and that restored shell parity at the time. - Ownership and exact modes are unchanged: the step runs as root, where
/usr/bin/tar -xfalready implied-pand restored numeric owners. That is why the scratch-directory extraction also used.ontoGuestVolumedespite the name — the preset is what bsdtar did as root, and several of those members were copied onto the volume verbatim.
GPU bundle now comes from the selected PCC image (2026-09-24)
The application no longer ships AppleParavirtGPUMetalIOGPUFamily.tar. During
fw prepare, vphone-cli creates a temporary PV=3 VM, boots it in DFU, and
restores the selected cloudOS IPSW through its own VPhoneRestoreBridge. The
preparer copies AppleParavirtGPUMetalIOGPUFamily.bundle from the sealed System
volume into the VM restore tree and removes the temporary VM. This is the
default path; --gpu-driver-bundle reuses a validated bundle instead. Both
cloudOS 26.1 and 26.4 PCC AEA key URLs returned HTTP 403 on 2026-09-24, so
the preparer no longer requests those keys before the temporary restore.
The temporary VM has no network device (2026-10-13): the restore runs over
the virtual USB device, and with the default nat NIC the Mac's shared vmnet
network and DHCP service came up for some seconds after the restore.
Both the JB host-mount installer and the filesystem merge copy this staged
bundle into the iPhone guest. The 26.4 23E5207q restored bundle has
DTPlatformVersion=26.4, CFBundleVersion=64.4.4, and binary SHA-256
29ba36c7bc87d82c40fa1cecdf357e7a383fa70eb46df0d75869f5ac562aedbc.
It lacks the compiler-plugin dylib present in the 26.1 bundle. A first boot
without it connected vphoned but left the VM window black: MTLCompilerService
repeatedly aborted in messageHandler, and backboardd reported
XPC_ERROR_CONNECTION_INTERRUPTED after repeated Metal compilation attempts.
The repository now builds Siblings/GraphicLoader/main.mm (the 0xjohnnydev compiler
plugin reimplementation) into an arm64e iPhoneOS dylib. build.sh signs and
compresses it into vphone-cli.app/Contents/Resources/gpu/compiler-plugin.tar.zst.
fw prepare decompresses and merges that dylib into the firmware-sourced GPU
bundle, and both JB installation paths require it. This adds a guest payload;
it does not patch the Apple GPU driver binary.
find -name '._*' -delete → deleteAppleDoubleFiles, and this one was nearly a
silent regression worth writing down. On a volume with native extended attributes,
Foundation does not show ._* entries at all: FileManager.contentsOfDirectory and
enumerator(at:) both omit them, because Foundation reads such a name as the partner
file's metadata rather than as a file. fileExists(atPath:) compounds it by answering
true for a ._x that is not in the directory. Measured on APFS:
So the first port of this sweep — a FileManager.enumerator walk — found nothing,
deleted nothing and reported success, on exactly the files it exists to remove. The
shipped version walks with opendir/readdir and removes with unlink(2), which is
what find did. chownRecursively shares that walk for the same reason. Both are
covered by Tests/FirmwarePatcherTests/CryptexFileOpsTests.swift, whose fixtures are
made with open(2) and asserted with readdir — a Foundation-built fixture would make
the test pass by having nothing to find.
The rest are like-for-like: chmod → FileManager.setAttributes, chown -R 0:0 →
lchown over that walk (BSD chown -R defaults to -P, so symlinks are not followed),
ln -sf → createSymbolicLink after removing what was there, with the one deliberate
difference that a real directory in the link's place is now an error instead of ln(1)'s
silent nested link. diskutil image resize --plist | plutil -extract max raw loses the
shell and plutil: the plist is parsed in-process, starting at <?xml so that a warning
on diskutil's merged stderr can no longer be handed back as the --size argument.
iOS 26.4 location delivery uses an app hook (2026-09-25)
The guest's locationd accepts simulation requests but did not deliver a usable
fused fix to Maps. This change adds no Apple binary patch. SystemHook loads
libvlocation.dylib into app processes, where it supplies the vphoned-published
coordinate through CLLocationManager for authorized clients. The location
state is an atomically replaced JSON file, and removal restores the native
path. Maps showed the Tokyo and Apple Park coordinates in the running 26.4 VM;
the automatic SystemHook injection path was verified after relaunching Maps.
Removed (2026-09-28). The hook worked around a symptom of the former EXP
patches (issue #438), which standard now leaves off; see the note at the top.
SystemHook no longer loads libvlocation.dylib, the bundle no longer ships it,
and vphoned's location.* methods call IcliKit directly again. A guest that
already has /usr/lib/libvlocation.dylib keeps the file, but nothing loads it.
fw patch re-patches the originals, not its own output (2026-09-30)
No new Apple binary patch. This changes how every boot-chain patch in this document is applied, so it is recorded here.
The defect. FirmwarePipeline patched each component in place: load the
file, run the patchers, save over the same path. Run fw patch a second time on
the same VM and the patchers were handed the first run's output, found none of
the shapes they had already replaced, and the component failed —
Patch site not found: iBSS, taking the whole run down at the second component.
fw prepare refuses to re-extract over an existing restore tree
("A restore tree already exists at …. Remove it, then prepare the firmware
again."), so there was no recovery path either: a VM could be patched exactly
once, for its whole life. Editing a VM's PatchSelection.plist to turn a patch
off and re-running was therefore impossible, which is why the only way to test a
preset change was to build a new machine, and why hand-editing the recorded
PatchPlan.plist was being used to get around it.
Not the same defect as issue #532's second failure. That one was a single
patcher, DyldSharedCacheMISTrustAuthPatcher, not recognising its own post-patch
shape on 27.0 — a DSC patcher, over a cache cfw install handles, fixed on
2026-09-30 by teaching locateSite the pacibsp ; mov x0,#0 ; retab form (see
row 17). This one is structural and sits above every boot-chain patcher: it
would bite even if each patcher were perfectly self-recognising, because nothing
kept the bytes they were meant to match against.
The fix. FirmwarePipelineOriginals.swift. The first run copies each
boot-chain file into <vmDirectory>/FirmwareOriginals/, mirroring its path
under the VM bundle, before anything is written; every later run loads from that
copy. fw patch is idempotent by construction — same VM, same plan, same bytes
on disk however many times it runs — and the pristine container is also put back
immediately before loader.save, because ContainerFirmwareLoader.save
repackages the IM4P it finds at the destination and would otherwise wrap the
second run's payload in the first run's container.
The stash is a direct child of the VM directory and never of the restore tree,
so findRestoreDirectory (which matches a directory name containing
"Restore") and the component globs (rooted at the restore tree, or
non-recursive in the VM root) cannot resolve a component to its own copy.
Turning a patch off now reverts it. When the resolved plan selects nothing for a component — either every patch blocked, or the whole patch set dropped so no patcher is built at all — the unpatched image is copied back. A component nobody has ever patched is not rewritten, so its modification date stays where the restore left it.
Two components opt out (restorable: false): Filesystem
(CryptexFilesystemPatcher) and Manifest (ManifestHashPatcher). Both name
BuildManifest.plist, but neither is a patcher over that one file — the first
rewrites cryptex images across the restore tree, the second rewrites hashes that
describe files other steps produced. Restoring the manifest alone would describe
a tree that no longer exists. Both run only for the minimal preset (see "The minimal
preset" below), which names no boot chain, kernel or TXM set: a minimal run over
a CFW-patched VM puts that boot chain back, which is what the restore image
expects.
Existing VMs. A machine patched by an earlier build has no stash, so the
first run under the new code would adopt its already-patched bytes as the
"original". That case is detected rather than accepted: if the component then
fails to patch, the copy is deleted — so it never becomes the baseline — and the
error says the VM was patched by a build that kept no originals and that the
restore tree must be removed and fw prepare re-run. Only patchSiteNotFound is
rewritten this way; every other failure means what it says and passes through.
Covered by FirmwarePatcherTests/Pipeline/FirmwarePipelineOriginalsTests.swift
(7 tests), which drives patchComponents over a synthetic component whose
patcher flips one byte and — like every real patcher — reports no site once that
byte is flipped. The real boot chain needs firmware fixtures that are not in the
repository. The same harness with restorable: false reproduces the old
failure, so the test for the fix and the test for the defect differ only in the
descriptor.
The MIS online-authorization patch leaves standard (2026-09-30)
No new Apple binary patch. Row 17 (mis_trust_auth) is now off by default
and renamed dyld-cfw-mis_trust_auth → dyld-exp-mis_trust_auth, which the
naming rule requires: effect is exp for a patch standard leaves off.
Renamed with it: DyldSharedCacheMISTrustAuthPatcher.patchID and the
on(...) gate in VPhoneCustomFirmwareInstaller. The cfw patch-mis-trust-auth
verb and the patcher itself are unchanged and still work when the patch is
ticked on.
What forced it. On a pristine 24A435 cache — iPhone17,3_27.0_24A435 +
cloudOS 26.4-23E5207q, first cfw install on a fresh VM — the patcher cannot
find its site at all:
and cfw install dies there, after the lockdown-mode patch and before
everything that follows. The cache really was pristine: the same log shows a
first-time maxSlide 0x20000000 -> 0x0 and fresh writes from the lsd, libxpc
and lockdown-mode patchers. So this is not the already-patched-anchor case
fixed earlier the same day for 26.x — 24A435's checkTrustAndAuthorization
genuinely matches neither shape locateSite accepts.
And the message was wrong. MIS has not been rewritten. Measured on the
pristine 24A435 SystemOS cryptex (043-70113-702.dmg.aea, decrypted with
fw aea-key + /usr/bin/aea and mounted read-only; cfw patch-mis-trust-auth --dry-run against it reproduces the failure verbatim, same VMA):
A whole-image decode of libmis (94,984 instructions, 0x224060000–0x2240BCC23)
finds zero mov-family instructions with immediate 0x8026 and zero raw
0xE8008026 words: on 24A435 the constant is never written literally at all.
Everything else is as the patcher expects — the naming literal at 0x2240BCC23
occurs once, has exactly one adrp+add reference (0x22406FB1C) and that
reference is inside the function; the nearest preceding pacibsp is the
function start itself, and the instruction before it is an unconditional b.
So the location routes were right and only findSeededError missed.
findSeededError now accepts both, and the two are not interchangeable. The
0x8026 low half stands on its own. The 0x8001 low half is only the bottom of
the MIS error range and proves nothing by itself, so it is accepted only when
the same function also contains an add w<result>, w<seed>, #imm that
arithmetically equals 0xE8008026; the scan stops at the next function's
pacibsp. That corroboration is not decoration: the function preceding
checkTrustAndAuthorization on 24A435 carries the identical
mov w8, #0x8001 ; movk w8, #0xe800 idiom 18 instructions earlier, which is
also why the seed window stays forward-only from the function start. Matching is
on Capstone-decoded immediates and registers throughout, and nothing new is
written, so no new encoder and no keystone trip.
A version gate went on the declaration with it. experimental is
Kind = All, so without one it would still turn this patch on for a 27 guest
and produce an unbootable VM — and after the matcher fix it would now succeed in
doing so. applicability is iOSBase: .oneOf([.major(18), .major(26)]). That
is not a preference (a preference belongs in a preset's block list, and
standard blocks it too) but the statement applicability exists for: applying
it on 27 breaks the guest. An unreadable base satisfies only .any, so an
unknown release skips the patch, which is the safe direction.
Why teaching the patcher the new shape is not, by itself, the answer. Even
when it applies, this patch stops an iOS 27 guest booting (issue #532:
TXM [Error]: Errno: selector: 45 | 78 → dyld[1]: Library not loaded: /usr/lib/libSystem.B.dylib … (no such file, no dyld cache) → initproc failed to start). Making it apply on 24A435 without also taking it out of standard
would have converted a failed install into a guest that installs and then does
not boot. Both were done, in that order.
VERIFIED (2026-09-30): with the patch out of standard, cfw install test-27.0 completes, and iPhone17,3_27.0_24A435 + cloudOS 26.4-23E5207q
boots clean — panicked: false, vphoned answering 6s after launch,
SpringBoard running (pid 36), apps.list returning 264 apps. Issue #532 is
closed, and its cause is confirmed to have been this patch rather than
anything else in the 27 install.
What replaced it. libmisfix.dylib already reaches the same outcome from
userspace, and by the better route — it steers the call rather than forging the
return. vpWidenedOptions in VPhoneGuestComponents/MISFix/MISFixSignature.c
sets RespectUppTrustAndAuthorization = kCFBooleanFalse in the options
dictionary; libmis calls checkTrustAndAuthorization only when that flag is
set, so 0xE8008026 is never produced, and because the ordinary success path
still runs, the info dictionary (CdHash, Entitlements, SignerType,
TeamID, SigningID, ProfileUUID) is filled for real. No cache page is
written and nothing is re-attested, so there is nothing for TXM to reject.
The gap this leaves, stated plainly. cfw install injects libmisfix.dylib
into installd and misagent only, so the hook covers installation. An app
signed with a free personal-team certificate is launched by SpringBoard,
which asks MIS itself and is not hooked, so on a 26.x base that launch can still
hit 0xE8008026 — a regression against the 2026-09-30 on-device validation of
row 17 on 26.6.2. Ad-hoc / ldid-signed apps are unaffected either way: they
carry no provisioning profile, so the online-authorization branch is not
reached, and AllowAdHocSigning is what they need. Paid-team profiles were
never affected. Closing the SpringBoard gap means injecting the same hook there,
which cannot be done with a load command (SpringBoard's load-command padding at
0x548 is not free — inserting would overwrite 56 bytes of the first section)
and so has to go through SystemHook's posix_spawn interposition.
Effect on the shipped presets. standard adds dyld-exp-mis_trust_auth to
its block list, mirrored in FirmwarePatchSetCatalog.manualOnlyPatches via the
new FirmwarePatchSetCatalog.misTrustAuthPatch; The shipped preset plists match the built-in copies checks the two agree. experimental is Kind = All and so
still turns it on, which is correct: that preset is documented as everything the
bundle declares, including patches a 26.4 guest does not survive.
An interpose does not cross the shared cache (2026-09-30)
This supersedes the paragraph above that says the hook "covers
installation". It does not. libmisfix.dylib reaches misagent and nothing
else that matters, and no version of it can reach installd, because
__DATA,__interpose replaces call sites in the images dyld links and every
call site on installd's path is inside the cache:
Measured on test-26.4, libmisfix[726], with LogQueries on, the query log
carrying each caller's image (MISFixCallerImage, dladdr on the return
address) and the validation log made unconditional. One
devicectl device install app of a paid-team-signed AirBuild.app produced
exactly one line from installd:
from installd is the finding: the only call the hook catches is the one the
main executable makes itself. misagent works for that reason and no other —
its own binary calls MGCopyAnswer, three times per install, each answered
-> override.
What installd did instead, in the same capture:
Three things follow, and each corrects something previously written here:
- The signature was never the problem.
cdhash … is trusted. The failure is the profile: libmis walked the installed profiles and skipped every one with0xE8008012— this device is not inProvisionedDevices— leaving0xE8008015, "a valid provisioning profile for this executable was not found". - libmis resolved a UDID without going through the interpose. No
MGCopyAnswer(UniqueDeviceID)line exists from installd, yet the comparison plainly happened. Its other route is closed —amfi_interface_query_bootarg_statereturnsENOSYS, so theamfi_emulate_device_udidpath libmis prefers is dead on this guest and the MobileGestalt fallback is what ran. AllowAdHocSigninghas never taken effect in installd. NoMISValidateSignatureAndCopyInfoline appears either, from a log that no longer returns early on an unconvertible path argument.MICodeSigningVerifierlives in MobileInstallation, not in installd, so that call is cache-to-cache too. The measured table at the top ofMISFixSignature.cwas taken by calling libmis directly; it is still true of libmis and was never reached through installd.
DYLD_INSERT_LIBRARIES does not change this. Commit d44a0e9 had already
moved libmisfix from a LC_LOAD_WEAK_DYLIB of installd to SystemHook's insert
list, which is the strongest position an interpose can hold, and the capture
above is from that arrangement.
What can still work. Three routes, in the order they were judged:
- Rewrite the callee, not the call sites. A detour at the top of
MGCopyAnswerandMISValidateSignatureAndCopyInfois reached by every caller, cache-internal or not. It stays a guest dylib, socfw update-environmentdeploys it to an existing VM and no cache page is written — nothing for TXM to reject, which is what makes it preferable to a new libmis patch after #532. It needs the process to make a cache text page writable (copy-on-write) and to obtain executable memory for the trampoline;MISFixCacheWriteProbe.cmeasures both behindProbeCacheWrite, in installd only. - A shared-cache patch on libmis, forcing the
ProvisionedDevicescheck to pass and the ad-hoc option on. Same family as row 17, so the same risk: row 17 is the patch that stopped a 27.0 guest booting. - Give the VM the right UDID instead of lying about it. A modern UDID is
<chip-id>-<ECID>;chip-idis fixed at0x0000FE01by the virtual SoC but the ECID is chosen atvm create, and the SHSH blob is personalised against it either way. A VM created with the ECID of a device the team has already registered needs no hook at all. It does not help an ad-hoc IPA, which has no profile to match.
A mistake in the first probe, recorded because it is easy to repeat. dyld
applies interposing to dlsym as well as to call sites, so
dlsym(RTLD_DEFAULT, "MGCopyAnswer") returns libmisfix's own replacement —
the probe measured its own text and never touched the cache. It then asked for
VM_PROT_WRITE in place of VM_PROT_EXECUTE on the page it was executing
from, which faults on the next instruction fetch and crash-looped installd
until the flag was cleared.
A handle-scoped dlsym is interposed too — measured, on a handle to
libMobileGestalt itself — so there is no spelling of dlsym that answers this.
What dyld leaves alone is the interposing image's own imports, which is why
MISFixDetour takes an address that libmisfix obtained with &, and refuses
to take a name at all.
An Xcode install works, and what it took (2026-09-30)
Measured on test-26.4, xcrun devicectl device install app with
AirBuild-Debug.ipa — a paid team's app (QDJ93ZUQ9B), signed
Apple Development, whose embedded profile provisions eight real devices and
no VM:
and it launches. So does a codesign --sign - bundle with no certificate and
no profile at all. Four separate refusals had to go, in this order, and each
one was only visible once the one before it was gone.
-
The interpose never ran. Replaced by
MISFixDetour: a four-word absolute jump at the top of the callee, the displaced instructions relocated onto anmmaped trampoline, the target page taken copy-on-write. Installed in installd's own address space, so nothing on disk and no other process changes — which is the whole difference between this and row 17, the libmis cache patch that stopped a 27.0 guest booting. Measured:detour: MISValidateSignatureAndCopyInfoWithProgress at 0x1bf41c830 in libmis.dylib.MISValidateSignatureAndCopyInfoitself is a thunk in front of the…WithProgressbody, shorter than the jump, andMISFixDetourrefuses it withMISFixDetourTooShortrather than write over whatever follows. Its callers are covered anyway, because it branches into the hooked function. -
0xE8008015, no valid profile. Widening the options does not help a CMS-signed app:AllowAdHocSigningis about ad-hoc signatures, and this one is real. The profile has to actually install, and misagent refuses it because a VM's UDID is in noProvisionedDevices. misagent asksMISProfileGetValue(profile, "ProvisionsAllDevices")first and only consults the device list when that is false — soMISFixProfileScope.cdetoursMISProfileGetValueand answers that one keytrue. The profile then installs for real and MIS validates the app against it:Nothing is faked: the signature, the certificate, the entitlements and the cdhash are the ones Apple issued. The only claim widened is which devices the profile covers.
-
0xE8008012from-[MIInstallableBundle _installEmbeddedProfilesWithError:]. Kept as a backstop for a profile that still cannot install, inMISFixInstallPolicy.c: the real implementation runs, and a refusal is logged and turned into "there is no profile" rather than a failed install. -
-[MICodeSigningVerifier performValidationWithError:], line 424, "Failed to extract signer identity". The gate behind the gate, and the one that stops an ad-hoc signature: MIS accepts the bundle and MobileInstallation then wants a CMS leaf certificate out of it, whichcodesign --sign -does not produce.The verifier already knows what to do. It carries
allowAdhocSigningas a settable property — the same shape as the MIS option — and installd never turns it on, soMISFixInstallPolicy.cforces the getter. The real validation then succeeds and fillssigningInfofor real.
Both Objective-C hooks are swizzles, not detours. A method list is data, so replacing an implementation reaches every caller without making any cache text writable; where that is available it is strictly better.
The dead end that proved the shape of the fix
Before allowAdhocSigning was found, performValidationWithError: was forced
to return YES after it had failed. That got no further:
The verifier had bailed before storing anything, so its caller read a nil signing identifier. A refusal can be allowed through; an answer that was never computed cannot be invented. The override was removed once the property made it unnecessary, and the rule generalises to every gate above MIS.
The class's interface came from the runtime, not from a disassembly:
class_copyMethodList and class_copyIvarList printed into the note log.
MISFixInstallPolicy.c still does that, but only when a selector it expects
has gone, which is the one moment the list earns its few hundred lines.
Still refused, deliberately
A bundle with no signature at all (0xE800801C) and an app with no
application-identifier entitlement (MIInstallerErrorDomain 63). Both are
real absences rather than policy, and everything downstream needs what they
are missing. Unsigned bundles reach the guest through vphoned's
apps.install, which re-signs in the container and never involves installd.
A trap in the measurement, not in the guest
Two runs failed with 0xE8008017 on a bundle whose signature was fine. The
IPA had been repacked on the host with zip -r, which writes AppleDouble
._* files next to every resource; they break the sealed resource envelope.
COPYFILE_DISABLE=1 zip -X after deleting them, and the same bundle installs.
Worth remembering before reading 0xE8008017 as a guest-side gate.
One route for libmisfix, SpringBoard included (2026-09-30)
Row 20. On test-27.0 (iPhone17,3 27.0 (24A435)) a paid team's IPA installed
through installd — ValidatedByProfile = true — and was then refused at
launch with "Unable to Verify App":
That is row 17's failure, raised by SpringBoard's own launch-time call into
libmis. libmisfix already declines that check — RespectUppTrustAndAuthorization = false in MISFixSignature.c — but SpringBoard never carried it.
Why 26.4 looked fine. test-26.4's PatchPlan.plist was written before
dyld-exp-mis_trust_auth left standard, so that guest's cache still carried
row 17 and SpringBoard's check was short-circuited there. 24A435 is outside
row 17's gate, so 27.0 was the first guest where the launch depended on the
hook. SpringBoard is started the same way on both, below.
The first fix was a load command, and it was built on a misreading
The first commit (7a41371) linked libmisfix into SpringBoard with
LC_LOAD_WEAK_DYLIB, as installd and misagent then were. SpringBoard's header
has 16 spare bytes on both 23E246 and 24A435 (sizeofcmds 1336, first section
at 1384), so that needed a /mf root alias and an inject-dylib --reclaim-source-version that dropped LC_SOURCE_VERSION to leave the signer
room. It worked — AirBuild launched on test-27.0 after a cold boot — and it
was reverted the same day, because the reason given for it was wrong.
It rested on "SpringBoard does not carry SystemHook", read off
vphone-systemhook.log: SystemHook's constructor writes a line for any
.app/ executable and SpringBoard's never appeared. That log cannot show it.
The first writer is always a root xpcproxy, which creates the file 0644, and
every later line from a mobile process is dropped silently. The only .app/
lines in it came from DeviceOMatic, which runs as root. SignTest, a sandboxed
mobile app with no line either, has /usr/lib/SystemHook-vphone.dylib in the
usedImages of its own crash report.
What SpringBoard actually carries
A temporary probe in libmisfix read each process's KERN_PROCARGS2 — the
environment the kernel copied at exec, which survives dyld pruning a variable
it refuses — alongside environ, dlopen(RTLD_NOLOAD) of SystemHook and
amfi_check_dyld_policy_self. On test-27.0:
So the chain works in SpringBoard; what it lacked was the second entry. Which
libraries to insert is decided in the parent, at the spawn. installd and
misagent are started through xpcproxy, whose SystemHook asks
vpIsMISFixTarget. SpringBoard's job is POSIXSpawnType App and launchd
starts it itself — the launchd hook's spawn log has every neighbouring
xpcproxy child but not SpringBoard's pid — and the launchd hook inserted
SystemHook alone. SystemHook's list naming SpringBoard was never consulted for
it. The _Conclave key on SpringBoard's job turned out to be irrelevant.
One route for libmisfix
vpIsMISFixTargetandvpMISFixFormoved toShared/InjectionEnvironment.h, and the launchd hook asks them as SystemHook does. A target getsVP_MIS_FIXinserted beside SystemHook whichever process starts it, and only when/usr/lib/libmisfix.dylibexists, so a guest without it never names a missing library.vpInsertHookshad a latent bug on that path: with SystemHook already in the environment and only the extra library to add, it built the newDYLD_INSERT_LIBRARIESand then discarded it, because substitution was keyed onaddHookalone. xpcproxy rebuilds its child's environment, so it never showed.make test-injection-environmentcovers it.- No guest binary carries a load command for libmisfix. The declarations
system-installd-cfw-adhoc_signature,system-misagent-cfw-device_identityandsystem-springboard-cfw-launch_authorizationare gone — rows 18–20 below describe what they were — andcfw installrenames each.bakan earlier install left back over the patched binary and removes/mf. The SystemHook route inserted libmisfix into installd and misagent regardless of those declarations, so unticking one never turned the hook off; libmisfix now ships withsystem-launchdaemons-boot-environmentlike the other hooks. inject-dylib --reclaim-source-versionand its test were removed with the only caller that needed them.- SystemHook's logs are opened 0666 and
fchmoded on each open, so the owner widens a file an earlier root writer created. Sandboxed processes still cannot reach/var/mobile/Library/Caches; their absence from the log means nothing, and a crash report'susedImagesis the check.
Inside SpringBoard libmisfix installs the MIS detours only:
MISFixInstallPolicy.c returns at once outside installd, so MobileInstallation
is never loaded into SpringBoard or misagent to be swizzled. SpringBoard is
not in vphoned's udidHookedDaemons: it never asks for the UDID, and stopping
it restarts the home screen.
Validated with the insertion, on fresh VMs (2026-09-30). test-26.4
(iPhone17,3 26.4 (23E246)) and test-27.0 (27.0 (24A435)), both on cloudOS
26.4 (23E5207q), deleted and recreated from local IPSWs with vm create and
this cfw install, so no binary on either ever carried a libmisfix load
command. On both, from the first boot:
SpringBoard's lines now appear in vphone-systemhook.log too. No new .ips
from SpringBoard, installd or misagent. AirBuild installed through installd
(ideviceinstaller) opens on both. The one panic-full on each guest is the
post-restore reboot vm create makes before cfw install has put the dyld
cache in place (initproc failed to start … libSystem.B.dylib), not a boot of
the installed guest.
Validated first with the load command, on test-27.0 (2026-09-30).
after a cold vm stop / vm start: SpringBoard reaches the home screen,
carries libmisfix from boot, no new .ips from SpringBoard, installd or
misagent, and AirBuild — a paid team's dev-signed IPA installed through
installd — opens to its own sign-in screen instead of "Unable to Verify App".
trustd still logs Trust evaluate failure: [leaf IssuerCommonName LeafMarkerOid SubjectCommonName]; that evaluation is what checkTrustAndAuthorization
consumes, and it is no longer asked.
The host is told the configured UDID (2026-09-30)
No binary change. vpIsMISFixTarget (VPhoneGuestComponents/Shared/InjectionEnvironment.h)
adds lockdownd and remoted, so both spawn hooks insert libmisfix.dylib
into them. There only the MobileGestalt interpose acts:
MISFixProcessOnlyNeedsIdentity keeps the two MIS detours out of their copy of
libmis. lockdownd then answers GetValue UniqueDeviceID, and remoted puts in
the RSD handshake, the UDID set through udid.set. The hook now also matches
the obfuscated key remoted asks with (re6Zb+zwFKJNlkQTUeT+/w), and declares
MGCopyAnswerWithError with its real three arguments; the two-argument
prototype crashed remoted.
The third place the host reads the UDID, the USB serial string, is set by
vphoned (VPhoneDaemon/Native/vphoned_usb.m, entitlement
com.apple.private.usbdevice.setdescription), not by a patch.
Validated on test-27.0: idevice_id, lockdown and remotectl show report the
override after udid.set and after a reboot, and the guest's own after
udid.clear. Details and the measurements are in
Research/Guest/xcode_install_signature_gate.md, "The host sees it too".
vphoned paravirtual user-client entitlements (2026-11-01)
No binary change. VPhoneDaemon.entitlements adds four classes to
com.apple.security.iokit-user-client-class, each the research board's
counterpart of a hardware class vphoned already lists:
The platform sandbox profile lets an unsandboxed process open an IOKit user
client only when its iokit-user-client-class or
exception.iokit-user-client-class entitlement names the class. vphoned named
only the hardware classes, so the minimal preset, which has none of
kernel-boot-iouc_macf_gate, kernel-boot-iouc_sandbox_gate and
kernel-cfw-paravirt_user_clients, refused it each paravirtual class. On an
iPhone17,3 26.5 23F77 Minimal guest created with the 2.12.2 bundle, each
first call after a boot logged, per call:
The OCR calls read back the JPEG icli writes and run Vision on it. The codec
class is refused before Vision loads its models, while the image is decoded;
the decode falls back to software (Vision logs the 738×1600 image either way),
so the refusal costs no output. With the first three classes a guest created
the same way logged nothing for screenshots and only the codec class for OCR;
with all four it logged no IOUC line for vphoned through screenshots,
ui.ocr and ui.describe. The minimal merge copies vphoned with the
signature the bundle build gives it and rebuilds the static trust cache over
the result, so the new cdhash is trusted.
The standard preset opens these classes in the kernel and gains nothing
from the entries; on a real device the classes do not exist.
The entries do not make OCR work on that guest. Its text detector,
TextRecognition.framework/cr_td_model_v3_e5.mlmodelc.bundle, ships only as
an H17 (Neural Engine) build; Espresso cannot plan it ("Failed to build the
model execution plan … error code: -2", "Could not initialize V3 detector
model"), and icli answers "System text recognition is unavailable on this
device." It does so with and without the Neural Engine class: with it, the
ANE client also logs "Unknown aneSubType" for the paravirtual device.
A test build of icli that retried with usesCPUOnly failed the same way;
the framework's other detector build, cr_td_model_v3_eir, has a
model.espresso.net but no model.espresso.weights. An iOS 18.6 22G86
Minimal guest with vphoned's earlier entitlements logs the same detector
error, so OCR did not work on Minimal guests before these entries either.
Not handled: Apple's own binaries. The same 26.5 boot logged IOUC AppleVideoToolboxParavirtualizationUserClient failed MACF … Preferences.
Preferences in 18.6, like 73 of the 74 18.6 binaries that list
AGXDeviceUserClient, lists no paravirtual class. Signing those again with
the counterparts added would change Apple binaries, so the guest would no
longer match a device's signatures. VPhoneSigner also leaves the
resource-seal slot of an app's main executable empty, and the list of binaries
would differ from one iOS version to the next. callservicesd and nearbyd,
which are refused AppleVirtIONeuralEngineDeviceUserClient, run under sandbox
profiles of their own, which an entitlement does not change.
Checked again on 2026-11-02 with two minimal guests that also carried the
other Minimal changes of that week (launch-constraint categories in the static
trust cache, a signed launchd.plist, a stock LLB). int-265 (iPhone17,3 26.5
23F77 on cloudOS 23F75, with --allow llb-boot-boot_args --allow ibec-boot-boot_args for a serial log): raw screenshot,
screen.screenshot, ui.ocr and ui.describe from vphoned logged no IOUC
or failed sandbox line for vphoned, where iphone-265.log, a 26.5 Minimal
boot with the earlier entitlements, had IOUC IOSurfaceAcceleratorParavirtClient failed MACF … vphoned. Its one IOUC line
is AppleVirtIONeuralEngineDeviceUserClient failed MACF … callservicesd, an
Apple binary (above). ui.ocr still answered "System text recognition is
unavailable on this device." int-186 (iPhone11,6 18.6 22G86) logged no IOUC
or failed sandbox line for vphoned through the same calls; the 18.6 guests
logged none before the change either.
vphoned launchd service entitlements (2026-10-20)
VPhoneDaemon.entitlements adds com.apple.private.xpc.service-configure
and com.apple.private.xpc.service-attach (true) for services.kickstart.
icli 0.9.1's device regression on iOS 27.0.1 guests found that launchd
refuses kickstart with status 144 unless the caller holds both; icli's own
binary carries them, and a library consumer such as vphoned has to sign
with them itself. Not yet observed on a guest with vphoned:
services.stop and unload were answered 144 without service-configure,
so whether they now succeed is unverified, and the notes that say so
(Research/Guest/service_trimming.md, GuestServiceProfile) are left as
they are until a guest shows otherwise.
vphoned accessibility client entitlements (2026-09-30)
VPhoneDaemon.entitlements adds three true booleans:
com.apple.private.accessibility.inspection, com.apple.accessibility.api,
and com.apple.private.accessibility.look-me-up-setup. All 258 existing
upstream 2.2.3 keys and values are preserved, including the USB entitlement
and keychain access groups. No daemon or AX bootstrap code changes.
Live evidence is from release 2.1.5, not 2.2.3. Its actual daemon was re-signed with only these three additions: all 257 original entitlement values and all 51 file-backed Mach-O sections were unchanged. Strict code signature verification, bundle validation, and preflight passed.
With identical fresh application lifecycles, the original daemon's tree
query and its alias failed with -25215. The re-signed daemon returned
12 nodes for Settings, 23 for Calculator, and 9 for Clock through both tree
routes, with non-null hit-test results. These results repeated after a
second full host-assisted VM boot.
Warm queries can work after another client initializes AX, so they alone do not establish fresh-client readiness. The three keys were tested together; each key's individual necessity was not isolated. Upstream 2.2.3 has passed the profile-preservation assertion but has not been live-tested with this change.
Opt-in native accessibility hierarchy (2026-09-30)
The opt-in native bridge behind ui.tree with nested:true follows private iOS attribute 5001 (immediate children) and checks each edge against attribute 5002 (parent) using CFEqual identity. The existing flat interface continues to use icli's visible-element query; this bridge does not infer ancestry from frames, labels, identifiers, or the visible-element array. Primary attribute mappings are in MCPAXNodeSource.m and MCPAXAttributeBridge.m.
An isolated entitled helper on an iOS 27 research VM returned 176 Settings nodes with 173 matching parent back-references and 3 mismatches, 44 Calculator nodes with 42 matches and 1 mismatch, and 66 Clock nodes with 65 matches and no mismatch. Settings and Calculator are explicitly partial. Tests cover unnamed, offscreen, zero-frame, and frameless structure; live snapshots preserve unnamed containers and repeated objects through snapshot-local references. Node, depth, query, and deadline limits are explicit, and a missing or failed messaging timeout blocks the AX query. Foreground PID stayed unchanged and accessibility switches were restored for each helper run. Temporary services were unloaded and their directories removed. Helper evidence is not an integrated daemon release or cold-start readiness claim.
Installed-daemon cold-read testing subsequently exposed an initial Settings snapshot containing only its unavailable root. The native walker now retries only the first root label copy with error -25215 when this invocation actually enabled accessibility, after one 400 ms readiness wait. It shares the original deadline and query budget, preserves the initial error in readiness metadata, and retains unresolved errors. No child error or action is retried.
After a supported bundle installation and VM restart, the first Settings CLI read recovered and returned 220 nodes with 217 matching parent checks and 3 disagreements; subsequent reads returned 176. Fresh Calculator returned 44 partial nodes and Clock 66 complete nodes. Flat queries and the nested alias also worked. These are diagnostic inspection results on that VM, not full action-cycle performance acceptance or repeat cold-start reliability.
The Release build also reproduced SIGPIPE failures in bundle validation: early-exiting grep and awk closed their file/vtool producers under pipefail. Those probes now drain producer output while keeping matching, first-platform selection, failure propagation, and all signature checks. The full workspace build and strict signatures then passed.
Guest audio: a HAL plugin, and an iPad's own audio node (2026-10-02)
The guest never produced sound, although vphone-vm has always attached a
virtio sound device with a host output sink. Two faults; the full reveal is
Research/Guest/virtio_sound.md.
fw patch now reads an iPad's board tree through the FirmwareOriginals stash,
so DeviceTree.<board>.im4p stays in the VM folder after the restore tree goes.
Why the haptics node goes. vphone600 carries /product/haptics
(closed-loop, supports-3rd-party-haptics). With it MobileGestalt answers
yes to DeviceSupportsHaptics and DeviceSupportsClosedLoopHaptics,
ToneLibrary sets playHapticTracks on every tone, and mediaplaybackd builds a
CHHapticEngine whose server, com.apple.audio.hapticd in audiomxd, never
answers on a VM: six one-second XPC timeouts, FigHapticEngineCreate fails
4099, and itemfig_rebuildRenderPipelinesAndBoss fails the item — the
ringtone never starts although its route and audio queue are built. It was
first seen on iPad guests, whose own trees (DeviceTree.j410ap, .j820ap)
have no such node, and fixed there by making the node follow the board. An
iPhone guest (iPhone99,11, iOS 27.0) then failed the same way — the same
hapticd timeouts, the same 4099, the tone dropped — and a real iPhone's tree
does have the node, so following the board cannot fix it. What is missing is
the actuator and the haptic server, and no VM has either; every guest's tree
loses the node, and the board-following patch and its Preboot repair were
replaced by these two. The restore tree loses it as well: an iPad guest's
RestoreDeviceTree is patched exactly as an iPhone guest's tree is, and
restore reads nothing from the node. Without it the guest answers as a device
with no Taptic Engine does and tones play. The answers are cached in
/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/com.apple.MobileGestalt.plist,
written at first boot, so a guest that has already booted keeps the old ones
until that file is removed and the guest restarted; a guest created with the
patch never has them. The host cannot remove it: the file is on the guest's
Data volume, a FileVault volume whose keys are in the guest's SEP, which the
host sees locked and cannot mount (the volume the installer mounts beside
System, s3, is xART). vphoned removes it at startup instead, when it is older
than the Preboot devicetree.img4 the guest booted
(VPhoneDaemon/Daemon/GuestMobileGestaltCache.swift): the host rewrites that
tree only with the VM stopped and only when a repair changed it, so an older
cache was worked out from an older tree. It logs vphoned: MobileGestalt cache predates the device tree, removed it; …, and /v1/health reports
mobilegestalt_restart_pending until the guest restarts, since running
processes keep the answers they read. An existing guest therefore needs cfw update-environment, one boot, and one restart. This is no patch declaration
of its own: it writes nothing to the tree, and with the repairs off the tree
is not rewritten and the cache stays. Removing the file by hand (files.remove, then
system.reboot) is what vphoned does, less the reboot. Detail and the
measurements: Research/Guest/virtio_sound.md §7.
Why a plugin. The cloudOS kernel has AppleVirtIOSound and its user client,
but only macOS ships the HAL plugin that drives it; neither cloudOS nor any iOS
image has one. VPhoneVirtIOSound.driver speaks the same user-client selectors
as the macOS AppleVirtIOSound.driver, on AudioServerDriver as Apple's own
plugins are. It is output only. Its host checks are make -C VPhoneGuestComponents test-virtiosound; Build/ValidateBundle.sh checks the staged bundle's signature
and factory.
Why the audio node. On an iPad guest VirtualAudio never initialized
(RoutingSettings_J98.cpp:805, then :856, PRECONDITION FAILURE), so audiomxd
had no device at all and Safari video would not start. J820's routing code builds
its stereo and webcam microphone sub-ports from the tuning directory the device
tree's acoustic-id names. devicetree-cfw-product_audio_node gives every guest
the D47 iPhone's node, acoustic-id = 8018; the iPhone image ships AID8018, the
iPad image ships AID2028/AID2029 (J820's is 2029). Removing the
stereo-sound-recording flag was tried first and only moved the failure to the
webcam sub-port, whose flag comes from AVFCapture's per-board table; that attempt
is not in the tree. iPhone guests are unchanged.
system-virtualaudio-cfw-speaker_route_throws. The counterweight to the
virtio plugin's PuffinOutput masquerade. iOS's speaker-route walker treats
the synthetic speaker's missing protection metadata as fatal six different
ways — five C++ throws out of an AudioServerPlugIn callback (terminate) and
one direct bl std::terminate after a fault log — and MediaExperience's
serialization replay hits the first of them on every launch. The patcher
(FirmwarePatcher/CustomFirmware/ExecutablePatches/VirtualAudio/) branches
each site to the function's own epilogue: anchored on the exception strings,
shape-matched through Capstone (allocation call, x1 message load, constructor
call, brk or branch terminator; the terminate as the last call of the fault
logger's back-to-back bl run), never an offset. The installer stage re-seals
the .plugin bundle after patchMachO re-signs the binary — the outer
CodeResources still named the old bytes, and the code-signing monitor killed
audiomxd on load. Research detail: Research/Guest/virtualaudio_speaker_route_throws.md.
VirtualAudio's mute-set throw — the chime's last blocker (2026-10-03)
system-virtualaudio-cfw-mute_set_throw (new), same
VirtualAudio.plugin binary, verb cfw patch-virtualaudio-mute, applied by
cfw install and cfw update-environment riding the same patchMachO
staged copy as patch-virtualaudio — staging restarts from the pristine
.bak, so the two verbs must share one call.
Fault. Establishing the speaker route asks the HAL device itself to
unmute: Device_HAL_Common's set wrapper pushes kAudioDevicePropertyMute
('mute'/'outp'/0) and reads it back. The HAL server pre-validates the
device-level set at HALS_UCPlugIn.cpp:1190 and rejects it 'what'
(2003329396) without ever forwarding the selector to the plugin — proven by a
guest probe whose hasProperty: override saw 60 selectors and never 'mute',
while the control-level 'bcvl' set on the same device succeeds (so no
plugin-side fix exists on iOS). The wrapper answers the failure by throwing
CAException("Unable to set property data.") out of the callback;
RoutingManager.cpp:3515 catches it and abandons the route — the aggregate's
master stays Null_Device, no PVMSetCurrentState, and the boot charge chime
is silent. Every caller ignores the wrapper's return value, so the fix is the
same shape as the walker patch: branch the throw to the wrapper's own
epilogue. The FAIL/EXCEPTION diagnostics still log; the route proceeds.
Reveal (all Capstone-decoded, no literal address): anchor on the cstring
containing Set mute value of %u on HAL device, referenced exactly once in
__text; the wrapper is the enclosing pacibsp-to-pacibsp function. The site
is the wrapper's CAException block — allocation call, vtable/status stores
into [x0]/[x0, #8], throw call, brk terminator; a CAException carries no
message string, so the walker patch's runtime_error shape does not match, and
the block runs 17 instructions where that scan budgeted 16 (the new scan
budgets 32). Discriminated from iOS 27's second, unrelated CAException (the
deactivated-device throw) by requiring an adrp+add forming a cstring
containing Unable to set property data. within 0x40 bytes above the site —
the EXCEPTION log that always precedes this throw. The epilogue is the
wrapper's single retab walked back through the shared guarded-canary chain;
it cannot be searched forwards from the throw because the throw sits below
the epilogue, so the retab is enumerated within the wrapper's bounds.
Verified on the real iPadOS 26.6.2 and iOS 27 binaries: exactly one site and
epilogue each (0xee224 → 0xedfcc; 0x111ac8 → 0x11179c), byte-identical
re-runs, and both verbs composing on one copy. Research detail:
Research/Guest/virtualaudio_speaker_route_throws.md.
VirtualAudio's SpeakerProtection chain — the speaker route's last blocker (2026-10-03)
system-virtualaudio-cfw-speaker_graph_chains (new), not a binary patch:
it rewrites the tuning set's graph_configurations.plist on the guest system
volume (/Library/Audio/Tunings/<acoustic ID>/VAD/, e.g. AID2029 for an
iPad Pro 13; the ID varies by board, so cfw install and
cfw update-environment walk the Tunings directory instead of naming one).
Verb cfw patch-virtualaudio-graph-configurations on a staged copy, format
preserved (binary in, binary out), idempotent. An image with no tuning sets —
iOS 27's VirtualAudio carries no graph_configurations.plist reference at
all — is skipped quietly. Patcher:
FirmwarePatcher/CustomFirmware/CustomFirmwareVirtualAudioGraphConfigurations.swift.
Fault. With the walker and mute-set throws quieted and
ProductIDOverride=8010 (the only accepted ProductID whose VAD map covers
'crnp', the RingtonePreview category), the ringtone route change runs its
full pipeline — route list built, aggregate master PuffinOutput — and then
dies building the route's DSP chain. graph_configurations.plist gives every
speaker_* configuration chainType "clhs", and the DSP chain factory
(0x117628, iPadOS 26.6.2) switches on it: clhs builds the HAL
SpeakerProtection chain, whose config constructor (0x3dcccc) calls
findDevice("Speaker") (0x3dc460) → registry lookup →
LockWeakPtrOrThrow throws Could not lock weak ptr (:60) — a VM's device
registry has no physical Speaker. The chain is destroyed,
RoutingManager.cpp:1774 logs Failed to activate a route list, the route
change fails nort (1852797556), and the guest stays silent with no
fallback. dflt — the chainType every beam_mic_*/omni_mic_*
configuration already ships — takes the generic graph chain instead: no
device lookup, the configuration's graph DSP file (which the tuning set
ships for every speaker configuration) is loaded directly. The patch flips
every speaker_* entry's chainType from clhs to dflt and touches
nothing else. Static analysis ruled out every null-tolerant patch of
findDevice itself: buildParams (0x3dc518) dereferences the returned
Device* immediately at two call sites (0x3dcda4, 0x1182a4), so a null
return would crash instead of degrade. Research detail:
Research/Guest/virtualaudio_speaker_route_throws.md.
VirtualAudio's speaker-protection gate — the decline after the build (2026-10-03)
system-virtualaudio-cfw-speaker_protection_gate (new), same
VirtualAudio.plugin binary, verb cfw patch-virtualaudio-sp-gate, riding
the same patchMachO staged copy as the other two VirtualAudio verbs
(patch-virtualaudio, patch-virtualaudio-mute) in both cfw install and
cfw update-environment. Patcher:
FirmwarePatcher/CustomFirmware/ExecutablePatches/VirtualAudio/CustomFirmwareVirtualAudio.swift.
Fault. With layers 1–3a quieted (walker throws, mute-set throw, plist
chains), the ringtone route change still declines at its last step:
RoutingHandler_Playback_GenericConfig1.cpp:223 — "HAL Speaker Protection is
missing. Failing route %s" — because the route's device list calls for HAL
Speaker Protection, a capability only a physical codec reports. The route is
finished when the decline lands: virtual stream on pspk at the matched
rate, the dflt DSP chain built, IOProc cache warm. The capability query
never leaves VirtualAudio — the ASD plugin's file logs show 0 'caps'
selectors reaching it — so no plugin-side fix exists. The handler's failure
path puts the vdef's output stream on Null_Device: a StartIO "Running"
signature still appears (on Null_Device, VAD [vdef] AggDev 8), which is
how an earlier validation was fooled; the audible era's signature is StartIO
on the PuffinOutput aggregate (AggDev 2). The guest is completely silent
and the volume is dead.
Reveal (all Capstone-decoded, no literal address): anchored on the
cstring containing HAL Speaker Protection is missing. Failing route — a
format three routing handlers share — and discriminated by requiring the
enclosing pacibsp-to-pacibsp function to also reference
RoutingHandler_Playback_GenericConfig1.cpp. Within that one handler, the
gate is the branch whose forward target heads the log block: a target at or
above which, within 0x100, the format's adrp+add sits, and the file name's
adrp+add too (both builds hold them 0x34 apart, 0x84 above the block's
entry), and whose target opens the os_log allocation itself (mov w0, #<size> followed by the allocation call). That last shape check is what
kills the near miss: iOS 27 holds a cbz x0, 0x146360 whose target lands
0xC4 below the format ref, inside the string window but on a sub, not a
block head. The scan decodes each word rather than trusting Capstone's jump
group — the gate is a tbz (tbz w21, #0 on 26.6.2, tbz w19, #0 on
iOS 27), which the group tagging misses — through
decodeConditionalBranchTarget, which reads the 19-bit immediates of
b.cond/cbz/cbnz and the 14-bit one of tbz/tbnz, sign-extended.
The write. The gate's branch is left untouched — that is what lets the
anchor resolve the site again on an already-patched binary. What changes is
the log block's own entry: the mov w0, #0x14 opening the os_log becomes a
b back to the gate's fall-through, so a taken gate lands directly on the
success continuation the handler already built. Verified on both builds
that the fall-through never re-reads the register the gate tested (w21 /
w19), so no stale verdict flows on. The block is entered only by the gate's
jump — the instruction above the head is an unconditional branch on both
builds (b 0x136d28 at 0x136874; b 0x1467a0 at 0x14639c), checked before
anything is written, so rewriting the head orphans no fall-through.
Idempotency is exact, not marker-based: a re-run finds the head holding an
unconditional branch, decodes it, and requires its target to be the gate
site's own successor — a shape no compiler emits — before reporting
already-patched; a foreign branch at the head is an error, never
overwritten. Verified on the real iPadOS 26.6.2 and iOS 27 binaries: the
expected sites on both (gate 0x136620 → head 0x136878, fall-through
0x136624; gate 0x1460b4 → head 0x1463a0, fall-through 0x1460b8), writes
byte-identical on re-run, and all three VirtualAudio verbs composing on one
staged copy with a clean second pass. Research detail:
Research/Guest/virtualaudio_speaker_route_throws.md.
VirtualAudio's volume-mode precondition — the decline after the gate (2026-10-03)
system-virtualaudio-cfw-volume_mode_precondition (new), same
VirtualAudio.plugin binary, verb cfw patch-virtualaudio-volume-gate,
riding the same patchMachO staged copy as the other three VirtualAudio
verbs in both cfw install and cfw update-environment. Patcher:
FirmwarePatcher/CustomFirmware/ExecutablePatches/VirtualAudio/CustomFirmwareVirtualAudio.swift
(the handler is resolved once for both gates, in locatePlaybackHandler).
Fault. With layers 1–3 quieted (walker throws, mute-set throw, plist
chains, speaker-protection gate), the same ringtone handler declines the
same finished route one step later, at
RoutingHandler_Playback_GenericConfig1.cpp:252: the route's
software-volume mode must be the packed pair
(present, kHardwareOnlyReadOnly) — present-flag in bit 32 over the
mode's low 32 bits, expected value 0x1_00000003. The virtio plugin's
device reports SoftwareHardwareMix, so the precondition fails and the
handler throws a std::logic_error — unlike the SP gate's quiet
decline — which unwinds past the route's own teardown into
RoutingManager.cpp:3520: the route change fails nort, the session is
torn down onto Null_Device, and the guest is silent with the volume
dead. iPadOS 26.6.2 logs this as a bare "Precondition failure."; iOS 27
still carries the assert text
softwareVolumeModeForPerDefaultScope.has_value() && softwareVolumeModeForPerDefaultScope.value() == VolumeControl::SoftwareVolumeMode::kHardwareOnlyReadOnly,
which is what named the fault.
Reveal (all Capstone-decoded, no literal address). The handler is the
one locatePlaybackHandler already resolves for the SP gate — the
function holding both the HAL Speaker Protection is missing. Failing route format and the RoutingHandler_Playback_GenericConfig1.cpp file
name. Within it, the guard chain reads: bl a wrapper (ActivationParams
check, else logs Aspen.cpp:1428 and returns 0; on hit it calls a map
getter that logs Aspen.cpp:1445/1451 and returns the packed 64-bit
map entry), then and xN, xRet, #0x1ffffffff — the 33-bit packing mask
— then add x9, xBase, #1 building the expected pair, cmp, and a
b.ne to the decline. The decline's discriminator among the handler's
three PRECONDITION FAILURE (std::logic_error) blocks (:142, :252,
:270) plus the Aspen.cpp:729 one is isVolumeModeComparison: within
six instructions below a non-call branch, a cmp seen, then an and
whose immediate operand is exactly 0x1ffffffff and whose preceding
word is a call. The inner exception-allocation labels (cbz x19 to a
mov w0, #0x10 sizing) pass every block-shape check; it is the mask
window that rejects them. Block layout matches the SP gate's: the
format's adrp+add 0x58 above the head, the file name's 0x2c above, the
sole entry the guard's branch, and an unconditional branch at the
pre-head — checked before anything is written.
The write. The comparison and its b.ne are left untouched; the
decline's log-block head — the mov w0, #0xe sizing the os_log —
becomes a b back to the comparison's own fall-through, so a failed
precondition lands on the continuation the handler already built. The
fall-through reloads its inputs from memory and never re-reads the
tested value, on either build. Idempotency is the SP gate's exact scheme:
a re-run finds an unconditional branch at the head and requires it to
decode to the comparison site's own successor. Verified on the real
iPadOS 26.6.2 and iOS 27 binaries: the expected sites on both
(comparison b.ne 0x1369c4 → head 0x138124, fall-through 0x1369c8;
comparison 0x1464e0 → head 0x1475d0, fall-through 0x1464e4), byte-
identical re-runs, and all four VirtualAudio verbs composing on one
staged copy with a clean second pass. Research detail:
Research/Guest/virtualaudio_speaker_route_throws.md.
VirtualAudio's speaker-protection gate, second site — the play-and-record handler (2026-10-04)
system-virtualaudio-cfw-speaker_protection_gate (changed: one more site,
record system-virtualaudio-cfw-speaker_protection_gate.playback_and_record),
same binary, same verb cfw patch-virtualaudio-sp-gate, which now runs the
patch once per handler in CustomFirmwareVirtualAudio.SPGateHandler.
Fault. The guest's virtio sound plugin now publishes a microphone
(Digital Mic, port pmbi; Research/Guest/virtio_sound_microphone.md).
A recording app asks for a route that plays and records (cpar), which
RoutingHandler_PlaybackAndRecord_GenericConfig1 builds in full —
aggregate of Digital Mic and PuffinOutput, a virtual stream on each
port — and then declines at :368 with the same "HAL Speaker Protection is
missing. Failing route %s" the playback handler declined the ringtone route
with. Before the microphone existed the route never got that far.
The site. The decline's format is shared by three handlers; this one
is the handler whose function also references
RoutingHandler_PlaybackAndRecord_GenericConfig1.cpp
(locatePlaybackHandler(in:file:), the same resolution with the other file
name; neither file name contains the other). The gate and its block have
the playback handler's shape on both builds: a tbz w9, #0x0 into a log
block that opens mov w0, #0x14; bl, with an unconditional branch above
the block's head, and a fall-through that reloads what it reads from the
stack and is also the join of the handler's raw-mode log path.
The write. As for the first site: the log block's head becomes a b
to the gate's fall-through; the gate's own branch stays. Verified on the
real iPadOS 26.6.2 and iOS 27 binaries: gate 0xea274 → head 0xea588,
fall-through 0xea278; gate 0x10e468 → head 0x10e6f0, fall-through 0x10e46c;
both sites written in one run, a second run reporting both already patched.
The handler's volume-mode test (and x28, x0, #0x1ffffffff at 0xea3fc) is
a branch between two builds of the route, not a precondition, and is not
touched.
Guest audio through a Mac's microphone and speakers (2026-10-04)
An iPhone guest's audio node and tuning set are the D47's. Four changes take
out what they assume about its hardware; the measurements are in
Research/Guest/virtio_sound_microphone.md §7 and
Research/Guest/virtio_sound.md §9.
All four are on in standard, need no firmware change, and are in the
installer's late list, so cfw update-environment brings them to a VM whose
plan predates them. The tuning files themselves are untouched except for the
one parameter word in each measurement strip; a strip whose saved state is
not whole parameter records is refused rather than rewritten.
Validation on mictest-iphone (iPhone99,11, iOS 27.0, cloudOS 26.4):
vphone-cli cfw patch-virtualaudio-microphone-chains on the guest's own
plist reported back_mic_general, beamformed_mic_general,
bottom_mic2_general, bottom_mic_general and front_mic_general, and a
second run none; patch-virtualaudio-microphone-gain on its
bottom_mic_measurement.austrip reported AUNBEQ_2 global gain 18.0 dB -> 0 dB, changing one word of the saved state; patch-virtualaudio-speaker-raw
reported eight speaker configurations. After cfw update-environment and a
boot, audiomxd built the recording route with DSP chain 'bottom_mic_measurement' and the guest's plist read back speaker_general: graph=speaker_raw austrip=speaker_measurement vol=["vugd"].
The paravirtual user-client allowlist joins standard (2026-10-04)
No new Apple binary patch and no new bytes. JB-31
(kernel-cfw-paravirt_user_clients, formerly kernel-exp-; see "Renamed" below) leaves
standard's block list and FirmwarePatchSetCatalog.manualOnlyPatches, so a
26.x or 18.x guest made with standard now gets the narrow IOUserClient
sandbox allowlist for classes beginning ApplePar, AppleVid, AppleVir and
IOSurfac. On 27 nothing changes: the declaration's iOSBase gate is
.oneOf([.major(26), .major(18)]), so the patch skips by version there, and the
boot-essential kernel-boot-iouc_sandbox_gate (JB-10b) already opens every user
client. The pipeline's no-plan fallback in buildComponentList follows the same
rule (on for an 18.x or 26.x base, off for 27 and for an unreadable base), so a
pipeline built without a preset agrees with standard. The patcher's own default
stays false, so KernelcacheCustomFirmwareComparisonTests still compares against
the records the reference was taken from.
Why. On an iPadOS 26.6.2 guest with the old standard, cameracaptured
crash-loops at every boot: its shader prewarm (FigCapturePreloadShadersInternal
→ CMCapture PrewarmThreadSafeSBPs → NRFV3 -[NRFProcessorV3 prewarm])
dies with SIGSEGV at 0xc inside
AppleParavirtGPUMetalIOGPUFamily. While it crash-loops, the first process to
touch the AVCapture defaults blocks on a synchronous XPC to it; when a guest app
starts recording that is SpringBoard's main thread (Control Center's sensor
indicator → AVCaptureDeviceDiscoverySession →
AVCaptureProprietaryDefaultsSingleton → csr_ensureClientEstablished), so the
UI freezes, audiomxd's recording StartIO waits 18 s, and the first recording
after boot fails.
Corrected the same day: this gate was not the cause. cameracaptured
already has a Metal device; it dies on a bug in the paravirtual driver's heap
textures, and row 23 of the CFW table (libvcamcaptured skipping the
prewarm) is what stops it. The allowlist stays on for daemons and tools that
want the GPU (#22).
What it widens, and why that is acceptable. Every process whose sandbox
refuses one of those classes at this gate — daemons, command-line tools and
system XPC services such as cameracaptured, WebKit, spotlightknowledged,
callservicesd and vphoned — now passes it; apps gain nothing, because their
container profile already opens these devices. The prefix match admits every
class with those first eight bytes, which on this board means the paravirtual
GPU and device clients, the paravirtual VideoToolbox decoder, the VirtIO drivers
(the Neural Engine among them) and IOSurface's clients. Every other class is
still refused, which is why the narrow patch rather than the broad gate-off is
the shape on 26.x. For a research VM whose sandbox hooks are already stubbed
(JB-09) and whose IOUC MACF gate is already open (JB-10), this was the last thing
stopping a daemon reaching a device every app reaches. See
Research/Guest/gpu_acceleration.md, "On in standard".
Renamed. The patch was introduced the same day (37ed2e6) as
kernel-exp-paravirt_user_clients, opt-in. The {component}-{effect}-{name}
rule derives exp for a patch standard leaves off and cfw otherwise, so it
is renamed kernel-cfw-paravirt_user_clients (records .redirect and
.cave), as dyld-cfw-mis_trust_auth was renamed the other way on 2026-09-30.
fw patch resolves a VM's PatchSelection.plist against the catalogue and
throws unknownPatch for an identifier nothing declares, so a VM whose record
still names kernel-exp-paravirt_user_clients fails there until that entry is
removed. There is no migration.
Opting out. vphone-cli fw set-patches <vm> --block kernel-cfw-paravirt_user_clients, then re-patch.
Guest patches follow the selection, both ways (2026-10-04)
No new Apple binary patch. This changes how every guest-side patch in this
document (the dyld shared cache rows, the guest Mach-O and file patches, the
Preboot device-tree repairs) is applied after a VM exists, so it is recorded
here. Full write-up and live validation:
Research/Firmware/post_creation_patch_changes.md.
Before. cfw install applied the guest half from PatchPlan.plist, and
turning a guest patch off on an installed VM did nothing: the step was skipped
and the patched bytes stayed. The dyld cache patches were applied in place with
no record of what they replaced.
Now. cfw install and cfw update-environment resolve the guest half from
the VM's current PatchSelection.plist, apply what is on and revert what is
off. Mach-O, entitlement and guest-file patches revert from the .bak beside
the file; the Preboot device tree from devicetree.img4.bak; the dyld cache
from .vphone-dsc-undo.json in the cache directory, which holds the original
bytes of every content write a cache verb makes (slot hashes are re-derived by
re-attesting, not logged). Each cache verb takes --undo-log/--undo-id, and
the hidden cfw patch-dsc-revert verb applies the log. What is live is recorded
in the Guest part of <vm>/PatchReceipt.plist.
Existing VMs. A guest installed before this change has no undo log and no backup for the files several patches share, so those patches are reported "not revertible" when turned off and stay recorded as live; nothing is guessed. Every dyld row above that a cache verb idempotently re-runs keeps its undo records, so a revert works however many installs later it happens.
Boot chain unchanged. Boot-chain patches still reach a guest only through a
restore (AVPBooter through fw patch); fw set-patches and fw patches <vm>
say so per patch.
An iPhone guest's product description from D47AP (2026-10-04)
An iPhone17,3 guest had no Siri page, no Camera Control, no Action Button and no
Model Name, whatever its identity patches. Most of vphone600's /product is
syscfg/xxxx placeholders (flag 0x8000) that no syscfg fills on a VM, and
MobileGestalt reads a placeholder as absent. An iPad guest already takes these
from its board tree (devicetree-cfw-ipad_*); an iPhone guest did not, although
its IPSW carries the iPhone's own tree, Firmware/all_flash/DeviceTree.d47ap.im4p.
What is copied, and why:
Deliberately left as vphone600 has them: anything that promises hardware the VM
does not have — bluetooth-*, baseband-chipset, nfc-express, exclaves-enabled,
has-*, graphics-featureset-*, framebuffer-identifier, wifi-chipset, the
display calibration placeholders — and the vphone600-only placeholders
(gestalt-variant-id, variant-name, thinning-product-type,
jetsam-properties-product-type, vm-supports-generative-models).
/buttons keeps no compatible, so no button driver binds to it and the
board's function reference in function-button_ringeren is never followed.
Measured on iOS 26.6.2 (23G90) + cloudOS 26.4 (23E5207q), preset standard plus
the nine identity patches, by applying the same edit to the installed tree by
hand: Settings shows Siri, Camera Control (Camera and Accessibility pages) and
the Action Button; Siri opens from a long side-button press (HID 0x0C/0x30); the
Action Button's long press (HID 0x0B/0x2D) runs Flashlight and Camera with the
Dynamic Island, with no tweak; the search field gains dictation. The guest boots
normally. Not yet measured: the change on a VM without the identity patches, on
iOS 18.x, and a Camera Control press.
The kernelcache reaches an installed guest without a restore (2026-10-05)
No new Apple binary patch. This adds a way to deliver the existing kernelcache patches to a VM that is already installed, so it is recorded here.
A kernel patch toggled with fw set-patches could only reach an installed guest
through a restore, and a restore erases it (restored in the cloudOS ramdisk
repartitions whatever the host asks — restore --no-erase refuses; see
Research/Restore/native_restore_architecture.md). But the booting kernel lives
in Preboot as an IMG4, and iBoot accepts a modified IM4P under the original
signed IM4M — the same image4 bypass that lets cfw install rewrite
devicetree.img4 there. cfw update-kernel (VPhoneCustomFirmwareInstaller
mode .kernelUpdate) host-mounts Preboot on a clone and swaps in a re-patched
kernelcache, keeping every volume. It re-patches the pristine kernelcache kept in
FirmwareOriginals with the VM's current selection
(FirmwarePipeline.patchKernelcacheFile, in the .jb variant — .regular
omits the AMFI/JB kernel patches and CS_KILLs launchd), so it needs no restore
tree and no prior fw patch. The splice is
CustomFirmwarePostRestoreDeviceTree.kernelcacheReplacingPayload, which carries
the whole IM4P (compression and any PAYP tail) verbatim and keeps the IMG4's
manifest.
Proven on test VMs (iPhone 27.0 24A435 + cloudOS 26.4) 2026-10-05, including a VM
with no restore tree, through the Launchpad helper: a marker in
/var/mobile/Documents, kernel-exp-display_refresh_120hz toggled with
fw set-patches, cfw update-kernel, then boot — the guest booted the
re-patched kernelcache and the marker survived. Flow: fw set-patches →
cfw update-kernel. fw set-patches, fw patches and the Launchpad inspector
route a kernel patch through cfw update-kernel and the rest of the boot chain
(TXM, DeviceTree, LLB, iBSS, iBEC) through the erasing restore. TXM and
DeviceTree are also in Preboot and could follow the same path later.
Optional Settings row hiding (WIP, 2026-10-07)
Two standard-blocked declarations hide Apple Account, Game Center, iCloud and Wallet in Preferences (system-preferences-exp-root_rows) and Software Update in GeneralSettingsUI (dyld-exp-settings_software_update_row). Native discovery is restricted to reviewed 24A435 inputs. The installer uses pristine executable backups and cache undo records with page re-attestation. All five rows passed guest rollback, reapplication and reboot checks. See research report. Broader firmware coverage and bundled deployment remain WIP; no kernel/SEP or runtime hook changes are included.
Pinned guest device name (2026-10-07)
Row 27. libdevicename.dylib makes configd publish the VM's name, which vphone-vm hands to vphoned after every connect and vphoned keeps in /var/db/vphone/devicename.plist, as the device name, and makes lockdownd refuse every rename; without the file it passes everything through. NVRAM was tried first and does not reach the guest: iBoot drops variables it does not know. It is the first library a process receives beside another: lockdownd already took libmisfix.dylib, so vpInsertedLibrariesFor in Shared/InjectionEnvironment.h now returns every installed library for a path and vpInsertHookLibraries adds each one an environment lacks. Single-library targets and their spawn log lines are unchanged. preferences.plist is never written: the pin is applied to configd's SCDynamicStoreSetMultiple publication, because the SCPreferencesGetValue(kSCPrefSystem) it flattens is also read and written back on configd's model-change path. Verified on iOS 27.0 (24A435) on 2026-10-07; iOS 26.x not run yet. See Research/Guest/device_name_pinning.md for the flow, the measurements and the verification.
CoreDevice install proxy persona lookup (2026-10-07)
system-installcoordination_proxy-cfw-persona_lookup is an iOS-27 guest
compatibility patch, enabled in standard. Its root-owned marker
/usr/lib/vphone-installcoordination-persona-lookup enables the existing
launchd hook to allow only the system installcoordination_proxy, verified by
executable path, signing identifier and kernel-validated audit token, to look
up com.apple.mobile.usermanagerd.xpc. Other requests retain their original
sandbox check. Disabling the patch removes the marker. Both CFW install and
environment update reconcile its receipt. No kernel or proxy binary patch is
needed; an experimental entitlement-only version is restored from backup.
The original failure is a Protobox mach-lookup denial before UserManager's persona-fetch authorization. Adding a temporary exception entitlement was verified ineffective on the running guest and has been removed. IDA analysis of this guest's launchd confirms the bootstrap lookup gate and name filters 2, 3 and 12. See evidence, implementation and validation.
Host disk-image command migration (2026-10-08)
The migration replaces hdiutil because macOS 27 marks it as deprecated.
The system's hdiutil(1) manual recommends diskutil image, and command help
prints deprecation warnings directing attach, detach and convert operations
to diskutil image attach, diskutil eject and diskutil image create from.
No guest binary patch or preset change. On macOS 15+, CFW install and kernel
update, PCC GPU recovery, seal-tool extraction and Cryptex filesystem attachment
use diskutil image attach. Writable conversion uses diskutil image create from -format RAW, which diskutil(8) identifies as the former UDRW format.
There is no hdiutil fallback or new disk-image tool abstraction: calls stay in
their existing functions. The macOS 15 diskutil(8) system manual already
documents image attach, create and resize. Single-hyphen options
preserve compatibility with older diskutil versions; conversion supplies
-source on macOS 15 and a positional source on macOS 26+. Existing function
names and CLI arguments are preserved. Eject uses diskutil eject; a busy image is
retried after diskutil unmountDisk force, avoiding the newer eject force
syntax on older systems. Attachment parsing retains the physical APFS store's
parent disk even when a synthesized container is reported first, and keeps
mount points containing spaces intact. PCC recovery tracks successful eject
before deleting its temporary library instead of querying hdiutil info.
CryptexFileOpsTests: all 12 existing tests pass. The
VPhone bundle build and bundle admission pass on macOS 27.0.1 / Xcode 27.1.
Real image attachment and execution on macOS 15 still require live validation;
no VM disk is modified by the validation tests.
Flutter AOT executable-remap compatibility
The existing guest SystemHook component has a guarded vm_remap fallback for
an observed Flutter AOT callback layout whose returned RX permission disagrees
with the actual read-only destination. The fallback creates a private RW copy,
then changes it to RX; other calls pass through. No kernel patch or protection
policy change is added. See Research/Guest/flutter_aot_remap_compatibility.md
for exact guards, failure semantics, reproduction and validation limits.
ProMotion joins standard (2026-10-09)
No new Apple binary patch and no new bytes. JB-30, the 120 Hz timing element,
leaves standard's block list and FirmwarePatchSetCatalog.manualOnlyPatches,
so every new VM's paravirtual display advertises 120 Hz. It has no version gate,
so it lands on every base. The pipeline's no-plan fallback in
buildComponentList is on too, so a pipeline built without a preset agrees with
standard; the patcher's own applyProMotion default stays false, so
KernelcacheCustomFirmwareComparisonTests still compares against the records the
reference was taken from.
Why. Measured since 2026-10-04 (Research/Guest/display_refresh_rate.md): the
host scans out every frame the guest presents up to 120, the userland renders at
the advertised rate with no change of its own, a static screen costs what it did
at 60 Hz, and a guest with the patch booted on iPadOS 26.6.2 and iOS 27.0. The cost
is unchanged: up to twice the host CPU and GPU while the guest animates.
Depends on Lakr233/vphone-cli#643 for iOS 26. On an iPhone17,3 26.6.2 guest
with the 2.9.0 guest components, SpringBoard deadlocked in three of three 120 Hz
boots. Its main thread held libvphoneattitude's @synchronized lock in the
setDeviceMotionUpdateInterval: hook while waiting for Core Motion's MotionThread,
which was waiting for that lock in the isDeviceMotionActive hook (guest spindump,
symbolicated against 23G90). #643 fixes that lock order. With its fix cherry-picked,
the same guest booted at 120 Hz and scrolled Settings at 119.3 frames per second.
Details: Research/Guest/display_refresh_rate.md, "iPhone 26.6.2, and the attitude
hook deadlock".
Renamed, with a migration. The editor titles it ProMotion and the naming rule
gives cfw to a patch standard turns on, so the record is
kernel-cfw-display_promotion. The old kernel-exp-display_refresh_120hz
shipped from 2.4.2, unlike the same-day kernel-exp-paravirt_user_clients, so a
VM may have it checked. FirmwarePatchSetCatalog.renamedPatches maps old to new,
and VPhonePatchPresetStore reads PatchSelection.plist, PatchPlan.plist and
PatchReceipt.plist through it: such a VM resolves instead of failing fw patch
with unknownPatch, and its kernelcache counts as already carrying the patch.
Existing standard VMs now read as wanting it in the kernelcache part and
keep 60 Hz until cfw update-kernel or a restore writes it.
Opting out. vphone-cli fw set-patches <vm> --block kernel-cfw-display_promotion, then cfw update-kernel.
Heap index buffers reach the host whole (2026-10-14)
New guest patch system-paravirt_gpu-cfw-heap_index_offset, on in
standard, gated to cloudOS 26.4: four words in the paravirtual GPU driver.
Symptom. Unity games (Wing Fighter on iOS 27.0.1, Subway Surfers on iPadOS
26.6.2) drew meshes with the right vertices and the wrong triangles. A probe
from a launchd job showed every plain-buffer path correct and every index
buffer sub-allocated 64 KiB or more into an MTLHeap wrong, unless the draw's
own indexBufferOffset forced the 64-bit command.
Cause. -[PGSerializerRenderCommandEncoder drawIndexedPrimitives:…] picks
a compact command with 16-bit fields when the draw's arguments fit in 16 bits,
then stores [indexBuffer parentResourceOffset] + indexBufferOffset in one of
those fields (strh). The heap offset is never part of the test, so it wraps.
Patch. The selecting cbz/cbnz in each of the three methods (two in the
baseVertex:baseInstance: one) becomes a b to the full command:
The sites are found by content, not offset: the compact command's mov w2, #size ; mov w3, #command pair is unique in __text, the word before it must
be the cbz/cbnz, and its target must hold the full command's pair. The
patched 23E5207q driver hashes to
5c172ab9ff049e7107ea9e4bd276c6b024f6b31e4556f9a847e5be5e6692cf2d before
signing (pristine 29ba36c7…aedbc); CustomFirmwareParavirtGPUIndexOffsetTests
checks it against VPHONE_MACHO_PRISTINE.
Where. cfw install, right after the GPU bundle step, through the
patch-paravirt-gpu-index-offset verb, from a .bak beside the driver, then
codesign over the bundle as for VirtualAudio.plugin. Off restores the .bak.
A bundle the same run reinstalled from the restore tree is already pristine, so
off then needs nothing and the receipt does not keep the patch live. An existing
VM gets it from cfw update-environment on the stopped machine: the guest patch
steps run there too, without the restore tree, over the driver already in place.
Not covered. drawPatches:…, drawIndexedPatches:… and
-[PGSerializerBlitCommandEncoder copyFromTexture:…toBuffer:…] store
parentResourceOffset in 16 bits too. They were not measured and are left as
they are.
OpenAVB replaces AVPBooter on new machines (2026-10-18)
New machines boot OpenAVB (Resources/OpenAVB/OpenAVB.bin, built from
owngoal-dev/OpenAVB at 3c7e7f6, with no carried patches; upstream
includes vphone's six Virtualization.framework fixes, the last of which lets a
guest reboot: the ROM waits for a SEP restarted by the reset) instead of
Virtualization.framework's AVPBooter.vresearch1.bin. OpenAVB checks no image
digest, so avpbooter-boot-dgst_bypass has nothing to patch on such a machine:
fw patch drops the AVPBooter component when romImages.avpBooter names an
OpenAVB image and logs Boot ROM: OpenAVB, which needs no ROM patch. The patch
stays in presets and plans, so fw patches reports no drift. Machines created
before keep Apple's ROM and the patch; vm config --rom openavb|apple switches
one without a restore.
Two boot-chain files change form, not content:
No patch site moved and no patch was added or removed. The OpenAVB-side
changes (read-only ROM aperture, chip ID 0, DFU suffix CRC, remote-boot bit,
USB device left running, SEP not ready after a warm reset) were carried as
patches at first and are now upstream (owngoal-dev/OpenAVB#1 and #2).
Firmware/openavb_boot_rom.md describes
each one and how it was found.
OpenSEP replaces AVPSEPBooter on new machines (2026-10-18)
New machines start the SEP from OpenSEP (Resources/OpenSEP/OpenSEP.bin,
built from owngoal-dev/OpenSEP at 39ff3d7 for its vresearch1 platform,
with no carried patches) instead of Virtualization.framework's
AVPSEPBooter.vresearch1.bin. Apple's SEP ROM was never patched and OpenSEP is
not either, so no patch, preset or plan changes: there is no SEP ROM component
in fw patch. The SEP firmware (sep-firmware.vphone600.RELEASE.im4p, sent as
RestoreSEP and loaded by iBoot) is unchanged and unpatched. Machines created
before keep Apple's SEP ROM; vm config --seprom opensep|apple switches one
without a restore. Firmware/opensep_sep_rom.md has the contract and the
validation.
Guest Metal caches survive a reboot (2026-10-19)
Row 29. system-systemhook-cfw-metal_cache_reuse, declared in the Guest
System set, is on in standard and experimental and blocked in both Apple TV
presets (they carry no SystemHook). Its target is the marker
/usr/lib/vphone-metal-cache-reuse: cfw install and cfw update-environment
write it when the patch is selected and remove it when it is not, so it is
revertible. While the marker exists, SystemHook's constructor sets
FS_CACHE_REUSE_ON_REBOOT=1 before anything else unless the variable already
has an explicit value, so libCoreFSCache can keep
each Metal cache stamped by an earlier boot instead of resetting it. No system
binary, shared cache or kernel byte changes.
Verified on Lychee (iPhone, iOS 27.0.1, cloudOS 26.4) with real reboots:
backboardd's functions.list keeps its boot UUID header and size across three
boots, guest compiles for backboardd during five Settings rounds fall from
about 145 to 0, no GPU restart, and the first go-home goes from about 31% to
37–44% on time. Numbers and method: Guest/gpu_acceleration.md, "Real reboots
with the patch (2026-10-19)".
Host cache storage (2026-10-20): the launcher keeps a durable whole-directory
snapshot in <machine>/ShaderCache, seeds Apple's normal ECID working cache
before boot, and checkpoints after guest shutdown and again in the parent CLI
after vphone-vm exits. A symlink experiment was rejected after sandbox denials
and repeated compilation; the working root remains a real directory. This adds
no binary patch and changes no Task3 key bytes. Index/data pairs stay together;
copies are staged before whole-directory replacement and never merged across
independent trees. Permissions use the shared symlink-safe host-file walker.
The final bundle passed two Settings/Camera boot cycles with zero host GPU task
compiles and zero GPU resets, including restoration after moving aside Apple's
working directory. All 14 cache file checksums matched after each shutdown.
Migration checks and runtime evidence are in Guest/gpu_acceleration.md,
"Cache location and migration".
Deletion correction: a missing machine snapshot now clears an already managed
host working copy on the next boot, instead of importing it again. A shutdown
checkpoint does not recreate a deleted snapshot. The working copy's existing
.host-ecid marker distinguishes this reset from the first-use import. This
resets host caches only; guest caches inside Disk.img remain. Frame-counter
gaps in the earlier measurements do not independently demonstrate visible
animation stutter; the user reports smooth warmed animations.
The corrected bundle's reset/reboot test recorded 607 then seven host GPU task
compilation events (129 then one during Settings), with zero GPU resets on both
boots. Fourteen cache/permission tests and the complete bundle build pass.
Standard promotion (2026-10-20): JB-32 is now enabled by Standard as
kernel-cfw-paravirt_gpu_task3; all four record suffixes and kernel writes are
unchanged. The shipped plist and catalog fallback agree. tvOS presets retain
an explicit block. The old kernel-exp-paravirt_gpu_task3 identifier migrates
in VM selections, plans and receipts, including explicit opt-outs. New template
keys include the enabled kernel patch, so a pre-Task3 template cannot satisfy
the new Standard plan. Existing VMs bind the new bundle and run
cfw update-environment and cfw update-kernel while stopped, keeping data.
See Documents/en/storage/shader-cache.md and the Standard promotion section in
Guest/gpu_acceleration.md. The previous opt-in notes describe development
validation; older host compatibility remains untested.
Promotion validation: 62 focused tests passed, as did the full VPhone bundle
build and admission check. The built CLI recognized Lychee's old Task3 receipt
under the new name as applied, enabled by its preset and not pending.
Review fixes (2026-10-21): JB-32 is declared for cloudOS 26.4 only. On the
cloudOS 26.1, 26.2 and 26.3 research kernels the owning-task anchor in the
superclass initializer does not resolve, and an ungated Standard patch made
fw patch and cfw update-kernel abort for every iOS 18.6.2–26.3.1 machine.
The .features site no longer loads a fixed 245: it branches to
.features_code (sub x0,xV,#246; tbnz x0,#63; tbnz wV,#31; mov w0,#245,
else mov x0,xV), which passes the granted version through when it is the
kext's own or lower, and a refusal unchanged. A host that does not know the
requested version grants its own maximum without the refusal bit
(-[_PGDevice setBinaryVersion:]: macOS 15.6.1 → 209, 26.0 → 233,
26.4 → 245, 27.0.1 → 252), so the fixed 245 had claimed 8 feature flags on a
15.6.1 host and 4 on 26.0 that the host never enabled. The clamp uses only
existing Keystone-checked encoders; the Task3 fixture test executes it for
252, 246, 245, 233, 209 and a refusal. The persistent host shader cache is no
longer required to launch: a VM that cannot prepare one logs a warning and
boots without it.
Apply selected Preboot identity during guest updates (2026-10-21)
preboot-exp-devicetree_identity was advertised as an update-environment
guest patch but its installer call enabled identity only for full installs.
Launchpad's Apply to Guest therefore completed with the identity still pending.
Environment updates now run the existing identity rewrite when the current
resolved plan selects it, using the installed tree without a restore directory.
The patch bytes, presets, board-presenting guest guard, post-identity repair
backup and existing off-state behavior are unchanged. This fixes the same
delivery path for CLI updates, bundle changes and template guest-patch steps;
other Preboot repairs already used it. It does not enable the eight firmware
DeviceTree identity edits or establish that Apple will deliver Photos models.
Validation on upstream/main 047642f8 (2.10.1): 77 existing tests in eight
suites pass, covering guest device trees, patch resolution/catalogue/drift,
board audio, microphone-array removal, iPhone product properties and haptics.
The complete VPhone Debug build and bundle admission also pass.
The minimal preset: a restore image instead of patches (2026-10-19)
The patchless variant (FirmwarePipeline.Variant.less) was unreachable: every
caller passed .jb. It is a preset now, minimal, and the variant is
gone. The preset names three sets:
com.vphone.patchset.bootchain, allowing only iBEC's and LLB's patches, which the old.lessvariant applied too ("for the serial logs"). AVPBooter, iBSS, TXM and the restore ramdisk stay stock, and the pipeline builds no patcher for a boot chain component none of whose patches is on. A stock LLB does not get the restored guest to its kernel: on 26.6 with cloudOS 23E5207q it restarts right after its banner, with Apple's ROMs as with OpenAVB and OpenSEP (2026-10-19). Which of LLB's patches it needs is open. Superseded 2026-11-01/02: none; see "minimalboots a stock boot chain" below. Reverted 2026-11-03: the stock LLB needs more guest memory than 4096 MB; see "minimalandwatchoskeep iBEC and LLB again" below.com.vphone.patchset.devicetree, allowing only the board presentation (devicetree-cfw-serial_number,-home_button_type,-artwork_device_subtype,-island_notch_location) and the haptics and microphone-array removals. They change no code.com.vphone.patchset.restore.image(FirmwareRestoreImagePatchSet), new, named by no other preset:
The kernel and TXM stay stock, and the restore installs a system whose seal
the stock kernel verifies. CryptexFilesystemPatcher and ManifestHashPatcher used
to emit filesystem.cryptex.merge and manifest.hash, which no set declared;
they emit the identifiers above, and the pipeline builds each patcher only when
its patch is enabled.
Nothing may write the System volume after the restore, because that breaks the
seal. So for a VM whose plan enables filesystem-boot-cryptex_merge:
cfw installtouches no disk: it records an emptyGuestreceipt part and the install, so the boot gate (restore-info.json) passes as for any VM.cfw update-environmentandcfw update-kernelrefuse.vm createbuilds without a template (the offline trim and setup boot write the system) and refuses--templateand the slimming switches, as for an Apple TV.fw patchneeds root for the merge (owner-preserving copies on a mounted image) and says so. Launchpad runs it through its helper's newpatchRestoreImageverb, which runsfw patch <vm> --restore-image-onlyfrom the receipt-checked store bundle; that flag refuses any selection that does not merge the restore image.fw preparestagesapfs_sealvolume_<iOS version>into<restore>/.tools(thefw seal-toolfetch; iOS 18.x.y takes macOS 15.x.y, or without a twin the newest macOS 15 below it) and the GPU driver forfilesystem-boot-gpu_driveras forsystem-extensions-boot-gpu_bundle.vm createand Launchpad now record the preset beforefw prepare, which readstandardbefore: the GPU staging followed the wrong selection for any other preset as well.
The merged image must not carry the host's .fseventsd. diskutil image attach mounts it browsable, so fseventsd starts a log on every writable
mount, and Apple's system volumes have none. With the log in the image, the
mtree listed .fseventsd, the restore wrote .fseventsd/.ramdisk-boot-done
into the restored volume, and seal_system_volume failed ("mtree: cannot
generate the XML dictionary", 26.6 on cloudOS 23E5207q, 2026-10-19). Each
writable mount now deletes the directory before it unmounts, which also stops
fseventsd writing it again for that mount.
The root hash fw patch computes has to be the one the restore computes, and
it was not ("Invalid root hash", apfs_sealvolume exit 65 in
seal_system_volume):
- The remap's
MODIFICATIONwas the time of/private/varin the mtree, on the assumption thatremoveSpecificSystemFileschanges it last. The restore's ownmtree -msets it from the root entry (.), not from the newest one: run on 2026-10-19, the device took the root's1792405582.069334279while/private/varsaid…582.936736187. The remap takes the root's time now. Corrected 2026-10-23:mtree -mtakes the time of the first entry in walk order whose time differs (checked with the host'smtree -mon a small tree). The restore gets the image as it was encrypted, beforeremoveSpecificSystemFiles, so that entry is the first one edited afterwards. The root was that entry only because the browsable mount had created.fseventsdand removing it edited the root. Since the mounts are-nobrowse(6dcc3f2c) there is no.fseventsd, the root keeps the IPSW's time, and sealing with it failed again ("Invalid root hash"; seen on 18.6 22G86 on cloudOS 22G86, where the device took/private/var's time). The remap now takes the first mtree entry edited sinceremoveSpecificSystemFilesstarted. createDigestAndHashmounted the image writable only to unmount it again forapfs_sealvolume, which needs the device; the mount changed the volume after the mtree described it. It attaches the image with-noMountnow.
On macOS 26.6.2 two more steps failed (2026-10-23, Mac mini M4):
diskutil image attach -mountPointrefuses a folder that does not exist ("Mount point … doesn't exist", exit 64), and the merge attached at a fresh<tmp>/volumeit had not created. Every preset that merges the restore image (watchos, minimal) stopped there. The folder is created first now.- The watchOS 26.4 system image has no partition map: its APFS container
spans the whole file, and
diskutil image resizegrows it no further than that ("Requested size exceeds maximum size"; min 11.05 GB, max = current = 11.26 GB once merged). Left at that size,apfs_sealvolumeran out of space remapping the fsroot tree with 541 MB free (ENOSPC, exit 65).growImagenow extends the file and runsdiskutil apfs resizeContainer <container> 0, which gave the same image 2.7 GB free. The iOS images, which diskutil can grow, are resized as before.
Corrected 2026-10-24 (macOS 26.5.1, Minimal preset): the iOS OS images have
no partition map either. iPhone17,3 26.5 23F77's 094-54920-099 converts to a
9.31 GB RAW image with 537 MB free, and diskutil image resize -plist reports
max = current for it, so convertToRawImage's resize to max left it as it
was. Merging the System OS cryptex (5.2 GB) then ran out of space in the image
("dyld_shared_cache_arm64e.09: No space left on device"), for iOS 18.6 22G86
as well; a 2.12.0 bundle that had built both a day earlier failed the same
way on the same host. convertToRawImage keeps the resize to max where it
grows the image and otherwise grows it as growImage does, by 16 GiB
(mergeHeadroom): the room is sparse, and shrinkImage gives it back after
the merge.
The binaries the merge patches (launchd_cache_loader, mobileactivationd)
are re-signed in Apple's ad-hoc shape (VPhoneSignOptions.Style.appleAdHoc),
like the stock binaries around them. The guest's TXM is stock here: it takes
an ad-hoc binary from the static trust cache the merge rebuilds, but rejected
ldid's signature (no ad-hoc flag, empty CMS blob), and launchd panicked at its
first boot task ("launchd_cache_loader - posix_spawn: 85: Bad executable").
vphoned, its launch daemon plist and the rewritten launchd.plist belong to
root:wheel, as the system's own; they kept the host user's owner and group.
minimal boots a stock boot chain (2026-11-01, iBEC 2026-11-02)
minimal allows no boot chain patch. It still names
com.vphone.patchset.bootchain, so --allow (fw set-patches) can turn one
back on, but with none on the pipeline builds no AVPBooter, iBSS, iBEC or LLB
patcher, and the restore writes Apple's LLB byte for byte (SHA-1 of the
restore tree's LLB.vresearch101.RELEASE.im4p equal to the IPSW's). The ten
it named before (ibec-boot-boot_args, ibec-boot-bootx_precondition,
ibec-boot-image4_callback, ibec-cfw-serial_label,
llb-boot-image4_callback, llb-boot-boot_args, llb-boot-rootfs,
llb-boot-panic_bypass, llb-cfw-serial_label, llb-cfw-display_scale) are
off. watchos allows none either: a Watch7,18 watchOS 26.4 (23T240) guest on
cloudOS 26.4 (23E244), created with --preset watchos and the ten blocked,
logged "iBEC: no patches" and "LLB: no patches", was sent the stock iBEC
(380,676 bytes; 746,427 patched), had iBEC and LLB SHA-256-equal to the
cloudOS IPSW's during the restore, and booted to the pairing screen with
vphoned answering device.info and processes.list (2026-11-02).
LLB is the only iBoot stage of a normal boot: its banner ("Local boot") is
followed directly by the kernel; the iBoot image the restore writes to NOR
is not used. With every BuildManifest digest rehashed
(manifest-boot-digest_rehash) the TSS ticket covers the images the restore
installs, so the stock LLB's Image4 checks, its APFS sealed-volume checks
(the 0x3B7/0x3C2/0x110 sites llb-boot-rootfs skips; the values are
source line numbers in the APFS mount path that looks up the
com.apple.os.update-<root hash> snapshot) and the check llb-boot-panic_bypass
skips (bl; cbnz w0 to a panic at source line 699 on 26.5) all pass.
Checked with Launchpad (vm create --preset minimal --no-template --block …),
one VM per row:
The failed 18.6 restore was the host's USB connection, not an Image4 check.
iBEC runs only in the DFU restore (it loads the restore SPTM, TXM, trust
cache, ramdisk, device tree, SEP and kernel), and ibec-boot-image4_callback
does not relax one property: on 18.6 (iBoot-11881.140.96, the same binary as
iBSS and LLB) its b.ne is the stack-canary check in the epilogue of the
property callback (0x91BC) and its mov x0, x22 the return value, so every
property (AMNM, BNCH, BORD, CEPO, CHIP, CPRO, CSEC, SDOM, ECID, osev,
slvn, anrd, dpoc, hclo, nrde, pndp; DGST, EKEY, EPRO, ESEC, DPRO)
passes. In the failed run the stock callback had accepted the ramdisk ("loaded
ramdisk" is printed only after the rdsk load returns 0, 0x4ae20), and the
device tree never reached iBEC: no ea0f64a4253252:981 (a USB request, 0x25050)
follows. The three 337a834f05a86eb:373 after it are iBoot's USB
SET_CONFIGURATION handler (0x23f9c), so iBEC was still running and answered
three re-enumerations by the host, which invalidated idevicerestore's handle.
The stock-iBEC repeats show the same serial log as the patched run: "loaded
device tree" after the ramdisk, then restore mode.
Without ibec-boot-boot_args/llb-boot-boot_args the kernel gets no
serial=3 -v, so the serial log ends at "End of iBoot serial output";
turn the patches back on with --allow (fw set-patches) for a verbose
kernel log. The serial labels only renamed the iBoot banner, and the display
scale patch is emitted only for a guest that is not 3x, which minimal does
not configure (it has no iPad device tree patches).
Why a stock LLB "restarted right after its banner" on 2026-10-19 (26.6 on
cloudOS 23E5207q) was not reproduced: those IPSWs are no longer on this host,
and that day's pipeline still computed a wrong root hash for
the resealed system (both fixed since, see above). Those are the inputs of
the sealed-volume checks llb-boot-rootfs skipped, but this is not proven.
Checked again on 2026-11-02 together with the other Minimal changes of that
week (launch-constraint categories in the static trust cache, a signed
launchd.plist instead of the launchd_cache_loader patch, vphoned's
paravirtual user-client entitlements): int-186 (iPhone11,6 18.6 22G86 on
cloudOS 22G86, --preset minimal with no --allow/--block) applied the
two iBEC image-4 callback instructions (the preset still kept them then),
logged "LLB: no patches" and restored;
its serial log shows the stock LLB's "Local boot" banner on the first boot and
again after a reboot, and SpringBoard and vphoned came up both times.
minimal and watchos keep iBEC and LLB again (2026-11-03)
The stock boot chain above was checked only at the default 8192 MB. A guest
with 4096 MB does not boot. On a macOS 26.7.2 host OpenAVB logs iLLB container accepted, iLLB decompressed and virtual firmware prepared, and
the virtual machine stops at once (VZErrorDomain 1, "The virtual machine
stopped unexpectedly"), so vm create ends with "Boot check ended before the
guest connected"; on a macOS 27.2 host it keeps running and vphoned never
answers within the 300 s first boot check. That is the same stop the userspace debugging section below
records for a watch guest at 2048 MB. With the ten iBEC and LLB patches back
on (FirmwarePatchSetCatalog.minimalBootChainPatches, as before 2026-11-01)
the same guests boot at 4096 MB. AVPBooter, iBSS, TXM and the restore ramdisk
stay stock, and the rest of minimal (the signed launchd.plist instead of
the launchd_cache_loader gate, the kernel sets named for userspace
debugging) is unchanged.
The regression sweep for bundle 2.15.0 plus main at 58368476 created every
machine with --cpu 4 --memory 4096 on Mac mini M4 hosts (Mac16,10, 16 GB),
one VM per row:
Neither preset installs the roothide bootstrap: irisin-cli is refused as a
bad executable on a stock kernel, with release 2.15.0 as with main, so
--bootstrap is not part of these presets' checks.
Which LLB patch the smaller guest needs, and why memory matters to a stock LLB, is open. Until it is known, a stock boot chain is not the default for a preset that can be created with less than 8192 MB.
The research board stands in for vphone600 (2026-10-19)
Only the 26.1–26.4 beta cloudOS releases (up to 23E5207q) carry a vphone600ap
build identity. The 26.4 release and every one up to 26.7, and the 18.x
releases, have the research board alone (vresearch101ap). FirmwareManifest
now takes the research identities for the vphone600 components when there is
no vphone600ap, and leaves RecoveryMode out when the identity has none.
The new set com.vphone.patchset.research.board turns those components into
vphone600's. Every shipped preset names it. When the restore tree's
RestoreDeviceTree is not the research board's, fw patch records its patches
as not applicable (FirmwarePipeline.resolveFirmwareApplicability), so a
cloudOS with vphone600 components builds no SEP component, converts nothing
and stays byte-identical, and receipts do not claim the patches.
devicetree-boot-vresearch101_conversion
(DeviceTreeResearchBoardConversion.swift) rewrites the research tree before
any other device tree patch runs, whatever the selection, because those are
written against vphone600 paths and values. It finds the research tree by its
root target-type (VRESEARCH101) and then:
- sets the root identity (
VPHONE600AP,iPhone99,11); - adds the seven nodes the research board lacks:
/buttons,/arm-io/multi-touch,/arm-io/bluetooth,/product/vphone600-gestalt-variantswith itsd47vm2025vre-variant,/product/mapsand/product/haptics. Each goes where vphone600 has it and gets an unusedAAPL,phandle; - sets the phone's
/product,/defaults,/filesystemsand gestalt variant properties, most of themsyscfg/placeholders (flag 0x8000); - removes
/defaults/pmap-max-asidsand/product/kernel-core-dump-location.
Existing phandles are kept, so interrupt-parent, device-interrupt-parent
and serial-device keep naming the nodes they named. The earlier port
renumbered every node and repaired those three references by hand, which
would have broken any other reference.
Checked against Apple: converting the vresearch101 tree of cloudOS 26.1
(399b664d) and of the 26.4 beta (c0ecdb4b) gives their vphone600 tree
property for property. The only differences are the build time-stamp, the
order properties are stored in, and the phandle numbers, which are compared
through the node each reference names. DeviceTreeResearchBoardTests runs
that comparison whenever an IPSW in ipsws/ carries both trees.
sep-boot-vresearch101_sepd_dispatch (SEPResearchBoardPatcher.swift)
patches SEPD in the research board's SEP firmware. On the 26.4 beta, which
carries both, vphone600's SEPD sends operation 0x31 to the handler it shares
with 0x12 and others, which zeroes the reply; vresearch101's gives 0x31 a
handler of its own, which replies with a constant from __const. The patch
copies 0x12's switch table entry into 0x31's. The table is found on decoded
operands, as the bounds-checked switch (cmp, csel ls, adrp + add or
adr + nop, ldrsw … lsl #2, adr, add, br) whose bound covers 0x31,
and must be the only one. SEPD is found through the root argument object after
the "Built by legion2P" banner. The table lies in __text, and from 26.4 on SEPD
carries __TEXT,__fipspost_hmac, an HMAC-SHA256 of __text with an all-zero
key (both stock 26.4 SEPDs match it), which is recomputed. 26.1's SEPD has no
such section. A rebuilt sepi container keeps its PAYP load properties
(arms, tz0s): IM4PHandler now finds the PAYP element by its DER length,
which gives the same 10-byte header for TXM and the kernel as before and 6
bytes for the SEP image. SEPResearchBoardTests checks all of this against
Apple's 26.1 and 26.4 beta images when an IPSW in ipsws/ carries both.
The research board's kernelcache
kernel-boot-vresearch101_kext_mix (KernelCacheMixer) gives the research
board's kernelcache the iPhone virtualisation kexts it lacks (paravirtual GPU,
touch, audio, FairPlay, ...). For the cloudOS's kernelcache.release.vresearch101
it finds the recipe in Contents/Resources/kernelcache_recipes by SHA-256, then:
- extracts the donor, the vphone600 kernelcache of the 26.4 beta (23E5207q,
read once from its cloudOS IPSW), and the base into KDKs with
the native
KDKExtractionpipeline, naming the stripped symbols from the recipe's maps; - keeps the recipe's bundle list: the base without its research-board-only kexts, plus the donor kexts that link against the base kernel;
- stages the bundles as a root-owned volume (kmutil reads no other kexts and
takes the host's own in their place without a word) and links it with
kmutil create -a arm64e -n boot -s all -z -x --compress --img4-encode --split-ro-rx-tags; - puts back Apple's CPU subtype if the builder wrote another, makes calls
through the auth stubs the macOS 27 builder keeps direct again
(
KernelCollectionStubCalls, below), and checks that every requested bundle was linked.
The mix replaces both KernelCache and RestoreKernelCache, so restore boots
it too. The pipeline keeps kmutil's container (its PAYP properties describe
the new layout) and saves the preset's kernel patches into it
(ComponentDescriptor.replacement); minimal adds none. The shipped kernels stay
in FirmwareOriginals. Mixes are cached in ~/.vphone/kernelcache/mix, keyed
by the kernelcaches, the maps and bundle list and the builder layout.
Only the link needs root; native extraction runs as the invoking user. fw kernelcache <vm> stages
the mix as the user (both extractions and the volume) and links it under sudo:
root copies the staged volume into a fresh root-only directory, walking it by
descriptor, refusing every symbolic link but the KDK's pointer to the volume's
extensions and normalising modes (kmutil skips a kext with a group- or
world-writable file as silently as one root does not own), and runs kmutil on
the copy. The Launchpad helper's link runs with --link-only, which never
stages. kmutil records the absolute path of every codeless kext, which moves
the end of __PRELINK_INFO: the volume path takes the length of the build root
Apple linked the base in, read from the base's own prelink info, so a mix no
longer depends on where it is built. The 26.6 mix then has the segment layout
of the hand-built research mix that booted.
Requirements: root for linking; and for every base here (26.4–26.7, all
kernel-last) the kmutil of macOS 26.4 or later. fw prepare now pairs a
cloudOS without vphone600ap and refuses one whose kernelcache no recipe covers.
cfw update-kernel refuses on a mixed kernel, whose originals are the research
board's.
Native extraction checked on 2026-10-23: all 264 fileset images of the 26.4 beta vphone600 and vresearch101 collections match the pinned reference in recovered references, synthesized symbols and exported bytes. The full KDK trees match 313/263 files, with plist values compared semantically. See native KDK extraction. On the 26.6 base (23G71), the volume it stages for kmutil matches what the reference extractor gives: all 151 executables byte for byte, all 160 Info.plists in value.
macOS 27's kmutil keeps auth stubs. The kmutil of macOS 27.0.1 (26A434)
links the same volume with every kext's __TEXT_EXEC,__auth_stubs left in place
(apfs: 0x2340 bytes), and a call to another image goes BL stub → ADRP/ADD x17 (the __auth_got slot) → LDR x16, [x17] → BRAA x16, x17. Apple's own
iOS 27 kernelcaches keep them as well (iPhone17,3 27.0 apfs: 0x2360 bytes), so
this is the new toolchain's layout, not a flag: kmutil create has none for it.
Apple's 26.6 vresearch101 kernelcache has them at size 0, every call direct,
and the kernel patches look for those direct calls. On the stub-kept mix,
kernel-boot-apfs_seal_broken, kernel-boot-apfs_mount_upgrade_checks.mov_w0_0,
the three kernel-boot-syscallmask sites and both kernel-cfw-debugger sites
missed (24 base patches instead of 28), and the guest panicked at its first boot
after cfw install disabled the root snapshot: "root volume seal is broken" @authapfs.c:698. Trimming is not the cause; --slim off panics the same way.
KernelCollectionStubCalls runs after the link. For each stub it checks the
four-instruction shape and reads the target from the __auth_got slot, a
DYLD_CHAINED_PTR_64_KERNEL_CACHE pointer into this collection's code (any
other format, cache level or target is refused). Every B/BL in a kext's
code sections that lands on a stub is re-encoded with ARM64Encoder to land on
the target. That is what the macOS 26 builder emits, and the stubs are left
unreferenced. Rewriting corecrypto's code breaks its FIPS power-on self test
("FIPS Kernel POST Failed (-2074)!" @corecrypto_kext.c:356, in the restore
ramdisk), so an image with __TEXT,__fips_hmacs is sealed again: HMAC-SHA256,
keyed with the byte 0, of its __text. The existing seal must equal that HMAC
of the code as linked, or the step refuses. A collection that calls directly
is left alone. When anything changes, the container is rewritten uncompressed,
as patched components are.
Checked on 2026-10-23 on macOS 27.0.1: the 26.6 mix has 12,096 stubs and
131,560 calls retargeted. The seven call-site patches above are found again,
and none that was found before is lost: 28 kernelcache patches applied. The
template of iPhone17,3 26.6.2 (23G90) on cloudOS 26.6 (23G71), standard preset,
restored, took the CFW install and the trim, and its setup boot connected.
Booted again on its own, it reported Darwin 25.6.0 with iOS 26.6.2, no panic
in its log, and vphoned answered device.info. The setup's first-boot step,
the template adoption and the clone were not finished: Launchpad restarted at
that point, which ends a creation in progress.
Checked on 2026-10-19: a minimal VM of iOS 26.6 on cloudOS 26.6 (23G71) restores
and boots, and vphoned answers. Checked on 2026-10-20 with the standard preset:
iOS 26.5 (23F77) on cloudOS 26.5 (23F75) and iOS 26.4 (23E246) on cloudOS 26.4
(23E244) restore and boot the same way, their GPU driver taken from a temporary
cloudOS restore. The 26.7 recipe is one the research workflow built and
validated statically; it has not run through this pipeline yet (26.7 has no
iPhone release to pair it with).
Apple Watch guests (2026-10-20)
A Watch7,18 (Apple Watch Series 11, N227b) IPSW restores as a guest of its own
family (VPhoneGuestDevice.watch7_18, 416x496 at 330 ppi, scale 2). No
cloudOS ships a kernel for a watch VM, and the vphone600 kexts are iOS builds,
so the watch boots the 26.4 research board kernel (23E244) rebuilt with the
watchOS kexts of its own IPSW. It takes the watchos preset: minimal with the
watch board's device tree in place of the iPhone17,3 identity, and activation
stock (filesystem-boot-mobileactivationd_should_hactivate is off; pairing
needs it).
The watch kernelcache
kernel-boot-vresearch101_kext_mix with the recipe watch_26.4_23T240. A
recipe now names the guest family it is for (guest) and its kext sources in
order (sources); each kext comes from the first source that has it, the KPI
bundles (System.kext) always from the base, so the base kernel resolves them.
A donor without a cloudOS URL is read from the guest's own restore tree.
The 23E244 kernel was chosen because its KPI symbol sets, C++ class sizes and
syscall, Mach trap and MIG tables equal the watch kernel's. Both maps are the
research results: the watch kernelcache symbolicated with BinDiff against the
23E5207i KDK, and the 23E244 map of the iPhone recipe with about 2,400 fixes the
watch kexts' imports need (70 kernel-import binds resolved by three-way
alignment against the macOS 26.5.1 vmapple kernel, every __ZTV/MetaClass
label from ipsw kernel cpp, and IOSurface getters labelled 0x14 before their
entry, which failed PAC on retab). On 2026-10-20 the staged volume's kernel
and all 124 kext executables were byte-identical to the research mix that
booted to Setup with working touch.
Device tree
The installed tree presents N227b through the board copy iPad and Apple TV
guests use (devicetree-cfw-ipad_*). A watch needs two more:
Restore image
A watchOS image has no cryptexes: its dyld caches (arm64_32 and arm64e) are in
the OS image. For a watch filesystem-boot-cryptex_merge skips the cryptex
copies and dyld symlinks and only reseals the image with the guest payload.
filesystem-boot-gpu_driver marks the iOS driver and its compiler plugin
watchOS (MachOBuildPlatform.watchOS, re-signed, bundle resealed), as the
tvOS installer does; all its imports are in the watchOS 26.4 arm64e cache.
filesystem-exp-launchd_cache_loader_unsecure_cache_gate thins the universal
binary to its arm64e slice first (MachOFatSlice). vphoned is the arm64 daemon
marked watchOS (vphoned-watchos); its HID presses reach the crown as Home
(consumer 0x40). The side button is consumer Help (0x0C/0x95), not Power:
N227b's /buttons node names two buttons, help and menu, and Carousel's
-[CSLHIDEventStreamWriter _eventForUsagePage:usage:down:] maps 0x0C/0x40 to
the crown, 0x0C/0x95 to the side button (to the Action button only when
shouldSimulateSting) and vendor 0xFF01/0x200 to the Action button, which a
Series 11 lacks. -[CSLUIHIDHandler init] reads consumer Power (0x30) as the
separate power mask, which only sleeps or wakes the display. The VM window's
Device › Side Button (watch guests only) and the host automation key side
send 0x95; on 2026-10-20 it woke the pair-watch guest and opened Control
Center from the watch face, where 0x30 turned the display off.
vphoned links Vision (IcliKit's OCR) weakly: watchOS ships no Vision, and dyld
refused the daemon. Every other library it links strongly, and every symbol it
imports from them, is in the watchOS 26.4 arm64e cache; ui.ocr and
ui.describe report that the guest has no text recognition.
Checked on 2026-10-20: vm create --device Watch7,18 with the Watch7,18 26.4
(23T240) IPSW and cloudOS 26.4 (23E244) links the mix, restores, boots, and
vphoned answers the first-boot ping.
Pairing with a phone
See Research/Guest/watch_pairing.md. The watch preset now patches activation
(filesystem-boot-mobileactivationd_should_hactivate, on the arm64e slice of
the universal binary), and three patches are new:
On 2026-10-26 the Apple TV and HomePod presets also enable the guest BTVC
patch, and their installer admission includes it for both plain-arm64 and
arm64e userlands. The existing OS 26+ applicability and opt-out restoration
remain in place. HomePod mini 27.0 (24J361) and tvOS 27.2 (24K120) ship the
BTVC Bonjour, advertisement and LE connection implementation; the HomePod
connection manager still queries EnableBTVCLeConnection. Their original
feature plists omit the entry, which the existing plist patcher can add.
This enables transport testing; HomePod onboarding, Apple TV companion setup
and Home/AirPlay pairing have not been validated. Existing machines need the
updated Core Bundle and cfw update-environment while stopped, then a boot.
HomePod mini on the research board (2026-10-26)
HomePod Software 26.5 (23L471, AudioAccessory5,1) on cloudOS 26.5 (23F75), which
has no vphone600 components: the 26.5 research board kernel (26.5_23F77)
rebuilt with the HomePod's own kexts, as the watch is. homepod-mini keeps its
kernel and TXM patches and links this kernelcache. The firmware catalog
pairs HomePod Software 26.5 with this cloudOS, so Launchpad offers it; 27.x
keeps cloudOS 26.4.
The HomePod kernelcache
kernel-boot-vresearch101_kext_mix with the recipe homepod_26.5_23L471
(guest: homePod; KernelCacheRecipes.ownRecipeGuests now names it, so a
HomePod no longer falls back to the iPhone recipe of the same kernel).
AMFI, corecrypto and AppleImage4 are pinned to the base with the new recipe key
kextOverrides: b520 ships them too, but the T8006 kernel has a PPL
(__PPLTEXT, __PPLDATA) and their HomePod builds call into it; the
VRESEARCH1 kernel is SPTM/TXM (__DATA_SPTM) and has none. The seven KPI
symbol sets of the two kernels (xnu-12377.123.3 and .122.4) are identical.
gPTP (2026-10-27): kextOverrides also takes IOTimeSyncFamily from the base
and IOgPTPPlugin from vphone600_26.4_23E5207q, the pair of the iPhone 26.5
mix (now 89 kexts from b520, 7 from vphone600). The b520 plugin's
IOTimeSyncgPTPManager requires the PMU temperature sensor PMU tdev1 and
fails to start on a VM, which left audioclocksyncd waiting and AirPlay 2's
PTP clock (CM8021ASClockCreate) hung. With Apple's VM build the manager
starts and the HomePod plays AirPlay 2 from the Mac. The vphone600 family
does not link against the 26.5 kernel (it imports an IOPlatformExpert
private function the base lacks). No instruction patch. See
HomePod AirPlay.
Both maps are research results: the b520 kernelcache symbolicated with BinDiff
against the 23E5207i KDK, and the 26.5 recipe map with additions. The fixes
(HomePod/mix in the research workspace, rebuilt by its pipeline.sh):
- Kernel imports of the HomePod kexts, through the kernel alignment: 54 fixed,
15 kept after disassembly. One-instruction stubs (
mov w0,#k; ret) lie 8 bytes apart in both kernels, so names drift there and identical-code folding gives one stub many names: a_kc.placeholder binds to a byte-identical base stub; a real name stays a name bind (checked against Apple's names in macOS 25F80:_t6000dart_vo_tteand_pmap_has_iofilter_protected_writesit on the wrong base stub, the config-specific_ml_page_protection_type,_secure_channel_shared_pageand_ecc_log_memory_error_cestay the base's). The base map has_ml_set_reset_timeinside_ml_get_conttime_offset, as 23E244 had; the import binds to theretthe base PMGR calls. - Family names the base VM kexts import from b520 builds: PAC-diversity vtable
slots (
IOMobileFramebufferLegacyslot 344 andIOGPUEventMachine::incrementStampNoRelease, as on the watch;IOUSBHostPipeslot 32),kext_xfer_names, and eleven IOSurface getters whose b520 labels sat 0x14 (two 0x2c) before their entry, on the previous function'sretab, which fails PAC on return. - Class data labels of both maps from
ipsw kernel cpp; none of the 40 the donor kexts import moves.
Checked on 2026-10-26: bindcheck reports no unresolved bind for the HomePod,
base VM and donor kexts; the entry checks find no bind after a prologue (110
family, 1052 kernel, 2 donor family binds); vphone-cli's native staging of
the recipe is byte-identical to the research mix (the kernel and all 119 kext
executables). Relinking the HomePod's own extraction reproduces the shipped
b520 kernelcache: the same size, and apart from the prelink InfoPlistDigest
entries only two words of the shared __LINKEDIT differ, as on the watch.
Checked on 2026-10-26 and 2026-10-27: homepod-std (Launchpad vm create with
the HomePod 26.5 IPSW, cloudOS 26.5 23F75 and --preset homepod-mini; fw kernelcache staged as the user and linked as root) restores, boots, takes
cfw install and answers vphoned as AudioAccessory5,1, iOS 26.5. Its plan
holds kernel-boot-vresearch101_kext_mix beside homepod-mini's kernel patches.
iOS 18 guests on the research board (2026-10-21)
iOS 18.1 to 18.7.2 run on their own release's research board kernel, rebuilt
with the vphone600 kexts of the public cloudOS 26.1 (23B78), which still ships
vphone600 components. The userland comes from the iPhone XS family's IPSWs
(iPhone11,2/4/6), the only ones carrying every 18.x release, 18.7.x included.
minimal boots them as restored; standard boots them since 2026-10-24 (see
"standard on the iOS 18 research board kernels" below).
The kernelcache
kernel-boot-vresearch101_kext_mix with the bases 18.1_22B83 to
18.7.2_22H124 and the donor vphone600_26.1_23B78. Across versions an
import cannot be bound by name, so every donor import was resolved by code
identity, from 26.1 to 18.7.2 and from 18.7.2 to each release, checked against
the Apple-symbolicated macOS 26.1 (25B5062e) and 15.7.3 (24G419) KDKs; both
maps carry the names those targets have. The 26.1 map and the 18.x maps had
given 60 donor imports a neighbour's name, consistently on both sides, so the
research builds bound them correctly: the IORegistryEntry twins below, and
54 OSMetaClass, IORegistryEntry, IOService, IOUserClient,
IOHIDDevice, IOHIDEventService and IOSCSIProtocolServices virtuals
(the SCSI task management run was three slots off). The recipes give them
their real names. Every virtual one is proven by its vtable slot: the PAC
discriminator a donor kext signs the slot with is the
ptrauth_string_discriminator of the method's mangled name, the same slot in
both KDKs carries it, and each of the eight kernelcaches holds a signed
pointer with it to the renamed address. The IORegistryEntry twins also
have code markers:
setName(const OSSymbol *) had carried an extraction placeholder
(_IOFreeDataSharable, an unused KPI name of the 26.1 kernel) that no 18.x
symbol set lists; with the real names every donor import is a name the base's
own com.apple.kpi.iokit lists, and the symbol sets stay as Apple built them.
One import had been bound wrongly in every 18.x map: the donor's
AppleEmbeddedHIDEventService::dispatchBiometricEvent went to the base's
IOHIDEventService version (15 vtable slots in AppleEmbeddedLightSensor and
AppleSPU); in 18.7.2 the two are instruction-identical, and the recipes bind it
to the base's own AppleEmbeddedHIDEventService override.
18.5 to 18.7.2 take 18 donor kexts (the 16 of the 26.x recipes plus
IISAudioIsolatedStreamECProxy and ExclavesAudioKext, which the 26.1
AppleARMIISAudio needs); 18.1 to 18.3.1 take 15, without those three: 18.1
lacks the tightbeam API they import, and its addresses in 18.2.1 and 18.3.1
are not mapped reliably.
The 18.x kernels are kernel-first. On macOS 26.4 and later the mix is linked
on another Mac or macOS VM with an older kmutil over ssh (VPHONE_KMUTIL_SSH,
RemoteKernelCollectionBuilder): the staged volume goes into a fresh
directory of the remote /private/var/tmp, at a path as long as the base's
build root, root links it there, and the result comes back. Linked with the
kmutil of macOS 15.6.1, the 18.7.2 mix differs from the research build that
booted only in the 15 rebound pointers and the symbol sets that build had
extended.
The iPhone XS Max guest
VPhoneGuestDevice.iPhone11_6 (1242x2688 at 458 ppi, scale 3; iPhone11,4
maps onto it) keeps the iPhone17,3 presentation of the converted tree, with
two changes its userland needs. Its asset catalogs only carry 2436 and 2688
artwork and its SpringBoard has no Dynamic Island, so
devicetree-cfw-artwork_device_subtype writes 2688 and
devicetree-cfw-island_notch_location removes island-notch-location
instead of setting it; without them Hello and the wallpaper stay empty and the
home gesture breaks.
Restore image
The GPU driver is the cloudOS's own, as for 26.x: 26.x drivers do not load on
iOS 18 (_OBJC_IVAR_$_IOGPUMetalHeap._device is missing). cloudOS 18.7.2
ships the 18.7 driver, so a driver now has to be of the cloudOS's release
(major and minor version), not its exact version. The compiler plugin is the
bundle's own, as for every guest.
Touch: iOS 18 guests take every touch through vphoned; a touch that starts at an edge carries the digitizer edge flags (the edge swipe fix), so SpringBoard's home gesture works there too.
Checked on 2026-10-21: vm create --device iPhone11,6 with the XS family's
18.7.2 (22H124) IPSW and cloudOS 18.7.2 (22H43) recovered the GPU driver in a
temporary cloudOS restore, linked the mix over ssh with the kmutil of macOS
15.6.1, restored, booted, and vphoned answered. Setup's Hello screen and the
lock screen show their artwork, the bottom-edge swipe unlocks, and from
Settings it returns to the home screen. 18.1 (22B83) on cloudOS 18.1, the
oldest base and one of the three with 15 donor kexts, and 18.5 (22F76) on
cloudOS 18.5 passed the same checks. The other four recipes resolve every
donor import but have not been booted yet.
2026-11-04 — kernel-boot-thread_guard_violation on iOS 18.6.2 / cloudOS 26.1
iPhone17,3 iOS 18.6.2 (22G100) pairs with cloudOS 26.1 (23B85), so the guest
boots the 26.1 kernelcache.research.vphone600 while its iOS 18 base turns
kernel-boot-thread_guard_violation on. The 2026-10-24 shape then picked the
wrong function: in 26.1 set_exception_behavior_violation (foff 0xB04178)
the guard call, thread_guard_violation(current_thread(), code, subcode, true),
is the seventh instruction after its mrs x0, tpidr_el1 (the code is built in
between), and the only BL within six instructions of an mrs x0, tpidr_el1 is
send_set_exception_telemetry's get_threadtask(current_thread()) (foff
0xB76068, 588 callers). RET there returns the thread as its own task. The
first page fault of boot (a memmove into an unmapped buffer while OSKext saves
kext mutable segments, ESR 0x96000046) calls counter_inc(&task->faults)
through it, faults on the garbage counter, and recurses in vm_fault until
sleh_invalid_stack raises Invalid kernel stack pointer (probable overflow).
That panic is taken on SP1, so its debugger trap (0xe7ffdeff in
DebuggerTrapWithState) panics again: the serial console only ever shows
"Nested panic … Unexpected debugger trap while SP1 selected", with entry 1
never printed (upstream #715). CPU1–3 are never started.
Reveal procedure: the frame-pointer chain of CPU0 in the post-panic spin, read
over the kernel GDB stub (vm launch --kernel-debug-port, lldb gdb-remote),
walked to its oldest frame; exception frames are the records whose return
address is not preceded by a call, and their arm_saved_state sits 0x10 above
the record (pc +0x108, far +0x118).
The fix keeps both existing rules and adds one in front: of the BLs within ten
instructions after mrs x0, tpidr_el1, the one whose argument setup contains
movk xN, #imm, lsl #48 with imm >> 13 == GUARD_TYPE_MACH_PORT
(EXC_GUARD_ENCODE_TYPE, bits 61–63 of the code) wins. When no call carries the
type, the six-instruction rule applies exactly as before; the 18.6 kernel
should keep 0xA21D34 either way, but that kernel was not re-run here (the
18.6 test case is the synthetic shape described above, not its words). On 26.1
the patch now lands on thread_guard_violation at
foff 0xB4FFCC (0xfffffe0007b53fcc: get_threadtask() against kernel_task,
code/subcode into thread+0x390/0x398, fatal bit in thread+0x4d2, then
ldset #0x1000 = AST_GUARD on thread+0x404).
The patch is still needed there. With it blocked, the 26.1 kernel boots and
vphoned connects, but SpringBoard and runningboardd crash-loop on
EXC_GUARD/GUARD_TYPE_MACH_PORT "Unknown flavor 67" and "flavor 10" from
mach_msg (77 reports within minutes, Apple logo only). With the corrected
target the guest reaches the Hello screen and
[control] connected to vphoned HTTP API (iOS 18.6.2).
Validation: KernelExcGuardTests carries 26.1's
set_exception_behavior_violation word for word and the 18.6 shape; with
VPHONE_VP600_KERNEL set, the real-kernel test checks that the patched
function raises AST_GUARD. Booted on 18.6.2 / cloudOS 26.1 (standard preset).
2026-10-24 — standard on the iOS 18 research board kernels
Checked on iOS 18.6 (22G86) on cloudOS 18.6 (22G86), VM restored with
--preset standard --no-template: boot, Hello screen with a working display,
vphoned, Setup skipped, and Frida 17.24.3 (frida-server as a launch daemon)
attaching to SpringBoard, trustd and backboardd, spawning Settings with an
Interceptor hook and following a foreign SpringBoard thread with Stalker. Every
change below is a second shape, tried only when the 26.x shape is absent: the
cloudOS 26.4 beta vphone600 kernel (as 26.x and as 27.x), the 26.4/26.5/26.6
mixes and the 26.4b/26.4/26.5/26.6 LLB, iBSS, iBEC and TXM patch byte for byte as
before. The 18.6 offsets are of the mix kernelcache and the decompressed images.
The kmem_init panic was kernel-boot-thread_guard_violation, the one
kernel patch pinned to iOS 18 bases and so never before applied to an 18.x
kernel. Its anchor chain takes the first BL near the
com.apple.security.only-one-exception-port reference whose target has a
TBZ and then a BL; on 18.x that is a zone helper (tbnz w2, #6 on the alloc
flags), whose callee is a per-CPU zone allocator. RET there made every
allocation return garbage, so kmem_init's first vm_map_enter failed with
KERN_INVALID_ARGUMENT. 18.x set_exception_behavior_violation tests
thid_should_crash with ldrb ; cmp #1 ; b.ne and calls
thread_guard_violation(current_thread(), …); the patch now takes the callee of
the BL after mrs x0, tpidr_el1 in the window's callees when exactly one exists
(18.6 0xA21D34, which compares get_threadtask() with kernel_task first).
The 26.x kernels match neither shape, as before — except that an iOS 18 base on
a 26.x kernel (18.6.2 on cloudOS 26.1) does match the second one, wrongly; see
2026-11-04 below.
kernel-boot-vm_map_protect finds no gate on 18.x, as on 26.5 and later.
The former txm-boot-precheck_admission (the 27.0.1 fix) and ibec-boot-bootx_precondition
have no 18.x site and are not needed there.
The four VirtualAudio throw patches (speaker_route_throws, mute_set_throw,
speaker_protection_gate, volume_mode_precondition) look for code iOS 18.6's
VirtualAudio does not have, and are gated on iOS 26. Audio on an 18.x guest is
not checked.
The surface-compression keys filesystem-boot-ios18_surface_compression writes
into a minimal image live on the Data volume, which cfw install cannot reach.
On any other preset vphoned sets them at startup on an iOS 18 guest
(GuestCoreAnimationCompression) and restarts backboardd once.
cfw update-kernel now also serves a research-board VM: it installs the
kernelcache fw patch (run as the user) re-mixed and re-patched into the
restore tree, instead of refusing. The restore tree has to still be there.
2026-10-23 — Integrating the minimal, research-board, watch and iOS 18 presets
The combined PR stack retains its declared patch sets and version gates. The
integration adds host-side confinement without changing instruction matching or
replacement bytes. Privileged fw patch clones only firmware inputs into a
private directory, rejects links and foreign-owned inputs, and publishes regular
files back through the pinned machine directory. Disk.img is never copied or
rewritten by this step. Guest image mounts also live below the private work tree;
guest file edits use descriptor-relative reads and atomic replacement.
External patch sets are refused before any native plugin is loaded as root. Kernel extraction runs as the invoking user; root only links a staged private copy. The root restore-image step ignores user seal-tool overrides and downloads the matching tool again from an Apple HTTPS restore CDN into its private tree, rather than executing the user-writable staged copy. This requires network access for privileged resealing. The advanced kernel extraction tool remains a firmware preparation/build tool, not a dependency for starting an already prepared guest.
Validation covers malicious input/guest symlinks, snapshot publication, large SSH stderr/stdin streams, legacy fixed-address NAT reporting, patch catalog and recipe consistency, plus full bundle/Launchpad builds. Firmware fixture tests still require the separately held reference IPSWs; a build or synthetic test is not a claim that every newly supported OS has been restored and booted on this host.
VirtualAudio's device-state null gate — the singleton a guest never gets (2026-10-24)
system-virtualaudio-cfw-device_state_null_gate (new), same
VirtualAudio.plugin binary, verb cfw patch-virtualaudio-device-state-null-gate, riding the same
patchMachO staged copy as the other four VirtualAudio verbs in both
cfw install and cfw update-environment. Patcher:
FirmwarePatcher/CustomFirmware/ExecutablePatches/VirtualAudio/CustomFirmwareVirtualAudio.swift
(patchDeviceStateNullGate, anchoring in locateDeviceStateNullGate
and gatedDeviceStateSite).
Fault. With the four speaker-route patches live on iPhone19,4 27.1
(24A94425, arm64e.x1), audiomxd segfaults (EXC_BAD_ACCESS far=0) on
queue AudioControl, in the plugin at 0xa16b0 reached
HALS_AHPPlugIn::ObjectSetPropertyData ←
__vaemVADSerializationListener_block_invoke. Publishing the route wakes
VirtualAudio_Device's update (function at 0xa08bc), which pushes the
route's state at DeviceStateManager twice, each push behind its own
capability gate (checks at 0xfe810 and 0x225b88): gate bl, cbz w0, <skip>, bl TheInstance (0x2b2988), mov x20, x0, ldr x21, [x0] —
the faulting unchecked read. The singleton's global (0x74ff28) is stored
only at 0x3ad0a8, deep in "Deferred Initialization" (0x3ab344, a
dispatch block whose only caller, 0x39924c, has no direct bl).
Why no patch can publish the manager on a guest. Deferred
initialization runs at plugin init — its "Beginning" log appears within a
second of launch — but on a machine with no haptic hardware it exits
without reaching SetInstance: the whole-life syslog of an audiomxd run
never logs "Ending Deferred Initialization", and when the fault lands no
thread is parked anywhere inside 0x3ab344…0x3ae2cc. It is not blocked;
it bails. The manager is a haptics-domain object (its accessor's own
assertion names DeviceStateManager; the actuator-port wait at 0x3ac024 —
100 ms polls against an 8-second deadline composed by the
movz/movk/movk triple at 0x3abed8 — is the same dependency in
another form) and a VM has no actuator to construct it around. So
TheInstance() logs "TheInstance() called before SetInstance()" and
returns null for the process's whole life, and any push that arrives
once the gates answer true faults. Observed crash times ran from ~10 s
to ~130 s into a life — the push timing varies with what triggers route
events; the first variant of this story ("0.6 s after every launch")
was the crash loop's cadence, not a fixed race window.
Reveal (all Capstone-decoded, no literal address). TheInstance is
found from its own diagnostic: the "TheInstance() called before
SetInstance()" cstring, referenced exactly once in __text; the
prologue above the reference is its entry. Every direct bl to that
entry — six on 27.1 — is filtered to the gated shape both device-state
updates share: a call at −8, the cbz w0 at −4 skipping forward past
the whole update (target beyond the ldr at +8), the return moved aside
at +4 (mov xN, x0), and the unchecked dereference at +8 (ldr xM, [x0]). Exactly two callers carry the shape; a build with a different
count is refused rather than half-patched. The plugin header gates
everything first: cputype arm64 and cpusubtype (masked of the LIB64
bit) 12 — arm64e.x1. Any other subtype reports "nothing to do" and
writes nothing, which is what keeps 26.6.2 and 27.0 byte-identical; a
file that is not a thin arm64 image falls through to the anchor's own
refusal.
The write. Each gate-check bl is redirected to TheInstance —
0xa16a0 bl 0xfe810 → bl 0x2b2988 (0x9401745c → 0x940844ba) and
0xa16dc bl 0x225b88 → bl 0x2b2988 (0x9406112b → 0x940844ab), from
ARM64Encoder.encodeBL. The cbz w0, <skip> below each now tests the
singleton: null, the update is skipped through the function's own path —
exactly what every build does when its gate answers false; present, the
fall-through re-calls TheInstance and the whole update (the save, the
dereference, the lock, the push) runs untouched. Nothing is deleted and
nothing is permanently disabled: the update happens whenever there is a
manager to receive it, which is everything this code can do on a guest
whose manager cannot be constructed and everything it does on one whose
manager can. The gate-check calls themselves are queries whose results
only fed those branches; their loss is the check, which the singleton's
existence now subsumes. Idempotent: a re-run finds the gate slot already
calling TheInstance and writes nothing.
The two variants this replaces, and what each taught. The first cut
(…-device_state_guard, 2026-10-23) made each gate's cbz w0 branch
unconditional, permanently skipping both pushes. Live-validated: five
minutes of stable audiomxd, crash reports frozen, the system-sound
stream routed on PuffinOutput with its DSP chain running. Rejected on
review: it wins by discarding the feature — on a foldable those pushes
are how the audio graph learns the posture. The second cut
(…-device_state_early_publish, same night) left the deferred-init
actuator wait at once (b.ls → b at 0x3ac044) so construction and
SetInstance would run immediately. Offline it resolved cleanly; live it
was a no-op — the wait loop's "Beginning loop to wait for actuator port"
log never appears on this guest, because the product-flag routing at
0x3abf28 branches around the loop, and deferred init bails before
SetInstance regardless. That run produced the construction evidence
above (no "Ending", no parked thread) and one more crash at 31 s of
process life. The null gate is the third and honest cut: it does not
pretend the manager can exist; it makes the update wait for one.
The other callers, examined and left alone. The remaining HapticAttenuationIODelegate callers (0x4f7dec dereferences unguarded behind a capability flag that reads false without a Taptic Engine; 0x4f7b94/0x4f9114/0x5013ac pass the singleton into 0x2b30b4, reached only through the coordinator's activation, which needs an actuator port) never faulted across every observed run. With the null gate they keep their existing behavior: on a guest they are never reached, and if a future build ever reaches them pre-construction the gate's matcher refuses (it requires the gate shape) and the site gets its own patch.
Status. Patcher, declaration, verb, installer wiring, tvOS block
lists and unit tests are in; on the real 27.1 binary the verb resolves
TheInstance at 0x2b2988 and redirects exactly the two gate-check calls
(six bytes), a second run is byte-identical, and the 27.0 and 26.6.2
plugins keep their SHA-256s. FirmwarePatcherTests shows no new
failures against a HEAD baseline. Verified in the guest (2026-10-24
morning, bundle 2.12.0-local.6a386de6): the update log records both
redirects (0xa16a0, 0xa16dc -> bl 0x2b2988), audiomxd holds one pid
from t+30 s past t+5 min — the very window (10-130 s) the early-publish
run died in — the audiomxd crash count stays at the 30 the earlier
variants left, and RTAID telemetry still shows the SystemSounds stream
routed on PuffinOutput with its DSP chain running.
Foldable second-display identity (2026-10-25):
system-springboard-cfw-fold_display(guest system set, declared but in no preset yet — experimental, foldable guests only, iOS 27+; installs/usr/lib/libvphonefolddisplay.dylib) answers the display-topology gate the fold layouts key on. SpringBoard sizes its home grid from+[FBDisplayManager alwaysConnectedIdentities].count >= 2(confirmed on the host 2026-10-25: oneFBSDisplayIdentity "Main", NSCopying-capable), and the guest's paravirt GPU binds a single connector (DisplayPortCount = 1with two host displays configured), so the count gate can never pass on hardware facts alone. The dylib — SystemHook loads it into SpringBoard only — swizzles that metaclass method and pads a single-identity answer with a copy of the real identity; it is inert whenever the answer already has two or none, and logs to/var/mobile/Library/Caches/vphone-folddisplay.log. Not yet delivered:cfw update-environmentcannot turn on a patch the machine's patch plan never had (effectiveEnabledintersects with the plan), and /usr/lib is sealed against runtime writes — see the bring-up doc for the delivery blocker and the observation target once it boots.
Follow-up (2026-10-25): The delivery blocker above was the installer's hard-coded guest library table, now extended; installation is verified. The count accessors were never called in SpringBoard, so the claimed causal gate above remains unproven. Its main configuration already reports 678×951 points while the grid stays narrow. The subsequent backboardd runtime-wide probe coincided with persistent RPC unavailability on
2.12.1-local.8a55d450: USB still sees both vphoned processes, SpringBoard and backboardd, with no vphoned crash report retrieved. A build with only the backboardd probe disabled (2.12.1-local.ad5f3ecd) restores RPC in 5 seconds. Replacing it with a delayed, named-class-only inspection (2.12.1-local.42c33f10) also boots in 5 seconds and completes its 23-method dump. The owner isBKDisplayCloneMirroringManager, whose interface concerns clone mirroring; it is not established as the home grid's topology source. Neither wide layout nor runtime switching has been demonstrated. See the bring-up document's ninth round. Tenth-round static analysis adds a concrete size mismatch: SpringBoardHome's D77 metric table is 669×951 while the guest reports 678×951. Configuration transformation and cached screen-type probes are in progress; the actual selected enum is not yet measured. No geometry override has been applied. The combined runtime probe subsequently measured D74 (23), not D76, for both the global and UIScreen lookup. The transformer preserves 678×951. A follow-up experiment remaps that D74 fallback to D77 atSBHDefaultIconListLayoutProvider's typed initializer/current-device accessor, resolving both enum values throughSBHScreenTypeForShortString. This changes derived grid generation without changing framebuffer size; visual acceptance and runtime switching remain pending. Both D74-only and D74/D76-to-D77 provider substitutions booted but left a narrow grid. A scoped live experiment now uses the runtime-verifiedSBHIconManager setScreenType:listLayoutProviderLayoutOptions:animated:selector, with diagnostic Darwin notifications for inner/cover/inspect. This is an unaccepted experiment, not a completed display switch. Rejected: the D77 provider's own 145.5-point horizontal root insets and inset Dock still give a narrow layout. Provider substitutions and the experimental switch notifications have been removed from source. The acceptance scope now explicitly includes status bar, Dock and rotation; follow the shared display/scene configuration boundary rather than accumulating application-level geometry overrides. The next system-boundary experiment uses CADisplay mode requests through vphoned. Read-only guest evidence confirms one primary 2034×2853@120 mode and virtual-mode setter availability; actual switching is unproven. All identity-padding hooks now pass the original collections through unchanged. The tested arbitrary mode failed: deprecated virtual-mode accessors are no-ops, and the modern display-properties request reached IOMFB but caused backboardd to abort. The write experiment was removed and recovery build862de5d7verified. Keep the native framebuffer intact for subsequent logical-display investigation; do not suppress this failure with a patch. A reversible software-render-display experiment now registers an actual CAWindowServer virtual display without replacing the physical mode. It is manually triggered, not created on boot. CADisplay sees id 7/type 3 at 1398×2034, but SpringBoard still has only SB-main; FrontBoard identity propagation and removal-cache verification are pending. Separately, the shared posture restore ladder now cancels on user requests and stops on confirmed restore; an early close remained closed beyond 121 seconds.6004bbe4verifies both software-display creation and removal in the render server and cache-refreshed CADisplay inventory, without restarting the display service. FrontBoard still exposes only Main in both connected and always-connected identity sets. The software probe was removed; display identity/scene publication remains the missing system integration.86558897establishes native connection publication: the system-defined unknown tag makes the real software display becomeUnknown[7-1]without restarting SpringBoard during that transition. Its connection remains virtual, non-main-like and non-always-connected, so it is not yet a Duo panel. The experiment was removed and stale frontend identity state cleaned; stable built-in role modeling and proper disconnect ordering remain open. A default-off, one-shot experiment now adapts only the owned virtual panel's CADisplay type/pointScale at the central SpringBoard display-source boundary. It preserves native IDs and uses the original FBS identity factory; runtime always-connected/scene acceptance is pending. No widget, status-bar or Dock layout override is added. The one-shot adapter reached count 2 and correct 3x cover bounds, but trapped in a managed-display cloning invariant and automatically reverted. A new, default-off experiment uses the native FBS builder's root-derived identity support and transform-set updates on Main, preserving the original renderer and hardware mode. It validates a preview before publishing; acceptance is pending.4efbeb4bvalidated the preview, but startup publication asserted in the outer SBFBSDisplayTransformer's exactly-one-result invariant. That enable path is now disabled, with baseline recovery verified.83435b6fexposes the more specific mismatch: native FoldingDual policy already requests primary content on .inner, while the sole scene proxy is legacyMain with .default surface. Cover transform mode/insets are inactive; UIScreen and regular scene size classes are already correct. Continue at the common display-profile/scene surface boundary; layout, live switching, status bar, Dock and rotation acceptance remain outstanding. The surface source is now traced to BKSDisplayService traits, populated from backboardd's CAWindowServerDisplay extendedProperties DeviceSurfaceType value. A default-off, 30-second primary-only metadata probe tests native inner classification without extra identities or framebuffer mutation; runtime acceptance is pending.0932888fconfirms that this single shared metadata change reaches the native .inner scene surface, but the resolver waits for a matching .cover. Static native readiness logic corroborates the required pair. Baseline readiness and SB-main were restored; backboardd never restarted. A bounded software-cover/native-inner metadata test is next, without type/count hacks.47b16e3fkeeps SpringBoard alive but the software Unknown display still gets no cover window scene. Recovery to Main/default and the visible home screen is verified. The built-in-adapter follow-up was rejected by automatic approval review and awaits explicit user permission; it has not executed. Read-only analysis identifies the earlier managed-display failure as "Cloning source already exists", rather than a missing optional. Static managed-profile admission is now resolved as.mainkind OR.integratedconnection; surface metadata alone does not meet it for the Unknown software source. A read-only per-UUID BKS trait query is added so intended cover metadata can be checked independently of scene admission. The first readback exposed stale CADisplay client inventory, with guest SpringBoard continuity verified. A local inventory refresh and observational public-monitor initialization trace are prepared; the native invalidation path's per-source updatable gate is now explicitly distinguished from successful transform delivery.baa0b497independently reads native primary=inner and owned software UUID=cover metadata, preserving SpringBoard PID/start time and default scene. Main invalidation is source-derived to depend on the isExternal trigger mask. A separate reversible, default-off refresh pulse is prepared with unchanged original configuration output; runtime delivery is pending.087cc63dverifies Main updatable=0 and native transform calls 1 -> 2 under a reversible refresh pulse, with flags restored and both system process PID/start times unchanged. A default-off single-Main effective- bounds experiment now tests live cover/native-inner selection through that path, preserving the singleton invariant and physical mode; UI acceptance remains pending.db4f509ereaches live UIScreen bounds 466x678 and restores 678x951 with unchanged SpringBoard/backboardd processes, but the visible home is clipped because the effective mode remains native-sized. Bounds-only publication is not acceptance. The next revision uses native FBS effective-mode copies to update the full frontend geometry descriptor, leaving CADisplay's hardware mode alone, and adds scene/window frame readback.c915dfd5updates effective mode to 1398x2034 and UIScreen bounds to 466x678 while physical CADisplay stays 2034x2853. The root scene and every visible system window remain 678x951; screenshots are still clipped. Explicit return to inner and process continuity pass. A guarded, default-off native SB-main scene-settings geometry update is prepared to connect this missing common coordinator, without per-window edits.fe02bc25only inspected the root baseline; generic displayConfiguration KVC is unavailable on UIApplicationSceneSettings. The next build uses SpringBoard's native hosted-display FBS settings extension and adds pure UIS device/display context copies for artwork-family classification.6eca5020updates SB-main server frame and scene coordinate bounds through the native hosted configuration extension, but all visible windows retain 678x951. No per-window fix is used. Artwork-only classification copies all remain D74. The next guarded common-context probe replaces Main's cached default-context selection with a configuration-based copy and coordinates every root profile scene by display identity; visible reflow is pending.bf39730crejects the stale-context hypothesis: cached/current contexts already agree at 466x678 while windows retain 678x951. That adapter is removed. Native pure geometry classification confirms 678x951 -> D74, 669x951 -> D77 and 466x678 -> D76; the requested size stays 678x951. Static SBWindow policy disables ordinary scene resize for Main. A guarded native pipeline refresh plus read-only window-policy/reference-bounds inspection is prepared; no per-window frame edit is used.2061f359confirms the native pipeline refresh fires but does not resize. Status/home windows are scene-sized/non-hosted and have correct scene-derived target bounds; Home's Main-display predicate disables resize. A scoped SBWindow base capability extension plus the common native scene-bounds notification is prepared. It handles only preexisting scene-sized windows, supplies no window rectangle and uses no per-component class list.ab83c88eand sizing-only3e74efa3both propagate native scene resize to 16 windows (13 changed frames), with coherent 466x678 window/root geometry and unchanged SB/backboardd process starts. Both still render a clipped home and vertical/overlapping Dock. The sizing-only version confirms portrait orientation and identity affine transforms, excluding the full callback's transform-layer update as the cause. Explicit inner restoration visibly recovers 678x951. This remains default-off diagnostic behavior; usable switching and the requested inner home layout are not accepted.ce4ec5ednative TRA readback receives landscape device inputs but keeps portrait interface output/home mask 2. Native home rotation capability is initialized from idiom or scene.surfaceType==inner, not just pixel size. A consumed-once single-real-Main/one-inner-proxy readiness experiment is prepared for native content resolution at 678x951; it does not create a virtual display, change CADisplay type/count or expand transform results. Runtime validation remains pending; the built-in panel retest stays off.75951707confirms single-real-Main inner readiness yields native primary content and home rotation mask 30 rather than baseline 2. The 678x951 manager remains D74 and interface rotation still refuses 90 degrees, so neither inner layout nor rotation is accepted. Both bounded tests restored native role 0 and a fresh unarmed SB; no SB crash report was returned. Additional changes only inspect native pipeline/arbiter policy state. Full second-test evidence corrects the RPC-only interpretation: Home and its content do rotate natively to 951x678/orientation 4, and the screenshot is 2853x2034, while StatusBarWindow stays portrait and layout is clipped. The RPC still refuses, so its global orientation readback also disagrees. This is partial native rotation, with usable whole-system rotation pending.0bdf35efadds read-only actual-scene/native orientation-state inspection. A pose-only sample also shows interface output lagging/disagreeing with device inputs across two 3-second observations, without AX rotate/rollback. Default role/portrait UI is restored through a fresh unarmed SB. Causality and settled, coherent rotation remain unproven; no per-app fix is added. The next read-only preview builds coherent inner/cover mode copies with native derived identities, retaining the same CADisplay. It inspects identity capabilities and native arbiter/stage descriptions. Neither is published; the obsolete two-result expansion code is removed entirely.42ab7eb3verifies coherent mode/identity previews, but both children lose isMainRootDisplay. Native mainConfiguration searches for that exact flag, so singleton replacement is excluded without a runtime publication. Native StatusBar arbitration supports all orientations. A default-off, ten-second root-scene orientation pulse and pure-copy ABI preview are prepared to test scene settings versus the native arbitration result.b3747114verifies an independent mutable settings copy changes direction 1->4 without changing the original or frame. Before runtime publication, detailed main-queue inspection was observed taking four seconds. Automatic heavy diagnostics are removed and ordinary inspection becomes a bounded, timed JSON snapshot. Short-wait negative UI conclusions require quiet revalidation; the root-orientation pulse remains default-off.a30e60dbquiet snapshots measure 0.59-2.30ms. The awake six-second cover/inner test preserves SB/backboardd starts and coherent geometry in both directions, but confirms the same unusable clipped/overlapping cover layout. D77's independent native profile is itself centered 4x6. Automatic approval rejects the next single-Main role/force-SB-recreation trial before execution; the once marker is verified absent. Explicit approval is pending, with no root-orientation publication or role probe left active. Source-only recovery review adds exact weak scene scope, atomic generation cancellation, current-value checks and verified/retryable restoration to the default-off orientation pulse. It is built but not deployed/tested; the rejected role/restart operation still awaits explicit authorization. The user subsequently authorizes the single-Main retest.4bdf2d54is deployed with verified dylib hash. Actual root settings direction 1->4->1 and readback-verified recovery leave StatusBarWindow/scene at direction 1 while Home stays 4; the setting is not the shared rotation driver. System process starts remain unchanged within each trial, and default portrait recovery is verified afterward. The next default-off probe uses UIKit's scene geometry request plus native settled-pose/input refresh controls; no type promotion, extra display or app-mask override is included.a9fd2032native geometry requests return UISceneErrorDomain 101 for this system windowing mode. Validated pose/input refresh calls also do not fix the result. Provider directions [3,4,3] are correct and measured deferral, coversheet/fold holds are inactive; system process starts remain stable. Default surface-0 portrait recovery is verified. The next analysis targets the native system arbitration policy extension point; no policy is applied. Standard lock/app events and paired SBS/gravity publication do not establish correct rotation. The bridge's delivery record is not native effective-state readback. Explicit BBD native getter snapshots, a scoped/restoring orientation event-enable probe and native forceOrientationResolution update-context control are prepared, without installing policies or altering app masks.ffe8184dproves native raw/effective direction is 0 after virtual nonzero publication while event delivery is enabled, unlocked and rotation-capable. The high-level virtual accelerometer sink's extra hardware refresh is traced to CMDeviceOrientationManager.deviceOrientationBlocking and re-publication. Remove that poll: the native update already recomputes effective state. Existing accelerometer regression suites and full bundle admission pass; guest endpoint/UI acceptance is still pending. No enum/mask change is made.63af20bdruntime validates that removal: native BBD raw/effective values persist at 4 for landscape and return to 1 for portrait. SB/BBD process lifetimes stay unchanged during the trial. The inner surface still maps pose device 3 to interface 1 and device 1 to interface 4; Home follows that result while status stays portrait. Usable rotation/layout still fails; the unarmed default portrait baseline is restored after the experiment. Native resolver tracing locates the inner-panel conversion table [4,3,1,2] in SBWindowScene (inverse [3,4,2,1]), exactly matching that measured offset. Prepare a default-off, ten-second, exact-Main-scene native toll-free conversion probe with paired automatic restoration; no per-window angles, masks, new display or policy are supplied.ac5e5f4dvalidates paired-map correction: native device/interface/root scene/Home agree at 1 -> 3 -> 1 without process restart. Status remains portrait and widgets/Dock still fail visual acceptance. Native status rotation also requires scene.sbhScreenType == D77 (32), while 678x951 classifies as D74. Prepare a separate consumed-once shared-scene-family probe using a native-classified reference copy; no reference geometry is published, no status-specific method or mask is changed. Validation pending.dfbcdc9csafely declines family selection because the scene has no displayConfiguration; type remains 23. The correction resolves configuration through FBDisplayManager Main with the same identity guard. Post-deadline runtime snapshot confirms automatic restoration of the direction map.04596e28guest hash matches build; scene type and actual icon provider both become D77 at unchanged 678x951. Status/page/Dock acceptance still fails. Native inverse-mapped physical pose also produces UI 1 -> 3 -> 1 without any conversion override, showing the original table is the native inner mounting convention. Root server settings remain 1 while client UI follows 3; prepare combined existing root-pulse verification. Combined D77-scene/native-pose/root-server pulse still fails status/page/ Dock acceptance. UIScreen bypasses the exported context classifier wrapper; both paths share exported _SBHScreenTypeLookupCommon. Prepare a separate consumed-once constructor probe at that common helper, using existing process-private MISFixDetour mechanics, native reference classification and all-argument passthrough. No on-disk cache is changed. Snapshot all three family paths and native status rotation permission. Runtime pending.652fa4fcvalidates all three families 32 and native status permission YES/mask 30. Landscape pages and Dock visibly separate correctly compared with the scene-only probe, but status native orientation remains 1 while scene/effectiveGeometry/Home are 3. This is partial shared-policy progress. Unarmed SB restores families 23 and native status NO/2. Prepare the shared native application actuation callback refresh and global-direction/setup readback; no individual window setter or mask is used.3efe7381proves pipeline setup is finished and global active direction is already 3; replaying native actuation leaves the embedded-display/status slot at 1. Prepare a separate ten-second native embedded-slot sync with exact scope/original-value guards and verified restoration. No individual window setter or mask is used; runtime acceptance remains pending.dee331c8native embedded setter does not apply 3 (readback stays 1). Static manager tracing identifies Main's legacy application route; the native per-scene route already exists. Prepare a consumed-once exact-Main- inner construction/manager policy adapter to select that native path, with thread-local construction scope and fresh-SB restoration. No identity, display type/count, window angle or mask is replaced. Runtime pending.6581d4c8installs hooks but does not enter the guarded native construction branch. Correct the manager's actual _windowScene ivar/native scene identity, and bound construction to SB-main/Main-root/678x951 with explicit startup surface 0-or-2 admission; runtime remains inner-only. Log scope inputs and retain fresh-unarmed-SB restoration. No route effect is yet accepted.896e6d1econfirms native per-scene construction: status and Home now follow 1 -> 3 -> 1 with matching frames and stable processes. Battery is visible, but time is missing and the overlay overlaps unmodified Safari's toolbar; full status/safe-area acceptance still fails. Prepare a separate root UI-role probe, preserving original isMainRootDisplay discovery while letting native Main roots select nonlegacy per-scene UI consistently. No type/root/identity fields or display counts change. The per-manager adapter is not armed in this new test. Runtime validation pending.8ae865f9root-role startup traps in FBSDisplayMonitor mainConfiguration initialization; unmarked automatic relaunch is not experiment acceptance. Baseline is restored. The revision preserves original Main semantics for both native monitor initializer scopes (finally-restored thread-local), retaining native validation rather than disabling it. Add startup guards for Main NO/MainRoot YES and all three families 32. Runtime pending.0613339dnative monitor initialization succeeds, then physical default orientation lookup returns nil and traps in FBInterfaceOrientationService. Startup guards reject the unmarked recovery and cleanup restores baseline. Extend the original-Main hardware scope around default orientation lookup/ submission and identity-based submission, passing all arguments through and preserving native assertions. No on-disk cache/identity fields change. Runtime outcome pending.49fcc209passes monitor/physical-orientation scopes but aborts at native XBApplicationController bootstrap. Reject global Main-predicate conversion; remove its implementation and discard stale markers. Prepare a read-only native UI configuration object (valid type 7, UIKitMainLike, same Main root/ backing) and copied settings only. Check physical/connected counts and non-nil render equality. No live scene or display inventory is modified.116c3c42read-only native UI candidate builds with exact Main parent and identical non-nil backing. Connected identities remain 1 -> 1 and CA objects 6 -> 6. Only a distinct settings copy changes. The user explicitly permits UI-derived identities with one physical Main. Prepare a ten-second exact- scene hosted lease, optional native client update and verified rollback; runtime publication remains pending. Hardware Main predicates stay native.fc9aa0d0pulse does not publish: it used a read-only FBS client rather than the writable FBScene server. Activation safely refuses; counts/PIDs remain unchanged. Resolve the real SB-main server and keep a separate exact client binding. UIScene's native update registers a UIScreen configuration then reads scene displayIdentity; distinguish hosted/server/client fields and inspect mutable setters read-only. Corrected runtime remains pending.249a5f50verifies hosted publication and rollback but not identity adoption: server/client type stays 0. Native setDisplayConfiguration: is available with a typed object ABI, and displayIdentity derives from that actual setting. Prepare a guarded two-field transaction and original-value rollback, keeping hardware Main unchanged. Native actual-identity adoption remains pending.ed2bf017validates real UI identity 0 -> 7 -> 0, legacy/embedded flags YES -> NO -> YES, stable SB/BBD and unchanged connected/CA counts 1/6. Hardware Main stays native. The preexisting legacy status component remains cached; prepare native component unregister/recreate/register lifecycle under the derived identity with original component/identity restoration. Chrome/data acceptance remains pending; no window angle/frame is supplied. User's native reference establishes Duo's top-right time/ring status module. Generic per-scene bar visibility from 3128f885 is not the target; rotation still disagrees at scene/server actuation. Stop generic bar tuning and trace native SBDuoDisplaySystemUISceneController / SBSUIDuoDisplaySceneSpecification.
Duo native-status follow-up (2026-10-26): native dynamic Duo session admission is enabled although no Duo controller is in the 18 startup controllers. SystemStatusUI's AxisAlignableSplit2007 factory matches native display width and scaled camera exclusion geometry; do not infer that 2034->2007 width alone fixes it. Added manifest-gated, default-off read-only status_provider.inspect (native factory previews only; no screen/provider publication). Corrected Duo inventory Class comparison and controller setup lookup. Runtime results pending; generic status component visibility remains outside native acceptance.
Native status readback (3c7497fe): actual exclusion is a 125.333x36.667-point pill; native factory selects DynamicSplitSmallIsland at both 2034 and 2007 reference widths. Width-only explanation rejected. Added isolated native notch-size factory preview (no publication). Extended ten-second root-scene orientation pulse to the exact approved UI alias only, requiring original Main root and unchanged CADisplay backing. Native server actuation/rollback under that alias remains pending runtime validation.
ca903010 runtime: isolated 2007-width + native notch-size (37x37) factory argument selects AxisAlignableSplit2007, without publication. On the approved UI alias the root server orientation updates 1->3 to match native arbitration; landscape Home/Dock is visible. Explicit direction and identity restoration verified, SB/BBD process start times stable, counts 1/6 unchanged. Persistent rotation, correct native status rendering, and runtime inner/outer switching remain unimplemented. JSON evidence recorded in Research/Guest/Evidence.
Status renderer verification: +[SpringBoard _statusBarClass] conditionally selects STUIStatusBar_Wrapper using Speakeasy feature flags. Added only class readback to the existing status inventory; no flag override, class selection hook or live renderer replacement. Runtime class inventory pending.
ed4011db readback confirms live STUIStatusBar_Wrapper -> STUIStatusBar -> DynamicSplitSmallIsland; feature-flag forcing is unnecessary. Added default-off eight-second inner reference-metric lookup scoped to the exact owned UI alias and screen. Original native factory must accept its 2007 profile. Real-screen construction, hardware and window geometry are retained; original factory restores before identity/component rollback. Visible native status result pending; this remains a bounded experiment.
1ca946c8 live lookup did not reach accepted-reference selection; provider and screenshot stay generic. Direction round trip and complete rollback verify without process restart. Extend bounded classmethod inventory to the four native factory levels and exact owned component construction/mounted reset, with thread-local recursion/scope guards and conditional IMP restoration. No C detour or physical/global Main override is added. Outcome pending.
Native status cache trace: UIKitMainLike, nonexternal screens use an embedded _referenceBounds-keyed provider cache before factory entry. Add a native non-MainLike UI-derived identity preview, using only an exact unpublished configuration's thread-local identity substitution during native building. Physical Main remains untouched; verify Main parent/CADisplay/geometry before any separate explicit ten-second publication. Add actual target-screen cache predicate readback. CoverSheet button geometry is included in common display acceptance; no button-specific patch. Runtime preview pending.
User now explicitly authorizes true-device dimensions: acceptance becomes 2007x2853@3 (669x951pt), superseding custom678. Correct the shared Duo inner boot profile from2034 to2007 pixels. Native-size tests must use native Home classification, without the custom678 classifier adapter. The independent non-MainLike native UI identity preview passes with original Main parent and same CADisplay; physical Main remains unchanged. Control Center and CoverSheet join unified layout acceptance; no independent UI scaling is added.
71240384 native-size boot verifies2007x2853@3, FBS/UIScreen669x951 and native D77 classification without custom678 mapping. Nonembedded UI migration reaches native factory but selects Fallback, not Duo; rollback/process stability pass. Wallpaper remains portrait-only; status/glyph/Control Center acceptance open. Touch now takes priority: right/center/bottom input attempts did not launch visible targets, including a clean rot0 boot. Added read-only HID geometry inventory and default-off15-second UIKit touch observation (unchanged event forwarding, bounded logs, restored original IMP). Both panels' actual hit ranges remain unverified. No controller/button scaling patch is introduced.
Native input trace e237688b proves expected pixel/3 coordinates across inner right/bottom regions; all are intercepted by STUIStatusBarForegroundView. HID touchscreen is present/integrated with four contacts and0..32767 X/Y. Correct read-only registryID ABI to SDK CFTypeRef/CFNumber (initial IDs invalid; input untouched). Add owned-alias native phone-idiom inheritance/trait propagation with verified restoration, plus foreground/status frame readback. Actual Duo rendering, normal Home hit delivery and both-panel acceptance remain pending; no coordinate/button/window scaling workaround is added.
2dc1eacf shows idiom field0 but trait collection remains-1 and Fallback persists. Use native _updateTraits before trait propagation. Complete reversible native component-window handoff: deactivate distinct old-profile window after new component connects; hide own replacement before disconnect and restore/verify old visibility. Native default bar frame is54x951 atx615; actual foreground still covers entire669x951 and intercepts touches. Added window-role tracing; no frame/hitTest/input remap. Runtime result pending.
bf9002f1 verifies published phone traits0, real native AxisAlignableSplit2007, compact right-top foreground bounds and restored SBIconView hit delivery. Visibility/component/idiom rollback pass, but logical-screen status data is empty. Add constructor-armed, default-pass COW lookup adapter on exported STUIStatusBarGetVisualProviderClassForScreen; an explicit8s reference-metadata pulse retains original Main/UI identity/wrapper/data channel and native selection, bypassing embedded cache only on an isolated lookup argument. No fabricated values/classes or geometry changes. Runtime result pending.
f3d69e48 native lookup succeeds but renderer remains cachedDynamic; resetVisualProvider only clears region/item/alpha dictionaries. Correctly clear class cache toNil and call native reinitializeStatusBar; preserve original actual provider for verified rollback. No explicit chosen-class assignment. Add read-only host socket display.geometry and richer touch orientation/type/coordinate traces for user's post-rotation offset report. Portrait mapping alone is not full panel/rotation acceptance; no unmeasured input remap is introduced.
64a73aa0 ordinary rotation90 is refused while SBS pose3 remains pinned; no actual landscape was reached. Host/VZ/capture remain2007x2853; both touch paths agree in portrait. Add fresh native SB inverse-map bridge and iPhone19,4-only synchronized SBS pose/gravity rotation, preserving hinge/tabletop and existing logical AX request; owned-state rollback and old-model behavior retained. Cover/inner regression matrix added. No generic gravity mapping or host input transform changed. Rotated acceptance remains pending.
FoldGuard crash correction: real21:06:04 startup IPS PID294 is PAC_EXCEPTION261 in objc_msgSend(retain) from vpGuardedCopyRender+84 during native render commit. The id-return guard ARC-retained an unmanaged C++ render-layer pointer. Change return/original-call types tovoid*, validate native pointer/GPR ABI, retainNULL exception behavior, add non-ObjC pointer regression fixture and bounded actual nonnull forwarding log. Native deployment/startup acceptance pending; new geometry experiments paused. Sanitized crash evidence saved.
76b6ff90 FoldGuard validation:7 host fixture checks and full build/admission pass. Real guest ABI ^v36@0:8^v16I24^I28 accepted. Boot plus two controlled SpringBoard first-commit reconstructions show no new IPS; existing invalid-name exception fallback remains operational. Non-null path regression is proven in host fixture, not yet observed in these guest launches.
Crash-fixed native pose isolation reaches90 degrees (scene4, host-90) before AX is called; subsequent same rotation succeeds. Avoid racing fold transition: Duo waits for native UI receipt after pose/gravity and uses native rollback; ordinary-device AX behavior retained. Logical reply fields stay consistent with the verified receipt. Multi-angle/runtime hit verification pending.
86eea0ac achieves true90-degree UI and host-90 with mapped pose1/gravity0; raw host/guest injection points agree. Fix confirmed socket contract mismatch: full screenshots are upright JPEGs but tap/swipe used fixed portrait pixels. Shared inverse transform now covers windowed/headless input and both Duo sizes, with current guest orientation/live VZ dimensions. Native-settle-first AX reassertion refreshes screenshot/pointer caches. Focused four-direction tests added; full live screenshot-coordinate acceptance pending.
Remove unconditional near-edge coordinate snapping from host touch dispatch: ordinary clicks must preserve their requested point. Native edge flags and explicit snappedScrollStart home gestures remain. Full-screenshot inverse mapping plus near-edge runtime matrix will verify the combined correction.
Same default-off original-Main lookup now accepts only the two canonical Duo panel bounds and verifies native AxisAlignableSplitV68Cover selection on outer 466x678, alongside proven inner2007 profile. Outer live acceptance pending.
Duo db26a8cc inner input runtime checkpoint: four orientations now deliver upright screenshot pixel(60,1050) to Home point(20,350) through both host and native fixed-panel input paths, with unchanged SpringBoard process. Evidence: Guest/Evidence/iphone_duo_inner_four_direction_touch_20261026.json. Initial rotation settle timeout still requires investigation; outer panel/full-area coverage and persistent native status remain unaccepted. No new binary patch was introduced for this checkpoint.
Duo cleanup: removed the rejected virtual-panel-to-builtin CADisplay type and scale override; constructor now only discards its stale marker. Outer baseline has correct1398x2034 pixels but surfaceType0 and failed visual status/Dock acceptance. Native cover role1 remains the next verification target.
Bounded cover-role verification: extend the existing backboardd primary-only DeviceSurfaceType probe to native cover1 via surface.cover. Retain30-second rollback, original identity/mode/count, and guard older rollback timers by lease generation. Orientation-events probe remains inner2-only. No secondary CADisplay or runtime mode mutation. Runtime acceptance pending.
c6f0cdbe runtime: primary cover1 reaches SB-main at native466x678, but native outer UI remains incorrect (side Dock, invisible status, auxiliary window receiving lower-right touch). Role-only hypothesis is insufficient. Keep the cover probe default-off; no new always-on behavior is accepted from this test.
Cover acceptance correction: explicit closed pose after SB reconstruction makes native FullyClosed policy ready and primaryContent without a readiness patch. Main cover status is visibly rendered after1-second settling (earlier instant screenshot was premature). Keep native cover layout distinct from inner; side Dock lacks a true-cover comparison. Status remains pulse-only.
Duo lifecycle: vphoned captures actual completed pose readback and replays it on SpringBoard finishedstartup with generation cancellation. This addresses observed unknown pose after SB-only relaunch; no new binary patch or geometry override. Built, not yet guest-validated.
290da141 pose lifecycle runtime: closed/device1 and closed/device3 survive separate SB-only reconstructions with identical SBS readback. Final closed/ device1 restored. Evidence in Guest/Evidence/iphone_duo_pose_replay_20261026.json. This acceptance is pose lifecycle only, not the remaining live display switch.
Duo same-Main viewport: pair root scene displayConfiguration with sb_hostedDisplayConfiguration and frame, validating both native setter ABIs before mutation. Derived from the already verified UI migration path; no new identity or CADisplay mode. Runtime acceptance pending.
cf557df1 paired-update test was blocked by an overly strict generated-object ABI check, not a native setter failure. Correct to semantic object-argument validation. Same-process dimension roundtrip verified, rendered cover still clipped; rerun required before evaluating the paired-update implementation.
0cd9e242 live paired-configuration acceptance: root/Home bounds switch both ways,15 window frames update natively, SB PID unchanged. Rendering not accepted: scene family31 versus cached UIScreen/global32 during cover. Native family cache/re-evaluation and backing/capture/input viewport remain unresolved.
Duo default-off viewport-family experiment: native context reclassification at exported SBH screen/global lookup boundaries, scoped to active same-Main cover lease. Preserves original paths otherwise, with recursion guard; no cache memory edits or hardcoded component layout. Runtime pending.
0d172c0d runtime: screen lookup adapter accepted, global adapter safely refused Unrelocatable. Cover screen/scene31 versus global32; rendered layout still invalid. Same-process return to inner32 verified. Default-off experiment only, no global relocation guard bypass.
MISFixDetour bounded B.cond support for the revealed global-family prologue: ordinary conditional branch relocates through inverse condition + absolute jump; overwritten-range destinations and AL/NV/BC.cond remain rejected. Native host taken/fallthrough and condition/destination tests passed. Shared helper affects future admitted detours; complete build/runtime checks pending.
c75a6a9a global adapter experiment failed runtime acceptance: both hooks installed but pre-transition diagnostics/RPCs stalled. No new SB crash report. VM recovered after restart without the consumed experiment marker. Host relocation tests remain limited evidence; global experiment stays default-off pending hang diagnosis. Goal not achieved.
Global-family hang diagnostic adds bounded raw entry/return/stack records to the existing default-off experiment; no behavioral bypass. Static slow path confirmed dispatch_once; actual blocking location remains unproven.
7afd766f runtime: both global/per-screen native reclassification work; all families32->31->32 with unchanged SB PID293. Raw trace shows first24 calls return normally depth1. Prior hang not reproduced, cause unresolved. Visual layout still invalid, so family-query coherence alone is not sufficient.
Duo controlled viewport now invokes the revealed native SBHIconManager screen-type/options setter after scene sizing, updating its retained layout provider and native downstream consumers. Scoped SB-main only, unchanged layout options, scene-derived family31/32, no icon-model reset. Runtime pending.
afa31362 runtime: native SBHIconManager family32->31->32 and matching native providers, same SB PID292. Cover widgets/grid reflow, Dock no longer overlaps. Remaining physical-canvas black area/status/host viewport-input integration; not end-to-end panel switching acceptance. Inner restored.
Duo geometry diagnostics: expose fresh SB main-queue configuration/coordinate bounds, scale, orientation and viewport generation through display.geometry. Avoid stale device.screen extent for forthcoming host viewport/input work. No rendering or input behavior changed by this diagnostic addition.
Duo host automation viewport geometry separates active extent from physical HID backing; cropped screenshots and inverse input use the same four-angle transform. Two focused geometry tests pass. Native GUI integration and guest runtime acceptance pending; no new binary patch.
ae8d0e56 live automation: cover screenshot1398x2034, inner restored2007x2853; four edge screenshot points map exactly to expected UIKit points. Native status pulse removes full-screen interception (three Home hits, one actual status-region hit). GUI/rotation/persistent status acceptance still pending.
Duo persistent native Main status: actual GUI click confirms full-screen old status intercepts correct coordinates. Enable verified native profile at SB startup only for iPhone19,4, with bounded readiness retries and native refresh after logical viewport sizing. No hitTest override or arbitrary view frames. Build/runtime validation pending; this promotes the existing gated patch.
2f446502 persistent status runtime: no pulse, real GUI Settings icon click hits SBIconView and opens verified Settings; further GUI About navigation works. Native ring/time visible. Inner portrait GUI interception fixed; outer/rotation/viewport integration still pending.
Duo native window integration: clipped active viewport around fixed physical VM view, fresh logical-size polling and enclosing-rotation input mapping. Host-only change, pending live GUI acceptance. No physical VZ resize.
22357146 native GUI viewport: cropped cover Home and real mouse Settings click accepted, plus Settings General navigation. Timed inner restore leaves existing app scene at cover size; app-scene transition propagation still required. Not complete runtime inner/outer usability. Explicit inner Home restored.
Duo read-only workspace request preview added to investigate system-level foreground app resizing. Native factory + context/entity readback only; no transaction execution, no per-app frame patch. Runtime preview pending.
Workspace preview correction: fresh unfinalized request has empty entities; no transaction run. Current layout must be read from scene.layoutStateProvider, not transition coordinator. Diagnostic corrected; runtime readback pending.
c1ff6b18 live layout provider identifies the single primary Preferences scene. Unsubmitted preview now populates identical native previous/target entities and locally finalizes to verify preservation. No transaction submission.
Native app-scene refresh experiment preserves current entity/role and native layout attributes, validates from/to identity and unchanged state, then uses executeTransitionRequest under explicit workspace.refresh notification only. Finalized preview identity preservation verified; submission runtime pending.
a81fcd09 live workspace refresh: same Settings PID317/scene ID retained through cover narrow -> inner dual-column layouts; both native transactions accepted. Prior stale app size on return resolved in controlled trial. Persistent switch integration and rotation acceptance remain outstanding.
Persistent Duo fold integration: native family/status default initialization, nonexpiring apply viewport controls, automatic guarded app transaction refresh, pose RPC fresh-bounds acknowledgement and boot/relaunch replay. Host always uses inner-sized physical backing and crops cover; old blackout chrome disabled. Build passed before UI-copy cleanup; full runtime acceptance pending.
e4c3c421 formal Fold Posture GUI drives persistent cover/inner/Book without VM/SB/app restart: SB37, Settings306 stable; correct native dimensions and visible app layouts. Cover holds beyond45s. Open restored. Broader acceptance and research-path cleanup pending.
Default Duo rotation still refuses with logical scene surface0. Added default- off one-shot native-size SB-main surface role adapter (inner2/cover1), preserving physical CADisplay/resolver identity and all unrelated scenes. Runtime pending.
604ff90e role experiment not accepted: native mapping and masks update, but scene4 disagrees with legacy orientation and wallpaper/status transforms. Exited one-shot experiment and restored default inner. Keep default-off.
Rotation diagnostic: retarget existing default-off native per-scene status policy to native669x951 for combination with now-verified native Axis profile. No default enablement or per-window angle assignment. Runtime pending.
Combined rotation trial revealed scene.inner versus resolver default/unknown content mismatch. Default-off logical-role probe now updates the existing single Main proxy via native setter/resolution with matching size-derived role. No auxiliary-window hiding or touch interception override. Runtime pending.
985fa8f7: coherent logical role restores primaryContent; landscape GUI Settings launch and rotation90 RPC succeed. Home wallpaper/time still missing, so no default promotion. Point trace expired and is not touch-coordinate acceptance. Recovered default persistent inner afterward.
Read-only presentation diagnostic compares scene/embedded status data providers and native cloak flags plus wallpaper activation/window state. Targets remaining rotation time/wallpaper issue without forcing visibility or data. Pending live.
Native presentation comparison finds separate servers for scene versus embedded bar, not an in-process cloak flag. Added bounded original-Main server binding probe with native setters and restoration, only within logical-role experiment. No default behavior change; build/runtime validation pending.
f0609c2b data lease was refused before mutation, not a negative data-channel result. Add precise preflight reasons and semantic object-setter ABI checks; verify restore readback. No default enablement.
c2f9d947 proves old embedded server has nil client, so proposed active-server handoff precondition is false. Removed that unused pulse path, retained richer read-only item/registration diagnostics. No default behavior change.
4ce7a072 rules out homeItemsDisabled: visible scene client registered with normal flags; old embedded client hidden/unregistered. Add default-off native registration refresh plus data/parts diagnostic, no fabricated values.
419bdab4 native re-registration did not restore missing time data. Added source aggregator/provider diagnostics and scoped native time subscription refresh. No fabricated clock value or visibility override; default path unchanged.
750ccd86 upstream time differs normal versus experiment despite false cloak flags; native timer refresh ineffective. Add formatter/date observation and narrow constructor-only Main override to one query. No default enablement.
Time diagnostic: identical normal/experimental date formats did not explain empty cache. Add bounded pass-through native update trace and formatter-output readback under existing one-shot role experiment. Default path unchanged.
Native callback trace narrows time failure to empty callback input despite valid getter/formatter output. Add input type versus provider-current-date observation. No data substitution or callback behavior change.
457e820d native minute callback carries NSDate with empty description versus valid current getter; type mismatch hypothesis excluded. Add numeric interval and underlying provider observation only. No date replacement.
Native minute date +inf traced to detached reference view -> nil screen -> 1/0 FPS. Scoped experiment selects existing native nil-reference scheduling branch during registration/tick, preserving weak-slot semantics/restoration. No timestamp substitution, cadence guess or default enablement. Pending live.
4e40b3b6 actual installed clock fallback verified: detached wrapper/nil screen/ FPS0 takes native nil-reference path, finite date notifications and visible minute time restored. Fixed missing constructor installation discovered via unchanged hash/unused warnings. Initial renderer data timing and wallpaper remain incomplete; no default role/scene-policy promotion.
Native status initialization now requests existing native data as well as layout refresh; bounded retry for empty initial renderer data. Separate default- off wallpaper controller rotation pulse uses scene direction and native restore. No timestamp, wallpaper image or manual window-frame replacement.
Wallpaper pulse exposed stale direction, not valid fix. Native legacy landscape gate is PF_IS_PAD_DEVICE; default-off Main-inner container scope now asks existing per-display capability API, preserving native result. No image/frame override. Runtime pending; production default restored.
69f677e2 global/per-display wallpaper support bothfalse; removed ineffective adapter. Added one-process exact TraitsArbiterWallpaperTokyoDrift flag probe under separate once marker, leaving all other queries native. Default-off, no on-disk feature override. Runtime pending.
7eda0424 feature override activates native traits path but wallpaper still fails, including bounded real-Main inner metadata comparison. Add read-only participant/delegate/remote-view evidence; no further default enablement.
Wallpaper managed-role gap verified: allowed delegate, no participant. One-shot probe supplies the same native wallpaper role hint before visibility on exact Main-inner wallpaper subclass, leaving native registration/geometry in charge. No default enablement or manual wallpaper frame. Pending live.
2026-10-27 Duo active-viewport input mapping
The host VM view now derives its input geometry from the active Duo viewport (activePanelPixels / screenScale) and the bottom-left clipped viewport origin. Mouse, trackpad, and synthetic touch events therefore use the same visible outer/inner rectangle while the fixed backing framebuffer remains available for rendering and screenshot crop. This closes the path where outer-screen touches were normalized against the hidden 2007x2853 backing surface.
Duo landscape integration (2026-10-27): The previously one-shot logical-role and per-scene status policies are now enabled for the exact
iPhone19,4Duo default path. They retain one physical Main and native framebuffer modes while allowing the native scene orientation/status arbitration to see the active inner role. Build/admission passed; runtime rotation and wallpaper acceptance remain under validation.
Duo rotation arbitration refresh (2026-10-27):
display.rotationnow publishes the native pose and immediately emits the existing mapping, input, arbitration, and actuation refresh notifications before waiting for SpringBoard's scene receipt. Without this sequence, the pose changed butdisplay.orientationstayed portrait; with it, the running scene read back landscape bounds951×669and orientation 90 without restarting SpringBoard.
Landscape presentation follow-up (2026-10-27): The live rotation path exposed a delayed wallpaper rollback:
vpPulseNativeWallpaperOrientationrestored portrait after eight seconds, recreating the black landscape band. The rollback was removed for the normal Duo path; wallpaper now follows SpringBoard's current native arbitration, while clock refresh remains tied to the same orientation sequence.
Duo Fold Posture titlebar control (2026-10-27): Foldable VM windows now expose a Duo-only circular control beside Home. It cycles
closed → book → open → closedthrough the existingVPhoneGuestControl.setFoldPosition/display.posturepath, mirrors the same fold-position callback used by the Fold Posture panel, and updates its accessibility value/tool tip after each accepted reply.
CoverSheet lock glyph scale (2026-10-27): Runtime class enumeration identified
UICoverSheetButtonas the shared lock-screen action button class. The Duo path now wraps its nativelayoutSubviewsand applies a 0.82 transform only to childUIImageViewglyphs; the circular button container and action handling are unchanged. The hook installed successfully onduotest-m5(cover sheet glyph scale: UICoverSheetButton UIImageView scale=0.82).
Host rotation authority correction (2026-10-27): Duo
display.geometry.interfaceOrientationcan remain portrait while the authoritativedisplay.orientationendpoint is already landscape because the inner panel uses an inverse native mounting map. The host orientation poll now prefersdisplay.orientationand uses geometry only for active bounds, preventing a portrait host window with sideways notifications/status content.
Glyph sizing correction (2026-10-27): The initial
UICoverSheetButtontransform experiment did not produce a reliable layout callback/readback, so the guessed scale was removed. The current source retains only a recursive native layout probe; no arbitrary glyph scale is accepted until Duo's actual constraint/bounds metrics are observed.
FoldDisplay source split (2026-10-27):
libvphonefolddisplay.mwas reduced from the monolithic implementation by extracting the status-bar provider/profile section intoFoldDisplay/StatusProfile.incand the wallpaper traits section intoFoldDisplay/WallpaperTraitsExperiment.inc. The files are include-only implementation fragments copied from the original source; no new runtime target or duplicate dylib is introduced. A full VPhone bundle build passed after restoring declaration-order bridges for the existing monitor/diagnostic helpers.
Split recovery (2026-10-27): After an interrupted edit temporarily left declaration-order bridges incomplete, the status and wallpaper include fragments were restored with explicit shared helper declarations. The source now builds cleanly again; the split remains source-only and the existing runtime bundle was not replaced by this maintenance pass.
FoldDisplay module split (2026-10-28): The two include fragments are gone.
libvphonefolddisplay.dylibnow builds from thirteenFoldDisplay<Section>.mtranslation units pluslibvphonefolddisplay.m, which keeps only the constructor (backboardd probes, SpringBoard hooks, thecom.vphone.display.*notification registry, then the display pipeline observation). State shared across files is declared inFoldDisplayPrivate.hunder#pragma GCC visibility push(hidden); everything else staysstatic. The export table is unchanged (onlyVPhoneStatusScreenPreview), no patch or hook changed, and on duotest-m5 the split and pre-split builds gave the same install log and the same rotation/fold results.
QuickAction glyph scale made layout-proof (2026-10-28): The button lays out its glyph with
setFrame:, which undid the glyph transform whenever a layout pass followed the walk. EachCSQuickActionImageGlyphViewnow moves to a runtime subclass (VPhoneQuickActionGlyphView) whosesetFrame:keeps the transform, and both glyphs take one factor, 0.70 of their native layout size (flashlight 14.2x25.7pt, camera 24.3x18.4pt, matching a real iPhone Duo screenshot), replacing the 16pt-long-side and 0.82 targets. The glyph class itself is still not modified.
Duo inner display spike, Stage 0 (2026-10-28, branch spike/duo-virtual-inner, opt-in marker):
libvphonefolddisplay.dylibadds a 2853x2007CAWindowServerVirtualDisplayin backboardd, taggedunknown(65536), and in SpringBoard reports it as CADisplay type 0 everywhere except inside-[FBSDisplayConfiguration _initWithImmutableDisplay:originalDisplay:isMainDisplay:assertIfInvalid:], where it is type 4 (always-connected, not UIKit-main-like). BacklightServices and SpringBoard's embedded-scene gate both require type 0; FrontBoard's cloning source requires that only Main be main-like. Result on duospike-inner: always-connected{Main, Unknown[7]}, inner scenes onUnknown[7], PosterKitDISPLAY_TYPE_AUXwith dynamic rotation, SpringBoard stable. Details inResearch/Guest/iphone_duo_bringup.md.
CoverSheet native metrics probe (2026-10-27): Static UIKit analysis of
UICoverSheetButtonidentifies_contentView(UIImageView),_image,_selectedImage,imageContentMode, andedgeInsetsas the real glyph layout inputs. Runtime inspection ofUIApplication.windowswhile locked found no liveUICoverSheetButtoninstances, so no scale was applied. The source now records the native metrics entry points and live hierarchy count; a guessed transform remains disabled.
Native Duo quick-action metrics (2026-10-27): The real lock-screen classes are
CSQuickActionImageGlyphViewandCSQuickActionsButton, notUICoverSheetButton. Runtime SpringBoard evidence captured native initializer values: button diameter 58pt; camera symbol scale 0.31648; flashlight symbol scale 0.368; button frame 84pt with icon image frame 48pt. Temporary lifecycle/metrics probes were removed from the source after capture; no guessed scale or transform is shipped.
Duo quick-action profile evidence (2026-10-27): The real initializer path supplies button diameter 58pt and per-symbol scales (questionmark 0.368, camera 0.31648, flashlight 0.368).
CSQuickActionsButtonreports iconContentScale=1, iconImageSize=48pt, and native iconImageFrame 48pt inside the 84pt button. These values are now the authoritative runtime baseline; all temporary probes were removed and no guessed scaling is shipped.
QuickAction profile resolution (2026-10-27): The native
CSQuickActionsButtonclass method and glyph initializers were traced at runtime. The active Duo scene resolvesbuttonDiameter=58,iconContentScale=1,iconImageSize=48, camerasymbolScaleValue=0.31648, and flashlightsymbolScaleValue=0.368. These values originate in SpringBoard's profile path; no transform or replacement image was installed. The remaining icon-size discrepancy therefore belongs to profile selection/trait inputs, not a missing host scale conversion.
QuickAction profile investigation (2026-10-27): The live Duo profile path was confirmed to construct
CSQuickActionImageGlyphViewwith native scales and button diameter, whileCSQuickActionsButtoncomputes a 48pt icon frame inside an 84pt button. Runtime source/profile searches found no external QuickActions plist override; the remaining discrepancy is the SpringBoard profile/trait selection that supplies those native values. No arbitrary scale has been added.
Duo wallpaper admission startup correction (2026-10-27): The round-16 caller-scoped
PF_IS_PAD_DEVICElandscape wallpaper gate and traits admission were previously armed only by a one-shot cache marker. Fresh boots could therefore return to the two-band path. ForiPhone19,4, startup now installs the same gate unconditionally; the marker remains accepted for older guests. This changes admission only for the known Duo model and keeps the lock-screen wallpaper path unchanged.
Poster compositor follower (2026-10-28):
libvphoneposterprobe.dylibnow coalesces orientation requests for hosted poster renderer processes and leaves the PosterBoard wallpaper editor untouched. It reads the settled daemon orientation notification before falling back to the map file and asks SpringBoard to reconcile newly-created floating poster hosts. The SpringBoard-side adapter preserves the editor's portrait bounds and centers it inside the landscape presenter, avoiding the repeated-request loading loop and the off-center sheet.
Duo display: two displays only (2026-10-29):
system-springboard-cfw-fold_display(now titled "Foldable inner display") keeps only the two-display path, and it is always on for iPhone19,4:libvphonefolddisplay.dylibcheckshw.machinein backboardd and SpringBoard instead of thevphone-duo-inner-displaymarker. backboardd: the 2853x2007CAWindowServerVirtualDisplayadded from-[CAWindowServer _detectDisplays](server-ready fallback), taggedunknown;DeviceSurfaceType1 for the primary and 2 for the inner display through-[CAWindowServerDisplay extendedProperties];BKHIDDirectTouchEventProcessordigitizer routing and the 270° position turn while the inner display is lit. SpringBoard: the CADisplay type/pointScale/isExternal adapter with the FrontBoard classification scope,mainLikein the inner identity's XPC encoding, the inner screen's idiom, the shared scanout (-[CADisplayStateControl transitionToDisplayState:withCompletion:]), the compositor, the content mode resolver hand-over (the resolver is now recorded by a pass-through-[SBFDisplayContentModeResolver _startResolvingContentModeOfDisplay:]), and the per-screenSTUIStatusBarGetVisualProviderClassForScreendetour. Removed hooks: the Main configuration transformer, monitor and identity observers,SBWindowScene surfaceType/sbhScreenType,SBWindow _shouldResizeWithScene, the scene geometry callback, the clock and time-item adapters, the scene status policy, the Main status lookup and status profile factories,SBHScreenTypeFor*,_os_feature_enabled_impl(TraitsArbiterWallpaperTokyoDrift),PF_IS_PAD_DEVICE,_SBWallpaperSecureWindow setHidden:, the quick-action glyph subclass, theUIWindowtarget-window fallback, theSBSDeviceDeterminedConfiguration/CSProminentLayoutControllerloggers, and everycom.vphone.display.*notification.libvphoneposterprobe.dylib(the poster orientation follower) is no longer built, staged, listed in the guest environment, or loaded by SystemHook. Details inResearch/Guest/iphone_duo_bringup.md.
Duo display: flicker, capture and rotation (2026-10-29): in
system-springboard-cfw-fold_display, the compositor is now a layer at the top of Main'sUIRootSceneWindow(points) instead of a display-levelCAContext(pixels), its hosts sync from a main run loop observer before Core Animation's commit and change only when a value changes, and its per-frame damage alternates two clear colors. New SpringBoard hook:+[CMMagicPoseManager orientationForMagicPose:deviceSurface:]answers with+[CMDeviceStateEvent propertyAForConfigType:propertyA:propertyB:propertyC:]directly, because the guest has noCMMagicPoseManagershared manager and every panel read ambiguous, so neither panel rotated. vphoned'sdisplay.rotationon a foldable sets the pose orientation for the lit panel. Details inResearch/Guest/iphone_duo_bringup.md.
Local Duo inner-screen corner diagnostic (2026-10-29): The existing FoldDisplay guest component checks UIScreen corner getter ABIs and, only for its recorded inner display identity, fills a zero radius from the main screen's native metric. Nonzero native radii and other displays pass through. This is a source-level experiment in the existing component, with no new firmware patch or preset. See the expanded Now Playing entry in
Research/Guest/iphone_duo_bringup.mdfor evidence and deployment outcome.
Local optional external monitor prototype (2026-10-29): Existing Duo FoldDisplay injection gains an explicit, off-at-boot 1080p/4K virtual monitor and a named external-display adapter. Existing inner-only policies now match the recorded inner identity. Guest RPC captures the external display itself for a separate host window. No new binary patch, preset or hardware graphics port is introduced. See
Research/Guest/virtual_external_display.mdfor implementation boundaries and runtime results. Final implementation prepares two disabled render slots before SpringBoard discovery and refreshes their existing CADisplay objects after configuration. Native Broadcasts output, live 1080p-to-4K switching, and disconnect/reconnect passed on2.14.0-local.b0297e92; boot remains disconnected by default.
Duo: folds through SpringBoard, and DeviceHub follows them (2026-10-29 night, PR Lakr233/vphone-cli#774): no binary change. vphoned's
display.posturepositionasks SpringBoard to fold itself throughSBSDisplayToolService(entitlementcom.apple.springboard.sbdisplay.service) and then sets only the virtual hinge (Research/vphoned_http_api.md, "Device pose"). Insystem-springboard-cfw-fold_display,libvphonefolddisplay.dylib(1) re-reads the shared panel's owner on-[SpringBoard displayContentModeCoordinator:handleSwapTransitionFromPrimaryWindowScene:toPrimaryWindowScene:withContinuationLockProvider:]as well, because a continuous close swaps the primary content as it finishes and the panel kept showing the (black) inner display for 3 s; and (2) in backboardd keeps every clone on the panel:-[CAWindowServerDisplay addClone:options:]on the inner render display attaches the clone to display 1 instead, andremoveClone:removes a clone from its realcloneMaster(CoreAnimation aborts otherwise). backboardd moves a mirror (DeviceHub's capture display, AirPlay, recording) to the display with the primary content, so open it carried the inner display upright, while DeviceHub draws an open Duo's stream as the inner panel's native portrait framebuffer and turned it a quarter more; the panel is that framebuffer. Row 26'slibdevicehubfixgains aCMDeviceStateManagerstand-in in dtdeviceinfod (VPCMAngleManager.m:isAvailable,startUpdatesToQueue:withHandler:,stopUpdates, realCMDeviceStateEvents with A device orientation 1, B the hinge state fromVPhoneHingeLatest, C 0, delivered at start and on change): Xcode 27.1's DeviceHub (CoreDevicePop plugins) draws a foldable from CoreDevice's device motion state (streamdevicemotionstate), which a guest's CoreMotion could not answer ("Pose monitoring failed … 1001"), so the device was always drawn closed. Validated on iPhone19,4 27.1 (24A94425): closed shows the folded device with the cover screen, open the opened device with the inner screen upright and filling the panel, following live folds; the VM window is unchanged. Open: at book DeviceHub draws the cover (it names the inner panel only from DisplayInfo'sactiveintegrated display, and reporting the inner display as integrated makes DeviceHub want a per-display stream the guest cannot supply); DeviceHub's own pose controls (vendor-defined HID) are not wired to the guest.
Duo: a hardware-shaped inner display, and DeviceHub's controls (2026-10-29 late, PR Lakr233/vphone-cli#774): supersedes the clone redirect above and closes both of its open items. In
system-springboard-cfw-fold_display, backboardd's innerCAWindowServerVirtualDisplayis now a portrait 2007×2853 framebuffer at 3× (the simulator's innerCADisplay), itsextendedPropertiesaddingDisplayRotation270,DisplayScale3,ChromeIdentifiercom.apple.dt.devicekit.chrome.phone14andDeviceCornerRadius55 besideDeviceSurfaceType2; its one 120 Hz timing is 2007×2853; the digitizer's 270° position turn and both clone hooks (addClone:options:,removeClone:) are removed; SpringBoard's compositor draws the inner display's frames onto the panel without a turn. SpringBoard lays the inner UI out at interface orientation 90 into that framebuffer. Row 26'slibdevicehubfix:dtremotedisplaydanswers0x184(adds0x100, drops system audio0x8, video output by display ID: DeviceHub draws each integrated panel from its own stream and shows its pose controls only then); indtdeviceinfod,CADisplaydisplayTypeanswers 0 (integrated) andpointScale3 for the display namedVPhone Duo Inner; theCMAngleManagerstand-in skips-[CMAngleManagerInternal stopAngleUpdatesPrivate](reached from-[CMAngleManager dealloc], it asserts the real hinge notifier is available and aborteddtdeviceinfodwhenever DeviceHub reconnected), itsminAngleDegrees/maxAngleDegreesreturn floats, and itsangleDegrees,mechanicalAngleDegreesandvelocityDegreesPerSecondsreturn floats, asdtdeviceinfod'sCMAngle_Bridgedeclares them (Tf, read froms0): doubles read as 0° for every whole angle, which in book (where DeviceHub takes the angle from this stream) drew the device nearly shut and focused the cover;CMDeviceStateManager's A is the device orientation vphoned publishes (VPhoneDeviceOrientation). vphoned (no binary change) decodes DeviceHub's vendor-defined HID controls (usage page0xFF61, usage0x5B, an IOKit binary-serialized dictionary from the DDI'sdtuhidd) and folds or turns throughdisplay.posture's paths. Validated on iPhone19,4 27.1 (24A94425), sbdisplay-duo: open and closed from DeviceHub's buttons follow and settle; book at 130° (DeviceHub's preset) draws the inner face upright with a vertical crease, as DeviceHub draws a simulator Duo; open and book fill the panel without black bars; 48 scripted folds over every path came back in the expected orientation;display.rotation0/90/180/270 on the open inner panel turns it and DeviceHub's model with it. Open: DeviceHub's orientation picker and its book button were not pressed since the float fix (both run the paths measured above). With the foldable product-id (on in standard since Lakr233/vphone-cli#778) bothdtdeviceinfodstand-ins (CMAngleManager,CMDeviceStateManager) step aside for CoreMotion's own managers, and locationd's relay takes DeviceHub's controls itself (dtuhidd's avpCustom service has the host controls' usage) while vphoned keeps the virtual hinge on each slider value and lights a sleeping cover before an opening run; measured on sbdisplay-duo, one guest fold per DeviceHub click. 2026-10-30: insystem-springboard-cfw-fold_displayFoldDisplay turns SpringBoard's fold fade to black off for folds made awake (SBDeviceContinuousFoldInteractionSettings.fadeToBlackEnabled, via+[SBDeviceContinuousFoldInteractionDomain rootSettings]; kept for a fold that starts with the panel off or just lit, where it hides the inner content the effect carries over) and hands the shared panel to the inner display inside the primary-swap callback, so opening out of closed shows a few dark frames instead of 0.12 to 0.2 s of black (Research/Guest/iphone_duo_bringup.md, "the fold's fade to black on one panel").
HomeView shell Metal compiler lookup (2026-10-30)
The existing system-breadboard-exp-mach_lookup patch now also appends exactly
com.apple.MTLCompilerService to BreadBoard's global Mach lookup exception list.
Campo's shared backboard/frontboard list is unchanged. This retains the native
temporary-sandbox profile, every donor entitlement, and the existing executable
backup/deselection path; no kernel or shader binary is changed.
Live HomeView 27.2 / 24V514 on cloudOS 26.4 repeatedly stalled BreadBoard during
app/Home gestures in RenderBox → Metal function specialization → compiler queue
wait. A unique-source public Metal probe, signed with the installed BreadBoard's
exact entitlements and run as mobile501, failed with MTLLibraryErrorDomain3,
lookup error159, Sandbox restriction. Earlier identical-source probes hit
cached libraries and concealed this denial. Adding only the compiler lookup to
that probe's entitlements made a fresh unique library compile in 0.05217s and
specialize in 0.001099s. Cache-path sandbox-extension warnings remained nonfatal;
no file permission exceptions were added. Raw evidence and variant entitlements
are in .build/homeview-distance-runtime/public-metal-*; native consumer tracing
is in Research/Firmware/homeview_distance_consumers.md. Full shell retest after
deployment remains pending at this entry's creation.
Deployment retest: Xcode built VPhone successfully (60.743s,
BuildProject-Log-20261030-211312.txt); Launchpad installed
2.15.0-local.0cfd6bd1 with policy/preflight passing and updated HomeView's host
and guest environment bindings. The installed BreadBoard entitlements differ
from b53c29c8 only by the compiler lookup. The formerly hanging bottom Home
gesture now renders the glass app drawer, native environment fetch stays fast,
and Timers visibly launches to its native preset grid. Screenshot evidence is
0cfd-after-home.jpg and 0cfd-timers-settled.jpg under the runtime directory.
The entitlement merge suite passed all7 tests, including the new HomeView
sandbox-preservation/Campo-isolation regression. Native distance ABI checks
also passed. No extra patch selection or boot-chain update was needed.
Final rollout: 2.15.0-local.e2850909 adds the native app-latching explanation to Controls, preserving the same validated compiler lookup repair. Host and guest environment bindings updated successfully, vphoned answered in6seconds, and an acknowledged environment reset plus independent fetch returned Automatic/detail2/override=false/style0/backlight2. The native clock is visibly awake. Far/Near/Far actual FrontBoard settings and UIKit scene/view traits were verified0→2→0 in the temporary observer; the Far reduction flushes when the app backgrounds, matching Charcuterie's explicit latching policy. No ActivityKit permission expansion is part of this patch or the installed bundle. All guest test timers, activities, apps, helpers and overrides were cleaned up.
Experimental iPhone 15 / iOS 17.7 desktop bring-up
See Research/Guest/ios17_bringup.md. The existing seputil data patch now
accepts a Capstone-decoded ADR reference as well as ADRP+ADD. The measured
21H16 input has ADR x2 at 0x10000B85C referencing the complete format literal
at 0x10001D3B3; no UUID or instruction replacement is hardcoded. The existing
SwapEnd patch resolves size 0x508 and emits the cloudOS 26.1 size 0x560.
Existing thread-guard and paravirtual-user-client gates include 17. The iOS 18
BSD-only skywalk boot argument also covers 17. New opt-in declaration
system-springboard-exp-ios17_desktop installs a SpringBoard-only, iOS-17-only
library. It skips synchronous initial widget discovery and unavailable
AVFlashlight construction; no shared-cache code is modified for these hooks.
Selection restores SpringBoard and removes the library on opt-out; a restart
restores native methods.
No native iOS 17 kernel, Metal driver, camera or networking support is claimed.
OpenGL ES in guests (2026-11-02)
Upstream issue Lakr233/vphone-cli#793: every -[EAGLContext initWithAPI:]
returned nil in a guest while Metal worked, so apps that need OpenGL ES refused
to start or aborted (WeChat's OpenGLContextManager: root_ctx_ != nullptr).
Three pieces make OpenGL ES render, each needed by the next.
1. Kernel: the renderer name (JB-36, kernel-cfw-paravirt_gles_renderer).
gfxInitializeLibrary (libGFXShared) loads
/System/Library/Extensions/<name>.bundle from the IOAcceleratorES service's
IOGLESBundleName / IOGLESMetalBundleName; AppleParavirtGPU had neither.
AGX personalities carry IOGLESBundleName = AppleMetalGLRenderer, and the
patch adds that key after IOClass in the AppleParavirtGPUARM personality in
__PRELINK_INFO. On 23E5207q the record is 609 bytes at file offset
0x256824C: the key's two lines, and the next lines' indentation shortened by
the same 76 bytes, so nothing else in the kernelcache moves. The patched XML must
parse to the original plus that key. Contexts are created from here on, but every
draw failed in the next two pieces.
2. Compiler plugin: GLES modules (no binary patch; VPhoneGuestComponents/GraphicLoader).
AppleMetalGLRenderer hands the backend AIR whose air.language_version is
"OpenGL ES GLSL" 1.0.0, with no air.version and with the argument metadata
the AGX backend reads: a buffer stops after "air.read", most nodes have no
air.arg_type_name or air.arg_name. MTLDowngradeAIRModule,
MetalModule::materialize and air::Reflection::create index those
operands without checks, so MTLCompilerService crashed (NamedMDNode::getOperand,
then MDString::getString) on every GLES shader. The plugin now completes such a
module before the downgrade: every node gets the fields the Metal front end
writes after its own, in its order (air.address_space, air.arg_type_size,
air.arg_type_align_size for a buffer; air.arg_type_name; air.arg_name
except on a render target), derived from the entry point's parameter and return
types, and the module gets !air.version = !{i32 2, i32 7, i32 0}, the version
the guest's front end (metalfe-32023.921) writes and the plugin's target
(the value MTLCompilerService passes the plugin as targetTriple is a packed
version tuple, 0x700000002). The guest's libLLVM C API cannot replace a
node's operand, so the module is printed, its argument lines extended, and the
text parsed back in the same context. make -C VPhoneGuestComponents test-gles-air
runs three guest-captured modules through the plugin on the host (same
GPUCompiler 32023) and into the host's Metal.
The plugin comes from the bundle (guest-resources), and an installed guest
used to keep the one it was restored with. cfw update-environment (and cfw install without a restore tree) now replaces it when the bundle's differs and
reseals the GPU bundle ([+] system-extensions-boot-gpu_bundle: compiler plugin updated from this bundle); tvOS guests, whose plugin is retargeted, are left alone.
3. Driver: primitive restart (guest row 30, system-paravirt_gpu-cfw-primitive_restart).
With shaders compiling, the host's ParavirtualizedGraphicsGPUTask threw
NSGenericException: Invalid viewport index replaying each GL command buffer
(caught with lldb on the task; Debugging Restrictions are off on this host). The
throwing handler is command 0x91, setPrimitiveRestartEnabled:index:, which
checks the index against [device maxViewportCount]; GL sends the restart index
itself, 0xffffffff. The guest saw a GPU hang and restart. The serialiser method
now returns at entry: 0x174b4 7f2303d5 pacibsp -> c0035fd6 ret. Patched
alone, 23E5207q hashes to 1992ab68…24d3dc; with the heap index patch, as the
installer stages both, 870ba415…3996d8.
Verified on glestest-iphone (iPhone17,3 iOS 27.0.1, cloudOS 26.4, host
macOS 27.2, bundle built from this branch; plugin and driver delivered by
vm set-bundle --update-environment, kernel by the restore): EAGL APIs 1-3 OK,
GL_RENDERER "Apple Paravirtual device GPU", GL_VERSION "OpenGL ES 3.0 Metal -
105"; as root and as mobile, a shader draw reads back exactly; an ES 2 program
with a 2x2 texture, mat4/float/vec4 uniforms and varyings reads back all four
texels; an ES 3 program with a uniform block, a flat int varying, texelFetch
and a GL_PRIMITIVE_RESTART_FIXED_INDEX strip split at 0xFFFF reads back right
with the gap clear; an app drawing through CAEAGLLayer presented 1560 frames
with a rotating triangle on screen. No MTLCompilerService crash and no GPU
restart in those runs.
Not covered. Real apps (WeChat, the #463 game) were not run. The metadata completion covers the argument kinds the probes produced (buffers, the vertex fetch buffer, vertex id, textures, samplers, fragment inputs, position and vertex outputs, render targets); an argument kind the reflection reads in a layout the completion does not produce could still crash the compiler service. With primitive restart no longer sent, a strip that draws vertex 65535 while restart is disabled is cut there. This includes ES2 and ES3's initial or explicitly disabled state; row 33 addresses that gap.
Native GLES follow-up (2026-11-02)
Rows 31–36 are selected by standard only for iOS 26.6/cloudOS 26.4,
including patch releases; TV, HomePod and HomeView presets exclude them.
A missing plan enables none, opt-in cannot widen the version gate, and current
guest selections can enable declarations absent from an older boot plan.
Orientation, system-systemhook-cfw-gles_upper_left (row 31). Reveal
AppleMetalGLRenderer's AGX_GL_VIEWPORT_INVERT_CONFIG / UPPER_LEFT strings
and populateConfigData, then follow drawableInvert, drawablePBEInvert,
updateRenderViewport and updateRenderScissor. SystemHook selects this
existing viewport/scissor path at constructor entry only while the installer
marker exists, preserving explicit environment values. It avoids the private
IOSurface attachment yInvert flag that had no effect in PV controls.
No Apple binary changes; opt-out removes the marker.
Descriptor, dyld-cfw-metal_vertex_id_format (row 32). Resolve Metal's
image-local PipelineLibrarySerializer::serializeVertexDescriptor and format
getter; prove the unique wire-property-1 write from decoded operands/control
flow. A 32-byte trampoline converts only private ID formats 43/44 to UInt
(36) at that write, after guest shader variant generation. Original descriptors,
AIR and other formats remain intact. Private format 43 reproduced the host
CloneAttributeDescriptor crash; the same AIR with UInt compiled successfully.
Restart, dyld-cfw-gles_primitive_restart (row 33). Resolve
gldRenderVertexArray and updateRenderPrimitiveRestart; derive state fields
from the decoded updater. A 68-byte entry trampoline returns zero only for
disabled indexed line/triangle strips, selecting GLEngine's existing primitive
expansion, which retains vertex/instance IDs in a nonindexed draw. Other draws
keep their normal path. All affected strips pay CPU copying cost. Dependencies
are command suppression (row 30) and ID-format repair (row 32); removing only
the unsupported command leaves Metal's maximum-index restart active.
Points, dyld-cfw-gles_point_draw (row 34). Recover full next-symbol
function bounds and context fields from setRenderPipeline. Replace three
point-size bit-7 TBZ guards in gldRenderVertexArray and one in
gldEndPrimitiveBuffer with NOPs; preserve point-mode, pipeline, buffer and
other gates. The indexed path must prove the exact NUL-terminated draw selector
in beginRenderPass, encoder/selector arguments to methodForSelector:,
returned IMP store and use of the recovered encoder/IMP slots. Validate all
four sites before writing. PV's inherited pipeline-flags method returns zero;
no global reflection flags are fabricated to compensate.
Identity, system-paravirt_gpu-cfw-compiler_cache (row 35). Reveal
setupCompiler through relative Objective-C metadata, then verify imported
SHA-256 Init/Update/Update/Final dataflow, both eight-byte date/time inputs and
use of the digest as cacheUUID. Decoded code references to both literals
must be exclusive to these inputs. Replace only their 16 bytes with base64 of
the installed plugin's first 12 SHA-256 bytes (96 identity bits), preserving
terminators; validate and read back both writes. The installer hashes the
actual plugin after refresh, composes selected driver patches from one pristine
backup and signs the result. Opt-out restores the original seed while retaining
other selected driver changes.
Reuse, dyld-cfw-metal_cache_reuse_key (row 36). Row 29's original
interpretation was incomplete: FS_CACHE_REUSE_ON_REBOOT=1 skips the 32-byte
key check as well as the 64-byte boot/system-stamp check. In libCoreFSCache,
follow the environment branch and both comparisons; prove the recovered
skip-boot flag's complete lifetime, semantic uses and callee-save restoration.
Redirect only the reuse-enabled branch through an eight-byte trampoline that
sets the flag to one and resumes at the existing key comparison after its
normal assignment. A mismatched key rejects the cache; a matching key skips
only the boot comparison. The no-reuse path is unchanged. This and row 35
invalidate stale compiler output without deleting application caches.
All four DSC patches snapshot code/hash bytes, re-attest changed pages and capture undo before publication. Trampolines require verified unused executable header padding. Unsupported shapes and occupied space are refused. Failed writes are restored and read back; failed restoration retains recovery records and reports possible partial modification. Actual 23G90 point and cache-reuse apply/repeat/undo checks each restore all 85 files exactly; repeats change neither bytes nor undo records. Points change four instructions and one page hash; reuse changes one branch, an eight-byte cave and two page hashes.
Selection preflight rejects compiler identity plus cross-boot reuse without
key validation, exiting 64 before disk attachment or mutation. Installation
also verifies that key validation actually applied before any plugin refresh,
requires a private APFS clone of Disk.img, and discards it if identity patching
fails. Host tests cover the applied-key dependency and failure handling;
clone-discard paths were not exercised through injected VM failures.
Retained tests cover malformed discovery, exact identity writes, same/different
plugin replay, rollback and selection/dependency guards. Generated ARM64 executes
192 restart draw/state combinations; cache tests validate the production
trampoline and semantic discovery in a verify_list_file fixture. AIR tests
cover ES1 metadata and GLES-only point-coordinate adaptation, preserving unrelated
inputs and rejecting malformed ones. Development builds were tested on iOS
26.6.2 (23G90)/cloudOS 26.4 (23E5207q), macOS 26.6.2 (25G83, Apple M5 Max).
See GPU research for the
failure controls, historical installed-VM results, replay procedure and limits.
service. With primitive restart no longer sent, an OpenGL ES 2 strip that draws vertex 65535 is cut there.
The stock launchd_cache_loader takes a re-signed launchd.plist (2026-11-01)
minimal used to keep one userland security patch,
filesystem-boot-launchd_cache_loader_unsecure_cache_gate, because vphoned's
entry in /System/Library/xpc/launchd.plist made the stock loader refuse the
file. It no longer needs it: filesystem-boot-vphoned signs the edited file
the way Apple signs it, and the static trust cache the restore image already
rebuilds (and TSS signs) carries the new cdhashes. The patch is now
filesystem-exp-launchd_cache_loader_unsecure_cache_gate, not boot-essential,
left out of minimal and watchos; renamedPatches migrates a VM's old
record. A Watch7,18 watchOS 26.4 (23T240) guest on cloudOS 26.4 (23E244),
restored with the gate blocked, booted to the pairing screen with vphoned up:
its stock loader logged "cdhash: … 0xeacd92f9… is trusted" for the re-signed
launchd.plist.sig and "Cache sent to launchd successfully" (2026-11-02).
What the loader checks (/usr/libexec/launchd_cache_loader, libxpc-2894.140.12,
iOS 18.6 22G86, read from the disassembly):
0x100000a44: the default paths,/System/Library/xpc/launchd.plistand/System/Library/xpc/launchd.plist.sig("Code: %s Sig: %s").0x100000b2c:kern.bootargs; withlaunchd_unsecure_cache=thecbz x0at0x100000b58falls into "Using unsecure cache" and skips 3-6. Thatcbzis what the old patch NOPs.SecStaticCodeCreateWithPath(plist),SecCodeSetDetachedSignature(sig),SecStaticCodeCheckValidity(code, 6)(no executable or resource validation; errSecCSSignatureNotVerifiable, -67060, is let through).SecCodeCopySigningInformation→kSecCodeInfoUnique(the cdhash) →IOConnectCallMethodselector 6 onAppleMobileFileIntegrity(AppleMobileFileIntegrityUserClient::isCdhashInTrustCachein the kernel log). Not found (0xe00002f0) logs "cdhash: %s is not in trust cache" and aborts (abort_with_reason).SecCodeMapMemoryattaches the signature to the vnode ("Failed to push signature to kernel" otherwise), so the pages read next are checked against the code directory.open/mmapthe plist, copy it into memory mapped into launchd ("Sending validated cache to launchd").
launchd.plist.sig is a detached ad hoc signature: a SuperBlob with a SHA-1
code directory, empty requirements, a SHA-256 alternate code directory, an
empty CMS wrapper and an identification wrapper (slot 0x10001) holding the
file's SHA-1. Both directories are version 0x20500, flags 0x10002 (ad hoc,
runtime), runtime version 11.0.0, 4 KiB pages, identifier
launchd-<sha1 of the file>. The stock static trust cache
(043-19071-100.dmg.aea.trustcache) lists both cdhashes of Apple's file
(72c3cf9a… SHA-256, 2e45eca3… SHA-1), as it does those of
extensions.plist.sig. cryptexctl generate-trust-cache hashes Mach-O code
directories only, so the edited plist's new signature has to be added by hand.
CustomFirmwareDetachedSignature writes that signature: for 22G86's
extensions.plist it reproduces Apple's extensions.plist.sig byte for byte
(codesign -s - -o runtime --pagesize 4096 --detached differs only in its
0x20100 header). resignLaunchdCache replaces launchd.plist.sig (root:admin
0644) after vphoned's entry is merged; createTrustcache reads it back from the
image and hands cryptexctl a second --base-trust-cache, a raw version 1
trust cache with its two cdhashes. Apple's stale entries stay; nothing has
their bytes any more.
Checked on 2026-11-01: vm create lcl-186 --preset minimal --device iPhone11,6
(18.6 22G86 on cloudOS 22G86, bundle 2.12.2-local.442efa84) signed the plist
(c6a34260… SHA-1, b4ace6ca… SHA-256), the rebuilt new.trustcache held
both, the patch log listed the gate under "Turned off", and the boot log shows
the secure path:
with no "Using unsecure cache". SpringBoard came up and guest rpc processes.list answered from /usr/bin/vphoned.
Launch-constraint categories in the rebuilt static trust cache (2026-11-02)
filesystem-boot-cryptex_merge rebuilds the static trust cache with
cryptexctl generate-trust-cache, which writes a version 1 cache: entries of
cdhash, hash type and flags, no launch-constraint category. Apple's iOS trust
caches (the OS's StaticTrustCache, Cryptex1,SystemTrustCache and
Cryptex1,AppTrustCache) are version 2 and give each cdhash a category, so on
a minimal guest every system service ran with category 0 and the kernel
logged Launch Constraint Violation (not enforcing) … launched as a system service unexpectedly but met System Service constraints for each: 212 lines
(355 distinct programs) in lcl-186's serial log of one 18.6 Minimal boot,
303 lines (898 occurrences) in iphone-265.log, a 26.5 Minimal boot.
createTrustcache now rewrites cryptexctl's output as version 2
(TrustCache.withConstraintCategories): each entry gets the category its
cdhash has in Apple's three caches; a file the merge signs again gets the
category its IPSW cdhashes had (resignedCDHashes: launchd_cache_loader
where it is still patched, mobileactivationd, and the detached
launchd.plist.sig of "The stock launchd_cache_loader takes a re-signed
launchd.plist"); what Apple's caches do not list (vphoned, its Swift library,
the GPU driver bundle, kexts Apple left out) keeps 0. Entries, order, flags,
UUID and the IM4P's type and description stay as cryptexctl wrote them.
The detached signature's cdhashes reach cryptexctl through the second base
trust cache and are in none of Apple's caches. Apple's 22G86 static trust
cache (043-19071-100.dmg.aea.trustcache) gives both cdhashes of its own
launchd.plist.sig (72c3cf9a… SHA-256, 2e45eca3… SHA-1) category 2, and
resignLaunchdCache maps the new cdhashes to those. Apple's 26.5 static
trust cache (094-54920-099.dmg.aea.trustcache) does the same for its
launchd.plist.sig (b2f662df… SHA-1, cb7da9ce… SHA-256).
Checked on 2026-11-02 with two minimal guests that also carried the other
Minimal changes of that week (a stock LLB with only iBEC's image-4 callback
patched, vphoned's paravirtual user-client entitlements, the restore tree
published as clones). int-186 (iPhone11,6 18.6 22G86 on cloudOS 22G86, bundle
2.12.2-local.c632b261), patch log:
The installed new.trustcache is version 2, 3428 CDHashes, with both new
cdhashes at category 2. A reboot's unified log (Persist tracev3 pulled with
guest pull and read with log show over a hand-built archive, since the
stock LLB passes no serial boot-args) shows:
isCdhashInTrustCache … Process 28 is checking if a cdhash is in the trust cache/Returning IOReturn 0x0 to process 28, the same pid and pair lcl-186's serial log shows just beforecdhash: … b4ace6ca… is trusted. The loader's own lines are not in the persisted log (it runs before logd).- Two
Launch Constraint Violation (not enforcing) … met System Service constraintslines, for/usr/libexec/init_exclavekitand/usr/bin/vphoned, both at category 0 because Apple's caches do not list them. - No
Launch Constraint Violation (enforcing), no CODESIGNING exit, no crash report (idevicecrashreport -k: onlyrestore_perform.txt); launchd_cache_loader, mobileactivationd and vphoned ran.
int-265 (iPhone17,3 26.5 23F77 on cloudOS 23F75) was built with minimal
plus --allow llb-boot-boot_args --allow ibec-boot-boot_args, so that the
host serial log has the boot. Its patch log gives 6729 of 7277 entries a
category, the new launchd.plist.sig cdhashes (4230469c…, 20f70244…)
category 2 and mobileactivationd's category 3, and the serial log shows the
secure path:
with three Launch Constraint Violation (not enforcing) lines
(MobileAssetEarlyBootTask, init_exclavekit, vphoned), no enforcing
violation and no crash report.
Always On display (2026-11-02)
No new patch identifier and no new bytes in an Apple binary.
preboot-cfw-devicetree_iphone_product (the iPhone product repair, which cfw install and cfw update-environment run) also adds
/product/vphone-always-on-display = 1 when the board's own tree has
/arm-io/disp0 always-on-time. MobileGestalt reads nothing by that name.
vphoned reads it at startup and answers DeviceSupportsAlwaysOnDisplay
(2OOJf1VhaM7NxfRok3HbWQ) and DeviceSupportsAlwaysOnTime
(j8/Omm6s1lsmTDFsXjsBfA) yes in the MobileGestalt cache's CacheExtra
(GuestMobileGestaltAnswers, GuestMobileGestaltCache.presentAlwaysOnDisplayOnStartup).
Settings then offers Always On Display and SpringBoard dims the Lock Screen
instead of turning it off. Issue #650.
Why not the tree. libMobileGestalt (24A446) answers the three questions from one node:
Settings shows the row only with AlwaysOnTime (DisplayAndBrightnessSettings
asks for it; with AlwaysOnDisplay alone there is no row, measured). The board's
always-on-time therefore answers the flipbook yes too. CoreBrightness reads it
in backboardd (-[CBAODState initializeAODSupportConfiguration]) and drives the
flipbook through the panel controller (DCP), which the paravirtual panel does
not have. An iPhone18,1 27.0.1 guest with an /arm-io/disp0 stub carrying the
board's always-on-time lost backboardd at every boot (rebooting due to critical process crashes: backboardd) and launchd restarted it into the device
recovery environment. A second guest made without the stub booted, and
answering all three yes in its CacheExtra crashed it the same way. Without
the flipbook, -[BLSHBacklightOSInterfaceProvider initWithPlatformProvider:]
gives SpringBoard BacklightServices' own renderer (BLSHPseudoFlipbook), which
works on a guest. Answering the first two in the cache leaves the flipbook
unanswered there and so no. Keeping always-on-time in the tree and answering
the flipbook no in the cache would not do: the tree decides whenever the cache
is missing, at a guest's first boot and after every drop, and one boot with the
flipbook is enough to lose backboardd.
A guest that went through that crash loop kept losing backboardd after the answers and the whole cache were removed; the cause was not found. Do not test the flipbook answer on a VM you want to keep.
When the answers land. libMobileGestalt writes the cache when a process
first asks after it is gone: at a guest's first boot, and after vphoned drops a
cache older than the tree. vphoned waits for it (up to 10 minutes), adds the
answers once, and sets mobilegestalt_restart_pending; the cache keeps them
across boots, and a rebuilt cache gets them again at the next start. A new
machine's creation boots more than once, so it is offered from its first use.
The window. On a foldable guest the VM window covers the panel while
display.orientation says screen_off, so a waking panel does not show its
frame from before (bde6440c). SpringBoard posts hasBlankedScreen for the
always-on Lock Screen too, so the window covered it. FoldDisplay now publishes
whether the panel went off since SpringBoard last lit the screen
(com.vphone.folddisplay.panel-dark: 1 stayed on, 2 went off), and
screen_off is SpringBoard's blank and that.
Verified.
- New machine: iPhone18,1 27.0.1, cloudOS 26.4, bundle from this branch on
2.14.2.
cfw installloggedproduct/vphone-always-on-display: 'absent' -> '01000000'; with nothing done by hand, the cache had both answers yes and the flipbook unanswered, Settings showed Always On Display On, and the locked screen stayed dimmed with the clock moving (9:34 to 9:36). No backboardd crash. - Existing machine: iPhone19,4 27.1 (24A94425),
vm set-bundle --update-environment. The repair added the marker; the next boot dropped the cache, the rebuilt one got the answers and the restart was reported pending; after the restart the Lock Screen went always-on, the panel state stayed 1,screen_offfalse, and the VM window showed it. With Always On Display switched off in Settings the panel went off (state 2),screen_offwas true and the window covered it, then uncovered it on wake.
Not covered. Other iPhones with the display were not run. Apple Watch:
N227b's tree has always-on-time too, but on a Watch7,18 26.4 guest the same two
answers left the screen fully dark after the timeout (Carousel decides; not
investigated), so the repair stays iPhone-only. No iPad board has the property.
The VM keeps rendering while dimmed: there is no low-power display.
Experimental iOS 16.6.1 desktop bring-up
Measured pairing: iPhone15,2 (iPhone 14 Pro), iOS 16.6.1/20G81 userland,
cloudOS 26.1/23B85 vphone600 boot chain. All additions below are manual opt-ins;
shipped presets and the no-plan path leave them disabled. See
Research/Guest/ios16_bringup.md for procedure, evidence and limitations.
system-launchd-exp-ios16_mach_portsbridges the old out-of-line self-task Mach-port API to the newer fixed-three-port RPCs in the native launch hooks.dyld-exp-ios16_mach_ports_lookupupdates the old libxpc client's pre-constructor lookup through a symbol-derived, bounded cache patch with undo capture.system-backboard-exp-ios16_migrationskips the fresh lab's measured migration wait and releases its local shell sentinel/PreBoard re-block. It does not migrate existing user data or persist completion.system-springboard-exp-ios16_desktopavoids unavailable startup media, widget and flashlight services and uses the native local setup-state setter. These services remain unavailable.system-systemhook-exp-ios16_surfaceconverts capability replies 0x28 -> 0x18 and allocation/lookup replies 0xC68 -> 0xA68, including narrowed fields and the changed plane stride. Offline metadata derives the framework-private transport stub, verifies its instruction pattern and records its Mach-O UUID. The runtime detour checks UUID/expected bytes, suspends peer threads, uses private COW memory without writable executable pages, and restores protection.system-systemhook-exp-ios16_framebuffertranslates the measured old 0x46C SwapEnd payload into the modern 0x560 layout. It inserts the new event/port slots, retaining frame token, surfaces, rectangles and measured flags. Native create/lock/write/read/unlock and surface lookup probes pass; submitted color bars and the real SpringBoard desktop are visible.preboot-exp-ios16_ephemeral_storagechanges only the existing chosen/product ephemeral-storage fields, preserving their widths and tree size. Nativehw.ephemeral_storageandos_variant_uses_ephemeral_storagethen report 1; PreBoard no longer owns the shell. Restore pre-ephemeral snapshots to undo guest data effects; restoring device-tree bits alone is insufficient.system-vphoned-exp-ios16_touchscreenregisters a built-in HID touchscreen only when none exists. With desktop startup selected, native backlight-state changes also publish the legacy HID factor. Real mouse swipes and clicks now reach the guest rather than being dropped as missing-digitizer/display-off.system-springboard-exp-ios16_keybagless_uiis restricted to a fresh ephemeral lab. The root daemon requires explicit markers and a strict session/persona keybag-directory allowlist before publishing a single-link, root-owned, magic-checked lease. Three signature-checked UI getters honor that lease for at most three seconds and otherwise forward to native. MKB/AKS and encrypted data remain unchanged; this is not working credentials or protected storage.- The existing battery helper weak-imports the unavailable newer precise-power API and falls back to its existing ByType API. This resolves the measured Preferences pre-main dyld crash without synthesizing an IOKit export.
The real guest displays Home Screen, accepts mouse input and opens Settings > General > About (16.6.1, iPhone 14 Pro, 64 GB). Initial About lookup can wait for IOKit/SystemConfiguration quiescence; a proposed fallback was never deployed and is excluded. Wallpaper preview fails without Metal; its cancelled, unvalidated prototype is excluded as well. No general iOS 16/15, Metal, full audio, camera, networking or persistent protected-data compatibility is claimed.
Userspace debugging on minimal, watchOS and HomePod (2026-11-02)
system-debugserver-cfw-install re-signs the guest's /usr/libexec/debugserver
(strips seatbelt-profiles, sets task_for_pid-allow, ad hoc) in cfw install,
which minimal never runs. filesystem-exp-debugserver makes the same change in
the restore-image merge, before the reseal, so the new hash lands in the
regenerated static trust cache. Both stages share the entitlement transform
(CustomFirmwareDebugserver.attachEntitlements) and are a logged no-op when the
system ships no debugserver. The merged iPhone17,3 26.5, Watch7,18 26.4 and
HomePod Software 27.0 systems all ship one. A watch's debugserver also carries an
arm64_32 slice the signer cannot rewrite, so the merge thins it to arm64e first.
The entitlement alone does not let debugserver run or attach on minimal's stock
kernel and TXM: AMFI asks TXM for developer mode and kills it ("Developer mode is
disabled, task_for_pid-allow is disallowed"), and an enrolment from the guest does
not survive a forced reboot. The userspace-debugging feature
(FirmwarePatchSetCatalog.userspaceDebugging) is the smallest set found to attach
to a system daemon and read its memory:
A part names one patch per delivery, and a preset takes the first its sets
declare. standard already has every part, so Launchpad offers no box there. The
Apple TV presets get none either: cfw install refuses debugserver and launchd
patches not validated for tvOS (FirmwareGuestPatchResolution), and the feature
checks that list. HomePod's list now admits the two, as validated below.
AMFI patches are not needed. kernel-boot-amfi_trustcache and
kernel-boot-amfi_execve were in the first cut "should debugserver run outside
the image's trust cache". It doesn't: the re-sign is in the resealed trust cache.
An iPhone17,3 26.5 minimal guest without them launches the re-signed
debugserver from launchd and attaches.
launchd's jetsam guard. With developer mode on, the host's CoreDevice (a
minimal guest pairs with the Mac over the NAT) mounts Xcode's personalized
Developer disk image whenever a developer service needs it, for example
xcrun devicectl device info processes. MobileStorageMounter mounts
Xcode_iOS_DDI, launchd loads its daemons, and launchd kills init:
initproc exited -- ... jetsam property category (Daemon) is not initialized.
Observed on the first boots of an iPhone guest without the patch (and in an
earlier minimal-ctf machine's log). standard avoids it with
system-launchd-boot-jetsam_panic_guard_bypass; on minimal launchd is in the
sealed system, so filesystem-exp-launchd_jetsam_panic_guard applies the same
CustomFirmwareJetsamPatcher change in the merge. With it, the same
devicectl call mounts the DDI, dtappserviced starts, and the guest stays up.
On HomePod, launchd is patched for the persona adapter, which returned before
the jetsam verb; cfw install now applies both to the one copy.
To make the feature selectable without a preset of its own, minimal and
watchos name the two kernel sets and select nothing from them. The pipeline
builds a kernel patcher only when the plan turns on a patch from those sets (the
research board's kext mix targets the kernelcache too, so the target alone does
not say), so a plain minimal kernel stays stock. A feature is only a list of
patch identifiers: turning it on is --allow for each, and fw patches reports
it (features in --json, with available and the patches for the active
preset) so Launchpad's patch sheet offers one box without declaring a patch
itself. On an installed minimal VM a kernel patch is reported as delivered by
a restore, not by cfw update-kernel, which refuses a VM whose system was
customised before its restore.
Validation (Launchpad vm create with the feature's patches allowed, then a
launchd job running /usr/libexec/debugserver 0.0.0.0:<port> --attach=<configd>
and LLDB over gdb-remote: attach, (int)getpid() evaluated in the target, 16
bytes of its code read):
On iOS 18 the feature needs the iOS 18 kernel and TXM finders of
feature/ios18-standard ("Find the standard kernel patches in the iOS 18
research board kernels", "Find the LLB rootfs and TXM CFW patches on cloudOS
18"). Without them fw patch stops at TXM: on cloudOS 18.6 the developer-mode,
debugger and get-task-allow sites are not found. The iOS 18 row ran with those
two commits on top of this branch.
On a stock boot chain (minimal and watchos with no iBEC or LLB patch, and
the launchd_cache_loader gate off; 2026-11-02) the feature works the same: the
same 22 kernelcache and 9 TXM records, iBEC: no patches and LLB: no patches in the patch log, the stock LLB booting the patched kernel and TXM to
SpringBoard (iPhone17,3 26.5) and Carousel (Watch7,18 26.4) with vphoned up and
developer mode enabled, and LLDB attached to configd through the re-signed
debugserver, evaluated (int)getpid() and read 16 bytes at its pc. The watch
guest needed more than 2048 MB: at 2048 MB a stock LLB, with or without the
feature, stops the VM after iLLB decompressed; at 8192 MB both boot.
iOS 18 GPU transport (2026-11-05)
Row 37. An iPhone17,3 iOS 18.6.2 guest on the cloudOS 26.1 kernel had no Metal
device, because fw prepare staged the cloudOS's paravirtual driver and iOS 18
cannot load a 26.x one. system-systemhook-cfw-ios18_gpu_transport is selected by
standard for iOS 18.6 on cloudOS 26.1 only. TV, HomePod and HomeView presets
exclude it, a VM with no plan does not get it, and opt-in cannot widen the gate.
Reveal. Trace each user-client call from a guest test process with the
prototype detours; diff every bl _IOConnectCall* site of IOGPU.framework in the
22G100 and 23B85 caches by caller symbol (w1 selector, scalar and struct
counts); sweep accepted struct sizes of each AppleParavirtGPU method against the
26.1 kernel from a guest test binary. The full table and the steps for another
pairing are in Research/Guest/ios18_gpu_transport.md.
Change. No Apple binary is modified. fw prepare stages the driver from the
iOS release's own cloudOS (18.6, 22G86), cached in ~/.vphone/gpu-drivers and
otherwise recovered by the usual temporary restore. SystemHook detours four IOKit
entry points with MISFixDetour, only while the marker exists, and translates IOGPU
device-client calls only. cfw install writes the marker only when the installed
driver's DTPlatformVersion is the iOS release; switching the patch off removes
it. Rules are unit-tested by make test-ios18-gpu.
Validation. See Research/Guest/ios18_gpu_transport.md, "Verified".
Icon store (2026-11-05). The guest's IconServices store kept the transparent
graphic icons it rendered before the transport, so Settings' rows stayed blank.
cfw install writes the marker once, and vphoned (GuestIconServicesCache)
removes the store at startup when it was created before the marker, then stops
iconservicesagent; the Data volume is out of the host's reach. No Apple binary
change.
Re-sealing through the shared CodeResources (2026-11-05)
No new patch identifier and no new bytes in an Apple binary. Every installer
step that re-seals a guest bundle (sealGuestBundle, first reached by the
VirtualAudio patches) ran codesign -f on a CodeResources that iOS stores as
one file for every bundle sealing no resources: 6111 hard links on iOS 27.2
24B91020. codesign rewrote it in place, so the plugin's seal landed in all of
them. xpcroleaccountd then refused Instruments' dtsecurity XPC service
(errSecCSBadResource, setup_role_account failed, error 0x4f), and
DTServiceHub could build no symbolicator: Time Profiler stacks were bare
addresses (issue Lakr233/vphone-cli#824).
Change. sealGuestBundle gives a hard-linked CodeResources or executable a
file of its own before codesign runs. When the shared CodeResources is no longer
Apple's seal and the bundle's original executable (.bak) was signed over that
seal (CodeDirectory special slot 3), the installer writes Apple's seal back
through every link. The seal is rebuilt by CustomFirmwareBundleSeal, byte for
byte the shipped file (18.6.2 and 27.2 checked). A damaged guest is repaired by
the next install or Update Guest Environment that reaches the VirtualAudio
step.
Reveal and validation. Research/Guest/instruments_symbolication.md.
Local pairing connections in remotepairingdeviced
system-remotepairingdeviced-cfw-local_connections permits pairing control and
tunnel connections originating on the device itself, so apps inside the guest
can connect. It is enabled in standard and is not boot-essential.
On 24A446 and 24C5037i, the daemon's local-path predicate calls
nw_connection_copy_current_path, nw_path_is_local, and
nw_path_uses_interface_type(path, 4). Three callers use that result to reject
connections: one control-channel callback and two tunnel callbacks. The
control rejection logs Rejecting control channel connection originating from local machine; both tunnel callbacks log Rejecting tunnel connection originating from local machine.
The imported symbols identify the predicate; the rejection strings' ADRP/ADD
references and branches identify its three callers. Replacing those callers'
bl <local-path predicate> with mov w0, #0 bypasses the local-connection
rejections.
Offline validation on 24A446 and 24C5037i changes exactly 12 bytes before signing, and a second application is a no-op. Other builds have not been tested and may need different matching.
The iPhone19,4 builds of 27.1 (24A94425) and 27.2 (24B91020) are arm64e.x1:
their functions open with pacibsppc, so function bounds also accept the
PC-salted prologues. On those two builds and 24A446 the patch again changes
exactly 12 bytes at the three gates and a second application is a no-op. This
is offline validation only; the 27.1 and 27.2 Duo guests have not run it.
Apple TV, HomeView and HomePod presets block the patch, as they block the
other iOS 27 guest-executable patches, because no tvOS 27 remotepairingdeviced
has been checked, and a site miss there would abort a strict OTA update.
Live validation on 24A446 with bundle 2.15.2 confirmed that same-device
connections were reset during the control handshake before patching.
After patching, pairing verification, TLS tunnels, Accessibility and screen
capture passed on bridged and NAT networking and after reboot.
The installed CLI's cfw update-environment also passed application,
reversion and reapplication on 24A446: the guest receipt followed the
selection, reversion restored the original binary, and both applications
produced the same signed binary and working local connections after boot.
2026-11-06: standalone HomePop accessibility X1 lowering
On iPhone19,4 / iOS 27.1 (24A94425), an app's accessibility initialization loaded SpringBoardHomePopAccessibility.axbundle outside the shared cache. Its arm64e.x1 image faulted at +[AXSpringBoardHomePopGlue initialize] + 0: image offset 0xc58, PACIBSPPC, SIGILL 0xdac1a7fe. The original image UUID is BA1A37E2-D388-3876-A68E-89FDB4145618. Existing static X1 lowering converts 66 instructions, re-attests three code slots and leaves zero candidates.
The new system-homepopaccessibility-cfw-arm64e_x1 declaration is gated to the measured iOS 27.1 base and follows the existing reversible Mach-O and bundle-seal installer path. TV, HomePod and HomeView presets block it like CIBarcode. No shared-cache bytes or application code are intentionally changed.
Validation completed on the same guest with a local build of this change: 50 focused X1 and bundled-catalogue tests passed. The installed image reads PACIBSP at 0xc58; repeating lowering leaves its SHA-256 unchanged and reports zero unvisited candidates. A separate vphoned script process loaded the real bundle and invoked +[AXSpringBoardHomePopGlue initialize] successfully without crashing. The bank app loaded its homepage and answered accessibility UI queries. Open/closed pose requests and a second app launch were exercised; these checks are local VM evidence, not broad accessibility or real-device acceptance.
Experimental visionOS 26.4 native UI
The RealityDevice14,1 / N301AP userland (23O247) can render native Home and Settings with the PCC 26.4 research kernel. This is an experimental pairing, with a manually configured cold-boot checkpoint rather than a fresh-install certification. See the recipe, evidence and limitations.
preboot-exp-visionos_virtual_productchanges only the known generic VM product token to native V201. The board identity remains RealityDevice14,1. The existing Preboot composition backup provides reversal.system-launchdaemons-exp-visionos_nonfatal_servicesjournals only panic-on-crash policy fields for polarisd, realitycamerad, realitycoprocessorloaderd and arkitd. It retains the native jobs; it does not emulate their hardware.system-launchd-exp-visionos_uxm_ipcinjects a PID-1-only adapter for two exact UXM/Polaris lookups absent from the PCC sandbox. Each optional display rule has a separate selection and root-owned marker:system-launchd-exp-visionos_hmd_properties_ipc,system-launchd-exp-visionos_hmd_presentable_ipc,system-launchd-exp-visionos_hmd_system_setter_ipc, andsystem-launchd-exp-visionos_wakeboard_system_ipc. Rules validate audit token/PID version, executable/signing identity, operation and filter type; other checks retain the native function's ABI and errno.system-launchd-exp-visionos_environment_ipcadds a separate root-owned/usr/lib/vphone-visionos-environment-ipcmarker for RealityEnvironment's lookup ofcom.apple.surfboard.environmentservice. The 2026-11-08 runtime log records launchd error 159 and a matching kernel sandbox denial; the native app then traps inBSServiceConnectionbecause its endpoint is nil. This rule requires the shared UXM adapter and validates the exact app path, signing identity and audit token. Removing its marker leaves the other rules unchanged. It does not supply environment assets or certify rendering.system-featureflags-exp-visionos_environmentsenables only the nativeRealityEnvironment/ShowEnvironmentsOnSimulator/Enabledflag. The inspected 23O247 app reports non-synthetic environments unavailable on nonphysical hardware while this flag is off. Its field journal preserves the original Boolean or absence and feature-entry presence; opt-out restores those fields while retaining unrelated metadata. It remains independent of the IPC rule and does not download or install environment assets.system-wakeboard-exp-visionos_frame_pacingchanges the V201 dequeue's positive-lateness conversion from ceiling to truncation so a sub-period return from synchronous presentation does not skip another full display tick. The existing 60 Hz native clock, rendering and submission waits remain intact. The patcher resolves in-image symbols and verifies typed instruction data flow plus the imported wait call, then re-attests only its code-page hash while preserving signature identity, entitlements and profile blobs. Opt-out restores the pristine executable backup. See V201 pacing for evidence and limits.- The visionOS agent's native immersion getter/setter and the host's
Device > Environment Immersionactions replace the missing physical Crown operation. Onlyvphoned-visionosreceives the native immersion-client entitlement; other agent variants and host programs retain theirs. The setter uses the original type-0 SurfBoard service and independently reads its level back. White Sands rendered through the normal native composite in the existing VM. See the environment follow-up for ABI, request validation, reversal and runtime limits. system-preferences-exp-visionos_skip_buddysupplies a System policy and a one-shot UID 501 helper. It durably journals only mobile's original native ForceNoBuddy Boolean/absence before applying it; opt-out restores that key.system-featureflags-exp-visionos_legacy_uxmjournals two UXM feature fields for a diagnostic legacy trial. Keep it off for native ClientSession users.
All twelve choices are off in shipped presets. UI adapters are pinned to the 26.4/26.4 pairing; the nonfatal policy is pinned to the 26.4 userland. GPU LC_BUILD_VERSION retargeting and the exact-pair OOL-port-array boot argument reuse the existing GPU install and boot-argument patch paths. No new kernel instruction patch is introduced.
visionOS application management continuation
The default visionOS guest agent now runs the existing full iOS application and Irisin interfaces through iPad compatibility. The Foundation-only bring-up agent is not shipped. Runtime detection includes RealityDevice hardware because compatibility mode reports iOS; iPhone startup policy and state polling are excluded. Bootstrap selection and guest validation allow rootless only. The existing vphoned installation step also installs its Span runtime dependency. No new firmware patch identifier or kernel instruction change is introduced. Runtime app installation/launch/removal, Irisin installation and registration rebuild are recorded in the bring-up doc.
Accurate text recognition on the paravirtual Neural Engine
dyld-cfw-text_recognition_ane makes accurate text recognition work in iOS 27
guests. It is enabled in standard, is not boot-essential, and is blocked in
the Apple TV, HomeView, HomePod and visionOS presets.
Symptom. VNRecognizeTextRequest at the accurate level, and vphoned's
ui.ocr, fail with CRImageReaderError 9; the fast level works. The log shows
Espresso loading cr_td_model_v3_eir.mlmodelc.bundle and failing with
Missing weights path model.espresso.weights_nonane status=-2.
Cause. TextRecognition chooses its detector, its Latin, Arabic and
Devanagari recognizers and its orientation model by
deviceHasModernNeuralEngine: E5 models when true, Espresso CPU models
otherwise. The predicate requires deviceHasAppleNeuralEngine, which answers
false as soon as MobileGestalt_get_isVirtualDevice is true, before it asks
Espresso for a Neural Engine context. The iPhone firmware ships the CPU
detector's network without its weights: the 24A446 restore image has no
weights file in that bundle and no MobileAsset carries one, and a physical
iPhone (24C5037i) loads the E5 detector, with the CPU detector failing the same
way there when loaded directly. vphone removes nothing here.
The guest's Neural Engine itself is usable: espresso_create_context(10007, -1)
succeeds and AppleNeuralEngineSubtype is 128. Two simpler changes fail:
- Selecting E5 alone: each firmware E5 bundle holds only
H18.bundle. E5RT looks for the host's family (H14G,H14and their variants for an M2), then compiles<bundle>/model.mil, which the firmware does not ship. - Dropping the VM check from
deviceHasAppleNeuralEngine: the Espresso Chinese/Japanese and Korean recognizers then ask for the Neural Engine (cp=65552), whose H18-only binaries fail on the paravirtual device (submit with arguments requires context_metal,CRImageReaderErrorDomain -8).
Change. Two parts under one identifier, because neither works alone:
- In the shared cache, the
tbzthat makes the modern answer depend on the cached existence answer becomes anop. The block then cachesMGGetSInt64Answer("AppleNeuralEngineSubtype", 1) > 31alone; the existence predicate keeps its VM answer. The block is found by its local symbol; its call must reach_MGGetSInt64Answerin libMobileGestalt, directly or through a branch stub, and the compare with 31, thecset gt, the store and thetbztarget must all match.___deviceHasH16OrLaterNeuralEngine_block_invoke, the same block with 207, is rejected. One page is re-attested, and the original word goes to the undo log. cfw installandcfw update-environmentadd the host'smodel.miland the weight files it reads (@model_path/..., onlyweights/weight.bintoday) besideH18.bundlein eachcr_*_e5.mlmodelc.bundle. macOS ships the same five models as MIL in/System/Library/PrivateFrameworks/TextRecognition.framework/Resources, and compiles them per process itself. On macOS 27.0.1 (26A434) they are byte-identical to the iOS 27.0 Simulator's, and the guest'sH18.e5recordsespressoc_host -i .../cr_td_model_v3_e5.mlmodelc.bundle/model.mil. A model is accepted only when every file both sides carry (codemap.bin,constant_input_pe_pe_to_fp16.bin) is byte-identical and each guestH*.e5names every input and output in the host'smetadata.json. All models are checked before anything is written. A bundle whosemodel.milthe firmware already ships is left alone. The added files are journaled in/usr/share/vphone/TextRecognitionModels.plist; turning the patch off removes exactly those, the folders it created and the journal. A file whose contents are already current is left in place, because E5RT keys each app's compiled models by the source file and would otherwise recompile them after every environment update. The cache patch is not applied when the models could not be added.
Cost. Each app compiles the models on its first accurate request, about
50 s on an M2 host, then keeps them in its own
Library/Caches/<app>/com.apple.e5rt.e5bundlecache; later requests take
0.1–0.8 s, across reboots too. macOS does the same for these models (44–47 s
for a fresh process on the same host). The paravirtual Neural Engine refuses
precompiled binaries outside internal builds
(validateEnvironmentForPrecompiledBinarySupport), and the firmware's only
precompiled variant is H18 anyway.
Validation. The cache patch was dry-run on the 24A446 caches of iPhone18,2
and iPad17,1, which place the site at the same address. Live validation used
an iPhone18,2 guest on 24A446 with an M2 host on macOS 27.0.1, through the
built CLI's cfw update-environment. Before, accurate recognition failed as
above. After, with no hook in the test process, accurate recognition
succeeded by default, for English, for Chinese, Japanese and Korean with
their languages, with the CPU, GPU and Neural Engine devices requested
explicitly, and at revisions 1–3; the E5 models were compiled from the
system path and loaded on the Neural Engine, and the CPU detector was not
loaded. ui.ocr returned the home screen's text. Both kept working after a
reboot and after another update-environment, without recompiling. Turning
the patch off restored all 82 cache files and the undo log byte for byte,
removed the added files, folders and journal, and brought back the original
failure; turning it on again reproduced the same cache bytes. Other builds
have not been tested and may need different matching.