Compare commits

...
Author SHA1 Message Date
dh c01ae13185 ci: qualify supported macOS emulation before Recovery download 2026-10-04 10:30:23 +02:00
dh 720a43158c reuse verified native platform version without duplicate query 2026-10-04 09:41:25 +02:00
dh 227884723a retain bounded disk stack and resource pressure evidence 2026-10-04 09:14:39 +02:00
dh 25989cf0eb diagnose native Recovery disk IPC with bounded observation 2026-10-04 08:41:06 +02:00
dh 94a70b2005 ci: patch known Recovery variants and preserve UDIF checksums
PR and Push Build/Test / build-and-test (push) Failing after 18m28s
PR and Push Build/Test / portable-build-and-test (push) Successful in 6m35s
2026-10-03 21:53:38 +02:00
dh 45d7bde71f ci: probe macOS Ventura on existing KVM hardware
PR and Push Build/Test / build-and-test (push) Canceled after 0s
PR and Push Build/Test / portable-build-and-test (push) Canceled after 0s
2026-10-03 20:58:12 +02:00
dh 4606de0696 ci: preserve Apple Recovery daemon during read-only readiness probe
PR and Push Build/Test / build-and-test (push) Canceled after 0s
PR and Push Build/Test / portable-build-and-test (push) Canceled after 0s
2026-10-03 20:17:56 +02:00
dh 40281b57a5 Isolate measured UID watchdog failure in the native TCG diagnostic
PR and Push Build/Test / portable-build-and-test (push) Canceled after 0s
PR and Push Build/Test / build-and-test (push) Canceled after 1m33s
2026-10-03 18:58:10 +02:00
dh c92e62bdf5 Reduce native readiness probe overhead and preserve screenshot evidence
PR and Push Build/Test / portable-build-and-test (push) Canceled after 0s
PR and Push Build/Test / build-and-test (push) Canceled after 1m15s
2026-10-03 18:05:15 +02:00
dh b40234d3b5 ci: capture native recovery startup context before platform probe
PR and Push Build/Test / portable-build-and-test (push) Canceled after 0s
PR and Push Build/Test / build-and-test (push) Canceled after 3m24s
2026-10-03 16:38:34 +02:00
dh 0a30a1ca5a ci: retain current build fixes in the readonly native diagnostic 2026-10-03 16:22:03 +02:00
dh 6b1896660f ci: derive the macOS probe from the pinned QEMU filesystem image 2026-10-03 15:01:00 +02:00
dh 9a91a81992 ci: probe native macOS Recovery readiness on the existing Ubuntu runner 2026-10-03 14:18:50 +02:00
9 changed files with 1585 additions and 0 deletions
@@ -0,0 +1,36 @@
name: macOS 14 TCG Recovery prerequisite diagnostic
on:
workflow_dispatch:
jobs:
macos-native-diagnostic:
runs-on: ubuntu-latest
timeout-minutes: 95
env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: "1"
DOTNET_NOLOGO: "1"
steps:
- name: Checkout diagnostic source
uses: actions/checkout@v7
- name: Setup .NET for the diagnostic helper
uses: actions/setup-dotnet@v6
with:
dotnet-version: "10.0.x"
- name: Verify actual AVX2 emulation then probe macOS 14 Recovery
run: dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --run --output artifacts/native-macos
- name: Always clean up only this diagnostic's owned resources
if: always()
run: dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --cleanup --output artifacts/native-macos
- name: Preserve native diagnostic evidence
if: always()
uses: actions/upload-artifact@v3
with:
name: native-macos-recovery-diagnostic
path: artifacts/native-macos/
if-no-files-found: error
retention-days: 7
@@ -3,6 +3,11 @@ name: PR and Push Build/Test
on:
pull_request:
push:
# This temporary branch changes only the native prerequisite diagnostic.
# Its manual workflow provides that evidence; the PR branch still runs all jobs.
branches-ignore:
- codex/macos-ci-kvm-compatibility
- codex/macos-ci-tcg-supported
workflow_dispatch:
jobs:
+2
View File
@@ -175,6 +175,8 @@ Ubuntu does not compile the Swift helpers or execute Apple frameworks. Tests req
[Docker-OSX](https://github.com/sickcodes/Docker-OSX) runs a macOS VM rather than providing a Wine-style compatibility layer. Its launcher supports software emulation with `KVM=accel=tcg`, so KVM is not an absolute requirement. A supported .NET 10 guest needs macOS 14 or later plus the Swift build tools. The documented `auto` build downloads a preinstalled guest disk through `IMAGE_URL`; its documented ready-made tags and disk downloads were unavailable when checked on 2026-10-03. No verified native guest bootstrap is owned by this repository. CI validates source; it does not publish or deploy the workstation application.
The separate manual [native Recovery diagnostic](docs/macos-native-diagnostic.md) probes macOS startup and disk readiness through an unprivileged TCG guest on the existing Ubuntu Docker runner. It neither installs macOS nor runs application tests; its result is a prerequisite for a future native test job, not verification of macOS CI support.
## Operations And Limitations
- Treat recording, transcription drain, speaker finalization, OCR, and summarization as live user work. Never restart, kill, or clean runtime files until `/recording/status` is idle unless interruption is explicitly intended.
+46
View File
@@ -0,0 +1,46 @@
# macOS 14 TCG prerequisite diagnostic
This manual candidate uses the existing Ubuntu/x64 Docker runner. Before downloading Apple Recovery it tests actual AVX/AVX2 instruction execution in the pinned QEMU binary, then probes a fresh macOS 14+ Recovery guest. It does not install macOS, erase a disk, provision .NET/CLT or run Meeting Assistant tests. Readiness is only a prerequisite for full native CI.
## Profile and evidence
The existing Intel Celeron 1037U has neither AVX nor AVX2. KVM run 4187 at `720a431` reached macOS 13.6/x86_64/root and visible whole writable 64-GiB media. Diskutil timed out after 122 seconds; the sampler produced no report after 61 seconds. The screen remained at the Apple boot progress bar. CPU throttling, memory-limit/OOM events and container swap were zero; memory peaked at 2.67 GB. Host paging occurred. No unsupported-instruction crash or particular IPC wait is proved.
[CryptexFixup 1.0.5](https://github.com/acidanthera/CryptexFixup/blob/1.0.5/kern_start.cpp) selects the installed/updated Rosetta Cryptex and patches APFS hash checking; it does not replace the running Recovery cache or emulate instructions. macOS 13 is outside the [.NET 10 supported-OS policy](https://github.com/dotnet/core/blob/main/release-notes/10.0/supported-os.md). This candidate therefore uses macOS 14 and software CPU emulation without Cryptex. It changes the compatibility profile, not one isolated causal variable; actual success must be measured.
Earlier TCG run 4159 observed guest AVX2. Runs 4161/4163 measured slow native startup and reached the 40-minute host limit before readiness. They predated the UDIF CRC repair at `94a70b2`, reuse of successful sw_vers output and capturing the large Recovery hash only once. They do not qualify this candidate. Host/workflow limits are 90/95 minutes; a readiness pass does not establish that full installation/build/tests fit the pipeline.
## Entry points and dependencies
Orchestration remains the .NET 10 file-based app `tools/ci/MacOsNativeDiagnostic.cs`. Existing Bash/Python boot integration is necessary before a guest SDK exists. NASM assembles the CPU probe in the disposable image build, without host/runner installation. No new runner, device, capability, secret or service is used.
```sh
dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --validate
dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --validate --source /path/to/clean/pinned/dockur-clone --output /path/to/fresh/validation
dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --run --output artifacts/native-macos
dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --cleanup --output artifacts/native-macos
```
The native diagnostic workflow is manual only. Temporary diagnostic branches are excluded from ordinary push jobs to avoid repeating unchanged Wine/portable jobs. Remove this routing when integrating qualified CI into the actual PR.
## Before Apple downloads
The existing daemon must be Linux/x64 with two CPUs and 6 GiB memory; the runner must have 5 GiB available memory and the Docker filesystem 8 GiB free. These checks do not reconfigure resources. Dockur commit `16a5b470cdd601bae8b05b02d748d7edfb36c12e`, both imported QEMU image digests and original source seams remain pinned.
Actual `Haswell-noTSX` CPU flags under TCG use `enforce=on` to reject unsupported requests. The CPU preflight uses that same composed flag list and QEMU binary before Recovery download/boot. `tools/ci/macos-tcg-cpu-preflight.asm` enables long mode/YMM state, executes AVX and AVX2 integer arithmetic, and checks an Int32 from the upper 128-bit lane. Only the correct result reaches [QEMU debug-exit](https://github.com/qemu/qemu/blob/v11.1.1/hw/misc/debugexit.c) code 33. No disks/network attach; failure/timeout fails preflight. This tests that instruction chain, not the complete ISA or macOS.
The locally assembled NASM 2.16.03 ROM is 65,536 bytes, SHA256 `c32746122cc68f3ed642aa46c21b677f803c58f0d4ff665841723fcc5625f549`. Assembly/static review does not prove remote execution.
## Native gates, bounds and cleanup
The original Apple recoveryosd runs under its existing job/PID beside the read-only probe. Exact known macOS 13/14 plist layouts and same-length replacements retain their allowlist. The patcher validates UDIF boundaries, updates changed mish/koly CRCs and reads back the image. Four raw/zlib positive and twelve rejection fixtures use an independent C# CRC32 reader. Apple chunklist authentication applies to the input, not the deliberately modified image.
Native readiness requires x86_64, UID 0, macOS 14+, successful launchd service queries and exactly one writable whole 64-GiB disk. The complete successful sw_vers output must contain one valid ProductVersion field and EOF within 1,024 bytes. The actual native diskutil query remains mandatory.
Required commands retain 45 seconds, UID 180 seconds and the single disk query 120 seconds. The owned observer uses `/bin/ps -M -p <diskutil-child>` with a separate 60-second limit and two-second TERM/KILL grace. It avoids stack symbolication; thread waiting states do not identify an IPC endpoint. Observation failure passes no gate. Owned children are stopped on completion/cancellation; output remains 512 KiB per command and 4 MiB proof.
The container retains 6 GiB memory/swap, two-CPU limit, 512 MiB shared memory and a 4-GiB/two-vCPU guest. One fresh anonymous /storage volume holds the sparse 64-GiB target. Inspection rejects devices, capabilities, binds, ports, host networking and privileged mode. KVM is disabled with no /dev/kvm mapping; guest networking stays slirp.
Evidence retains run/source/profile identity, CPU preflight, original/patched Recovery identity, container/QEMU state, native proof/result and cleanup. Optional bounded before/during/after pressure snapshots record host/cgroup counters. The /storage/14/setup.dmg hash is captured once after staging; successful evidence survives later capture failure. Screenshots/pressure observations pass no gate.
Both cleanup paths verify exact token/label/ID before removing only the owned container, anonymous volume and image. No pruning, host changes, original checkout changes or Meeting Assistant restart occurs. Artifacts remain seven days. Full CI remains unverified until an installed supported guest builds/signs fresh helpers and passes all 577 tests, including the five native macOS tests, with zero skips.
+871
View File
@@ -0,0 +1,871 @@
#:property PublishAot=false
using System.Diagnostics;
using System.Buffers.Binary;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Xml.Linq;
// .NET 10 file-based CI diagnostic. See docs/macos-native-diagnostic.md.
return await NativeDiagnostic.Execute(args);
static class NativeDiagnostic
{
const string DockurCommit = "16a5b470cdd601bae8b05b02d748d7edfb36c12e";
const string Profile = "tcg-haswell-sonoma";
const string CpuModel = "Haswell-noTSX";
const string CpuFlags = "Haswell-noTSX,l3-cache=on,+hypervisor,vendor=GenuineIntel,vmx=off,vmware-cpuid-freq=on,-pdpe1gb,-pcid,-invpcid,-tsc-deadline,-xsavec,-xsaves,+ssse3,+sse4.2,+popcnt,+avx,+avx2,+aes,+fma,+bmi1,+bmi2,+smep,+xsave,+xsaveopt,+xgetbv1,+movbe,+rdrand,enforce=on";
const string OpenCoreTemplateHash = "287328995d4198f1b05166f087d85bf7ef66bedafe150d17ad112ac8de60051d";
const string UdifChecksumBindingHash = "6109d04619e800c483fdac363d593cd1cd69f34131d2521417334e11d41c8bfa";
const string OwnerLabel = "org.meeting-assistant.native-diagnostic";
const long GuestDiskBytes = 64L * 1024 * 1024 * 1024;
const long ContainerMemoryBytes = 6L * 1024 * 1024 * 1024;
const int MaximumCapturedCharacters = 8 * 1024 * 1024;
static readonly JsonSerializerOptions JsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true };
const string OriginalBootstrap = "[ ! -e /tmp/m ]&&{ /sbin/mount_9p installstate >/dev/null 2>&1;exec /Volumes/installstate/launch.sh;};: >/tmp/m\n";
const string MountOnlyBootstrap = "[ ! -e /tmp/m ]&& /sbin/mount_9p installstate >/dev/null 2>&1; : >/tmp/m\n";
static readonly string OriginalDaemon = """
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
\t<key>Label</key>
\t<string>com.apple.recoveryosd</string>
\t<key>OnDemand</key>
\t<false/>
\t<key>ProcessType</key>
\t<string>App</string>
\t<key>EnablePressuredExit</key>
\t<false/>
\t<key>ProgramArguments</key>
\t<array>
\t\t<string>/usr/libexec/recoveryosd</string>
\t</array>
</dict>
</plist>
""".Replace("\\t", "\t", StringComparison.Ordinal);
static readonly string DiagnosticDaemon = (OriginalDaemon + "\n")
.Replace("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n", "", StringComparison.Ordinal)
.Replace("\t\t<string>/usr/libexec/recoveryosd</string>", "\t\t<string>/bin/bash</string>\n\t\t<string>/Volumes/installstate/launch.sh</string>", StringComparison.Ordinal);
// Exact XML framing read from the Apple 13 comparison download, not an assertion
// about the unretained bytes downloaded by run 4173.
static readonly string OriginalDaemon13 = ReplaceOnce(OriginalDaemon + "\n", "<string>App</string>", "<string>Interactive</string>");
static readonly string DiagnosticDaemon13 = ReplaceOnce(DiagnosticDaemon, "<string>App</string>", "<string>Interactive</string>");
public static async Task<int> Execute(string[] args)
{
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]");
return 0;
}
var output = Path.GetFullPath(Option(args, "--output") ?? "artifacts/native-macos");
if (args.Contains("--validate"))
{
ValidateContracts();
if (Option(args, "--source") is { } source)
{
await PrepareSource(Path.GetFullPath(source), output, "validation", false, CancellationToken.None);
await ValidateResourceRetention(output);
await ValidateRecoveryPatch(output);
await ValidateTcgPreflight(output, CancellationToken.None);
Save(Path.Combine(output, "validation.json"), new
{
success = true, profile = Profile,
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, diskThreadObservationLimitSeconds = 60, stackSamplingUsed = false,
resultNegativeCases = 6, containerNegativeCases = 11, recoveryPositiveCases = 4, recoveryNegativeCases = 12,
independentFixtureCrc32Readback = true, resourceSnapshotRetention = true, cpuProfileSourceContractsVerified = true,
preflightGateFixtureCases = 8, qemuRuntimePreflightExecuted = false, templateIsoDownloaded = 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;
}
if (args.Contains("--cleanup"))
return await Cleanup(output) ? 0 : 1;
if (!args.Contains("--run")) throw new ArgumentException("Choose --run, --cleanup or --validate.");
Directory.CreateDirectory(output);
var statePath = Path.Combine(output, "owned-resources.json");
if (File.Exists(statePath)) throw new InvalidOperationException("Output already contains a run identity; choose a fresh directory or clean up its run first.");
var token = Guid.NewGuid().ToString("N");
var work = Path.Combine(Environment.GetEnvironmentVariable("RUNNER_TEMP") ?? Path.GetTempPath(), "meeting-assistant-native-" + token);
var state = new OwnedResources(token, "meeting-assistant-native-" + token, "meeting-assistant-native-diagnostic:" + token, work);
Save(statePath, state);
Directory.CreateDirectory(work);
File.WriteAllText(Path.Combine(work, "run.owner"), token);
using var deadline = new CancellationTokenSource(TimeSpan.FromMinutes(90));
using var signal = OperatingSystem.IsLinux() ? PosixSignalRegistration.Create(PosixSignal.SIGTERM, context => { context.Cancel = true; deadline.Cancel(); }) : null;
ConsoleCancelEventHandler cancelHandler = (_, context) => { context.Cancel = true; deadline.Cancel(); };
Console.CancelKeyPress += cancelHandler;
var outcome = "failed";
string? error = null;
try
{
if (!OperatingSystem.IsLinux() || RuntimeInformation.ProcessArchitecture != Architecture.X64)
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 = Profile, causalSingleVariableTest = false, kvm = false, cpuModel = CpuModel, recoveryMajor = 14, cpuFlags = CpuFlags, runId = Environment.GetEnvironmentVariable("GITHUB_RUN_ID"), server = Environment.GetEnvironmentVariable("GITHUB_SERVER_URL"), architecture = RuntimeInformation.ProcessArchitecture.ToString(), deadlineMinutes = 90 });
var info = await Command("docker", ["info", "--format", "{{json .}}"], output, "docker-info", deadline.Token);
using (var document = JsonDocument.Parse(info.Output))
{
var data = document.RootElement;
if (data.GetProperty("OSType").GetString() != "linux" || data.GetProperty("Architecture").GetString() is not ("x86_64" or "amd64"))
throw new InvalidOperationException("The existing Docker daemon is not Linux/x64; this diagnostic does not reconfigure it.");
if (data.GetProperty("NCPU").GetInt32() < 2 || data.GetProperty("MemTotal").GetInt64() < ContainerMemoryBytes)
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.");
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.");
await PrepareSource(source, output, token, true, 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))
state = state with { ImageId = image.RootElement[0].GetProperty("Id").GetString() };
Save(statePath, state);
var create = await Command("docker", ["create", "--name", state.ContainerName, "--label", OwnerLabel + "=" + token, "--memory", "6g", "--memory-swap", "6g", "--cpus", "2", "--shm-size", "512m", "--log-opt", "max-size=8m", "--log-opt", "max-file=1", "--env", "KVM=N", "--env", "CPU_MODEL=" + CpuModel, "--env", "NETWORK=slirp", "--env", "DISPLAY=web", "--env", "MANUAL=N", "--env", "VERSION=14", "--env", "RAM_SIZE=4G", "--env", "CPU_CORES=2", "--env", "DISK_SIZE=64G", "--env", "DISK_TYPE=sata", "--env", "ARGUMENTS=-object iothread,id=io2", state.ImageTag], output, "docker-create", deadline.Token);
var id = create.Output.Trim();
if (!System.Text.RegularExpressions.Regex.IsMatch(id, "^[0-9a-f]{64}$")) throw new InvalidOperationException("Docker did not return a container identity.");
state = state with { ContainerId = id };
Save(statePath, state);
await Command("docker", ["inspect", id], output, "container-created", deadline.Token);
AssertContainer(File.ReadAllText(Path.Combine(output, "container-created.stdout.log")), token);
await Command("docker", ["start", id], output, "docker-start", deadline.Token);
await CapturePressure(id, output, "before", deadline.Token);
Console.WriteLine("The owned unprivileged TCG/Haswell macOS 14 guest is starting. Success requires native macOS 14+/x86_64 and a writable 64-GiB disk; no installer will run. This is not a single-variable causal test.");
var recoveryStarted = Stopwatch.StartNew();
var heartbeat = Stopwatch.StartNew();
var diskPressureCaptured = false;
while (true)
{
deadline.Token.ThrowIfCancellationRequested();
await CaptureGuest(id, output, deadline.Token);
var proofPath = Path.Combine(output, "guest-proof.log");
if (!diskPressureCaptured && File.Exists(proofPath) && File.ReadAllText(proofPath).Contains("[proof-start] disks", StringComparison.Ordinal))
{
diskPressureCaptured = true;
await CapturePressure(id, output, "during", deadline.Token);
}
var resultPath = Path.Combine(output, "guest-result.json");
if (File.Exists(resultPath))
{
var result = File.ReadAllText(resultPath);
ValidateResult(result, token);
Console.WriteLine("Native Recovery readiness passed. This run has not installed macOS, .NET, CLT, or run Meeting Assistant tests.");
outcome = "readiness-passed";
break;
}
var running = await Command("docker", ["inspect", "--format", "{{.State.Running}}", id], output, "container-running", deadline.Token);
if (running.Output.Trim() != "true") throw new InvalidOperationException("Guest container exited before a native readiness result.");
if (heartbeat.Elapsed >= TimeSpan.FromSeconds(60))
{
Console.WriteLine($"[native-diagnostic] phase=recovery; elapsed={recoveryStarted.Elapsed.TotalMinutes:F1} minutes; container=running; readiness=pending");
heartbeat.Restart();
}
await Task.Delay(TimeSpan.FromSeconds(20), deadline.Token);
}
}
catch (Exception exception)
{
error = exception is OperationCanceledException ? "The explicit 90-minute diagnostic deadline or cancellation was reached." : exception.Message;
Console.Error.WriteLine(error);
}
finally
{
Console.CancelKeyPress -= cancelHandler;
using var captureDeadline = new CancellationTokenSource(TimeSpan.FromSeconds(45));
try { await CaptureGuest(state.ContainerId ?? state.ContainerName, output, captureDeadline.Token, true, state.Token); } catch (Exception exception) { Console.Error.WriteLine("Final evidence capture: " + exception.Message); }
await CapturePressure(state.ContainerId ?? state.ContainerName, output, "after", captureDeadline.Token);
try { PrintGuestProof(output, state.Token); } catch (Exception exception) { Console.Error.WriteLine("Native proof output: " + exception.Message); }
var clean = await Cleanup(output);
if (!clean) { outcome = "failed"; error = (error ?? "") + " Owned-resource cleanup failed; inspect cleanup evidence."; }
Save(Path.Combine(output, "outcome.json"), new { token, outcome, error, completedUtc = DateTimeOffset.UtcNow });
}
return outcome == "readiness-passed" ? 0 : 1;
}
static string? Option(string[] args, string name)
{
var index = Array.IndexOf(args, name);
return index < 0 ? null : index + 1 < args.Length ? args[index + 1] : throw new ArgumentException("Missing value for " + name);
}
static void ValidateContracts()
{
var readiness = File.ReadAllText(Path.Combine("tools", "ci", "macos-native-readiness.sh"));
var baseline = NormalizeReadinessDiagnostics(readiness);
baseline = ReplaceOnce(baseline, "while (( attempt < 1 && SECONDS - readiness_start < 600 )); do", "while (( SECONDS - readiness_start < 600 )); do");
if (Hash(Encoding.UTF8.GetBytes(baseline)) != "4d428f594dac14eff64ed87b172c81ecf85ac91da8c5460cd6ec4b1d310800c3")
throw new InvalidOperationException("Outside seven explicit diagnostic blocks, the successful sw_vers version parser/sequence and one-attempt limit, baseline identity/service/disk gates and watchdogs must remain identical.");
if (Hash(File.ReadAllBytes(Path.Combine("tools", "ci", "macos-native-bootstrap.sh"))) != "94f069e116fdc7685a4d233cab6fa50df9f39274386bb82157674061e74fadb5")
throw new InvalidOperationException("Compatibility profile must preserve the baseline Apple recoveryosd wrapper.");
if (Hash(Encoding.UTF8.GetBytes(OriginalDaemon13)) != "af9d7f6c1948079bd4384d27b6882678d6fb4e338fcf6a8be8f84fceef174ad6") throw new InvalidOperationException("macOS 13 allowlist bytes differ from the independently read comparison plist.");
foreach (var variant in new[] { (OriginalDaemon + "\n", DiagnosticDaemon, "App"), (OriginalDaemon13, DiagnosticDaemon13, "Interactive") })
{
ValidateDaemon(variant.Item1, variant.Item3, false);
ValidateDaemon(variant.Item2, variant.Item3, true);
if (Encoding.UTF8.GetByteCount(variant.Item2) > Encoding.UTF8.GetByteCount(variant.Item1)) throw new InvalidOperationException("Daemon replacement exceeds original file.");
}
var good = JsonSerializer.Serialize(new { token = "validation", success = true, osVersion = "14.6.1", architecture = "x86_64", uid = 0, disk = "/dev/disk1", diskBytes = GuestDiskBytes, readOnly = false, systemExit = 0, diskArbitrationExit = 0, recoveryExit = 0, diskListExit = 0 });
ValidateResult(good, "validation");
foreach (var invalid in new[] { good.Replace("14.6.1", "13.6.1"), good.Replace("x86_64", "arm64"), good.Replace("\"readOnly\":false", "\"readOnly\":true"), good.Replace("\"success\":true", "\"success\":false"), good.Replace("68719476736", "17179869184"), good.Replace("validation", "stale") })
{
try { ValidateResult(invalid, "validation"); } catch (InvalidOperationException) { continue; }
throw new InvalidOperationException("Diagnostic validator accepted an invalid/stale result.");
}
var boundary = """
[{"Config":{"Labels":{"org.meeting-assistant.native-diagnostic":"validation"},"Env":["KVM=N","CPU_MODEL=Haswell-noTSX","VERSION=14"]},"HostConfig":{"Privileged":false,"NetworkMode":"default","Memory":6442450944,"MemorySwap":6442450944,"NanoCpus":2000000000,"ShmSize":536870912,"CapAdd":null,"DeviceRequests":null,"Binds":null,"PortBindings":{},"DeviceCgroupRules":null,"Tmpfs":null,"Devices":[]},"Mounts":[{"Type":"volume","Destination":"/storage","RW":true}]}]
""";
AssertContainer(boundary, "validation");
foreach (var invalid in new[] { boundary.Replace("\"Privileged\":false", "\"Privileged\":true"), boundary.Replace("\"Devices\":[]", "\"Devices\":[{\"PathOnHost\":\"/dev/kvm\",\"PathInContainer\":\"/dev/kvm\",\"CgroupPermissions\":\"rw\"}]"), boundary.Replace("KVM=N", "KVM=Y"), boundary.Replace("CPU_MODEL=Haswell-noTSX", "CPU_MODEL=host"), boundary.Replace("VERSION=14", "VERSION=13"), boundary.Replace("6442450944", "8589934592"), boundary.Replace("\"NetworkMode\":\"default\"", "\"NetworkMode\":\"host\""), boundary.Replace("\"CapAdd\":null", "\"CapAdd\":[\"NET_ADMIN\"]"), boundary.Replace("\"Type\":\"volume\"", "\"Type\":\"bind\""), boundary.Replace("/storage", "/host"), boundary.Replace("\"NanoCpus\":2000000000", "\"NanoCpus\":4000000000") })
{
try { AssertContainer(invalid, "validation"); } catch (InvalidOperationException) { continue; }
throw new InvalidOperationException("Diagnostic validator accepted an excessive/wrong-profile container boundary.");
}
}
static string NormalizeReadinessDiagnostics(string source)
{
const string start = "# BEGIN disk IPC diagnostic\n";
const string end = "# END disk IPC diagnostic\n";
var blocks = 0;
while (source.IndexOf(start, StringComparison.Ordinal) is var from && from >= 0)
{
var to = source.IndexOf(end, from + start.Length, StringComparison.Ordinal);
if (to < 0 || source.IndexOf(start, from + start.Length, to - from - start.Length, StringComparison.Ordinal) >= 0)
throw new InvalidOperationException("Readiness diagnostic blocks are unbalanced or nested.");
source = source.Remove(from, to + end.Length - from);
blocks++;
}
if (blocks != 7 || source.Contains(end, StringComparison.Ordinal))
throw new InvalidOperationException("Readiness must contain exactly seven explicit disk IPC diagnostic blocks.");
source = RestoreVersionBlock(source, "successful sw_vers version parser", "");
source = RestoreVersionBlock(source, "successful sw_vers version extraction", "run_command version /usr/bin/sw_vers -productVersion\n(( LAST_EXIT == 0 )) || fail_probe product_version_failed\nread_scalar || fail_probe product_version_invalid\n");
return source;
}
static string RestoreVersionBlock(string source, string name, string originalSequence)
{
var start = "# BEGIN " + name + "\n";
var end = "# END " + name + "\n";
if (source.Split(start, StringSplitOptions.None).Length != 2 || source.Split(end, StringSplitOptions.None).Length != 2)
throw new InvalidOperationException("Readiness requires exactly one named version marker pair: " + name);
var from = source.IndexOf(start, StringComparison.Ordinal);
var to = source.IndexOf(end, StringComparison.Ordinal);
if (to < from + start.Length) throw new InvalidOperationException("Readiness version markers are reversed: " + name);
return source.Remove(from, to + end.Length - from).Insert(from, originalSequence);
}
static async Task PrepareSource(string source, string output, string token, bool writeSource, CancellationToken cancellation)
{
Directory.CreateDirectory(output);
var patchPath = Path.Combine(source, "src/install/recovery/patch.py");
var originalPatch = File.ReadAllText(patchPath);
if (Hash(Encoding.UTF8.GetBytes(originalPatch)) != "84f13db88c02edbf5ce21a39571fe58f12bebf5b0886c2d012f16ddbaed45323") throw new InvalidOperationException("Pinned Recovery patcher hash mismatch.");
var daemon = OriginalDaemon + "\n";
var patch = PrepareRecoveryPatch(originalPatch);
var checksumBinding = File.ReadAllText(Path.Combine("tools", "ci", "macos-native-udif-checksums.py"));
if (Hash(Encoding.UTF8.GetBytes(checksumBinding)) != UdifChecksumBindingHash) throw new InvalidOperationException("Recovery UDIF checksum binding hash mismatch.");
File.WriteAllText(Path.Combine(output, "udif_checksums.py"), checksumBinding, new UTF8Encoding(false));
var dockerPath = Path.Combine(source, "Dockerfile");
if (Hash(File.ReadAllBytes(dockerPath)) != "a0e804235967400eb70e755d63eff8a33a7761922ddd6e9723faa8e828fd8aa3") throw new InvalidOperationException("Pinned Dockerfile hash mismatch.");
// The existing runner's BuildKit cannot checksum dangling manpage links during COPY /.
// This pinned filesystem image has an empty Config; FROM preserves the same runtime defaults.
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");
dockerfile = ReplaceOnce(dockerfile, " gzip \\\n", " gzip \\\n nasm \\\n");
dockerfile = ReplaceOnce(dockerfile, "COPY --chmod=755 ./assets /assets/\n", "COPY --chmod=755 ./assets /assets/\nRUN nasm -f bin /assets/ci-cpu-preflight.asm -o /assets/ci-cpu-preflight.bin && test \"$(stat -c%s /assets/ci-cpu-preflight.bin)\" = 65536\n");
var boot = PrepareTcgBoot(source);
var cpuPath = Path.Combine(source, "src/cpu.sh");
var cpu = File.ReadAllText(cpuPath);
if (Hash(Encoding.UTF8.GetBytes(cpu)) != "0f3e4b4e1c3e17743d3a8d27b77a76424ceebb576b269283d612c489bc70993e") throw new InvalidOperationException("Pinned CPU composition script hash mismatch.");
cpu = ReplaceOnce(cpu, ",+movbe,+rdrand,check\"", ",+movbe,+rdrand,enforce=on\"");
var preflight = File.ReadAllText(Path.Combine("tools", "ci", "macos-tcg-cpu-preflight.asm"));
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, ". cpu.sh # Configure CPU model\n", "");
entry = ReplaceOnce(entry, ". proc.sh # Initialize processor\n", "");
entry = ReplaceOnce(entry, ". init.sh # Initialize system\n", ". init.sh # Initialize system\n. cpu.sh # Compose the exact guest CPU before any Apple download\n. proc.sh # Compose the actual accelerator/CPU_FLAGS once\n" + TcgPreflight + "\n");
entry = ReplaceOnce(entry, "trap - ERR\n", "[[ \"$KVM_OPTS\" == ' -accel tcg,thread=multi' && \"$CPU_FLAGS\" == '" + CpuFlags + "' && \"$CPU_OPTS\" == \"-cpu $CPU_FLAGS -smp $SMP\" ]] || { error 'Supported profile refuses a CPU/accelerator fallback.'; exit 1; }\ninfo '[supported-profile] accelerator=tcg cpu=Haswell-noTSX recovery=14; AVX/AVX2 preflight passed; native guest gates still pending'\n\ntrap - ERR\n");
var hookPath = Path.Combine("tools", "ci", "macos-native-readiness.sh");
var hook = ReplaceOnce(File.ReadAllText(hookPath), "@@PROOF_TOKEN@@", token);
var wrapper = File.ReadAllText(Path.Combine("tools", "ci", "macos-native-bootstrap.sh"));
var imagePath = Path.Combine(source, "src", "image.sh");
var originalImage = File.ReadAllText(imagePath);
if (Hash(Encoding.UTF8.GetBytes(originalImage)) != "c08bf9436fb8b72ea82fdf0e677641ab2fc42a0a59e2cf0309c00df519884c5c") throw new InvalidOperationException("Pinned Recovery staging script hash mismatch.");
var image = ReplaceOnce(originalImage, " if ! cp -f \"$IMAGE_TOOLS/recovery/launch.sh\" \"$script\"; then\n", " if ! cp -f \"$IMAGE_TOOLS/recovery/launch.sh\" \"$script\" ||\n ! cp -f \"$IMAGE_TOOLS/recovery/readiness.sh\" \"${script%/*}/readiness.sh\"; then\n");
image = ReplaceOnce(image, " if ! cmp -s \"$IMAGE_TOOLS/recovery/launch.sh\" \"$script\" ||\n", " if ! cmp -s \"$IMAGE_TOOLS/recovery/launch.sh\" \"$script\" ||\n ! cmp -s \"$IMAGE_TOOLS/recovery/readiness.sh\" \"$state/readiness.sh\" ||\n");
image = ReplaceOnce(image, " if ! result=$(python3 \"$IMAGE_TOOLS/recovery/patch.py\" \"$image\"); then\n", " info \"[recovery-original] bytes=$(stat -c%s -- \"$image\") sha256=$(sha256sum \"$image\" | awk '{print $1}')\"\n if ! result=$(python3 \"$IMAGE_TOOLS/recovery/patch.py\" \"$image\"); then\n");
foreach (var pair in new[] { ("recovery-patch.py", patch), ("Dockerfile.patched", dockerfile), ("container-entry.sh", entry), ("guest-launch.sh", wrapper), ("guest-readiness.sh", hook), ("image.sh.patched", image), ("recoveryosd-original.plist", daemon), ("recoveryosd-diagnostic.plist", DiagnosticDaemon), ("recoveryosd-13-original.plist", OriginalDaemon13), ("recoveryosd-13-diagnostic.plist", DiagnosticDaemon13), ("early-bootstrap.sh", MountOnlyBootstrap), ("boot.sh.patched", boot), ("opencore-config.plist", File.ReadAllText(Path.Combine(source, "assets/config.plist"))), ("cpu.sh.patched", cpu), ("ci-cpu-preflight.asm", preflight) })
File.WriteAllText(Path.Combine(output, pair.Item1), pair.Item2, new UTF8Encoding(false));
Save(Path.Combine(output, "source-hashes.json"), Directory.GetFiles(output).Where(path => Path.GetFileName(path) is "recovery-patch.py" or "udif_checksums.py" or "Dockerfile.patched" or "container-entry.sh" or "guest-launch.sh" or "guest-readiness.sh" or "image.sh.patched" or "recoveryosd-original.plist" or "recoveryosd-diagnostic.plist" or "recoveryosd-13-original.plist" or "recoveryosd-13-diagnostic.plist" or "early-bootstrap.sh" or "boot.sh.patched" or "opencore-config.plist" or "cpu.sh.patched" or "ci-cpu-preflight.asm").ToDictionary(path => Path.GetFileName(path)!, path => Hash(File.ReadAllBytes(path))));
Save(Path.Combine(output, "cpu-preflight-source.json"), new { profile = Profile, cpuModel = CpuModel, cpuFlags = CpuFlags, expectedExitCode = 33, instructionProbeExecuted = false, qemuBinaryExecuted = false, sourceSha256 = Hash(Encoding.UTF8.GetBytes(preflight)) });
await Command("bash", ["-n", Path.Combine(output, "guest-launch.sh")], output, "guest-hook-syntax", cancellation);
await Command("bash", ["-n", Path.Combine(output, "guest-readiness.sh")], output, "guest-readiness-syntax", cancellation);
await Command("bash", ["-n", Path.Combine(output, "image.sh.patched")], output, "guest-staging-syntax", cancellation);
await Command("bash", ["-n", Path.Combine(output, "container-entry.sh")], output, "entry-syntax", cancellation);
await Command("bash", ["-n", Path.Combine(output, "boot.sh.patched")], output, "boot-staging-syntax", cancellation);
await Command("bash", ["-n", Path.Combine(output, "cpu.sh.patched")], output, "cpu-composition-syntax", cancellation);
if (!writeSource) return;
File.WriteAllText(patchPath, patch, new UTF8Encoding(false));
File.WriteAllText(Path.Combine(source, "src/install/recovery/udif_checksums.py"), checksumBinding, new UTF8Encoding(false));
File.WriteAllText(dockerPath, dockerfile, new UTF8Encoding(false));
File.WriteAllText(entryPath, entry, new UTF8Encoding(false));
File.WriteAllText(imagePath, image, new UTF8Encoding(false));
File.WriteAllText(Path.Combine(source, "src/install/recovery/launch.sh"), wrapper, new UTF8Encoding(false));
File.WriteAllText(Path.Combine(source, "src/install/recovery/readiness.sh"), hook, new UTF8Encoding(false));
File.WriteAllText(Path.Combine(source, "src/boot.sh"), boot, new UTF8Encoding(false));
File.WriteAllText(cpuPath, cpu, new UTF8Encoding(false));
File.WriteAllText(Path.Combine(source, "assets/ci-cpu-preflight.asm"), preflight, new UTF8Encoding(false));
}
static void ValidateDaemon(string xml, string processType, bool patched)
{
var pairs = XDocument.Parse(xml).Root!.Element("dict")!.Elements().ToArray();
if (pairs.Length != 10 || !pairs.Where((_, index) => index % 2 == 0).Select(element => element.Value).SequenceEqual(new[] { "Label", "OnDemand", "ProcessType", "EnablePressuredExit", "ProgramArguments" })) throw new InvalidOperationException("Recovery daemon fields changed.");
if (pairs[1].Value != "com.apple.recoveryosd" || pairs[3].Name != "false" || pairs[5].Value != processType || pairs[7].Name != "false" || pairs[9].Name != "array" || !pairs[9].Elements().Select(element => element.Value).SequenceEqual(patched ? new[] { "/bin/bash", "/Volumes/installstate/launch.sh" } : new[] { "/usr/libexec/recoveryosd" })) throw new InvalidOperationException("Recovery daemon identity/arguments changed.");
}
static string PrepareRecoveryPatch(string original)
{
var patch = ReplaceOnce(original, OriginalBootstrap, MountOnlyBootstrap);
patch = ReplaceOnce(patch, "import zlib\n", "import zlib\nfrom udif_checksums import ChecksumPlan\n");
var constants = "RECOVERY_13_ORIGINAL = b'''" + OriginalDaemon13 + "'''\nRECOVERY_13_REPLACEMENT = b'''" + DiagnosticDaemon13 + "'''.ljust(len(RECOVERY_13_ORIGINAL), b\" \")\nRECOVERY_14_ORIGINAL = b'''" + OriginalDaemon + "\n'''\nRECOVERY_14_REPLACEMENT = b'''" + DiagnosticDaemon + "'''.ljust(len(RECOVERY_14_ORIGINAL), b\" \")\nRECOVERY_LABEL = b'<string>com.apple.recoveryosd</string>'";
patch = ReplaceOnce(patch, "RECOVERY_ORIGINAL = b\"/usr/libexec/recoveryosd\"\nRECOVERY_REPLACEMENT = b\"/private/etc/rc.cdrom.sh\"", constants);
patch = ReplaceOnce(patch, " if len(RECOVERY_REPLACEMENT) != len(RECOVERY_ORIGINAL):\n raise RuntimeError(\"recoveryosd launch-path replacement length mismatch\")", " if len(RECOVERY_13_REPLACEMENT) != len(RECOVERY_13_ORIGINAL) or len(RECOVERY_14_REPLACEMENT) != len(RECOVERY_14_ORIGINAL):\n raise RuntimeError(\"recoveryosd launch-path replacement length mismatch\")");
patch = ReplaceOnce(patch, " (\"recoveryosd launch path\", RECOVERY_ORIGINAL, RECOVERY_REPLACEMENT),", " (\"recoveryosd macOS 13 launch path\", RECOVERY_13_ORIGINAL, RECOVERY_13_REPLACEMENT),\n (\"recoveryosd macOS 14 launch path\", RECOVERY_14_ORIGINAL, RECOVERY_14_REPLACEMENT),");
patch = ReplaceOnce(patch, " chunks = {}\n", " chunks = {}\n recovery_label_count = 0\n");
patch = ReplaceOnce(patch, " plist = plistlib.loads(image.read(xml_length))\n", " plist = plistlib.loads(image.read(xml_length))\n checksums = ChecksumPlan(image, koly, plist, xml_offset, xml_length, size)\n");
patch = ReplaceOnce(patch, " key = (blkx_index, run_index)\n", " recovery_label_count += decoded.count(RECOVERY_LABEL)\n key = (blkx_index, run_index)\n");
var variantValidation = """
if len(matches[patches[0][0]]) != 1:
raise RuntimeError("Expected exactly one rc.cdrom.sh bootstrap")
variants = [item for item in patches[1:] if matches[item[0]]]
if recovery_label_count != 1 or len(variants) != 1 or len(matches[variants[0][0]]) != 1:
raise RuntimeError("Expected exactly one known recoveryosd plist and launch path")
patches = (patches[0], variants[0])
print("[recovery-daemon] " + variants[0][0])
""";
patch = ReplaceOnce(patch, " for name, _, _ in patches:\n count = len(matches[name])\n if count != 1:\n raise RuntimeError(f\"Expected exactly one {name}, found {count}\")", IndentPython(variantValidation, 8));
// Plan all recompressed chunks before the first image write. A later
// compression failure must not leave an earlier chunk patched.
patch = ReplaceOnce(patch, " for key, chunk in chunks.items():\n patched = bytearray", " planned = []\n for key, chunk in chunks.items():\n patched = bytearray");
patch = ReplaceOnce(patch, " image.seek(chunk[\"physical_offset\"])\n image.write(stored)", " planned.append((chunk[\"physical_offset\"], stored))\n\n checksum_xml, checksum_koly = checksums.prepare(planned)\n for physical_offset, stored in planned:\n image.seek(physical_offset)\n image.write(stored)\n image.seek(xml_offset)\n image.write(checksum_xml)\n image.seek(size - 512)\n image.write(checksum_koly)");
patch = ReplaceOnce(patch, " image.flush()\n", " image.flush()\n checksums.verify()\n");
return patch;
}
static string IndentPython(string text, int spaces)
{
var lines = text.Split('\n');
var common = lines.Where(line => line.Length > 0).Min(line => line.TakeWhile(character => character == ' ').Count());
return string.Join("\n", lines.Select(line => new string(' ', spaces) + (line.Length > 0 ? line[common..] : "")));
}
static string PrepareTcgBoot(string source)
{
var boot = File.ReadAllText(Path.Combine(source, "src", "boot.sh"));
var config = File.ReadAllText(Path.Combine(source, "assets", "config.plist"));
if (Hash(Encoding.UTF8.GetBytes(boot)) != "82b56525707a8f586e040f56108b5034c02e7fecfea071f1857e596cba10cbed" || Hash(Encoding.UTF8.GetBytes(config)) != "3b0ec58b693cfa0fadf3e3f952486e87af8c27d504f9545d1e90ae2dc3777096") throw new InvalidOperationException("Pinned OpenCore staging/config hashes mismatch.");
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.");
boot = ReplaceOnce(boot, " PLIST=\"/assets/config.plist\"\n", " [ ! -e /custom.plist ] || { error 'Supported profile refuses an unverified custom OpenCore config!'; exit 12; }\n PLIST=\"/assets/config.plist\"\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 \"PROFILE=tcg-haswell-sonoma\"\n");
return boot;
}
static XElement PlistValue(XElement dictionary, string key)
{
var keys = dictionary.Elements("key").Where(element => element.Value == key).ToArray();
if (keys.Length != 1 || keys[0].ElementsAfterSelf().FirstOrDefault() is not { } value) throw new InvalidOperationException("Missing/duplicate plist key: " + key);
return value;
}
static readonly string TcgPreflight = """
# Realize this exact TCG model and execute AVX/AVX2 before Apple downloads.
disabled "$KVM" && [[ "$ARCH" == amd64 && "$CPU_MODEL" == Haswell-noTSX && "$VERSION" == 14 && "$CPU_FLAGS" == '@@CPU_FLAGS@@' ]] || { error 'Supported probe requires the exact TCG/Haswell/macOS 14 profile.'; exit 1; }
[[ "$(qemu-system-x86_64 --version | head -n 1)" == 'QEMU emulator version 11.1.1 (Reims 11.1.3)' ]] || { error 'Pinned QEMU runtime version mismatch.'; exit 1; }
printf '%s %s\n' c32746122cc68f3ed642aa46c21b677f803c58f0d4ff665841723fcc5625f549 /assets/ci-cpu-preflight.bin | sha256sum -c - || { error 'Compiled AVX/AVX2 preflight ROM hash mismatch.'; exit 1; }
probeTcgInstructions() {
/usr/bin/timeout --signal=TERM --kill-after=2 10 qemu-system-x86_64 \
-machine q35 -accel tcg,thread=multi -cpu "$1" -smp 2 -m 64 -bios /assets/ci-cpu-preflight.bin \
-nodefaults -display none -serial none -monitor none -nographic -no-reboot \
-device isa-debug-exit,iobase=0xf4,iosize=0x04
}
if probeTcgInstructions "$CPU_FLAGS" > "$QEMU_DIR/cpu-preflight-positive.log" 2>&1; then positive=0; else positive=$?; fi
cat "$QEMU_DIR/cpu-preflight-positive.log"
(( positive == 33 )) || { error "Actual TCG AVX/AVX2 instruction preflight failed: exit=$positive"; exit 1; }
if probeTcgInstructions "$CPU_FLAGS,-avx2" > "$QEMU_DIR/cpu-preflight-negative.log" 2>&1; then negative=0; else negative=$?; fi
cat "$QEMU_DIR/cpu-preflight-negative.log"
(( negative != 33 )) || { error 'AVX2-disabled negative control unexpectedly passed.'; exit 1; }
info "[cpu-preflight] positive=$positive negative=$negative cpu=$CPU_FLAGS; actual AVX/AVX2 executed before Apple download"
""".Replace("@@CPU_FLAGS@@", CpuFlags, StringComparison.Ordinal);
static async Task ValidateTcgPreflight(string output, CancellationToken cancellation)
{
var fixture = Path.Combine(output, "validation-cpu-preflight-gate");
Directory.CreateDirectory(fixture);
var entry = File.ReadAllText(Path.Combine(output, "container-entry.sh"));
if (entry.IndexOf(TcgPreflight, StringComparison.Ordinal) >= entry.IndexOf(". download.sh", StringComparison.Ordinal)
|| entry.Split(". cpu.sh", StringSplitOptions.None).Length != 2
|| entry.Split(". proc.sh", StringSplitOptions.None).Length != 2)
throw new InvalidOperationException("Actual composed CPU preflight must execute once before any Apple download.");
var cases = new[]
{
("positive-negative-exit", 33, 0, "14", CpuFlags, true, true),
("positive-negative-timeout", 33, 124, "14", CpuFlags, true, true),
("positive-normal-exit", 0, 0, "14", CpuFlags, true, false),
("positive-timeout", 124, 0, "14", CpuFlags, true, false),
("negative-passed", 33, 33, "14", CpuFlags, true, false),
("wrong-recovery", 33, 0, "13", CpuFlags, true, false),
("wrong-cpu-flags", 33, 0, "14", CpuFlags.Replace("enforce=on", "check"), true, false),
("wrong-rom-hash", 33, 0, "14", CpuFlags, false, false)
};
foreach (var item in cases)
{
var work = Path.Combine(fixture, item.Item1);
Directory.CreateDirectory(work);
var script = """
set -euo pipefail
disabled() { [ "$1" = N ]; }
error() { printf '%s\n' "$*" >&2; }
info() { printf '%s\n' "$*"; }
qemu-system-x86_64() { printf '%s\n' 'QEMU emulator version 11.1.1 (Reims 11.1.3)'; }
sha256sum() { cat >/dev/null; return "@@HASH_EXIT@@"; }
mock_timeout() {
printf '[fixture-command] %s\n' "$*"
case "$*" in *,-avx2*) return @@NEGATIVE@@ ;; *) return @@POSITIVE@@ ;; esac
}
KVM=N; ARCH=amd64; CPU_MODEL=Haswell-noTSX; VERSION='@@VERSION@@'
CPU_FLAGS='@@FLAGS@@'; QEMU_DIR='@@WORK@@'
""".Replace("@@HASH_EXIT@@", item.Item6 ? "0" : "1", StringComparison.Ordinal)
.Replace("@@NEGATIVE@@", item.Item3.ToString(), StringComparison.Ordinal)
.Replace("@@POSITIVE@@", item.Item2.ToString(), StringComparison.Ordinal)
.Replace("@@VERSION@@", item.Item4, StringComparison.Ordinal)
.Replace("@@FLAGS@@", item.Item5, StringComparison.Ordinal)
.Replace("@@WORK@@", work.Replace("'", "'\\''", StringComparison.Ordinal), StringComparison.Ordinal)
+ "\n" + TcgPreflight.Replace("/usr/bin/timeout", "mock_timeout", StringComparison.Ordinal)
+ "\nprintf '[fixture] Apple download reached after gate\\n'\n";
var path = Path.Combine(work, "fixture.sh");
File.WriteAllText(path, script, new UTF8Encoding(false));
var result = await Command("bash", [path], work, "gate", cancellation, requireSuccess: false);
var reached = result.Output.Contains("Apple download reached after gate", StringComparison.Ordinal);
Save(Path.Combine(work, "receipt.json"), new { success = (result.ExitCode == 0) == item.Item7 && reached == item.Item7, result.ExitCode, reachedAppleDownloadSeam = reached, qemuExecuted = false });
if ((result.ExitCode == 0) != item.Item7 || reached != item.Item7)
throw new InvalidOperationException("Actual TCG preflight gate fixture failed: " + item.Item1);
}
}
static string ReplaceOnce(string text, string oldValue, string newValue) => ReplaceAllExact(text, oldValue, newValue, 1);
static string ReplaceAllExact(string text, string oldValue, string newValue, int expected)
{
var count = text.Split(oldValue, StringSplitOptions.None).Length - 1;
if (count != expected) throw new InvalidOperationException($"Pinned source contract expected {expected} match(es), found {count}: {oldValue.Split('\n')[0]}");
return text.Replace(oldValue, newValue, StringComparison.Ordinal);
}
static void ValidateResult(string json, string token)
{
using var document = JsonDocument.Parse(json);
var result = document.RootElement;
if (result.GetProperty("token").GetString() != token || !result.GetProperty("success").GetBoolean() || !Version.TryParse(result.GetProperty("osVersion").GetString(), out var version) || version.Major < 14 || result.GetProperty("architecture").GetString() != "x86_64" || result.GetProperty("uid").GetInt32() != 0 || !System.Text.RegularExpressions.Regex.IsMatch(result.GetProperty("disk").GetString() ?? "", "^/dev/disk[0-9]+$") || result.GetProperty("diskBytes").GetInt64() != GuestDiskBytes || result.GetProperty("readOnly").GetBoolean() || new[] { "systemExit", "diskArbitrationExit", "recoveryExit", "diskListExit" }.Any(key => result.GetProperty(key).GetInt32() != 0))
throw new InvalidOperationException("The fresh guest receipt did not prove native macOS 14+/x86_64, service readiness and the writable 64-GiB disk.");
}
static void AssertContainer(string json, string token)
{
using var document = JsonDocument.Parse(json);
var container = document.RootElement[0];
var config = container.GetProperty("HostConfig");
var devices = config.GetProperty("Devices");
var mounts = container.GetProperty("Mounts");
var environment = container.GetProperty("Config").GetProperty("Env").EnumerateArray().Select(value => value.GetString()).ToArray();
if (container.GetProperty("Config").GetProperty("Labels").GetProperty(OwnerLabel).GetString() != token
|| config.GetProperty("Privileged").GetBoolean()
|| config.GetProperty("NetworkMode").GetString() is not ("default" or "bridge")
|| config.GetProperty("Memory").GetInt64() != ContainerMemoryBytes
|| config.GetProperty("MemorySwap").GetInt64() != ContainerMemoryBytes
|| config.GetProperty("NanoCpus").GetInt64() != 2000000000
|| config.GetProperty("ShmSize").GetInt64() != 536870912
|| new[] { "CapAdd", "DeviceRequests", "Binds", "PortBindings", "DeviceCgroupRules", "Tmpfs" }.Any(key =>
config.TryGetProperty(key, out var value) && value.ValueKind != JsonValueKind.Null
&& (value.ValueKind == JsonValueKind.Array ? value.GetArrayLength() != 0 : value.EnumerateObject().Any()))
|| devices.GetArrayLength() != 0
|| mounts.GetArrayLength() != 1
|| mounts[0].GetProperty("Type").GetString() != "volume"
|| mounts[0].GetProperty("Destination").GetString() != "/storage"
|| !mounts[0].GetProperty("RW").GetBoolean()
|| new[] { "KVM=N", "CPU_MODEL=" + CpuModel, "VERSION=14" }.Any(expected =>
environment.Count(value => value is not null && value.StartsWith(expected.Split('=')[0] + "=", StringComparison.Ordinal)) != 1
|| !environment.Contains(expected)))
throw new InvalidOperationException("Created container exceeds the owned unprivileged TCG/Haswell/macOS 14 boundary.");
}
static async Task CaptureGuest(string id, string output, CancellationToken cancellation, bool final = false, string? token = null)
{
if (final && token is not null) await CaptureMonitor(id, output, token, cancellation);
var logs = await Command("docker", ["logs", "--tail", "3000", id], output, "container", cancellation, requireSuccess: false);
foreach (var file in new[] { ("proof.log", "guest-proof.log"), ("result.json", "guest-result.json") })
{
var result = await Command("docker", ["exec", id, "cat", "/dev/shm/installstate/" + file.Item1], output, "capture-" + file.Item1, cancellation, requireSuccess: false);
if (result.ExitCode == 0 && !string.IsNullOrWhiteSpace(result.Output)) File.WriteAllText(Path.Combine(output, file.Item2), result.Output);
}
// The immutable Recovery image is complete only after this staging marker.
// Hash it once instead of rereading the image on every twenty-second poll.
if (logs.Output.Contains("[supported-profile] accelerator=tcg", StringComparison.Ordinal)
&& !File.Exists(Path.Combine(output, "guest-container-resources.last-success.json")))
await Command("docker", ["exec", id, "sh", "-c", "printf '[qemu]\n'; qemu-system-x86_64 --version | head -n 1; printf '[Recovery hash]\n'; test -f /storage/14/setup.dmg && sha256sum /storage/14/setup.dmg || exit 1; printf '[resources]\n'; df -Pk /storage; cat /sys/fs/cgroup/memory.max /sys/fs/cgroup/cpu.max 2>/dev/null || true"], output, "guest-container-resources", cancellation, requireSuccess: false, retainSuccessful: true);
}
static async Task CapturePressure(string id, string output, string phase, CancellationToken cancellation)
{
using var snapshotDeadline = CancellationTokenSource.CreateLinkedTokenSource(cancellation);
snapshotDeadline.CancelAfter(TimeSpan.FromSeconds(20));
const string snapshot = """
printf '[snapshot UTC]\n'; date -u '+%Y-%m-%dT%H:%M:%SZ'
for path in /proc/meminfo /proc/pressure/cpu /proc/pressure/memory /proc/pressure/io \
/sys/fs/cgroup/cpu.max /sys/fs/cgroup/cpu.stat /sys/fs/cgroup/cpu.pressure \
/sys/fs/cgroup/memory.max /sys/fs/cgroup/memory.current /sys/fs/cgroup/memory.peak \
/sys/fs/cgroup/memory.events /sys/fs/cgroup/memory.stat /sys/fs/cgroup/memory.pressure \
/sys/fs/cgroup/memory.swap.current; do
printf '\n[%s]\n' "$path"
if [ -r "$path" ]; then cat "$path"; else printf 'unavailable\n'; fi
done
printf '\n[host paging counters]\n'
awk '/^(pgmajfault|pswpin|pswpout) / {print}' /proc/vmstat
""";
try
{
await Command("docker", ["exec", id, "sh", "-c", snapshot], output, "capture-pressure-" + phase, snapshotDeadline.Token, requireSuccess: false, retainSuccessful: true);
}
catch (Exception exception)
{
// Optional evidence must not replace the guest outcome or prevent cleanup.
try { Save(Path.Combine(output, "capture-pressure-" + phase + ".unavailable.json"), new { phase, error = exception.Message, capturedUtc = DateTimeOffset.UtcNow }); }
catch (Exception evidenceError) { Console.Error.WriteLine("Optional pressure evidence: " + evidenceError.Message); }
}
}
static async Task CaptureMonitor(string id, string output, string token, CancellationToken cancellation)
{
using var deadline = CancellationTokenSource.CreateLinkedTokenSource(cancellation);
deadline.CancelAfter(TimeSpan.FromSeconds(10));
int? monitorExit = null, copyExit = null;
string? error = null;
try
{
if (!System.Text.RegularExpressions.Regex.IsMatch(id, "^[0-9a-f]{64}$") || !System.Text.RegularExpressions.Regex.IsMatch(token, "^[0-9a-f]{32}$")) throw new InvalidOperationException("No saved owned container identity for the optional monitor capture.");
var inspection = await Command("docker", ["inspect", id], output, "capture-monitor-container", deadline.Token);
AssertContainer(inspection.Output, token);
using (var document = JsonDocument.Parse(inspection.Output))
if (document.RootElement[0].GetProperty("Id").GetString() != id || !document.RootElement[0].GetProperty("State").GetProperty("Running").GetBoolean()) throw new InvalidOperationException("Owned guest container is no longer running for the optional monitor capture.");
var screen = "/tmp/native-diagnostic-screen-" + token + ".ppm";
var monitor = await Command("docker", ["exec", id, "sh", "-c", """
test -S /run/shm/monitor.sock || exit 1
rm -f -- "$1" || exit 1
printf 'info kvm\ninfo status\nscreendump %s\n' "$1" | /usr/bin/timeout -s KILL 5 /usr/bin/nc.openbsd -q 1 -w 2 -U /run/shm/monitor.sock
monitor_exit=$?
printf '\n[monitor-exit] %s\n' "$monitor_exit"
[ "$monitor_exit" -eq 0 ] || exit "$monitor_exit"
bytes=$(stat -c%s "$1") || exit 1
[ "$bytes" -gt 0 ] && [ "$bytes" -le 8388608 ] || exit 1
printf '[screen-bytes] %s\n' "$bytes"
""", "native-monitor", screen], output, "capture-monitor", deadline.Token, requireSuccess: false);
monitorExit = monitor.ExitCode;
if (monitorExit != 0) throw new InvalidOperationException("Optional monitor status/screenshot command exited " + monitorExit + ".");
var copy = await Command("docker", ["cp", id + ":" + screen, Path.Combine(output, "guest-screen-" + token + ".ppm")], output, "capture-monitor-screen", deadline.Token, requireSuccess: false);
copyExit = copy.ExitCode;
if (copyExit != 0) throw new InvalidOperationException("Optional monitor screenshot copy exited " + copyExit + ".");
}
catch (Exception exception) { error = exception.Message; Console.Error.WriteLine("Optional final monitor capture: " + error); }
finally { Save(Path.Combine(output, "monitor-capture.json"), new { token, monitorExit, copyExit, success = error is null, error, capturedUtc = DateTimeOffset.UtcNow }); }
}
static async Task<bool> Cleanup(string output)
{
var path = Path.Combine(output, "owned-resources.json");
if (!File.Exists(path)) return true;
var state = JsonSerializer.Deserialize<OwnedResources>(File.ReadAllText(path), JsonOptions) ?? throw new InvalidOperationException("Invalid owned-resource receipt.");
if (!System.Text.RegularExpressions.Regex.IsMatch(state.Token, "^[0-9a-f]{32}$") || state.ContainerName != "meeting-assistant-native-" + state.Token || state.ImageTag != "meeting-assistant-native-diagnostic:" + state.Token) throw new InvalidOperationException("Invalid cleanup ownership identity.");
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(90));
try
{
foreach (var kind in new[] { "container", "image" })
{
var name = kind == "container" ? state.ContainerName : state.ImageTag;
var inspect = await Command("docker", [kind, "inspect", name], output, "cleanup-" + kind + "-inspect", deadline.Token, requireSuccess: false);
if (inspect.ExitCode != 0)
{
if (inspect.Error.Contains("No such object", StringComparison.Ordinal) || inspect.Error.Contains("No such container", StringComparison.Ordinal) || inspect.Error.Contains("No such image", StringComparison.Ordinal)) continue;
throw new InvalidOperationException("Cannot establish owned " + kind + " absence: " + inspect.Error);
}
using var document = JsonDocument.Parse(inspect.Output);
var resource = document.RootElement[0];
if (resource.GetProperty("Config").GetProperty("Labels").GetProperty(OwnerLabel).GetString() != state.Token) throw new InvalidOperationException("Cleanup refuses a resource without this run's exact ownership label.");
var id = resource.GetProperty("Id").GetString()!;
var expectedId = kind == "container" ? state.ContainerId : state.ImageId;
if (expectedId is not null && expectedId != id) throw new InvalidOperationException("Cleanup refuses a resource whose ID changed after creation.");
await Command("docker", kind == "container" ? ["rm", "--force", "--volumes", id] : ["image", "rm", id], output, "cleanup-" + kind + "-remove", deadline.Token);
}
if (Path.GetFileName(state.WorkDirectory) == "meeting-assistant-native-" + state.Token && File.Exists(Path.Combine(state.WorkDirectory, "run.owner")) && File.ReadAllText(Path.Combine(state.WorkDirectory, "run.owner")) == state.Token) Directory.Delete(state.WorkDirectory, true);
Save(Path.Combine(output, "cleanup.json"), new { state.Token, success = true, completedUtc = DateTimeOffset.UtcNow });
return true;
}
catch (Exception exception)
{
Save(Path.Combine(output, "cleanup.json"), new { state.Token, success = false, error = exception.Message, completedUtc = DateTimeOffset.UtcNow });
Console.Error.WriteLine("Owned diagnostic cleanup failed: " + exception.Message);
return false;
}
}
static async Task ValidateRecoveryPatch(string output)
{
if (Crc32(Encoding.ASCII.GetBytes("123456789")) != 0xcbf43926) throw new InvalidOperationException("Independent C# CRC32 known vector failed.");
var fixture = Path.Combine(output, "validation-recovery-patch");
Directory.CreateDirectory(fixture);
var patch = File.ReadAllText(Path.Combine(output, "recovery-patch.py"));
const string marker = "SCRIPT_ORIGINAL = b'''";
var start = patch.IndexOf(marker, StringComparison.Ordinal) + marker.Length;
var end = patch.IndexOf("'''", start, StringComparison.Ordinal);
var bootstrap = patch[start..end];
var cases = new[] {
("13-zlib", OriginalDaemon13, true, true), ("14-zlib", OriginalDaemon + "\n", true, true),
("13-raw", OriginalDaemon13, false, true), ("14-raw", OriginalDaemon + "\n", false, true),
("unknown", OriginalDaemon13.Replace("Interactive", "Unknown"), true, false),
("duplicate", OriginalDaemon13 + OriginalDaemon + "\n", true, false),
("duplicate-unknown", OriginalDaemon13 + OriginalDaemon13.Replace("Interactive", "Unknown"), true, false),
("malformed", OriginalDaemon13.Replace("</array>", "</broken>"), true, false),
("wrong-arguments", OriginalDaemon13.Replace("/usr/libexec/recoveryosd", "/usr/libexec/wrongdaemon"), true, false),
("duplicate-arguments", OriginalDaemon13.Replace("</array>", "<string>/usr/libexec/recoveryosd</string></array>"), true, false),
("corrupt-data-crc", OriginalDaemon13, true, false), ("unsupported-crc", OriginalDaemon13, true, false),
("xml-boundary", OriginalDaemon13, true, false), ("physical-boundary", OriginalDaemon13, true, false),
("unknown-zero-run", OriginalDaemon13, true, false), ("logical-boundary", OriginalDaemon13, false, false)
};
foreach (var item in cases)
{
var path = Path.Combine(fixture, item.Item1 + ".dmg");
CreateRecoveryFixture(path, bootstrap, item.Item2, item.Item3);
if (item.Item1 is "corrupt-data-crc" or "unsupported-crc" or "xml-boundary" or "physical-boundary" or "unknown-zero-run" or "logical-boundary")
{
var corrupt = File.ReadAllBytes(path);
var trailer = corrupt.Length - 512;
if (item.Item1 == "corrupt-data-crc") corrupt[trailer + 88] ^= 1;
else if (item.Item1 == "unsupported-crc") BinaryPrimitives.WriteUInt32BigEndian(corrupt.AsSpan(trailer + 80), 3);
else if (item.Item1 == "xml-boundary") BinaryPrimitives.WriteUInt64BigEndian(corrupt.AsSpan(trailer + 224), (ulong)corrupt.Length);
else if (item.Item1 == "logical-boundary") BinaryPrimitives.WriteUInt64BigEndian(corrupt.AsSpan(trailer + 492), 1);
else
{
var xmlOffset = checked((int)BinaryPrimitives.ReadUInt64BigEndian(corrupt.AsSpan(trailer + 216)));
var xmlLength = checked((int)BinaryPrimitives.ReadUInt64BigEndian(corrupt.AsSpan(trailer + 224)));
var xmlText = Encoding.UTF8.GetString(corrupt, xmlOffset, xmlLength);
var data = XDocument.Parse(xmlText).Descendants("data").Single().Value;
var mish = Convert.FromBase64String(data);
if (item.Item1 == "physical-boundary") BinaryPrimitives.WriteUInt64BigEndian(mish.AsSpan(236), (ulong)corrupt.Length);
else BinaryPrimitives.WriteUInt32BigEndian(mish.AsSpan(204), 0);
var replacement = Encoding.UTF8.GetBytes(ReplaceOnce(xmlText, data, Convert.ToBase64String(mish)));
replacement.CopyTo(corrupt, xmlOffset);
}
File.WriteAllBytes(path, corrupt);
}
var before = File.ReadAllBytes(path);
var result = await Command("python3", ["-B", Path.Combine(output, "recovery-patch.py"), path], fixture, item.Item1, CancellationToken.None, requireSuccess: false);
var after = File.ReadAllBytes(path);
if ((result.ExitCode == 0) != item.Item4) throw new InvalidOperationException("Recovery fixture result mismatch: " + item.Item1 + ": " + result.Error);
if (!item.Item4 && !before.SequenceEqual(after)) throw new InvalidOperationException("Rejected Recovery fixture was modified: " + item.Item1);
if (item.Item4)
{
var decoded = DecodeRecoveryFixture(after, item.Item3);
var original = Encoding.UTF8.GetBytes(item.Item2);
var expectedText = item.Item1.StartsWith("13", StringComparison.Ordinal) ? DiagnosticDaemon13 : DiagnosticDaemon;
var expected = Encoding.UTF8.GetBytes(expectedText.PadRight(item.Item2.Length, ' '));
if (before.Length != after.Length || !decoded.AsSpan(4096, original.Length).SequenceEqual(expected)) throw new InvalidOperationException("Recovery fixture changed byte extent or daemon fields: " + item.Item1);
ValidateDaemon(expectedText, item.Item1.StartsWith("13", StringComparison.Ordinal) ? "Interactive" : "App", true);
VerifyRecoveryFixtureChecksums(after, decoded);
}
}
Save(Path.Combine(fixture, "receipt.json"), new { success = true, positiveCases = 4, negativeCases = 12, rejectedImagesUnmodified = true, knownDaemonFieldsPreserved = true, readBackCrc32IndependentlyVerified = true, syntheticUdifFixtures = true, guestExecuted = false });
}
static uint Crc32(ReadOnlySpan<byte> bytes)
{
var crc = uint.MaxValue;
foreach (var value in bytes)
{
crc ^= value;
for (var bit = 0; bit < 8; bit++) crc = (crc >> 1) ^ ((crc & 1) != 0 ? 0xedb88320u : 0);
}
return ~crc;
}
static void CreateRecoveryFixture(string path, string bootstrap, string daemon, bool compressed)
{
var decoded = new byte[16384];
Encoding.UTF8.GetBytes(bootstrap).CopyTo(decoded, 64);
Encoding.UTF8.GetBytes(daemon).CopyTo(decoded, 4096);
byte[] stored;
if (compressed)
{
using var memory = new MemoryStream();
using (var zipper = new ZLibStream(memory, CompressionLevel.Fastest, true)) zipper.Write(decoded);
stored = memory.ToArray();
}
else stored = decoded;
var mish = new byte[284];
Encoding.ASCII.GetBytes("mish").CopyTo(mish, 0);
BinaryPrimitives.WriteUInt32BigEndian(mish.AsSpan(4), 1);
BinaryPrimitives.WriteUInt64BigEndian(mish.AsSpan(16), 32);
BinaryPrimitives.WriteUInt32BigEndian(mish.AsSpan(64), 2);
BinaryPrimitives.WriteUInt32BigEndian(mish.AsSpan(68), 32);
BinaryPrimitives.WriteUInt32BigEndian(mish.AsSpan(72), Crc32(decoded));
BinaryPrimitives.WriteUInt32BigEndian(mish.AsSpan(200), 2);
BinaryPrimitives.WriteUInt32BigEndian(mish.AsSpan(204), compressed ? 0x80000005u : 1u);
BinaryPrimitives.WriteUInt64BigEndian(mish.AsSpan(220), 32);
BinaryPrimitives.WriteUInt64BigEndian(mish.AsSpan(236), (ulong)stored.Length);
BinaryPrimitives.WriteUInt32BigEndian(mish.AsSpan(244), 0xffffffff);
var xml = Encoding.UTF8.GetBytes("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<plist version=\"1.0\"><dict><key>resource-fork</key><dict><key>blkx</key><array><dict><key>Data</key><data>" + Convert.ToBase64String(mish) + "</data></dict></array></dict></dict></plist>\n");
var koly = new byte[512];
Encoding.ASCII.GetBytes("koly").CopyTo(koly, 0);
BinaryPrimitives.WriteUInt32BigEndian(koly.AsSpan(4), 4);
BinaryPrimitives.WriteUInt32BigEndian(koly.AsSpan(8), 512);
BinaryPrimitives.WriteUInt32BigEndian(koly.AsSpan(12), 1);
BinaryPrimitives.WriteUInt64BigEndian(koly.AsSpan(32), (ulong)stored.Length);
BinaryPrimitives.WriteUInt32BigEndian(koly.AsSpan(80), 2);
BinaryPrimitives.WriteUInt32BigEndian(koly.AsSpan(84), 32);
BinaryPrimitives.WriteUInt32BigEndian(koly.AsSpan(88), Crc32(stored));
BinaryPrimitives.WriteUInt64BigEndian(koly.AsSpan(216), (ulong)stored.Length);
BinaryPrimitives.WriteUInt64BigEndian(koly.AsSpan(224), (ulong)xml.Length);
BinaryPrimitives.WriteUInt32BigEndian(koly.AsSpan(352), 2);
BinaryPrimitives.WriteUInt32BigEndian(koly.AsSpan(356), 32);
BinaryPrimitives.WriteUInt32BigEndian(koly.AsSpan(360), Crc32(mish.AsSpan(72, 4)));
BinaryPrimitives.WriteUInt64BigEndian(koly.AsSpan(492), 32);
File.WriteAllBytes(path, stored.Concat(xml).Concat(koly).ToArray());
}
static byte[] DecodeRecoveryFixture(byte[] image, bool compressed)
{
var length = checked((int)BinaryPrimitives.ReadUInt64BigEndian(image.AsSpan(image.Length - 512 + 32)));
if (!compressed) return image[..length];
using var input = new MemoryStream(image, 0, length);
using var decoder = new ZLibStream(input, CompressionMode.Decompress);
using var output = new MemoryStream();
decoder.CopyTo(output);
return output.ToArray();
}
static void VerifyRecoveryFixtureChecksums(byte[] image, byte[] decoded)
{
var trailer = image.Length - 512;
var length = checked((int)BinaryPrimitives.ReadUInt64BigEndian(image.AsSpan(trailer + 32)));
var xmlLength = checked((int)BinaryPrimitives.ReadUInt64BigEndian(image.AsSpan(trailer + 224)));
var xml = XDocument.Parse(Encoding.UTF8.GetString(image, length, xmlLength));
var mish = Convert.FromBase64String(xml.Descendants("data").Single().Value);
if (Crc32(image.AsSpan(0, length)) != BinaryPrimitives.ReadUInt32BigEndian(image.AsSpan(trailer + 88)) || Crc32(decoded) != BinaryPrimitives.ReadUInt32BigEndian(mish.AsSpan(72)) || Crc32(mish.AsSpan(72, 4)) != BinaryPrimitives.ReadUInt32BigEndian(image.AsSpan(trailer + 360))) throw new InvalidOperationException("Independent C# fixture CRC32 readback failed.");
}
static async Task ValidateResourceRetention(string output)
{
var fixture = Path.Combine(output, "validation-resource-retention");
Directory.CreateDirectory(fixture);
const string label = "capture-resource-fixture";
const string successful = "Successful snapshot before stopped-container capture.\n";
await Command("bash", ["-c", "printf '%s\\n' 'Successful snapshot before stopped-container capture.'"], fixture, label, CancellationToken.None, retainSuccessful: true);
await Command("bash", ["-c", "printf '%s\\n' 'Container is not running.' >&2; exit 1"], fixture, label, CancellationToken.None, requireSuccess: false, retainSuccessful: true);
var retained = Path.Combine(fixture, label + ".last-success.stdout.log");
if (!File.Exists(retained) || File.ReadAllText(retained) != successful || File.ReadAllText(Path.Combine(fixture, label + ".stdout.log")) != "" || !File.ReadAllText(Path.Combine(fixture, label + ".stderr.log")).Contains("Container is not running."))
throw new InvalidOperationException("A failed final capture lost the last successful resource snapshot.");
using var receipt = JsonDocument.Parse(File.ReadAllText(Path.Combine(fixture, label + ".last-success.json")));
if (receipt.RootElement.GetProperty("stdoutSha256").GetString() != Hash(Encoding.UTF8.GetBytes(successful)) || receipt.RootElement.GetProperty("exitCode").GetInt32() != 0) throw new InvalidOperationException("Last successful snapshot receipt does not identify the retained bytes.");
}
static async Task<CommandResult> Command(string executable, string[] arguments, string output, string label, CancellationToken cancellation, bool requireSuccess = true, bool echo = false, bool retainSuccessful = false)
{
if (!label.StartsWith("capture-", StringComparison.Ordinal) && label is not "container" and not "container-running" and not "guest-container-resources")
Console.WriteLine("[native-diagnostic] " + label);
using var commandCancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellation);
var commandToken = commandCancellation.Token;
var start = new ProcessStartInfo(executable) { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false };
foreach (var argument in arguments) start.ArgumentList.Add(argument);
start.Environment["GIT_TERMINAL_PROMPT"] = "0";
using var process = Process.Start(start) ?? throw new InvalidOperationException("Cannot start " + executable);
async Task<string> Read(StreamReader reader, string stream)
{
var captured = new StringBuilder();
var buffer = new char[8192];
using var log = new StreamWriter(Path.Combine(output, label + "." + stream + ".log"), false, new UTF8Encoding(false));
while (true)
{
var count = await reader.ReadAsync(buffer.AsMemory(), commandToken);
if (count == 0) break;
if (captured.Length + count > MaximumCapturedCharacters)
{
commandCancellation.Cancel();
throw new InvalidOperationException(label + " exceeded its bounded diagnostic log size.");
}
captured.Append(buffer, 0, count);
await log.WriteAsync(buffer.AsMemory(0, count), commandToken);
await log.FlushAsync(commandToken);
if (echo) Console.Write(new string(buffer, 0, count));
}
return captured.ToString();
}
var stdout = Read(process.StandardOutput, "stdout");
var stderr = Read(process.StandardError, "stderr");
try
{
await Task.WhenAll(stdout, stderr, process.WaitForExitAsync(commandToken));
var result = new CommandResult(process.ExitCode, await stdout, await stderr);
if (retainSuccessful && result.ExitCode == 0 && !string.IsNullOrWhiteSpace(result.Output))
{
File.WriteAllText(Path.Combine(output, label + ".last-success.stdout.log"), result.Output, new UTF8Encoding(false));
Save(Path.Combine(output, label + ".last-success.json"), new { exitCode = result.ExitCode, stdoutSha256 = Hash(Encoding.UTF8.GetBytes(result.Output)), capturedUtc = DateTimeOffset.UtcNow });
}
if (requireSuccess && result.ExitCode != 0) throw new InvalidOperationException($"{label} exited {result.ExitCode}: {result.Error[..Math.Min(result.Error.Length, 1500)]}");
return result;
}
catch
{
try { if (!process.HasExited) process.Kill(entireProcessTree: true); } catch (InvalidOperationException) { }
throw;
}
}
static string Hash(byte[] bytes) => Convert.ToHexStringLower(SHA256.HashData(bytes));
static void PrintGuestProof(string output, string token)
{
var path = Path.Combine(output, "guest-proof.log");
if (!File.Exists(path)) { Console.WriteLine("[native-diagnostic] No native guest proof was captured."); return; }
var proof = File.ReadAllText(path).Replace(token, "<run-id>", StringComparison.Ordinal);
const int budget = 512 * 1024;
if (proof.Length > budget)
proof = proof[..(64 * 1024)] + "\n[native-diagnostic] Middle of proof omitted from CI stdout; complete bounded proof is retained in the artifact.\n" + proof[^((budget - 64 * 1024))..];
Console.WriteLine("[native-diagnostic] Final native guest proof:");
Console.Write(proof);
}
static void Save(string path, object value)
{
var temporary = path + ".tmp";
File.WriteAllText(temporary, JsonSerializer.Serialize(value, JsonOptions), new UTF8Encoding(false));
File.Move(temporary, path, overwrite: true);
}
sealed record OwnedResources(string Token, string ContainerName, string ImageTag, string WorkDirectory, string? ContainerId = null, string? ImageId = null);
sealed record CommandResult(int ExitCode, string Output, string Error);
}
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
# Apple Recovery has Bash before any SDK is installed. Preserve the original
# daemon under its launchd label/PID while the unchanged read-only probe runs.
/bin/bash /Volumes/installstate/readiness.sh &
exec /usr/libexec/recoveryosd
+360
View File
@@ -0,0 +1,360 @@
#!/bin/bash
# Existing macOS Recovery/launchd runtime hook; never installs or erases anything.
set -u
PATH="/usr/bin:/bin:/usr/sbin:/sbin"
export PATH
PROOF_TOKEN="@@PROOF_TOKEN@@"
STATE_DIR="/Volumes/installstate"
PROOF_LOG="$STATE_DIR/proof.log"
RESULT="$STATE_DIR/result.json"
EXPECTED_BYTES=68719476736
MAX_LOG_BYTES=4194304
MAX_OUTPUT_BYTES=524288
TIMER_FIFO="/tmp/native-diagnostic-$PROOF_TOKEN-$$.fifo"
PENDING_OUTPUTS=()
ACTIVE_COMMAND=""
ACTIVE_TIMER=""
# BEGIN disk IPC diagnostic
ACTIVE_OBSERVER=""
# END disk IPC diagnostic
os_version=""
architecture=""
uid=-1
system_exit=-1
arbitration_exit=-1
recovery_exit=-1
disk_list_exit=-1
selected_disk=""
disk_bytes=0
count=0
while [ ! -d "$STATE_DIR" ] && (( count < 120 )); do
/sbin/mount_9p installstate >/dev/null 2>&1 || :
count=$((count + 1))
sleep 1
done
[ -d "$STATE_DIR" ] || exit 1
: > "$PROOF_LOG" || exit 1
exec 3>> "$PROOF_LOG" || exit 1
rm -f "$RESULT" "$RESULT.tmp"
printf '[proof-token] %s\n' "$PROOF_TOKEN" >&3
finish() {
local success="$1" reason="$2"
flush_outputs || { success=false; reason=diagnostic_log_budget_exceeded; }
printf '[proof-result] %s: %s\n' "$success" "$reason" >&3
printf '{"token":"%s","success":%s,"reason":"%s","osVersion":"%s","architecture":"%s","uid":%s,"systemExit":%s,"diskArbitrationExit":%s,"recoveryExit":%s,"diskListExit":%s,"disk":"%s","diskBytes":%s,"readOnly":false}\n' \
"$PROOF_TOKEN" "$success" "$reason" "$os_version" "$architecture" "$uid" \
"$system_exit" "$arbitration_exit" "$recovery_exit" "$disk_list_exit" \
"$selected_disk" "$disk_bytes" > "$RESULT.tmp"
/bin/mv -f "$RESULT.tmp" "$RESULT" || exit 1
exec 9>&-
[ ! -p "$TIMER_FIFO" ] || /bin/rm -f "$TIMER_FIFO"
# Keep the service alive for the bounded host diagnostic to capture evidence.
while :; do sleep 60; done
}
init_timer_fifo() {
# Recovery has Bash 3.2 before any SDK is installed. Its read timeout uses
# alarm(), avoiding a separate sleep process for every command and grace period.
[ ! -e "$TIMER_FIFO" ] || exit 1
/usr/bin/mkfifo -m 600 "$TIMER_FIFO" || exit 1
exec 9<> "$TIMER_FIFO" || exit 1
}
flush_outputs() {
(( ${#PENDING_OUTPUTS[@]} > 0 )) || return 0
local started=$SECONDS sizes="/tmp/native-diagnostic-$$.sizes" proof_size output_size raw_size
local raw_count=0 raw_valid=1 pending_count=${#PENDING_OUTPUTS[@]}
local bounded="/tmp/native-diagnostic-$$.flush"
# One bounded native copy per group, rather than tail/stat startup per command.
# Keep native byte-oriented copying: Bash 3.2 read -n would read large outputs
# one byte per system call. Small scalar reads below have a separate tight bound.
/usr/bin/tail -c "$MAX_OUTPUT_BYTES" "${PENDING_OUTPUTS[@]}" > "$bounded" || return 1
/usr/bin/stat -f '%z' "$PROOF_LOG" "$bounded" "${PENDING_OUTPUTS[@]}" > "$sizes" || return 1
{
IFS= read -r proof_size; IFS= read -r output_size
while IFS= read -r raw_size; do
raw_count=$((raw_count + 1))
[[ "$raw_size" =~ ^[0-9]+$ ]] && (( raw_size <= MAX_OUTPUT_BYTES )) || raw_valid=0
done
} < "$sizes"
PENDING_OUTPUTS=()
[[ "$proof_size" =~ ^[0-9]+$ && "$output_size" =~ ^[0-9]+$ ]] || return 1
(( raw_valid == 1 && raw_count == pending_count )) || return 1
(( proof_size + output_size + 1024 <= MAX_LOG_BYTES )) || return 1
/bin/cat "$bounded" >&3 || return 1
printf '\n[proof-flush] outputs-bytes=%s elapsed=%ss\n' "$output_size" "$((SECONDS - started))" >&3
}
read_scalar() {
local value status
# All three values are short native machine/uid/version scalars. Reject excess
# content instead of accepting a truncated first line as a successful gate.
IFS= read -r -n 65 -d '' value < "$LAST_OUTPUT"; status=$?
# EOF is mandatory: the byte bound or a NUL delimiter must never hide a suffix.
(( status == 1 && ${#value} < 65 )) || return 1
value=${value%$'\n'}
[[ "$value" != *$'\n'* ]] || return 1
SCALAR="$value"
}
# BEGIN successful sw_vers version parser
read_product_version() {
local value status line version="" fields=0
# Read the entire successful native output. EOF is mandatory; a NUL delimiter
# or reaching the 1025-byte sentinel must never hide a suffix.
LC_ALL=C IFS= read -r -n 1025 -d '' value < "$LAST_OUTPUT"; status=$?
(( status == 1 && ${#value} <= 1024 )) || return 1
while IFS= read -r line || [ -n "$line" ]; do
if [[ "$line" =~ ^[[:blank:]]*ProductVersion: ]]; then
fields=$((fields + 1))
(( fields == 1 )) || return 1
[[ "$line" =~ ^[[:blank:]]*ProductVersion:[[:blank:]]*([0-9]+\.[0-9]+(\.[0-9]+)?)[[:blank:]]*$ ]] || return 1
version="${BASH_REMATCH[1]}"
fi
done <<< "$value"
(( fields == 1 )) || return 1
SCALAR="$version"
}
# END successful sw_vers version parser
# BEGIN disk IPC diagnostic
# Optional observations have their own child/timer ownership. Thread state/time
# targets only this probe's diskutil and does not request stack symbolication.
observe_disk_query() {
local disk_process="$1" output="$2" observation_child="" observation_timer=""
cancel_observation() {
trap '' TERM INT
if [ -n "$observation_child" ]; then
kill -TERM "$observation_child" 2>/dev/null || :
IFS= read -r -t 2 -u 9 unused || :
kill -KILL "$observation_child" 2>/dev/null || :
wait "$observation_child" 2>/dev/null || :
fi
[ -z "$observation_timer" ] || { kill -TERM "$observation_timer" 2>/dev/null || :; wait "$observation_timer" 2>/dev/null || :; }
printf '[disk-observation] stopped after the owned disk query\n' >> "$output"
exit 143
}
observe_command() {
local name="$1" status started=$SECONDS
shift
printf '\n[disk-observation-command] %s:' "$name" >> "$output"
printf ' %s' "$@" >> "$output"
printf '\n' >> "$output"
"$@" >> "$output" 2>&1 &
observation_child=$!
(
trap 'exit 0' TERM INT
IFS= read -r -t 60 -u 9 unused || :
printf '[disk-observation-timeout] %s child=%s limit=60s\n' "$name" "$observation_child" >> "$output"
kill -TERM "$observation_child" 2>/dev/null || :
IFS= read -r -t 2 -u 9 unused || :
kill -KILL "$observation_child" 2>/dev/null || :
) &
observation_timer=$!
wait "$observation_child"; status=$?
kill -TERM "$observation_timer" 2>/dev/null || :
wait "$observation_timer" 2>/dev/null || :
printf '[disk-observation-exit] %s status=%s elapsed=%ss\n' "$name" "$status" "$((SECONDS - started))" >> "$output"
observation_child=""; observation_timer=""
}
trap cancel_observation TERM INT
printf '[disk-observation] owned-diskutil-child=%s parent-shell=%s\n' "$disk_process" "$$" >> "$output"
if [ -x /bin/ps ]; then
if kill -0 "$disk_process" 2>/dev/null; then
observe_command diskutil-threads /bin/ps -M -p "$disk_process"
else
printf '[disk-observation-unavailable] diskutil already exited before thread observation\n' >> "$output"
fi
else
printf '[disk-observation-unavailable] /bin/ps is unavailable\n' >> "$output"
fi
}
stop_disk_observation() {
[ -n "$ACTIVE_OBSERVER" ] || return 0
kill -TERM "$ACTIVE_OBSERVER" 2>/dev/null || :
wait "$ACTIVE_OBSERVER" 2>/dev/null || :
ACTIVE_OBSERVER=""
}
# END disk IPC diagnostic
cancel_probe() {
trap '' TERM INT
# BEGIN disk IPC diagnostic
stop_disk_observation
# END disk IPC diagnostic
if [ -n "$ACTIVE_COMMAND" ]; then
kill -TERM "$ACTIVE_COMMAND" 2>/dev/null || :
IFS= read -r -t 2 -u 9 unused || :
kill -KILL "$ACTIVE_COMMAND" 2>/dev/null || :
wait "$ACTIVE_COMMAND" 2>/dev/null || :
fi
[ -z "$ACTIVE_TIMER" ] || { kill -TERM "$ACTIVE_TIMER" 2>/dev/null || :; wait "$ACTIVE_TIMER" 2>/dev/null || :; }
ACTIVE_COMMAND=""; ACTIVE_TIMER=""
finish false probe_cancelled
}
run_command() {
local name="$1"
shift
local process timer exit_code started waited command_limit=45
# Run 4161: even native uname/ps startup took 34-42s under TCG.
# Isolate only the failed UID gate; every other watchdog remains unchanged.
[[ "$name" != uid ]] || command_limit=180
# BEGIN disk IPC diagnostic
if [[ "$name" == disks && "${attempt:-0}" == 1 ]]; then command_limit=120; fi
# END disk IPC diagnostic
LAST_OUTPUT="/tmp/native-diagnostic-$name.out"
printf '\n[proof-command] %s:' "$name" >&3
printf ' %s' "$@" >&3
printf '\n' >&3
started=$SECONDS
"$@" > "$LAST_OUTPUT" 2>&1 &
process=$!
ACTIVE_COMMAND="$process"
printf '[proof-start] %s child=%s shell=%s parent=%s seconds=%s\n' "$name" "$process" "$$" "$PPID" "$started" >&3
printf '[proof-limit] %s %ss\n' "$name" "$command_limit" >&3
(
trap 'exit 0' TERM INT
IFS= read -r -t "$command_limit" -u 9 unused || :
printf '[proof-timeout] %s child=%s elapsed=%ss signal=TERM\n' "$name" "$process" "$((SECONDS - started))" >&3
kill -TERM "$process" 2>/dev/null || :
IFS= read -r -t 2 -u 9 unused || :
kill -KILL "$process" 2>/dev/null || :
) &
timer=$!
ACTIVE_TIMER="$timer"
# BEGIN disk IPC diagnostic
if [[ "$name" == disks && "$attempt" == 1 ]]; then
local observation_output="/tmp/native-diagnostic-disk-observation.out"
: > "$observation_output"
observe_disk_query "$process" "$observation_output" &
ACTIVE_OBSERVER=$!
printf '[disk-observation-start] observer=%s owned-diskutil-child=%s\n' "$ACTIVE_OBSERVER" "$process" >&3
PENDING_OUTPUTS+=("$observation_output")
fi
# END disk IPC diagnostic
wait "$process"
exit_code=$?
waited=$SECONDS
# Includes fork/exec/wait, but excludes timer cleanup and evidence copying.
printf '[proof-native-wait] %s child=%s elapsed=%ss exit=%s\n' "$name" "$process" "$((waited - started))" "$exit_code" >&3
kill -TERM "$timer" 2>/dev/null || :
wait "$timer" 2>/dev/null || :
# BEGIN disk IPC diagnostic
stop_disk_observation
# END disk IPC diagnostic
ACTIVE_COMMAND=""; ACTIVE_TIMER=""
printf '[proof-cleanup] %s child=%s elapsed=%ss total=%ss\n' "$name" "$process" "$((SECONDS - waited))" "$((SECONDS - started))" >&3
printf '[proof-exit] %s\n' "$exit_code" >&3
LAST_EXIT="$exit_code"
PENDING_OUTPUTS+=("$LAST_OUTPUT")
return 0
}
diagnose_failure() {
run_command kernel /usr/bin/uname -a
run_command account /usr/bin/id
run_command context /usr/sbin/sysctl kern.bootargs machdep.cpu.brand_string machdep.cpu.features machdep.cpu.leaf7_features
run_command parent /bin/ps -p "$$" -p "$PPID" -o pid=,ppid=,comm=
run_command processes /bin/ps -axo pid,ppid,comm
}
fail_probe() {
local reason="$1"
flush_outputs || finish false diagnostic_log_budget_exceeded
diagnose_failure
finish false "$reason"
}
init_timer_fifo
trap cancel_probe TERM INT
# Test the required native gates before optional process/CPU diagnostics.
run_command architecture /usr/bin/uname -m
(( LAST_EXIT == 0 )) || fail_probe architecture_probe_failed
read_scalar || fail_probe architecture_output_invalid
architecture="$SCALAR"
[ "$architecture" = x86_64 ] || fail_probe unexpected_guest_architecture
run_command uid /usr/bin/id -u
(( LAST_EXIT == 0 )) || fail_probe uid_probe_failed
read_scalar || fail_probe uid_output_invalid
uid="$SCALAR"
[ "$uid" = 0 ] || fail_probe recovery_account_not_root
run_command platform /usr/bin/sw_vers
platform_exit="$LAST_EXIT"
flush_outputs || finish false diagnostic_log_budget_exceeded
if (( platform_exit != 0 )); then
run_command system /bin/launchctl print system
system_exit="$LAST_EXIT"
run_command arbitration /bin/launchctl print system/com.apple.diskarbitrationd
arbitration_exit="$LAST_EXIT"
run_command recovery /bin/launchctl print system/com.apple.recoveryosd
recovery_exit="$LAST_EXIT"
printf '[proof-retry] sw_vers once after native service context; same 45-second deadline\n' >&3
run_command platform-warm /usr/bin/sw_vers
platform_exit="$LAST_EXIT"
fi
(( platform_exit == 0 )) || fail_probe sw_vers_failed
# BEGIN successful sw_vers version extraction
read_product_version || fail_probe product_version_invalid
# END successful sw_vers version extraction
os_version="$SCALAR"
[[ "$os_version" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]] || fail_probe product_version_invalid
(( ${os_version%%.*} >= 14 )) || fail_probe unsupported_macos_version
flush_outputs || finish false diagnostic_log_budget_exceeded
# BEGIN disk IPC diagnostic
run_command arbitration_before /bin/launchctl print system/com.apple.diskarbitrationd
run_command management_before /bin/launchctl print system/com.apple.diskmanagementd
run_command media_before /usr/sbin/ioreg -r -c IOMedia -l -w 0
flush_outputs || finish false diagnostic_log_budget_exceeded
# END disk IPC diagnostic
# Bound readiness independently of the host's 40-minute overall deadline.
readiness_start=$SECONDS
attempt=0
while (( attempt < 1 && SECONDS - readiness_start < 600 )); do
attempt=$((attempt + 1))
printf '\n[readiness-attempt] %s\n' "$attempt" >&3
run_command disks /usr/sbin/diskutil list physical
disk_list_exit="$LAST_EXIT"
if (( disk_list_exit == 0 )); then
disk_list=$(cat "$LAST_OUTPUT")
candidates=0
while IFS= read -r disk; do
[ -n "$disk" ] || continue
run_command "info-$disk" /usr/sbin/diskutil info "/dev/$disk"
(( LAST_EXIT == 0 )) || continue
info=$(cat "$LAST_OUTPUT")
if printf '%s\n' "$info" | grep -Eq '^[[:space:]]*(Read-Only (Media|Device)|(Media|Device) Read-Only):[[:space:]]*Yes'; then
continue
fi
printf '%s\n' "$info" | grep -Eq '^[[:space:]]*(Read-Only (Media|Device)|(Media|Device) Read-Only):[[:space:]]*No' || continue
size=$(printf '%s\n' "$info" | sed -nE 's/^[[:space:]]*Disk Size:.*\(([0-9]+) Bytes\).*/\1/p' | head -n 1)
[[ "$size" =~ ^[0-9]+$ ]] || continue
(( size == EXPECTED_BYTES )) || continue
candidates=$((candidates + 1))
selected_disk="/dev/$disk"
disk_bytes="$size"
printf '[writable-target] %s %s bytes\n' "$selected_disk" "$disk_bytes" >&3
done < <(printf '%s\n' "$disk_list" | sed -nE 's#^/dev/(disk[0-9]+).*#\1#p')
(( candidates <= 1 )) || fail_probe ambiguous_writable_64g_disks
if (( candidates == 1 )); then
# Re-probe live launchd domains after disk readiness, preserving native exits.
run_command system_ready /bin/launchctl print system
system_exit="$LAST_EXIT"
run_command arbitration_ready /bin/launchctl print system/com.apple.diskarbitrationd
arbitration_exit="$LAST_EXIT"
run_command recovery_ready /bin/launchctl print system/com.apple.recoveryosd
recovery_exit="$LAST_EXIT"
(( system_exit == 0 && arbitration_exit == 0 && recovery_exit == 0 )) || fail_probe service_domain_not_ready
finish true native_recovery_and_writable_64g_disk_ready
fi
fi
flush_outputs || finish false diagnostic_log_budget_exceeded
IFS= read -r -t 5 -u 9 unused || :
done
fail_probe disk_management_or_writable_target_not_ready
+181
View File
@@ -0,0 +1,181 @@
"""Checksum binding for the pinned Linux Recovery UDIF patcher, not a new CLI.
Source semantics: planetbeing/libdmg-hfsplus dmg/dmglib.c and dmg/blkx.c.
Only flattened, single-segment XML UDIF with CRC32 and raw/zlib data is accepted.
The caller plans same-length chunk writes; XML formatting and all offsets remain.
"""
import base64
import json
import plistlib
import re
import struct
import zlib
def u32(data, offset):
return struct.unpack_from(">I", data, offset)[0]
def u64(data, offset):
return struct.unpack_from(">Q", data, offset)[0]
def crc_contract(data, offset):
if u32(data, offset) != 2 or u32(data, offset + 4) != 32 or any(data[offset + 12:offset + 136]):
raise RuntimeError("Unsupported UDIF checksum type/size/padding")
return u32(data, offset + 8)
class ChecksumPlan:
def __init__(self, image, koly, plist, xml_offset, xml_length, size):
self.image, self.koly, self.plist = image, koly, plist
self.xml_offset, self.xml_length, self.size = xml_offset, xml_length, size
self.data_offset, self.data_length = u64(koly, 24), u64(koly, 32)
if (u32(koly, 4) != 4 or u32(koly, 8) != 512 or u32(koly, 12) != 1
or self.data_offset != 0 or u64(koly, 40) or u64(koly, 48)
or u32(koly, 60) not in (0, 1) or self.data_length != xml_offset
or xml_offset + xml_length > size - 512 or xml_length > 8 * 1024 * 1024):
raise RuntimeError("Unsupported or out-of-bounds flattened UDIF layout")
crc_contract(koly, 80)
crc_contract(koly, 352)
image.seek(xml_offset)
self.xml = image.read(xml_length)
if len(self.xml) != xml_length or not self.xml.lstrip().startswith(b"<?xml"):
raise RuntimeError("Unsupported UDIF metadata framing")
self.blocks = plist["resource-fork"]["blkx"]
self.physical_runs = {}
intervals = []
for block in self.blocks:
mish = block["Data"]
if len(mish) < 244 or mish[:4] != b"mish" or (len(mish) - 204) % 40 or u32(mish, 200) != (len(mish) - 204) // 40:
raise RuntimeError("Malformed UDIF block table")
crc_contract(mish, 64)
if u64(mish, 8) + u64(mish, 16) > u64(koly, 492):
raise RuntimeError("UDIF partition exceeds logical disk boundary")
count = u32(mish, 200)
if u32(mish, 204 + (count - 1) * 40) != 0xffffffff:
raise RuntimeError("UDIF block table has no final terminator")
for kind, offset, length, sectors in self.runs(mish):
if kind in (2, 0x7ffffffe, 0xffffffff):
if length:
raise RuntimeError("Non-data UDIF run has stored bytes")
continue
if kind not in (1, 0x80000005) or not sectors or length <= 0 or sectors * 512 > 32 * 1024 * 1024:
raise RuntimeError("Unsupported UDIF compression/run boundary")
if offset < self.data_offset or offset + length > self.data_offset + self.data_length:
raise RuntimeError("UDIF data run exceeds data-fork boundary")
intervals.append((offset, offset + length))
self.physical_runs[offset] = length
intervals.sort()
if any(left[1] > right[0] for left, right in zip(intervals, intervals[1:])):
raise RuntimeError("Overlapping UDIF physical data runs")
def runs(self, mish):
for entry in range(204, len(mish), 40):
kind = u32(mish, entry)
sector, sectors = u64(mish, entry + 8), u64(mish, entry + 16)
if sector + sectors > u64(mish, 16):
raise RuntimeError("UDIF run exceeds its partition boundary")
offset = self.data_offset + u64(mish, 24) + u64(mish, entry + 24)
yield kind, offset, u64(mish, entry + 32), sectors
def read(self, offset, length):
self.image.seek(offset)
result = self.image.read(length)
if len(result) != length:
raise RuntimeError("Short UDIF checksum read")
return result
def logical_crcs(self, mish, replacements):
original_crc = patched_crc = 0
for kind, offset, length, sectors in self.runs(mish):
# IGNORE runs are excluded by the independently verified Apple 13
# baseline. Unknown ZERO/compression types are rejected above.
if kind not in (1, 0x80000005):
continue
old = self.read(offset, length)
new = replacements.get(offset, old)
old_decoded = old if kind == 1 else zlib.decompress(old)
new_decoded = new if kind == 1 else zlib.decompress(new)
if len(old_decoded) != sectors * 512 or len(new_decoded) != sectors * 512 or len(old) != len(new):
raise RuntimeError("UDIF checksum run changed physical/logical extent")
original_crc = zlib.crc32(old_decoded, original_crc)
patched_crc = zlib.crc32(new_decoded, patched_crc)
return original_crc, patched_crc
def data_crcs(self, replacements):
old_crc = new_crc = 0
cursor = self.data_offset
def same_until(stop):
nonlocal cursor, old_crc, new_crc
while cursor < stop:
data = self.read(cursor, min(1024 * 1024, stop - cursor))
old_crc, new_crc = zlib.crc32(data, old_crc), zlib.crc32(data, new_crc)
cursor += len(data)
for offset, new in sorted(replacements.items()):
same_until(offset)
old = self.read(offset, len(new))
old_crc, new_crc = zlib.crc32(old, old_crc), zlib.crc32(new, new_crc)
cursor += len(new)
same_until(self.data_offset + self.data_length)
return old_crc, new_crc
def prepare(self, planned):
replacements = dict(planned)
if len(replacements) != len(planned):
raise RuntimeError("Duplicate planned UDIF physical writes")
if any(self.physical_runs.get(offset) != len(data) for offset, data in replacements.items()):
raise RuntimeError("Planned UDIF write does not preserve an existing data-run boundary")
old_master = bytearray()
new_master = bytearray()
changed = []
for block in self.blocks:
mish = block["Data"]
old_crc, new_crc = self.logical_crcs(mish, replacements)
if old_crc != crc_contract(mish, 64):
raise RuntimeError("Original UDIF logical CRC32 mismatch")
old_master.extend(struct.pack(">I", old_crc))
new_master.extend(struct.pack(">I", new_crc))
if new_crc != old_crc:
new_mish = bytearray(mish)
struct.pack_into(">I", new_mish, 72, new_crc)
changed.append((mish, bytes(new_mish)))
old_data_crc, new_data_crc = self.data_crcs(replacements)
if old_data_crc != crc_contract(self.koly, 80) or zlib.crc32(old_master) != crc_contract(self.koly, 352):
raise RuntimeError("Original UDIF data-fork/master CRC32 mismatch")
xml = self.xml
for old_mish, new_mish in changed:
matches = [match for match in re.finditer(rb"<data>([\sA-Za-z0-9+/=]*)</data>", xml)
if base64.b64decode(match.group(1)) == old_mish]
if len(matches) != 1:
raise RuntimeError("UDIF block checksum XML identity is ambiguous")
match = matches[0]
encoded = iter(base64.b64encode(new_mish))
text = bytes(value if chr(value).isspace() else next(encoded) for value in match.group(1))
xml = xml[:match.start(1)] + text + xml[match.end(1):]
if len(xml) != self.xml_length:
raise RuntimeError("UDIF checksum update changed XML region length")
new_plist = plistlib.loads(xml)
expected = dict(self.plist)
expected["resource-fork"] = dict(self.plist["resource-fork"])
expected["resource-fork"]["blkx"] = [dict(block, Data=dict(changed).get(block["Data"], block["Data"])) for block in self.blocks]
if new_plist != expected:
raise RuntimeError("UDIF checksum update changed unrelated metadata")
koly = bytearray(self.koly)
struct.pack_into(">I", koly, 88, new_data_crc)
struct.pack_into(">I", koly, 360, zlib.crc32(new_master))
self.receipt = dict(originalDataCrc32=f"{old_data_crc:08x}", patchedDataCrc32=f"{new_data_crc:08x}",
originalMasterCrc32=f"{zlib.crc32(old_master):08x}", patchedMasterCrc32=f"{zlib.crc32(new_master):08x}",
changedBlockChecksums=len(changed), imageBytes=self.size, xmlOffset=self.xml_offset,
xmlBytes=self.xml_length, physicalAndLogicalExtentsPreserved=True)
return xml, bytes(koly)
def verify(self):
koly = self.read(self.size - 512, 512)
xml = self.read(self.xml_offset, self.xml_length)
verifier = ChecksumPlan(self.image, koly, plistlib.loads(xml), self.xml_offset, self.xml_length, self.size)
verifier.prepare([])
self.image.seek(0, 2)
if self.image.tell() != self.size:
raise RuntimeError("Patched UDIF image length changed")
print("[recovery-udif] " + json.dumps(dict(self.receipt, readBackChecksumsVerified=True), sort_keys=True))
+79
View File
@@ -0,0 +1,79 @@
; Bare 64-KiB BIOS for the existing Linux QEMU binary, not macOS firmware.
; Assemble: nasm -f bin -o ci-cpu-preflight.bin macos-tcg-cpu-preflight.asm
; No disks/network. isa-debug-exit returns 33 only after AVX + AVX2 execute
; and the upper 128-bit lane contains the expected arithmetic result.
; Unsupported instructions/triple faults cannot produce the success code.
BITS 16
ORG 0
start:
cli
cld
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x8000
; QEMU zeroes fresh RAM. Identity-map the first 2 MiB through three tables.
mov dword [0x1000], 0x2003
mov dword [0x2000], 0x3003
mov dword [0x3000], 0x0083
lgdt [cs:gdt_descriptor]
mov eax, 0x40620 ; PAE, OSFXSR, OSXMMEXCPT, OSXSAVE
mov cr4, eax
mov eax, 0x1000
mov cr3, eax
mov ecx, 0xc0000080 ; EFER.LME
rdmsr
or eax, 0x100
wrmsr
mov eax, cr0
and eax, ~0x0c ; clear EM and TS before vector instructions
or eax, 0x80000003 ; paging, protected mode, monitor coprocessor
mov cr0, eax
jmp dword 0x08:(0xf0000 + long_mode)
ALIGN 8
gdt:
dq 0
dq 0x00af9a000000ffff ; ring-0 long-mode code, base 0
dq 0x00cf92000000ffff ; ring-0 data, base 0
gdt_descriptor:
dw gdt_descriptor - gdt - 1
dd 0xf0000 + gdt
BITS 64
long_mode:
mov ax, 0x10
mov ds, ax
mov es, ax
mov ss, ax
mov rsp, 0x8000
xor ecx, ecx
mov eax, 7 ; XCR0 enables x87, SSE and AVX state
xor edx, edx
xsetbv
vxorps ymm0, ymm0, ymm0 ; AVX, including the upper YMM lane
vpcmpeqd ymm1, ymm1, ymm1 ; AVX2: all eight int32 lanes become -1
vpsrld ymm1, ymm1, 31 ; AVX2: all lanes become 1
vpaddd ymm2, ymm1, ymm1 ; AVX2: all lanes become 2
vextracti128 xmm3, ymm2, 1 ; AVX2: inspect the upper half, not only SSE
vmovd eax, xmm3
cmp eax, 2
jne fail
vzeroupper
mov eax, 0x10 ; QEMU debugexit computes (value << 1) | 1
jmp exit_qemu
fail:
mov eax, 0x11
exit_qemu:
mov dx, 0xf4
out dx, eax
hlt
jmp $
; CPU reset starts at the last 16 bytes; reload the real-mode CS base.
BITS 16
TIMES 0xfff0 - ($ - $$) db 0xff
jmp 0xf000:start
TIMES 0x10000 - ($ - $$) db 0xff