diff --git a/docs/macos-native-diagnostic.md b/docs/macos-native-diagnostic.md index d9a8284..4ec1cac 100644 --- a/docs/macos-native-diagnostic.md +++ b/docs/macos-native-diagnostic.md @@ -1,9 +1,11 @@ -# macOS 13 KVM/Cryptex compatibility diagnostic +# macOS 13 KVM/Cryptex/NoAVX 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. +The NoAVX continuation compares against KVM Recovery commit `720a431`. Its only guest change is adding `NoAVXFSCompressionTypeZlib-AVXpel.kext` to the existing OpenCore overlay and `Kernel.Add`. Existing Lilu/CryptexFixup, CPU passthrough, macOS 13 Recovery, disk, probes and deadlines are preserved. The hypothesis is that an AVX-dependent filesystem decompression path blocks native file loading on the Celeron; this has not been established as the cause of the disk-readiness hang. Application source is unchanged, and this candidate has only offline validation evidence. + ## 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. @@ -23,7 +25,7 @@ Orchestration/validation remain the .NET 10 file-based app `tools/ci/MacOsNative ~~~sh 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 +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 --noavx-archive /path/to/NoAVXFSCompressionTypeZlib-AVXpel-v12.6.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. @@ -35,6 +37,8 @@ dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --run --output artifacts/ dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --cleanup --output artifacts/native-macos ~~~ +The optional `--noavx-archive` supplies the exact local upstream ZIP; omitting it downloads only the pinned 98,356-byte archive. Offline validation reads the actual generated OpenCore plist and staged executable bytes, rejects four invalid archive inputs, two missing/corrupted staging cases and five wrong `Kernel.Add` variants, then restores the valid fixture. `noavx-validation.json` records these checks. No Docker or guest is executed, and no new runner dependencies are introduced. + ## 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. @@ -51,6 +55,10 @@ The [original LongQT v0.7 template](https://github.com/LongQT-sea/OpenCore-ISO/r 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](https://github.com/acidanthera/OpenCorePkg/blob/1.0.7/Docs/Configuration.tex) requires dependencies first; bounds are Darwin versions. Runtime rechecks order/enabled/paths/architecture/bounds and rejects unverified `/custom.plist`. +The additional [OCLP 2.5.1 NoAVX AVXpel archive](https://raw.githubusercontent.com/dortania/OpenCore-Legacy-Patcher/f40057a5292f4804b51bcfe78d5047c7302a6434/payloads/Kexts/Misc/NoAVXFSCompressionTypeZlib-AVXpel-v12.6.zip) is pinned to commit `f40057a5292f4804b51bcfe78d5047c7302a6434`, size 98,356 and SHA256 `b5d6319d0a1f335684a92ecf23369bc3deb776be19e92b0a40860021409d20df`. The checksum is a locally verified content pin. Only its two expected bundle files are staged; ZIP resource-fork metadata is excluded. Bundle identity `com.apple.AppleFSCompression.NoAVXFSCompressionTypeZlib`, versions `1.0.0` / `132.100.2` and `OSBundleRequired=Root` are checked before copying. + +NoAVX follows Cryptex in `Kernel.Add`, enabled with `Arch=x86_64`, `ExecutablePath=Contents/MacOS/NoAVXFSCompressionTypeZlib`, `PlistPath=Contents/Info.plist`, `MinKernel=22.0.0` and empty `MaxKernel`. The executable name intentionally omits `-AVXpel`. [OCLP's upstream configuration](https://github.com/dortania/OpenCore-Legacy-Patcher/blob/2.5.1/payloads/Config/config.plist#L1270) selects this 12.6-based patched binary for Ventura 13.0+, rather than the older non-AVX 12.3.1 bundle limited to Darwin 21. Both overlay files enter the existing SHA256SUMS checks before and after the guest-EFI copy. OpenCore boot injection also applies to Recovery; this is no installed-APFS-only root patch. Whether it fixes this guest's hang remains an operational question. + 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 diff --git a/tools/ci/MacOsNativeDiagnostic.cs b/tools/ci/MacOsNativeDiagnostic.cs index 766845e..ea8573f 100644 --- a/tools/ci/MacOsNativeDiagnostic.cs +++ b/tools/ci/MacOsNativeDiagnostic.cs @@ -16,6 +16,8 @@ static class NativeDiagnostic const string DockurCommit = "16a5b470cdd601bae8b05b02d748d7edfb36c12e"; const string CryptexUrl = "https://github.com/acidanthera/CryptexFixup/releases/download/1.0.5/CryptexFixup-1.0.5-RELEASE.zip"; const string CryptexHash = "25041d94a0fe9a0261caf0ba89b36dfcb21682bf3c697a34bcaddc839576ab30"; + const string NoAvxUrl = "https://raw.githubusercontent.com/dortania/OpenCore-Legacy-Patcher/f40057a5292f4804b51bcfe78d5047c7302a6434/payloads/Kexts/Misc/NoAVXFSCompressionTypeZlib-AVXpel-v12.6.zip"; + const string NoAvxHash = "b5d6319d0a1f335684a92ecf23369bc3deb776be19e92b0a40860021409d20df"; const string OpenCoreTemplateHash = "287328995d4198f1b05166f087d85bf7ef66bedafe150d17ad112ac8de60051d"; const string UdifChecksumBindingHash = "6109d04619e800c483fdac363d593cd1cd69f34131d2521417334e11d41c8bfa"; const string OwnerLabel = "org.meeting-assistant.native-diagnostic"; @@ -57,7 +59,7 @@ static class NativeDiagnostic { if (args.Length == 0 || args.Contains("--help")) { - Console.WriteLine("dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --run|--cleanup|--validate [--output artifacts/native-macos] [--source existing-dockur-clone] [--cryptex-archive verified-release.zip]"); + Console.WriteLine("dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --run|--cleanup|--validate [--output artifacts/native-macos] [--source existing-dockur-clone] [--cryptex-archive verified-release.zip] [--noavx-archive verified-upstream.zip]"); return 0; } var output = Path.GetFullPath(Option(args, "--output") ?? "artifacts/native-macos"); @@ -66,10 +68,12 @@ static class NativeDiagnostic ValidateContracts(); if (Option(args, "--source") is { } source) { - await PrepareSource(Path.GetFullPath(source), output, "validation", false, Option(args, "--cryptex-archive"), CancellationToken.None); + await PrepareSource(Path.GetFullPath(source), output, "validation", false, Option(args, "--cryptex-archive"), Option(args, "--noavx-archive"), CancellationToken.None); + ValidateNoAvxStaging(output); + ValidateNoAvxRejections(output); await ValidateResourceRetention(output); await ValidateRecoveryPatch(output); - Save(Path.Combine(output, "validation.json"), new { success = true, profile = "kvm-host-ventura-cryptex", helperSha256 = Hash(File.ReadAllBytes(Path.Combine("tools", "ci", "MacOsNativeDiagnostic.cs"))), udifChecksumBindingSha256 = Hash(File.ReadAllBytes(Path.Combine("tools", "ci", "macos-native-udif-checksums.py"))), baselineReadinessNormalized = true, readinessDiagnosticBlocksExcluded = 7, productVersionParserBlockExcluded = true, productVersionSequenceRestored = true, productVersionMaximumBytes = 1024, nativeProductVersionCommandRemoved = true, diskReadinessAttemptLimit = 1, diskCommandLimitSeconds = 120, diskSampleLimitSeconds = 60, diskSampleDurationSeconds = 3, diskSampleIntervalMilliseconds = 100, resultNegativeCases = 6, containerNegativeCases = 11, recoveryPositiveCases = 4, recoveryNegativeCases = 12, independentFixtureCrc32Readback = true, resourceSnapshotRetention = true, cryptexArchiveVerified = true, configurationAndStagingContractsVerified = true, templateIsoDownloaded = false, activeLiluRuntimeChecked = false, sourceModified = false, dockerExecuted = false, guestExecuted = false, completedUtc = DateTimeOffset.UtcNow }); + Save(Path.Combine(output, "validation.json"), new { success = true, profile = "kvm-host-ventura-cryptex-noavx", helperSha256 = Hash(File.ReadAllBytes(Path.Combine("tools", "ci", "MacOsNativeDiagnostic.cs"))), udifChecksumBindingSha256 = Hash(File.ReadAllBytes(Path.Combine("tools", "ci", "macos-native-udif-checksums.py"))), baselineReadinessNormalized = true, readinessDiagnosticBlocksExcluded = 7, productVersionParserBlockExcluded = true, productVersionSequenceRestored = true, productVersionMaximumBytes = 1024, nativeProductVersionCommandRemoved = true, diskReadinessAttemptLimit = 1, diskCommandLimitSeconds = 120, diskSampleLimitSeconds = 60, diskSampleDurationSeconds = 3, diskSampleIntervalMilliseconds = 100, resultNegativeCases = 6, containerNegativeCases = 11, recoveryPositiveCases = 4, recoveryNegativeCases = 12, independentFixtureCrc32Readback = true, resourceSnapshotRetention = true, cryptexArchiveVerified = true, noAvxArchiveVerified = true, configurationAndStagingContractsVerified = true, templateIsoDownloaded = false, activeLiluRuntimeChecked = false, sourceModified = false, dockerExecuted = false, guestExecuted = false, completedUtc = DateTimeOffset.UtcNow }); } Console.WriteLine("Source patch contracts and diagnostic result validation passed; no Docker or guest execution occurred."); return 0; @@ -98,7 +102,7 @@ static class NativeDiagnostic throw new InvalidOperationException("This diagnostic runs on the existing Linux/x64 runner only."); ValidateContracts(); var sourceCommit = (await Command("git", ["rev-parse", "HEAD"], output, "candidate-commit", deadline.Token)).Output.Trim(); - Save(Path.Combine(output, "run-metadata.json"), new { token, startedUtc = DateTimeOffset.UtcNow, sourceCommit, dockurCommit = DockurCommit, profile = "kvm-host-ventura-cryptex", causalSingleVariableTest = false, kvm = true, cpuModel = "host", recoveryMajor = 13, cryptexVersion = "1.0.5", liluVersion = "1.7.1", runId = Environment.GetEnvironmentVariable("GITHUB_RUN_ID"), server = Environment.GetEnvironmentVariable("GITHUB_SERVER_URL"), architecture = RuntimeInformation.ProcessArchitecture.ToString(), deadlineMinutes = 40 }); + Save(Path.Combine(output, "run-metadata.json"), new { token, startedUtc = DateTimeOffset.UtcNow, sourceCommit, dockurCommit = DockurCommit, profile = "kvm-host-ventura-cryptex-noavx", causalSingleVariableTest = true, comparisonBaselineCommit = "720a43158c17253b65eaadc6fcce6d27f52e373e", kvm = true, cpuModel = "host", recoveryMajor = 13, cryptexVersion = "1.0.5", liluVersion = "1.7.1", noAvxBaseVersion = "12.6", noAvxSha256 = NoAvxHash, runId = Environment.GetEnvironmentVariable("GITHUB_RUN_ID"), server = Environment.GetEnvironmentVariable("GITHUB_SERVER_URL"), architecture = RuntimeInformation.ProcessArchitecture.ToString(), deadlineMinutes = 40 }); var info = await Command("docker", ["info", "--format", "{{json .}}"], output, "docker-info", deadline.Token); using (var document = JsonDocument.Parse(info.Output)) { @@ -117,7 +121,7 @@ static class NativeDiagnostic await Command("git", ["-C", source, "checkout", "--detach", DockurCommit], output, "dockur-checkout", deadline.Token); var actualCommit = (await Command("git", ["-C", source, "rev-parse", "HEAD"], output, "dockur-commit", deadline.Token)).Output.Trim(); if (actualCommit != DockurCommit) throw new InvalidOperationException("Dockur source pin mismatch."); - await PrepareSource(source, output, token, true, Option(args, "--cryptex-archive"), deadline.Token); + await PrepareSource(source, output, token, true, Option(args, "--cryptex-archive"), Option(args, "--noavx-archive"), deadline.Token); await Command("docker", ["build", "--platform", "linux/amd64", "--label", OwnerLabel + "=" + token, "--tag", state.ImageTag, source], output, "docker-build", deadline.Token, echo: true); var imageInspect = await Command("docker", ["image", "inspect", state.ImageTag], output, "image-inspect", deadline.Token); using (var image = JsonDocument.Parse(imageInspect.Output)) @@ -257,7 +261,7 @@ static class NativeDiagnostic return source.Remove(from, to + end.Length - from).Insert(from, originalSequence); } - static async Task PrepareSource(string source, string output, string token, bool writeSource, string? cryptexArchive, CancellationToken cancellation) + static async Task PrepareSource(string source, string output, string token, bool writeSource, string? cryptexArchive, string? noAvxArchive, CancellationToken cancellation) { Directory.CreateDirectory(output); var patchPath = Path.Combine(source, "src/install/recovery/patch.py"); @@ -275,7 +279,7 @@ static class NativeDiagnostic var dockerfile = ReplaceOnce(File.ReadAllText(dockerPath), "FROM scratch AS base\nCOPY --from=qemux/qemu:7.50 --exclude=usr/bin/qemu-system-x86_64 / /\n", "FROM qemux/qemu:7.50@sha256:e7f6fda52503a546fd649670ba46e4bc23dc6dcef275bc3fac48877fbbc430df AS base\n"); dockerfile = ReplaceAllExact(dockerfile, "--from=qemux/qemu-macos:latest ", "--from=qemux/qemu-macos:latest@sha256:af64297171228f27d5f616249e18f6ad5e2fbc79c1cc517252521e8bcd8eadaa ", 2); dockerfile = ReplaceOnce(dockerfile, "ADD $REPO_KVM_OPENCORE/releases/download/v$VERSION_KVM_OPENCORE/LongQT-OpenCore-v$VERSION_KVM_OPENCORE.iso /opencore.iso", "ADD --checksum=sha256:" + OpenCoreTemplateHash + " $REPO_KVM_OPENCORE/releases/download/v$VERSION_KVM_OPENCORE/LongQT-OpenCore-v$VERSION_KVM_OPENCORE.iso /opencore.iso"); - var compatibility = await PrepareCompatibility(source, output, cryptexArchive, cancellation); + var compatibility = await PrepareCompatibility(source, output, cryptexArchive, noAvxArchive, cancellation); var entryPath = Path.Combine(source, "src/entry.sh"); var entry = ReplaceOnce(File.ReadAllText(entryPath), "set -Eeuo pipefail\n", "set -Eeuo pipefail\n\n# Diagnostic budget: inspect existing Docker storage before Recovery download/boot.\ndf -Pk /storage\nfree_kib=$(df -Pk /storage | awk 'NR==2 {print $4}')\n[[ \"$free_kib\" =~ ^[0-9]+$ ]] && (( free_kib >= 8 * 1024 * 1024 )) || { echo 'Existing Docker storage has less than the 8-GiB diagnostic budget.' >&2; exit 1; }\n"); entry = ReplaceOnce(entry, ". init.sh # Initialize system\n", ". init.sh # Initialize system\n# Fail before Apple downloads if the existing daemon cannot retain this profile.\nenabled \"$KVM\" && [[ \"$CPU_MODEL\" == host && \"$VERSION\" == 13 ]] && grep -Eq '^vendor_id[[:space:]]*:[[:space:]]*GenuineIntel$' /proc/cpuinfo || { error 'Compatibility probe requires existing Intel KVM and the exact host/13 profile.'; exit 1; }\n"); @@ -360,7 +364,7 @@ static class NativeDiagnostic return string.Join("\n", lines.Select(line => new string(' ', spaces) + (line.Length > 0 ? line[common..] : ""))); } - static async Task<(string Boot, string Config, string Assets)> PrepareCompatibility(string source, string output, string? archivePath, CancellationToken cancellation) + static async Task<(string Boot, string Config, string Assets)> PrepareCompatibility(string source, string output, string? archivePath, string? noAvxArchivePath, CancellationToken cancellation) { var boot = File.ReadAllText(Path.Combine(source, "src", "boot.sh")); var config = File.ReadAllText(Path.Combine(source, "assets", "config.plist")); @@ -391,24 +395,62 @@ static class NativeDiagnostic var info = XDocument.Load(Path.Combine(assets, required[0])).Root!.Element("dict")!; if (PlistValue(info, "CFBundleIdentifier").Value != "com.khronokernel.CryptexFixup" || PlistValue(info, "CFBundleVersion").Value != "1.0.5" || PlistValue(info, "CFBundleExecutable").Value != "CryptexFixup" || PlistValue(PlistValue(info, "OSBundleLibraries"), "as.vit9696.Lilu").Value != "1.4.7") throw new InvalidOperationException("Cryptex bundle identity/version/Lilu dependency mismatch."); var fileHashes = required.ToDictionary(name => name, name => Hash(File.ReadAllBytes(Path.Combine(assets, name)))); + byte[] noAvxBytes; + if (noAvxArchivePath is not null) noAvxBytes = await File.ReadAllBytesAsync(noAvxArchivePath, cancellation); + else + { + using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30), MaxResponseContentBufferSize = 2 * 1024 * 1024 }; + noAvxBytes = await client.GetByteArrayAsync(NoAvxUrl, cancellation); + } + var noAvxFiles = ReadNoAvxArchive(noAvxBytes); + File.WriteAllBytes(Path.Combine(output, "NoAVXFSCompressionTypeZlib-AVXpel-v12.6.zip"), noAvxBytes); + foreach (var (name, content) in noAvxFiles) + { + var destination = Path.Combine(assets, name); + Directory.CreateDirectory(Path.GetDirectoryName(destination)!); + File.WriteAllBytes(destination, content); + fileHashes.Add(name, Hash(content)); + } File.WriteAllText(Path.Combine(assets, "SHA256SUMS"), string.Concat(fileHashes.Select(pair => pair.Value + " " + pair.Key + "\n")), new UTF8Encoding(false)); - Save(Path.Combine(output, "compatibility-boot-assets.json"), new { cryptexUrl = CryptexUrl, cryptexSha256 = CryptexHash, cryptexBytes = bytes.Length, cryptexFiles = fileHashes, templateUrl = "https://github.com/LongQT-sea/OpenCore-ISO/releases/download/v0.7/LongQT-OpenCore-v0.7.iso", templateSha256 = OpenCoreTemplateHash, templateBytes = 15884288, liluVersion = "1.7.1", liluBinarySha256 = "0c016d93cfe40c7fa3965813175c1b991a76f3d295efd5be66ae712b4a3ffb52", liluBinaryBytes = 526984, liluInfoSha256 = "fc885f3319f326e3af60e7965a5216b671772d39d40993ec695758bb43d6ea3a", causalSingleVariableTest = false }); + Save(Path.Combine(output, "compatibility-boot-assets.json"), new { cryptexUrl = CryptexUrl, cryptexSha256 = CryptexHash, cryptexBytes = bytes.Length, compatibilityFiles = fileHashes, noAvxUrl = NoAvxUrl, noAvxSha256 = NoAvxHash, noAvxBytes = noAvxBytes.Length, noAvxBaseVersion = "12.6", noAvxMinimumDarwin = "22.0.0", noAvxRequired = "Root", templateUrl = "https://github.com/LongQT-sea/OpenCore-ISO/releases/download/v0.7/LongQT-OpenCore-v0.7.iso", templateSha256 = OpenCoreTemplateHash, templateBytes = 15884288, liluVersion = "1.7.1", liluBinarySha256 = "0c016d93cfe40c7fa3965813175c1b991a76f3d295efd5be66ae712b4a3ffb52", liluBinaryBytes = 526984, liluInfoSha256 = "fc885f3319f326e3af60e7965a5216b671772d39d40993ec695758bb43d6ea3a", causalSingleVariableTest = true, comparisonBaselineCommit = "720a431", changedBootAsset = "NoAVXFSCompressionTypeZlib-AVXpel.kext" }); var document = XDocument.Parse(config, LoadOptions.PreserveWhitespace); var add = PlistValue(PlistValue(document.Root!.Element("dict")!, "Kernel"), "Add"); var expected = new[] { "Lilu.kext", "VMHide.kext", "VirtualSMC.kext", "WhateverGreen.kext", "VoodooPS2Controller.kext", "VoodooPS2Controller.kext/Contents/PlugIns/VoodooPS2Keyboard.kext", "AppleMCEReporterDisabler.kext" }; if (!add.Elements("dict").Select(dict => PlistValue(dict, "BundlePath").Value).SequenceEqual(expected) || add.Elements("dict").Any(dict => PlistValue(dict, "Enabled").Name != "true")) throw new InvalidOperationException("Pinned Kernel.Add order/enabled contract mismatch."); var cryptex = XElement.Parse("Archx86_64BundlePathCryptexFixup.kextCommentOfficial CryptexFixup 1.0.5; owned compatibility guest onlyEnabledExecutablePathContents/MacOS/CryptexFixupMaxKernelMinKernel22.0.0PlistPathContents/Info.plist"); add.Elements("dict").First().AddAfterSelf(cryptex); + cryptex.AddAfterSelf(XElement.Parse("Archx86_64BundlePathNoAVXFSCompressionTypeZlib-AVXpel.kextCommentOCLP 2.5.1 AVXpel 12.6; Ventura filesystem compression hypothesisEnabledExecutablePathContents/MacOS/NoAVXFSCompressionTypeZlibMaxKernelMinKernel22.0.0PlistPathContents/Info.plist")); var bootArguments = PlistValue(PlistValue(PlistValue(PlistValue(document.Root.Element("dict")!, "NVRAM"), "Add"), "7C436110-AB2A-4BBB-A880-FE41995C9F82"), "boot-args").Value.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (bootArguments.Intersect(new[] { "-cryptoff", "-liluoff", "-crypt_allow_hash_validation", "-crypt_force_avx", "-cryptbeta", "-lilubetaall" }).Any()) throw new InvalidOperationException("Unexpected Cryptex/Lilu disabling or forcing boot argument."); boot = ReplaceOnce(boot, " cp -a \"$template/OC/Resources\" \"$EFI_DIR/OC/\"\n", " cp -a \"$template/OC/Resources\" \"$EFI_DIR/OC/\"\n" + CompatibilityStaging + "\n"); boot = ReplaceOnce(boot, " PLIST=\"/assets/config.plist\"\n", " [ ! -e /custom.plist ] || { error 'Compatibility profile refuses an unverified custom OpenCore config!'; exit 12; }\n PLIST=\"/assets/config.plist\"\n"); boot = ReplaceOnce(boot, " checkOpenCoreConfig\n addVmHideKext\n", " checkOpenCoreConfig\n" + CompatibilityConfigCheck + "\n addVmHideKext\n"); boot = ReplaceOnce(boot, " if [ -s \"$target\" ] && [ \"$previous\" = \"$current\" ]; then\n IMG=\"$target\"\n return 0\n fi\n", " # This owned compatibility probe always rebuilds; never trust a cached boot.img.\n"); - boot = ReplaceOnce(boot, " echo \"VMHIDE=$vmhide\"\n", " echo \"VMHIDE=$vmhide\"\n echo \"COMPATIBILITY=kvm-host-ventura-cryptex\"\n sha256sum /assets/native-compatibility/SHA256SUMS\n"); + boot = ReplaceOnce(boot, " echo \"VMHIDE=$vmhide\"\n", " echo \"VMHIDE=$vmhide\"\n echo \"COMPATIBILITY=kvm-host-ventura-cryptex-noavx\"\n sha256sum /assets/native-compatibility/SHA256SUMS\n"); return (boot, document.ToString(), assets); } + static Dictionary ReadNoAvxArchive(byte[] bytes) + { + if (bytes.Length != 98356 || Hash(bytes) != NoAvxHash) throw new InvalidOperationException("Pinned OCLP NoAVX archive size/hash mismatch."); + using var archive = new ZipArchive(new MemoryStream(bytes), ZipArchiveMode.Read); + var required = new[] { "NoAVXFSCompressionTypeZlib-AVXpel.kext/Contents/Info.plist", "NoAVXFSCompressionTypeZlib-AVXpel.kext/Contents/MacOS/NoAVXFSCompressionTypeZlib" }; + var entries = archive.Entries.Where(entry => entry.FullName.StartsWith("NoAVXFSCompressionTypeZlib-AVXpel.kext/", StringComparison.Ordinal) && !entry.FullName.EndsWith('/')).ToArray(); + if (entries.Length != 2 || required.Any(name => entries.Count(entry => entry.FullName == name) != 1) || entries.Any(entry => entry.Length <= 0 || entry.Length > 1024 * 1024)) throw new InvalidOperationException("NoAVX archive layout/size mismatch."); + var files = new Dictionary(); + foreach (var entry in entries) + { + using var input = entry.Open(); + using var content = new MemoryStream(); + input.CopyTo(content); + if (content.Length != entry.Length) throw new InvalidOperationException("NoAVX archive entry length mismatch."); + files.Add(entry.FullName, content.ToArray()); + } + var info = XDocument.Parse(Encoding.UTF8.GetString(files[required[0]])).Root!.Element("dict")!; + if (PlistValue(info, "CFBundleIdentifier").Value != "com.apple.AppleFSCompression.NoAVXFSCompressionTypeZlib" || PlistValue(info, "CFBundleExecutable").Value != "NoAVXFSCompressionTypeZlib" || PlistValue(info, "CFBundleVersion").Value != "1.0.0" || PlistValue(info, "CFBundleShortVersionString").Value != "132.100.2" || PlistValue(info, "OSBundleRequired").Value != "Root") throw new InvalidOperationException("NoAVX bundle identity/version/root requirement mismatch."); + return files; + } + static XElement PlistValue(XElement dictionary, string key) { var keys = dictionary.Elements("key").Where(element => element.Value == key).ToArray(); @@ -416,6 +458,59 @@ static class NativeDiagnostic return value; } + static void ValidateNoAvxStaging(string output) + { + var root = Path.Combine(output, "compatibility-assets", "NoAVXFSCompressionTypeZlib-AVXpel.kext", "Contents"); + if (!File.Exists(Path.Combine(root, "Info.plist")) || !File.Exists(Path.Combine(root, "MacOS", "NoAVXFSCompressionTypeZlib"))) + throw new InvalidOperationException("Offline validation requires the staged NoAVX bundle, with its upstream executable path."); + var files = ReadNoAvxArchive(File.ReadAllBytes(Path.Combine(output, "NoAVXFSCompressionTypeZlib-AVXpel-v12.6.zip"))); + foreach (var (name, content) in files) + if (!File.ReadAllBytes(Path.Combine(output, "compatibility-assets", name)).SequenceEqual(content)) throw new InvalidOperationException("Staged NoAVX bytes differ from the pinned archive."); + var document = XDocument.Load(Path.Combine(output, "opencore-config.plist")); + ValidateNoAvxConfig(document); + } + + static void ValidateNoAvxConfig(XDocument document) + { + var add = PlistValue(PlistValue(document.Root!.Element("dict")!, "Kernel"), "Add"); + var entries = add.Elements("dict").ToArray(); + var expected = new[] { "Lilu.kext", "CryptexFixup.kext", "NoAVXFSCompressionTypeZlib-AVXpel.kext", "VMHide.kext", "VirtualSMC.kext", "WhateverGreen.kext", "VoodooPS2Controller.kext", "VoodooPS2Controller.kext/Contents/PlugIns/VoodooPS2Keyboard.kext", "AppleMCEReporterDisabler.kext" }; + if (!entries.Select(dict => PlistValue(dict, "BundlePath").Value).SequenceEqual(expected) || entries.Any(dict => PlistValue(dict, "Enabled").Name != "true")) + throw new InvalidOperationException("Actual OpenCore Kernel.Add order or enabled contract mismatch."); + var noAvx = entries[2]; + if (PlistValue(noAvx, "Arch").Value != "x86_64" || PlistValue(noAvx, "ExecutablePath").Value != "Contents/MacOS/NoAVXFSCompressionTypeZlib" || PlistValue(noAvx, "PlistPath").Value != "Contents/Info.plist" || PlistValue(noAvx, "MinKernel").Value != "22.0.0" || PlistValue(noAvx, "MaxKernel").Value != "") + throw new InvalidOperationException("Actual NoAVX Kernel.Add paths, architecture or Darwin bounds mismatch."); + } + + static void ValidateNoAvxRejections(string output) + { + static void Reject(Action validation, string name) + { + try { validation(); } catch (InvalidOperationException) { return; } + throw new InvalidOperationException("NoAVX validator accepted invalid " + name); + } + var bytes = File.ReadAllBytes(Path.Combine(output, "NoAVXFSCompressionTypeZlib-AVXpel-v12.6.zip")); + var corrupt = (byte[])bytes.Clone(); + corrupt[corrupt.Length / 2] ^= 1; + foreach (var invalid in new[] { Array.Empty(), bytes[..^1], bytes.Concat(new byte[] { 0 }).ToArray(), corrupt }) Reject(() => ReadNoAvxArchive(invalid), "archive size/hash"); + var staged = Path.Combine(output, "compatibility-assets", "NoAVXFSCompressionTypeZlib-AVXpel.kext", "Contents", "MacOS", "NoAVXFSCompressionTypeZlib"); + var original = File.ReadAllBytes(staged); + try + { + File.Delete(staged); + Reject(() => ValidateNoAvxStaging(output), "missing staging executable"); + var changed = (byte[])original.Clone(); + changed[0] ^= 1; + File.WriteAllBytes(staged, changed); + Reject(() => ValidateNoAvxStaging(output), "changed staging executable"); + } + finally { File.WriteAllBytes(staged, original); } + var config = File.ReadAllText(Path.Combine(output, "opencore-config.plist")); + foreach (var invalid in new[] { config.Replace("NoAVXFSCompressionTypeZlib-AVXpel.kext", "Wrong.kext", StringComparison.Ordinal), config.Replace("Contents/MacOS/NoAVXFSCompressionTypeZlib", "Contents/MacOS/NoAVXFSCompressionTypeZlib-AVXpel", StringComparison.Ordinal), config.Replace("22.0.0", "21.0.0", StringComparison.Ordinal), config.Replace("x86_64", "arm64", StringComparison.Ordinal) }) Reject(() => ValidateNoAvxConfig(XDocument.Parse(invalid)), "Kernel.Add"); + ValidateNoAvxStaging(output); + Save(Path.Combine(output, "noavx-validation.json"), new { success = true, archiveNegativeCases = 4, stagingNegativeCases = 2, kernelAddNegativeCases = 5, archiveSha256 = NoAvxHash, actualStagedBytesVerified = true, actualGeneratedKernelAddVerified = true, dockerExecuted = false, guestExecuted = false }); + } + const string CompatibilityStaging = """ # Only the freshly extracted, owned guest EFI is changed; never the host. local lilu="$EFI_DIR/OC/Kexts/Lilu.kext/Contents" @@ -424,23 +519,29 @@ static class NativeDiagnostic 0c016d93cfe40c7fa3965813175c1b991a76f3d295efd5be66ae712b4a3ffb52 "$lilu/MacOS/Lilu" | sha256sum -c - || { error "Pinned active Lilu files mismatch!"; exit 12; } [ "$(xmlstarlet sel -T -t -v '/plist/dict/key[.="CFBundleVersion"]/following-sibling::string[1]' "$lilu/Info.plist")" = 1.7.1 ] || { error "Active Lilu version mismatch!"; exit 12; } [ ! -e "$EFI_DIR/OC/Kexts/CryptexFixup.kext" ] || { error "Unexpected pre-existing Cryptex kext!"; exit 12; } + [ ! -e "$EFI_DIR/OC/Kexts/NoAVXFSCompressionTypeZlib-AVXpel.kext" ] || { error "Unexpected pre-existing NoAVX kext!"; exit 12; } (cd /assets/native-compatibility && sha256sum -c SHA256SUMS) || { error "Pinned Cryptex staging files mismatch!"; exit 12; } cp -a /assets/native-compatibility/CryptexFixup.kext "$EFI_DIR/OC/Kexts/" + cp -a /assets/native-compatibility/NoAVXFSCompressionTypeZlib-AVXpel.kext "$EFI_DIR/OC/Kexts/" (cd "$EFI_DIR/OC/Kexts" && sha256sum -c /assets/native-compatibility/SHA256SUMS) || { error "Active Cryptex copy mismatch!"; exit 12; } - info "[compatibility-boot] Lilu=1.7.1 CryptexFixup=1.0.5 files=verified; guest injection and Recovery readiness remain unproved" + info "[compatibility-boot] Lilu=1.7.1 CryptexFixup=1.0.5 NoAVX=AVXpel-12.6 files=verified; guest injection and Recovery readiness remain unproved" """; const string CompatibilityConfigCheck = """ local kernel='/plist/dict/key[.="Kernel"]/following-sibling::dict[1]/key[.="Add"]/following-sibling::array[1]' local actual expected actual=$(xmlstarlet sel -T -t -m "$kernel/dict" -v 'key[.="BundlePath"]/following-sibling::string[1]' -n "$CFG") || exit 12 - expected=$(printf '%s\n' Lilu.kext CryptexFixup.kext VMHide.kext VirtualSMC.kext WhateverGreen.kext VoodooPS2Controller.kext VoodooPS2Controller.kext/Contents/PlugIns/VoodooPS2Keyboard.kext AppleMCEReporterDisabler.kext) + expected=$(printf '%s\n' Lilu.kext CryptexFixup.kext NoAVXFSCompressionTypeZlib-AVXpel.kext VMHide.kext VirtualSMC.kext WhateverGreen.kext VoodooPS2Controller.kext VoodooPS2Controller.kext/Contents/PlugIns/VoodooPS2Keyboard.kext AppleMCEReporterDisabler.kext) [ "$actual" = "$expected" ] || { error "Active Kernel.Add order mismatch!"; exit 12; } [ "$(xmlstarlet sel -T -t -v "name($kernel/dict[1]/key[.='Enabled']/following-sibling::*[1])" -v "name($kernel/dict[2]/key[.='Enabled']/following-sibling::*[1])" "$CFG")" = truetrue ] || { error "Active Lilu/Cryptex must both be enabled!"; exit 12; } actual=$(xmlstarlet sel -T -t -m "$kernel/dict[2]" -v 'key[.="Arch"]/following-sibling::string[1]' -n -v 'key[.="ExecutablePath"]/following-sibling::string[1]' -n -v 'key[.="PlistPath"]/following-sibling::string[1]' -n -v 'key[.="MinKernel"]/following-sibling::string[1]' -n -v 'key[.="MaxKernel"]/following-sibling::string[1]' "$CFG") || exit 12 expected=$(printf '%s\n' x86_64 Contents/MacOS/CryptexFixup Contents/Info.plist 22.0.0 '') [ "$actual" = "$expected" ] || { error "Active Cryptex Kernel.Add paths/architecture/Darwin bounds mismatch!"; exit 12; } - info "[compatibility-config] Kernel.Add=Lilu,CryptexFixup before remaining baseline kexts; MinKernel=22.0.0 MaxKernel=empty" + [ "$(xmlstarlet sel -T -t -v "name($kernel/dict[3]/key[.='Enabled']/following-sibling::*[1])" "$CFG")" = true ] || { error "Active NoAVX must be enabled!"; exit 12; } + actual=$(xmlstarlet sel -T -t -m "$kernel/dict[3]" -v 'key[.="Arch"]/following-sibling::string[1]' -n -v 'key[.="ExecutablePath"]/following-sibling::string[1]' -n -v 'key[.="PlistPath"]/following-sibling::string[1]' -n -v 'key[.="MinKernel"]/following-sibling::string[1]' -n -v 'key[.="MaxKernel"]/following-sibling::string[1]' "$CFG") || exit 12 + expected=$(printf '%s\n' x86_64 Contents/MacOS/NoAVXFSCompressionTypeZlib Contents/Info.plist 22.0.0 '') + [ "$actual" = "$expected" ] || { error "Active NoAVX Kernel.Add paths/architecture/Darwin bounds mismatch!"; exit 12; } + info "[compatibility-config] Kernel.Add=Lilu,CryptexFixup,NoAVX before remaining baseline kexts; MinKernel=22.0.0 MaxKernel=empty" """; static string ReplaceOnce(string text, string oldValue, string newValue) => ReplaceAllExact(text, oldValue, newValue, 1);