forked from Manuel/meeting-assistant
prepare full KVM native CI candidate with verified NoAVX boot injection
This commit is contained in:
@@ -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";
|
||||
@@ -60,21 +62,25 @@ static class NativeDiagnostic
|
||||
{
|
||||
if (args.Length == 0 || args.Contains("--help"))
|
||||
{
|
||||
Console.WriteLine("dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --run|--cleanup|--validate [--full] [--output artifacts/native-macos] [--source existing-dockur-clone] [--cryptex-archive verified-release.zip] [--compression-chunk readonly-qualified-chunk]");
|
||||
Console.WriteLine("dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --run|--cleanup|--validate [--full] [--output artifacts/native-macos] [--source existing-dockur-clone] [--cryptex-archive verified-release.zip] [--noavx-archive verified-upstream.zip] [--compression-chunk readonly-qualified-chunk]");
|
||||
return 0;
|
||||
}
|
||||
var output = Path.GetFullPath(Option(args, "--output") ?? "artifacts/native-macos");
|
||||
var full = args.Contains("--full");
|
||||
if (args.Contains("--validate"))
|
||||
{
|
||||
ValidateRunnerMemoryGate();
|
||||
ValidateGuestProgress(output);
|
||||
ValidateContracts();
|
||||
if (full) ValidateFullContracts();
|
||||
if (Option(args, "--source") is { } source)
|
||||
{
|
||||
await PrepareSource(Path.GetFullPath(source), output, full ? new string('0', 32) : "validation", false, CancellationToken.None, full, Option(args, "--cryptex-archive"));
|
||||
await PrepareSource(Path.GetFullPath(source), output, full ? new string('0', 32) : "validation", false, CancellationToken.None, full, Option(args, "--cryptex-archive"), Option(args, "--noavx-archive"));
|
||||
ValidateNoAvxStaging(output);
|
||||
ValidateNoAvxRejections(output);
|
||||
await ValidateResourceRetention(output);
|
||||
await ValidateRecoveryPatch(output);
|
||||
Save(Path.Combine(output, "validation.json"), new { success = true, profile = "kvm-host-ventura-cryptex", mode = full ? "full" : "readiness", 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, 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", mode = full ? "full" : "readiness", 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, 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 });
|
||||
}
|
||||
if (full) await ValidateDiskSerialParser(output);
|
||||
if (Option(args, "--compression-chunk") is { } chunk)
|
||||
@@ -108,7 +114,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, mode = full ? "full" : "readiness" });
|
||||
Save(Path.Combine(output, "run-metadata.json"), new { token, startedUtc = DateTimeOffset.UtcNow, sourceCommit, dockurCommit = DockurCommit, profile = "kvm-host-ventura-cryptex-noavx", causalSingleVariableTest = true, comparisonBaselineCommit = "a356b88ae4a0a26d68098460df86038f9831c080", 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, mode = full ? "full" : "readiness" });
|
||||
var info = await Command("docker", ["info", "--format", "{{json .}}"], output, "docker-info", deadline.Token);
|
||||
using (var document = JsonDocument.Parse(info.Output))
|
||||
{
|
||||
@@ -119,16 +125,15 @@ static class NativeDiagnostic
|
||||
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);
|
||||
var available = System.Text.RegularExpressions.Regex.Match(File.ReadAllText("/proc/meminfo"), @"(?m)^MemAvailable:\s+(\d+) kB$");
|
||||
if (!available.Success || long.Parse(available.Groups[1].Value) < 5L * 1024 * 1024)
|
||||
throw new InvalidOperationException("Existing runner memory has less than the 5-GiB available diagnostic budget; no infrastructure change was requested.");
|
||||
if (!HasAvailableGuestMemory(File.ReadAllText("/proc/meminfo")))
|
||||
throw new InvalidOperationException("Existing runner memory cannot fit the 4-GiB guest plus its 512-MiB QEMU overhead budget; no infrastructure change was requested.");
|
||||
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", ["-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.");
|
||||
if (full) await PreparePayload(source, output, token, sourceCommit, deadline.Token);
|
||||
await PrepareSource(source, output, token, true, deadline.Token, full, Option(args, "--cryptex-archive"));
|
||||
await PrepareSource(source, output, token, true, deadline.Token, full, Option(args, "--cryptex-archive"), Option(args, "--noavx-archive"));
|
||||
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))
|
||||
@@ -204,6 +209,7 @@ static class NativeDiagnostic
|
||||
if (heartbeat.Elapsed >= TimeSpan.FromSeconds(60))
|
||||
{
|
||||
Console.WriteLine($"[native-diagnostic] phase={phase}; elapsed={phaseStarted.Elapsed.TotalMinutes:F1}/{phaseBudget.TotalMinutes:F0} minutes; container=running; readiness={(permitted ? "passed" : "pending")}");
|
||||
foreach (var progress in LastGuestProgress(Path.Combine(output, "guest-proof.log"))) Console.WriteLine("[native-diagnostic] guest-progress=" + progress);
|
||||
heartbeat.Restart();
|
||||
}
|
||||
await Task.Delay(TimeSpan.FromSeconds(20), deadline.Token);
|
||||
@@ -267,7 +273,7 @@ static class NativeDiagnostic
|
||||
}
|
||||
}
|
||||
|
||||
static async Task PrepareSource(string source, string output, string token, bool writeSource, CancellationToken cancellation, bool full = false, string? cryptexArchive = null)
|
||||
static async Task PrepareSource(string source, string output, string token, bool writeSource, CancellationToken cancellation, bool full = false, string? cryptexArchive = null, string? noAvxArchive = null)
|
||||
{
|
||||
Directory.CreateDirectory(output);
|
||||
var patchPath = Path.Combine(source, "src/install/recovery/patch.py");
|
||||
@@ -285,7 +291,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");
|
||||
@@ -379,7 +385,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"));
|
||||
@@ -410,24 +416,109 @@ 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 = "a356b88ae4a0a26d68098460df86038f9831c080", 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("<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);
|
||||
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);
|
||||
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 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)
|
||||
{
|
||||
var keys = dictionary.Elements("key").Where(element => element.Value == key).ToArray();
|
||||
@@ -435,6 +526,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<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 = """
|
||||
# Only the freshly extracted, owned guest EFI is changed; never the host.
|
||||
local lilu="$EFI_DIR/OC/Kexts/Lilu.kext/Contents"
|
||||
@@ -443,23 +587,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);
|
||||
|
||||
Reference in New Issue
Block a user