Compare commits

...
3 changed files with 183 additions and 20 deletions
@@ -7,6 +7,7 @@ on:
# Its manual workflow provides that evidence; the PR branch still runs all jobs. # Its manual workflow provides that evidence; the PR branch still runs all jobs.
branches-ignore: branches-ignore:
- codex/macos-ci-kvm-compatibility - codex/macos-ci-kvm-compatibility
- codex/macos-kvm-noavx-recovery
workflow_dispatch: workflow_dispatch:
jobs: jobs:
+14 -2
View File
@@ -1,9 +1,15 @@
# 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. 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. 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.
The host-memory admission check is copied unchanged from RAW candidate `ec5508e`: the unchanged 4-GiB guest plus 512 MiB QEMU overhead requires 4.5 GiB available. Offline validation accepts the captured run-4204 value of 5,138,696 KiB and rejects 4 GiB, missing and invalid values. Guest RAM and the 6-GiB container cap are unchanged. This admission budget reserves no host memory against other workloads. The `codex/macos-kvm-noavx-recovery` branch skips only the PR/Push workflow's push trigger; pull requests and manual workflows retain their existing triggers.
The existing one-minute heartbeat prints at most the last two captured `[proof-start]`, `[proof-done]`, `[proof-native-wait]`, `[proof-result]` or `[native-version]` lines, each capped at 256 characters. `[proof-native-wait]` is this candidate's existing command-completion marker. It reads only the already retained `guest-proof.log`; no additional Docker/guest query or polling timer is added. Five offline fixture cases verify marker selection, missing/unrelated output and the bounds. The controlled 40-minute host deadline and existing cleanup are preserved.
## Reasons and remaining gaps ## 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. 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 +29,7 @@ Orchestration/validation remain the .NET 10 file-based app `tools/ci/MacOsNative
~~~sh ~~~sh
dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --help dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --help
dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --validate 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. `--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 +41,8 @@ dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --run --output artifacts/
dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --cleanup --output artifacts/native-macos 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 ## 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. 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 +59,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`. 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. 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 ## Gates, privileges and cleanup
+168 -18
View File
@@ -16,6 +16,8 @@ static class NativeDiagnostic
const string DockurCommit = "16a5b470cdd601bae8b05b02d748d7edfb36c12e"; 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 CryptexUrl = "https://github.com/acidanthera/CryptexFixup/releases/download/1.0.5/CryptexFixup-1.0.5-RELEASE.zip";
const string CryptexHash = "25041d94a0fe9a0261caf0ba89b36dfcb21682bf3c697a34bcaddc839576ab30"; 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 OpenCoreTemplateHash = "287328995d4198f1b05166f087d85bf7ef66bedafe150d17ad112ac8de60051d";
const string UdifChecksumBindingHash = "6109d04619e800c483fdac363d593cd1cd69f34131d2521417334e11d41c8bfa"; const string UdifChecksumBindingHash = "6109d04619e800c483fdac363d593cd1cd69f34131d2521417334e11d41c8bfa";
const string OwnerLabel = "org.meeting-assistant.native-diagnostic"; const string OwnerLabel = "org.meeting-assistant.native-diagnostic";
@@ -57,19 +59,23 @@ static class NativeDiagnostic
{ {
if (args.Length == 0 || args.Contains("--help")) 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; return 0;
} }
var output = Path.GetFullPath(Option(args, "--output") ?? "artifacts/native-macos"); var output = Path.GetFullPath(Option(args, "--output") ?? "artifacts/native-macos");
if (args.Contains("--validate")) if (args.Contains("--validate"))
{ {
ValidateRunnerMemoryGate();
ValidateGuestProgress(output);
ValidateContracts(); ValidateContracts();
if (Option(args, "--source") is { } source) 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 ValidateResourceRetention(output);
await ValidateRecoveryPatch(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."); Console.WriteLine("Source patch contracts and diagnostic result validation passed; no Docker or guest execution occurred.");
return 0; return 0;
@@ -98,7 +104,7 @@ static class NativeDiagnostic
throw new InvalidOperationException("This diagnostic runs on the existing Linux/x64 runner only."); throw new InvalidOperationException("This diagnostic runs on the existing Linux/x64 runner only.");
ValidateContracts(); ValidateContracts();
var sourceCommit = (await Command("git", ["rev-parse", "HEAD"], output, "candidate-commit", deadline.Token)).Output.Trim(); 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); var info = await Command("docker", ["info", "--format", "{{json .}}"], output, "docker-info", deadline.Token);
using (var document = JsonDocument.Parse(info.Output)) using (var document = JsonDocument.Parse(info.Output))
{ {
@@ -109,15 +115,14 @@ static class NativeDiagnostic
throw new InvalidOperationException("Existing Docker resources cannot fit this bounded 2-CPU/6-GiB diagnostic; no infrastructure change was requested."); throw new InvalidOperationException("Existing Docker resources cannot fit this bounded 2-CPU/6-GiB diagnostic; no infrastructure change was requested.");
} }
await Command("sh", ["-c", "cat /proc/meminfo; printf '\n[cgroup]\n'; cat /sys/fs/cgroup/memory.max /sys/fs/cgroup/cpu.max 2>/dev/null || true; printf '\n[workspace disk]\n'; df -Pk ."], output, "runner-resources", deadline.Token); await Command("sh", ["-c", "cat /proc/meminfo; printf '\n[cgroup]\n'; cat /sys/fs/cgroup/memory.max /sys/fs/cgroup/cpu.max 2>/dev/null || true; printf '\n[workspace disk]\n'; df -Pk ."], output, "runner-resources", deadline.Token);
var available = System.Text.RegularExpressions.Regex.Match(File.ReadAllText("/proc/meminfo"), @"(?m)^MemAvailable:\s+(\d+) kB$"); if (!HasAvailableGuestMemory(File.ReadAllText("/proc/meminfo")))
if (!available.Success || long.Parse(available.Groups[1].Value) < 5L * 1024 * 1024) throw new InvalidOperationException("Existing runner memory cannot fit the 4-GiB guest plus its 512-MiB QEMU overhead budget; no infrastructure change was requested.");
throw new InvalidOperationException("Existing runner memory has less than the 5-GiB available diagnostic budget; no infrastructure change was requested.");
var source = Path.Combine(work, "dockur"); var source = Path.Combine(work, "dockur");
await Command("git", ["clone", "--no-checkout", "https://github.com/dockur/macos.git", source], output, "dockur-clone", deadline.Token); await Command("git", ["clone", "--no-checkout", "https://github.com/dockur/macos.git", source], output, "dockur-clone", deadline.Token);
await Command("git", ["-C", source, "checkout", "--detach", DockurCommit], output, "dockur-checkout", deadline.Token); 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(); 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."); 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); 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); var imageInspect = await Command("docker", ["image", "inspect", state.ImageTag], output, "image-inspect", deadline.Token);
using (var image = JsonDocument.Parse(imageInspect.Output)) using (var image = JsonDocument.Parse(imageInspect.Output))
@@ -132,7 +137,7 @@ static class NativeDiagnostic
AssertContainer(File.ReadAllText(Path.Combine(output, "container-created.stdout.log")), token); AssertContainer(File.ReadAllText(Path.Combine(output, "container-created.stdout.log")), token);
await Command("docker", ["start", id], output, "docker-start", deadline.Token); await Command("docker", ["start", id], output, "docker-start", deadline.Token);
await CapturePressure(id, output, "before", deadline.Token); await CapturePressure(id, output, "before", deadline.Token);
Console.WriteLine("The owned restricted KVM/host-CPU macOS 13 compatibility guest is starting. Success requires native macOS 13+/x86_64 and a writable 64-GiB disk; no installer will run. This is not a single-variable causal test."); Console.WriteLine("The owned restricted KVM/host-CPU macOS 13 compatibility guest is starting. Success requires native macOS 13+/x86_64 and a writable 64-GiB disk; no installer will run. The additional NoAVX boot kext is the only guest variable against KVM baseline 720a431.");
var recoveryStarted = Stopwatch.StartNew(); var recoveryStarted = Stopwatch.StartNew();
var heartbeat = Stopwatch.StartNew(); var heartbeat = Stopwatch.StartNew();
var diskPressureCaptured = false; var diskPressureCaptured = false;
@@ -159,7 +164,8 @@ static class NativeDiagnostic
if (running.Output.Trim() != "true") throw new InvalidOperationException("Guest container exited before a native readiness result."); if (running.Output.Trim() != "true") throw new InvalidOperationException("Guest container exited before a native readiness result.");
if (heartbeat.Elapsed >= TimeSpan.FromSeconds(60)) if (heartbeat.Elapsed >= TimeSpan.FromSeconds(60))
{ {
Console.WriteLine($"[native-diagnostic] phase=recovery; elapsed={recoveryStarted.Elapsed.TotalMinutes:F1} minutes; container=running; readiness=pending"); Console.WriteLine($"[native-diagnostic] phase=recovery; elapsed={recoveryStarted.Elapsed.TotalMinutes:F1}/40 minutes; container=running; readiness=pending");
foreach (var progress in LastGuestProgress(proofPath)) Console.WriteLine("[native-diagnostic] guest-progress=" + progress);
heartbeat.Restart(); heartbeat.Restart();
} }
await Task.Delay(TimeSpan.FromSeconds(20), deadline.Token); await Task.Delay(TimeSpan.FromSeconds(20), deadline.Token);
@@ -257,7 +263,7 @@ static class NativeDiagnostic
return source.Remove(from, to + end.Length - from).Insert(from, originalSequence); 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); Directory.CreateDirectory(output);
var patchPath = Path.Combine(source, "src/install/recovery/patch.py"); var patchPath = Path.Combine(source, "src/install/recovery/patch.py");
@@ -275,7 +281,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"); 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 = 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"); 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 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"); 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"); 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 +366,7 @@ static class NativeDiagnostic
return string.Join("\n", lines.Select(line => new string(' ', spaces) + (line.Length > 0 ? line[common..] : ""))); 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 boot = File.ReadAllText(Path.Combine(source, "src", "boot.sh"));
var config = File.ReadAllText(Path.Combine(source, "assets", "config.plist")); var config = File.ReadAllText(Path.Combine(source, "assets", "config.plist"));
@@ -391,24 +397,109 @@ static class NativeDiagnostic
var info = XDocument.Load(Path.Combine(assets, required[0])).Root!.Element("dict")!; 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."); 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)))); 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)); 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 document = XDocument.Parse(config, LoadOptions.PreserveWhitespace);
var add = PlistValue(PlistValue(document.Root!.Element("dict")!, "Kernel"), "Add"); 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" }; 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."); 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("<dict><key>Arch</key><string>x86_64</string><key>BundlePath</key><string>CryptexFixup.kext</string><key>Comment</key><string>Official CryptexFixup 1.0.5; owned compatibility guest only</string><key>Enabled</key><true/><key>ExecutablePath</key><string>Contents/MacOS/CryptexFixup</string><key>MaxKernel</key><string></string><key>MinKernel</key><string>22.0.0</string><key>PlistPath</key><string>Contents/Info.plist</string></dict>"); var cryptex = XElement.Parse("<dict><key>Arch</key><string>x86_64</string><key>BundlePath</key><string>CryptexFixup.kext</string><key>Comment</key><string>Official CryptexFixup 1.0.5; owned compatibility guest only</string><key>Enabled</key><true/><key>ExecutablePath</key><string>Contents/MacOS/CryptexFixup</string><key>MaxKernel</key><string></string><key>MinKernel</key><string>22.0.0</string><key>PlistPath</key><string>Contents/Info.plist</string></dict>");
add.Elements("dict").First().AddAfterSelf(cryptex); add.Elements("dict").First().AddAfterSelf(cryptex);
cryptex.AddAfterSelf(XElement.Parse("<dict><key>Arch</key><string>x86_64</string><key>BundlePath</key><string>NoAVXFSCompressionTypeZlib-AVXpel.kext</string><key>Comment</key><string>OCLP 2.5.1 AVXpel 12.6; Ventura filesystem compression hypothesis</string><key>Enabled</key><true/><key>ExecutablePath</key><string>Contents/MacOS/NoAVXFSCompressionTypeZlib</string><key>MaxKernel</key><string></string><key>MinKernel</key><string>22.0.0</string><key>PlistPath</key><string>Contents/Info.plist</string></dict>"));
var bootArguments = PlistValue(PlistValue(PlistValue(PlistValue(document.Root.Element("dict")!, "NVRAM"), "Add"), "7C436110-AB2A-4BBB-A880-FE41995C9F82"), "boot-args").Value.Split(' ', StringSplitOptions.RemoveEmptyEntries); 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."); 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, " 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, " 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, " 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, " 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); return (boot, document.ToString(), assets);
} }
static bool HasAvailableGuestMemory(string meminfo)
{
var available = System.Text.RegularExpressions.Regex.Match(meminfo, @"(?m)^MemAvailable:\s+(\d+) kB$");
return available.Success && long.TryParse(available.Groups[1].Value, out var kib) && kib >= 4L * 1024 * 1024 + 512L * 1024;
}
static string[] LastGuestProgress(string path)
{
if (!File.Exists(path)) return [];
var markers = new[] { "[proof-start]", "[proof-done]", "[proof-native-wait]", "[proof-result]", "[native-version]" };
return File.ReadLines(path).Where(line => markers.Any(marker => line.StartsWith(marker, StringComparison.Ordinal)))
.TakeLast(2).Select(line => line[..Math.Min(line.Length, 256)]).ToArray();
}
static void ValidateGuestProgress(string output)
{
var fixture = Path.Combine(output, "validation-guest-progress");
Directory.CreateDirectory(fixture);
var path = Path.Combine(fixture, "guest-proof.log");
File.Delete(path);
if (LastGuestProgress(path).Length != 0) throw new InvalidOperationException("Missing guest progress was fabricated.");
File.WriteAllText(path, "[proof-start] platform child=12\nignored native output\n[proof-native-wait] platform exit=0\n[proof-start] uid child=13\n", new UTF8Encoding(false));
if (!LastGuestProgress(path).SequenceEqual(new[] { "[proof-native-wait] platform exit=0", "[proof-start] uid child=13" })) throw new InvalidOperationException("Heartbeat must show the last two captured native progress markers.");
File.WriteAllText(path, "[proof-done] uid\n[native-version] 13.6\n[proof-result] true: readiness\n", new UTF8Encoding(false));
if (!LastGuestProgress(path).SequenceEqual(new[] { "[native-version] 13.6", "[proof-result] true: readiness" })) throw new InvalidOperationException("Heartbeat lost native version/result progress.");
File.WriteAllText(path, "[proof-start] " + new string('x', 1024) + "\n", new UTF8Encoding(false));
if (LastGuestProgress(path).Single().Length != 256) throw new InvalidOperationException("Heartbeat progress line exceeded its output bound.");
File.WriteAllText(path, "unrelated output\n", new UTF8Encoding(false));
if (LastGuestProgress(path).Length != 0) throw new InvalidOperationException("Heartbeat selected unrelated guest output.");
Save(Path.Combine(fixture, "receipt.json"), new { success = true, fixtureCases = 5, maximumLines = 2, maximumLineCharacters = 256, existingProofOnly = true, dockerExecuted = false, guestExecuted = false });
}
static void ValidateRunnerMemoryGate()
{
// Run 4204: 4-GiB guest plus 512-MiB QEMU overhead fits its captured available memory.
var cases = new[]
{
("captured-run4204", "MemTotal: 16281732 kB\nMemAvailable: 5138696 kB\n", true),
("below-guest-plus-overhead", "MemAvailable: 4194304 kB\n", false),
("missing", "MemTotal: 16281732 kB\n", false),
("invalid", "MemAvailable: unavailable kB\n", false)
};
foreach (var (name, meminfo, expected) in cases)
if (HasAvailableGuestMemory(meminfo) != expected)
throw new InvalidOperationException("Existing runner memory admission failed: " + name);
}
static Dictionary<string, byte[]> 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<string, byte[]>();
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) static XElement PlistValue(XElement dictionary, string key)
{ {
var keys = dictionary.Elements("key").Where(element => element.Value == key).ToArray(); var keys = dictionary.Elements("key").Where(element => element.Value == key).ToArray();
@@ -416,6 +507,59 @@ static class NativeDiagnostic
return value; 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<byte>(), 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("<true", "<false", StringComparison.Ordinal), config.Replace("<string>x86_64</string>", "<string>arm64</string>", 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 = """ const string CompatibilityStaging = """
# Only the freshly extracted, owned guest EFI is changed; never the host. # Only the freshly extracted, owned guest EFI is changed; never the host.
local lilu="$EFI_DIR/OC/Kexts/Lilu.kext/Contents" local lilu="$EFI_DIR/OC/Kexts/Lilu.kext/Contents"
@@ -424,23 +568,29 @@ static class NativeDiagnostic
0c016d93cfe40c7fa3965813175c1b991a76f3d295efd5be66ae712b4a3ffb52 "$lilu/MacOS/Lilu" | sha256sum -c - || { error "Pinned active Lilu files mismatch!"; exit 12; } 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; } [ "$(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/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; } (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/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; } (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 = """ const string CompatibilityConfigCheck = """
local kernel='/plist/dict/key[.="Kernel"]/following-sibling::dict[1]/key[.="Add"]/following-sibling::array[1]' local kernel='/plist/dict/key[.="Kernel"]/following-sibling::dict[1]/key[.="Add"]/following-sibling::array[1]'
local actual expected local actual expected
actual=$(xmlstarlet sel -T -t -m "$kernel/dict" -v 'key[.="BundlePath"]/following-sibling::string[1]' -n "$CFG") || exit 12 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; } [ "$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; } [ "$(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 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 '') 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; } [ "$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); static string ReplaceOnce(string text, string oldValue, string newValue) => ReplaceAllExact(text, oldValue, newValue, 1);