15 KiB
macOS 13 KVM/Cryptex compatibility diagnostic
This separate manual candidate probes Recovery readiness on the existing Ubuntu Docker daemon with KVM, the real Intel host CPU and macOS 13. It does not install macOS, erase a disk, install .NET or Apple CLT, or run Meeting Assistant. Passing proves only a fresh macOS 13+ x86_64 Recovery guest with root identity, a working launchd system domain, DiskArbitration and exactly one writable 64-GiB guest disk.
Baseline: bootstrap commit 4606de069678e8f95dfe3c7dad1bf5ce5384d30c; separate branch codex/macos-ci-kvm-compatibility. KVM, CPU passthrough, Recovery major version and guest Cryptex staging change together. This is a compatibility experiment, not a causal single-variable A/B test. The TCG/bootstrap experiment remains separate.
Reasons and remaining gaps
The existing daemon's Intel Celeron 1037U lacks AVX/AVX2; a separate diagnostic proved KVM enabled/paused state and clean exit. CPU_MODEL=host preserves actual instruction availability rather than advertising AVX2 through emulated Skylake. This candidate refuses a TCG or CPU-model fallback.
All four Swift helpers target x86_64-apple-macos13.0; the macOS 14 EventKit call has an existing macOS 13 fallback. Inspected native Mach-O files in pinned .NET SDK 10.0.401 x64 declare minos 12.0. These source/binary minima are not runtime qualification or vendor support: macOS 13 is outside Microsoft's current .NET 10 supported-OS policy. This probe does not install that SDK, compile helpers or test calendar/audio permissions.
Official CryptexFixup 1.0.5 activates without AVX2 and registers for normal, installer/Recovery and safe-mode boots. It redirects installer/updater ramrod to Apple Silicon's Rosetta Cryptex and bypasses APFS root-hash authentication on Ventura and newer. It does not emulate missing instructions. This kernel patch affects only the owned guest, never a host module.
Recovery cache gap: CryptexFixup does not replace an already running Recovery BaseSystem shared cache. Its installer/update selector targets the installed Cryptex, but this readiness-only run invokes no installer. Staging or loading it therefore proves no Recovery userland compatibility. Actual CPU/kernel behavior, guest injection, all native gates and any later installed-Cryptex/build/test behavior remain unqualified until observed.
Apple Recovery uses the pinned public InternetRecovery protocol with board ID and session/asset tokens, without Apple ID or workstation credentials. The macOS 13 selection, downloaded hash and actual guest version are retained; the hook downloads no full installer or SDK.
Entry point and dependencies
Orchestration/validation remain the .NET 10 file-based app tools/ci/MacOsNativeDiagnostic.cs. Bash/Python stay only in the existing pinned Linux/macOS boot integration.
dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --help
dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --validate
dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --validate --source /path/to/clean/pinned/dockur-clone --cryptex-archive /path/to/CryptexFixup-1.0.5-RELEASE.zip --output /path/to/fresh/validation
--validate checks result/container contracts without Docker. With --source it verifies the actual Cryptex ZIP/bundle, source seams, generated OpenCore configuration and staging/checksum contracts, checks Bash syntax, then exercises four raw/zlib Recovery fixtures and twelve rejection cases with independent C# CRC32 readback. It also checks preservation of a successful resource snapshot after a later failed capture, leaving the supplied source untouched. It does not download/extract the LongQT ISO, verify a complete Apple Recovery image or execute the active-Lilu runtime checks. The ISO checksum is enforced during the later Docker build; active Lilu and EFI-copy checks execute only during container boot. The optional local Cryptex ZIP must match the release size/hash; omitting it downloads only the public 69,703-byte release. Use a fresh output directory. Dependencies are .NET 10, Git, Bash and Python 3 with its standard library; manual execution also requires the existing Linux/x64 Docker daemon and its existing KVM device.
The manual-only workflow keeps these owned run/cleanup entry points; validation invokes neither:
dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --run --output artifacts/native-macos
dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --cleanup --output artifacts/native-macos
Exact bootasset contract
Dockur stays pinned to 16a5b470cdd601bae8b05b02d748d7edfb36c12e. Original Recovery patcher/staging, Dockerfile, OpenCore script and active config hashes are verified before edits. Both existing QEMU image digests remain pinned; other existing upstream downloads are observed through image identity. source-hashes.json includes the generated Recovery patcher, both original/replacement daemon variants and udif_checksums.py, staged from tools/ci/macos-native-udif-checksums.py. This small Python module belongs to the existing Linux UDIF runtime; C# supplies orchestration, validation fixtures and an independent CRC32 implementation.
Run 4173 at 45d7bde71f9f5a1f7121585fe3ee9fc81f7c585f failed before QEMU started: the full macOS 14 plist pattern was absent from the macOS 13 download. The original image's hash was not retained. An independently downloaded comparison for the same board Mac-4B682C642B45593E is macOS 13.6/22G120, Apple product 042-23155, 710,918,897 bytes, SHA256 c19bd12f5cb1651b87b74d04f02a636da762ea46b81c7ebc9f205fa2a976d599. Its Apple chunklist signature and chunks verified before any changes. It is comparison evidence, not the missing run-4173 image identity.
The HFS+ catalog identifies /System/Library/LaunchDaemons/com.apple.recoveryosd.plist as file ID 57231, logical size 465 bytes and one 4,096-byte allocated block. Its exact XML SHA256 is af9d7f6c1948079bd4384d27b6882678d6fb4e338fcf6a8be8f84fceef174ad6. This variant has ProcessType=Interactive; the previous macOS 14 variant has App. The patch accepts only these two exact layouts with exactly one daemon label and original ProgramArguments=[/usr/libexec/recoveryosd]. It preserves each variant's fields and process type, removes only the XML doctype to fit the wrapper arguments, and pads to the original file size. Unknown, duplicate, malformed or wrong-argument layouts fail before image writes. The early rc.cdrom hook remains mount-only; the unchanged wrapper runs the Apple daemon.
The checksum binding validates the original flattened UDIF boundaries and CRC32 values, stages every recompressed chunk before writing, then updates only the changed mish CRC32 and koly data-fork/master CRC32. libdmg-hfsplus provides the checksum semantics; an independent C# reader matched all eight mish checksums on the unchanged comparison. Raw and inflated zlib bytes enter logical CRCs in run order; observed IGNORE runs are omitted. Unobserved ZERO runs, other compression/checksum types, overlaps and invalid boundaries are rejected. Base64 characters are replaced within the same metadata region, preserving its whitespace, length, partition tables and trailer offsets; the entire modified image is read again to verify CRCs. Apple chunklist authentication applies exclusively to the unchanged input, not the deliberately modified guest image. CRC integrity proves no Apple authenticity or native runtime gate.
The original LongQT v0.7 template, 15,884,288 bytes, is now Docker-ADD-checksummed to SHA256 287328995d4198f1b05166f087d85bf7ef66bedafe150d17ad112ac8de60051d. Runtime copies actual EFI_RELEASE/EFI/OC/Kexts, including Lilu 1.7.1, even with official OpenCore DEBUG executables. Active Lilu: executable 526,984 bytes, SHA256 0c016d93cfe40c7fa3965813175c1b991a76f3d295efd5be66ae712b4a3ffb52; Info.plist SHA256 fc885f3319f326e3af60e7965a5216b671772d39d40993ec695758bb43d6ea3a. Staging checks both hashes and bundle version. Cryptex declares Lilu 1.4.7; Lilu history includes Ventura/Sonoma installer/Recovery support before 1.7.1. Existing Lilu is kept.
CryptexFixup-1.0.5-RELEASE.zip, 69,703 bytes, SHA256 25041d94a0fe9a0261caf0ba89b36dfcb21682bf3c697a34bcaddc839576ab30, is checked in C#. Only expected Info.plist/executable files are accepted; identity/version/dependency and individual hashes are recorded. Runtime checks files before/after copying into fresh guest EFI.
Active /assets/config.plist receives exactly one enabled Cryptex immediately after enabled Lilu, preserving every other kext's order. Entry: Arch=x86_64, BundlePath=CryptexFixup.kext, ExecutablePath=Contents/MacOS/CryptexFixup, PlistPath=Contents/Info.plist, MinKernel=22.0.0, empty MaxKernel. OpenCore Kernel.Add requires dependencies first; bounds are Darwin versions. Runtime rechecks order/enabled/paths/architecture/bounds and rejects unverified /custom.plist.
No new force/beta argument is needed for actual no-AVX2 CPUs. Baseline arguments remain. Validation rejects disabling arguments, -crypt_allow_hash_validation (disables the APFS patch) and unexpected Cryptex force/beta overrides. Manifest/profile enter the boot signature; this candidate always rebuilds boot.img and accepts no old cache as evidence.
Gates, privileges and cleanup
The Apple wrapper is byte-identical to baseline: background /Volumes/installstate/readiness.sh then exec /usr/libexec/recoveryosd under the same launchd job/PID. Source evidence does not prove Apple's executable ran.
The disk IPC continuation contains seven explicitly marked diagnostic blocks and limits disk enumeration to one attempt. Its disk query receives 120 seconds so the owned sample can finish while the query is still running. Validation removes only those marked blocks, including that command-budget exception, restores the former attempt condition and normalizes macOS 13 to 14 before requiring baseline SHA256 4d428f594dac14eff64ed87b172c81ecf85ac91da8c5460cd6ec4b1d310800c3. The receipt explicitly records the exclusions, attempt limit and sample/query budgets. Architecture, UID, native service exits, disk size/writability/uniqueness, proof bounds and native-wait/cleanup/flush metrics remain identical. All other required native commands retain 45 seconds, UID retains 180 seconds, and outer limits remain ten minutes maximum disk readiness, 40 minutes host and 45 minutes workflow.
Run 4175 at 94a70b200508d3ba295124896d923fbb785d1658 reached macOS 13.6, x86_64 and UID 0 with KVM enabled; its nine diskutil list physical attempts timed out. Run 4185 at 25989cf0eb7205f30ac0bb44eb279aa3b463f12c proved the whole writable 64-GiB target as IOMedia disk2; it measured approximately 14 seconds for the final native process listing and 33 seconds for IOMedia. Both Apple disk jobs were running; DiskManagement's endpoint was still inactive. The former eight-second observer allowance was shorter than observed native startup, so its killed sample did not establish an IPC wait point. A missing target is ruled out for that run; service initialization, IPC or resource delays remain unresolved.
Before the only disk attempt the continuation records bounded launchctl print output for com.apple.diskarbitrationd and com.apple.diskmanagementd, plus ioreg -r -c IOMedia -l -w 0. Its observer starts only /usr/bin/sample <owned-diskutil-child-pid> 3 100 -file <owned-output>, with no preceding process list or additional service query. Three seconds at a 100-millisecond interval reduces sampling overhead. The sample has 60 seconds for startup/reporting plus the existing two-second TERM/KILL grace; its separate report is flushed into the proof alongside command output. Missing sample tooling or an already completed diskutil is reported explicitly; nonzero observation exits are logged and cannot satisfy any native gate.
The observer owns its command/timer PIDs and is stopped when the disk query completes or the probe is canceled. Its output enters the existing 512-KiB per-output and 4-MiB proof budgets. The hook only reads media/service/process state and writes its existing diagnostic files: it does not load, restart, erase or modify any service or disk. Bash remains necessary because Apple Recovery runs this hook before a .NET SDK is installed. The CPU, Recovery, QEMU, Apple wrapper and container profile are unchanged. These observations are prepared diagnostics, not a new successful guest or full native CI receipt.
Container profile: KVM=Y, CPU_MODEL=host, VERSION=13, 4-GiB guest, two guest/host CPUs, 6-GiB memory/swap and 512-MiB shared memory. Fresh anonymous /storage holds the 64-GiB disk; evidence reads /storage/13/setup.dmg. Existing resource budget checks remain.
Only device mapping: exactly /dev/kvm:/dev/kvm:rw. Inspection rejects other devices/permissions, added capabilities, device requests/rules, binds, tmpfs overrides, published ports, host networking, privileged mode, wrong limits, unexpected persistent mounts or changed CPU/OS profile. No host modules, infrastructure, secrets, SSH or app lifecycle actions are involved. Guest slirp networking remains.
Evidence retains run/profile identity, source/assets, EFI staging, container/resources, macOS 13 Recovery hash, native proof/result/outcome and cleanup. [recovery-original] logs the exact download's size/SHA256 before modifying it, including when patch failure later deletes the source. guest-container-resources.last-success.stdout.log and its timestamp/hash receipt preserve the last successful resource snapshot independently of a later failed stopped-container docker exec. Optional final Unix HMP capture includes info kvm, info status and a bounded PPM exported from /tmp; capture success passes no native gate.
Optional 20-second resource snapshots before, during and after the guest probe retain cgroup CPU usage/throttling/pressure, memory events/pressure/statistics and host page-fault/swap counters. These observations test resource contention as a hypothesis; no resource failure is established by the existing guest timing alone. The large Recovery image hash is captured once after compatibility-profile staging is observed, with its successful receipt retained, instead of repeatedly hashing the image while collecting guest progress. Snapshot or hash observation failure cannot satisfy a native readiness gate.
Both cleanup paths keep exact token/label/ID checks. docker rm --force --volumes removes only the owned container and anonymous volume, then its exact image; no unrelated objects or pruning. Evidence stays seven days. Full native CI still needs a subsequent actual installed remote guest to build/sign helpers and pass the full suite, including five native tests without skips.