forked from Manuel/meeting-assistant
Qualify guest version through one-way raw evidence before permit
This commit is contained in:
@@ -16,6 +16,8 @@ Run 4190 used synchronous kernel serial output and QEMU interrupt/register traci
|
||||
|
||||
Full run 4191 at `9735db1` reached native Recovery: x86_64/root, Darwin 23.6.0 and successful launchd service queries. Both `sw_vers` attempts were stopped by the existing 45-second watchdog at about 50 seconds. Other successful commands took 24–47 seconds, and small log-copy batches took 85–181 seconds. Thus this run proves broad native startup latency and a probe-imposed abort, without proving a permanent `sw_vers` hang. Disk enumeration, installation and application tests were not reached. The next candidate obtains the version from the current guest's SystemVersion plist to reduce process launches; it does not claim that `sw_vers` has become functional.
|
||||
|
||||
Run 4192 at `6122be2` captured the actual 603-byte guest file and strictly parsed version 14.6.1. Its host reply was published successfully, but the guest's reply-existence check timed out before services or disk enumeration. Guest request/timeout UTC timestamps were not retained, so late delivery and 9p visibility cannot be distinguished. The current candidate removes this version reply: the guest publishes its raw file and a provisional version candidate; the host's full XML validation and exact result binding remain mandatory before any installation permit. The later installation-permit transport is still unqualified.
|
||||
|
||||
## 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.
|
||||
@@ -41,9 +43,9 @@ The locally assembled NASM 2.16.03 ROM is 65,536 bytes, SHA256 `c32746122cc68f3e
|
||||
|
||||
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 guest's existing Bash runtime reads its own `/System/Library/CoreServices/SystemVersion.plist` with a 4-KiB bound and mandatory EOF. Apple documents this path as the [system-version source](https://developer.apple.com/documentation/installer_js/system/1812284-version). The existing C# controller parses the captured XML with external resolution disabled, requires a flat string-valued dictionary with exactly one valid direct `ProductVersion` string and macOS 14+, and returns a token-bound answer to that guest. Missing, binary, oversized, ambiguous or malformed content fails. Before installation, the readiness receipt's version must match this current-file evidence. No configured `VERSION` or host OS value serves as proof. The actual native diskutil query remains mandatory.
|
||||
Native readiness requires x86_64, UID 0, macOS 14+, successful launchd service queries and exactly one writable whole 64-GiB disk. The guest's existing Bash runtime reads its own `/System/Library/CoreServices/SystemVersion.plist` with a 4-KiB bound and mandatory EOF. Apple documents this path as the [system-version source](https://developer.apple.com/documentation/installer_js/system/1812284-version). Bash extracts only a provisional numeric version from the same bytes it publishes; subsequent guest probes remain read-only. The existing C# controller parses the full captured XML with external resolution disabled and requires a flat string-valued dictionary with exactly one valid direct `ProductVersion` string and macOS 14+. Missing, binary, oversized, ambiguous or malformed content fails. Before either readiness success or an installation permit, the result's version must exactly match this current-file evidence. No configured `VERSION` or host OS value serves as proof. The actual native diskutil query remains mandatory.
|
||||
|
||||
The raw file, exact source path, length, SHA256 and parsing receipt are retained. The guest exchange uses its existing Bash before any SDK exists; the XML logic stays in C#/.NET. Its reply wait has a 180-second bound and requires the current token, bounded complete content and a valid version. This method establishes the current guest version, not successful execution of `sw_vers`.
|
||||
The raw file, exact source path, length, SHA256 and parsing receipt are retained and bound to the current token. This version evidence travels only from guest to host and requires no reply. The guest uses its existing Bash before any SDK exists; authoritative XML logic stays in C#/.NET. A Bash candidate alone cannot authorize installation or qualify readiness. This method establishes the current guest version, not successful execution of `sw_vers`.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -86,11 +86,13 @@ static class NativeDiagnostic
|
||||
{
|
||||
success = true, profile = Profile, mode = full ? "full" : "readiness",
|
||||
helperSha256 = Hash(File.ReadAllBytes(Path.Combine("tools", "ci", "MacOsNativeDiagnostic.cs"))),
|
||||
readinessSourceSha256 = Hash(File.ReadAllBytes(Path.Combine("tools", "ci", "macos-native-readiness.sh"))),
|
||||
udifChecksumBindingSha256 = Hash(File.ReadAllBytes(Path.Combine("tools", "ci", "macos-native-udif-checksums.py"))),
|
||||
baselineReadinessNormalized = true, readinessDiagnosticBlocksExcluded = 7,
|
||||
nativeVersionGetterBlocksExcluded = 2, nativeVersionGetterSequenceRestored = true,
|
||||
nativeVersionMethod = NativeVersionMethod, nativeVersionSource = NativeVersionSource,
|
||||
nativeVersionMaximumBytes = 4096, nativeVersionReplyLimitSeconds = 180,
|
||||
nativeVersionMaximumBytes = 4096, nativeVersionResponseRequired = false,
|
||||
nativeVersionCandidateIsQualifiedReadiness = false, nativeVersionBindingRequiredBeforePermit = true,
|
||||
nativeVersionFixtures = "native-system-version-fixtures.json", nativeProductVersionCommandRemoved = true,
|
||||
diskReadinessAttemptLimit = 1, diskCommandLimitSeconds = 120, diskThreadObservationLimitSeconds = 60, stackSamplingUsed = false,
|
||||
resultNegativeCases = 6, containerNegativeCases = 11, recoveryPositiveCases = 4, recoveryNegativeCases = 12,
|
||||
@@ -1227,7 +1229,7 @@ static class NativeDiagnostic
|
||||
var ready = await Command("docker", ["exec", id, "head", "-c", "257", state + "/native-system-version.request"], output, "capture-native-version-request", cancellation, requireSuccess: false);
|
||||
if (ready.ExitCode != 0 || string.IsNullOrEmpty(ready.Output)) return;
|
||||
// The built-in producer publishes the final marker after closing raw bytes.
|
||||
// A still incomplete marker remains pending; its guest wait is bounded.
|
||||
// A still incomplete marker remains pending within the outer Recovery deadline.
|
||||
var requestFields = ready.Output.Split('\n');
|
||||
if (ready.Output.Length <= 256 && (!ready.Output.EndsWith('\n') || requestFields.Length < 5 || !requestFields[^2].StartsWith("ready:", StringComparison.Ordinal))) return;
|
||||
File.WriteAllText(Path.Combine(output, "native-system-version.request"), ready.Output, new UTF8Encoding(false));
|
||||
@@ -1249,16 +1251,14 @@ static class NativeDiagnostic
|
||||
error = exception.Message;
|
||||
}
|
||||
Save(evidencePath, new NativeVersionEvidence(token, NativeVersionSource, raw.Length, Hash(raw), NativeVersionMethod, version, error is null, started.Elapsed.TotalMilliseconds, DateTimeOffset.UtcNow, error));
|
||||
var response = token + (error is null ? ":" + version : ":error:invalid_native_system_version") + "\n";
|
||||
var reply = Path.Combine(output, "native-system-version.reply");
|
||||
File.WriteAllText(reply, response, new UTF8Encoding(false));
|
||||
await Command("docker", ["cp", reply, id + ":" + state + "/native-system-version.reply.tmp"], output, "stage-native-version-reply", cancellation);
|
||||
await Command("docker", ["exec", id, "mv", state + "/native-system-version.reply.tmp", state + "/native-system-version.reply"], output, "publish-native-version-reply", cancellation);
|
||||
Console.WriteLine(error is null ? "[native-version] method=" + NativeVersionMethod + " source=" + NativeVersionSource + " version=" + version : "[native-version] method=" + NativeVersionMethod + " source=" + NativeVersionSource + " failed: invalid native file/request; see raw evidence.");
|
||||
if (error is not null) throw new InvalidOperationException("Native guest-file XML evidence failed; no installation permit: " + error);
|
||||
}
|
||||
|
||||
static void ValidateNativeVersionBinding(string output, string token, string readiness)
|
||||
{
|
||||
if (new[] { "native-system-version.json", "native-system-version.plist", "native-system-version.request" }.Any(name => !File.Exists(Path.Combine(output, name))))
|
||||
throw new InvalidOperationException("Native guest-file evidence is incomplete; no installation permit.");
|
||||
using var evidence = JsonDocument.Parse(File.ReadAllText(Path.Combine(output, "native-system-version.json")));
|
||||
using var result = JsonDocument.Parse(readiness);
|
||||
var raw = File.ReadAllBytes(Path.Combine(output, "native-system-version.plist"));
|
||||
@@ -1279,6 +1279,29 @@ static class NativeDiagnostic
|
||||
var fixture = Path.Combine(output, "validation-native-system-version");
|
||||
Directory.CreateDirectory(fixture);
|
||||
const string token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
var canonical = Encoding.UTF8.GetBytes("""
|
||||
<?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>BuildID</key>
|
||||
\t<string>5B24CC0E-5244-11EF-A61B-8668C9DC12C6</string>
|
||||
\t<key>ProductBuildVersion</key>
|
||||
\t<string>23G93</string>
|
||||
\t<key>ProductCopyright</key>
|
||||
\t<string>1983-2024 Apple Inc.</string>
|
||||
\t<key>ProductName</key>
|
||||
\t<string>macOS</string>
|
||||
\t<key>ProductUserVisibleVersion</key>
|
||||
\t<string>14.6.1</string>
|
||||
\t<key>ProductVersion</key>
|
||||
\t<string>14.6.1</string>
|
||||
\t<key>iOSSupportVersion</key>
|
||||
\t<string>17.6</string>
|
||||
</dict>
|
||||
</plist>
|
||||
""".Replace("\\t", "\t", StringComparison.Ordinal) + "\n");
|
||||
if (canonical.Length != 603 || Hash(canonical) != "0a652705e311f0346f7570007651bc13f4c98670950d376ddc21e3a567016b2b") throw new InvalidOperationException("Actual run4192 canonical SystemVersion fixture bytes differ.");
|
||||
var valid = Encoding.UTF8.GetBytes("<?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><key>ProductName</key><string>macOS</string><key>ProductVersion</key><string>14.6</string></dict></plist>\n");
|
||||
string Request(byte[] raw) => token + "\n" + NativeVersionSource + "\n" + raw.Length + "\nready:" + token + "\n";
|
||||
byte[] Changed(string from, string to) => Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(valid).Replace(from, to, StringComparison.Ordinal));
|
||||
@@ -1287,6 +1310,7 @@ static class NativeDiagnostic
|
||||
var entity = Encoding.UTF8.GetBytes("<!DOCTYPE plist [<!ENTITY version SYSTEM 'file:///must-never-be-read'>]><plist version='1.0'><dict><key>ProductVersion</key><string>&version;</string></dict></plist>");
|
||||
(string Name, byte[] Raw, string Request, string? Version)[] parserCases =
|
||||
{
|
||||
("actual-canonical603", canonical, Request(canonical), (string?)"14.6.1"),
|
||||
(Name: "valid14", Raw: valid, Request: Request(valid), Version: (string?)"14.6"),
|
||||
("valid14patch", Changed("14.6", "14.6.1"), Request(Changed("14.6", "14.6.1")), (string?)"14.6.1"),
|
||||
("duplicate", duplicate, Request(duplicate), (string?)null),
|
||||
@@ -1349,23 +1373,35 @@ static class NativeDiagnostic
|
||||
var to = readiness.IndexOf(end, from, StringComparison.Ordinal);
|
||||
var producer = readiness[from..to];
|
||||
var mapped = ReplaceOnce(ReplaceOnce(producer, "[ -f \"$source\" ]", "[ -f \"$SYSTEM_VERSION_FIXTURE\" ]"), "< \"$source\"", "< \"$SYSTEM_VERSION_FIXTURE\"");
|
||||
(string Name, byte[]? Raw, string Owner, string Existing, string Reply, bool Success, bool Request)[] shellCases =
|
||||
var gateStart = readiness.IndexOf("# END native SystemVersion plist getter\n", StringComparison.Ordinal) + "# END native SystemVersion plist getter\n".Length;
|
||||
var candidateGate = readiness[gateStart..readiness.IndexOf("flush_outputs || finish false diagnostic_log_budget_exceeded", gateStart, StringComparison.Ordinal)];
|
||||
var disagreement = Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(canonical).Replace("<dict>", "<dict><!--<key>ProductVersion</key><string>14.6</string>-->", StringComparison.Ordinal));
|
||||
(string Name, byte[]? Raw, string Owner, string Existing, string? Candidate, bool Readonly, bool Request, bool Permit)[] shellCases =
|
||||
{
|
||||
(Name: "valid", Raw: (byte[]?)valid, Owner: token, Existing: "", Reply: "valid", Success: true, Request: true),
|
||||
("missing", (byte[]?)null, token, "", "none", false, false),
|
||||
("nul", valid.Concat(new byte[] { 0 }).ToArray(), token, "", "none", false, false),
|
||||
("control-binary", valid.Concat(new byte[] { 1 }).ToArray(), token, "", "none", false, false),
|
||||
("oversize", Enumerable.Repeat((byte)'x', 4097).ToArray(), token, "", "none", false, false),
|
||||
("foreign-owner", valid, new string('b', 32), "", "none", false, false),
|
||||
("existing-request", valid, token, "request", "none", false, false),
|
||||
("existing-reply", valid, token, "reply", "none", false, false),
|
||||
("stale-reply", valid, token, "", "stale", false, true),
|
||||
("reply-bad-eof", valid, token, "", "bad-eof", false, true),
|
||||
("reply-overbound", valid, token, "", "overbound", false, true),
|
||||
("reply-nul", valid, token, "", "nul", false, true),
|
||||
("host-xml-error", "bplist00"u8.ToArray(), token, "", "valid", false, true),
|
||||
("reply-timeout", valid, token, "", "none", false, true)
|
||||
("actual-canonical603", canonical, token, "", "14.6.1", true, true, true),
|
||||
("valid-minified14", valid, token, "", "14.6", true, true, true),
|
||||
("missing", null, token, "", null, false, false, false),
|
||||
("nul", valid.Concat(new byte[] { 0 }).ToArray(), token, "", null, false, false, false),
|
||||
("control-binary", valid.Concat(new byte[] { 1 }).ToArray(), token, "", null, false, false, false),
|
||||
("oversize", Enumerable.Repeat((byte)'x', 4097).ToArray(), token, "", null, false, false, false),
|
||||
("stale-owner", valid, new string('b', 32), "", null, false, false, false),
|
||||
("stale-request", valid, token, "request", null, false, false, false),
|
||||
("stale-raw", valid, token, "plist", null, false, false, false),
|
||||
("numeric-duplicate", duplicate, token, "", "14.6", true, true, false),
|
||||
("numeric-nested", nested, token, "", "14.6", true, true, false),
|
||||
("numeric-bogus", Changed("<string>macOS</string>", "<bogus/>"), token, "", "14.6", true, true, false),
|
||||
("version-disagreement", disagreement, token, "", "14.6", true, true, false),
|
||||
("below14", Changed("14.6", "13.6"), token, "", "13.6", false, true, false),
|
||||
("candidate-invalid", Changed("14.6", "14.6junk"), token, "", null, false, true, false),
|
||||
("binary-plist", "bplist00"u8.ToArray(), token, "", null, false, true, false)
|
||||
};
|
||||
bool Eligible(string state, string candidate)
|
||||
{
|
||||
var result = JsonSerializer.Serialize(new { token, success = true, osVersion = candidate, architecture = "x86_64", uid = 0, disk = "/dev/disk1", diskBytes = GuestDiskBytes, readOnly = false, systemExit = 0, diskArbitrationExit = 0, recoveryExit = 0, diskListExit = 0 });
|
||||
try { ValidateResult(result, token); ValidateNativeVersionBinding(state, token, result); return true; }
|
||||
catch (Exception exception) when (exception is InvalidOperationException or XmlException or DecoderFallbackException) { return false; }
|
||||
}
|
||||
if (Eligible(Path.Combine(fixture, "absent-evidence"), "14.6")) throw new InvalidOperationException("Early numeric readiness result bypassed missing evidence.");
|
||||
var shellReceipts = new List<object>();
|
||||
foreach (var test in shellCases)
|
||||
{
|
||||
@@ -1375,42 +1411,32 @@ static class NativeDiagnostic
|
||||
if (test.Raw is not null) File.WriteAllBytes(raw, test.Raw);
|
||||
File.WriteAllText(Path.Combine(state, "run.owner"), test.Owner + "\n");
|
||||
if (test.Existing != "") File.WriteAllText(Path.Combine(state, "native-system-version." + test.Existing), "stale\n");
|
||||
await Command("/usr/bin/mkfifo", ["-m", "600", Path.Combine(state, "wait.fifo")], output, "native-version-fifo-" + test.Name, CancellationToken.None);
|
||||
var body = test.Name == "reply-timeout" ? ReplaceOnce(mapped, "SECONDS - started < 180", "SECONDS - started < 1") : mapped;
|
||||
var script = "set -u\nSTATE_DIR=\"$1\"; SYSTEM_VERSION_FIXTURE=\"$2\"; PROOF_TOKEN=\"$3\"; SCALAR=\"\"\nexec 3> \"$STATE_DIR/proof.log\"\nexec 9<> \"$STATE_DIR/wait.fifo\"\n" + body + "\nif read_native_system_version; then printf 'disk-boundary\\n' > \"$STATE_DIR/disk-boundary\"; exit 0; else printf 'failed:%s\\n' \"$NATIVE_VERSION_ERROR\"; exit 1; fi\n";
|
||||
var script = "set -u\nSTATE_DIR=\"$1\"; SYSTEM_VERSION_FIXTURE=\"$2\"; PROOF_TOKEN=\"$3\"; SCALAR=\"\"\nexec 3> \"$STATE_DIR/proof.log\"\nfail_probe() { printf 'failed:%s\\n' \"$1\"; exit 1; }\n" + mapped + "\nif read_native_system_version; then printf '%s\\n' \"$SCALAR\" > \"$STATE_DIR/candidate\"\n" + candidateGate + "printf 'readonly-boundary\\n' > \"$STATE_DIR/readonly-boundary\"; exit 0; else printf 'failed:%s\\n' \"$NATIVE_VERSION_ERROR\"; exit 1; fi\n";
|
||||
File.WriteAllText(Path.Combine(state, "producer.sh"), script);
|
||||
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(20));
|
||||
var child = Command("/bin/bash", ["-c", script, "native-version-producer-fixture", state, raw, token], output, "native-version-producer-" + test.Name, deadline.Token, requireSuccess: false);
|
||||
var result = await Command("/bin/bash", ["-c", script, "native-version-producer-fixture", state, raw, token], output, "native-version-producer-" + test.Name, deadline.Token, requireSuccess: false);
|
||||
var requestFile = Path.Combine(state, "native-system-version.request");
|
||||
string? request = null;
|
||||
while (!child.IsCompleted)
|
||||
{
|
||||
if (File.Exists(requestFile))
|
||||
{
|
||||
var current = File.ReadAllText(requestFile);
|
||||
if (current.EndsWith("\nready:" + token + "\n", StringComparison.Ordinal)) { request = current; break; }
|
||||
}
|
||||
await Task.Delay(10, deadline.Token);
|
||||
}
|
||||
if (request is not null && test.Reply != "none")
|
||||
{
|
||||
string response;
|
||||
try { response = token + ":" + ParseNativeSystemVersion(File.ReadAllBytes(Path.Combine(state, "native-system-version.plist")), request, token) + "\n"; }
|
||||
catch (Exception exception) when (exception is InvalidOperationException or XmlException or DecoderFallbackException) { response = token + ":error:invalid_native_system_version\n"; }
|
||||
response = test.Reply switch { "stale" => response.Replace(token, new string('b', 32), StringComparison.Ordinal), "bad-eof" => response.TrimEnd('\n'), "overbound" => response + new string('x', 81), "nul" => response + "\0", _ => response };
|
||||
var reply = Path.Combine(state, "native-system-version.reply");
|
||||
File.WriteAllText(reply + ".tmp", response);
|
||||
File.Move(reply + ".tmp", reply);
|
||||
}
|
||||
var result = await child;
|
||||
if (File.Exists(requestFile) && File.ReadAllText(requestFile).EndsWith("\nready:" + token + "\n", StringComparison.Ordinal)) request = File.ReadAllText(requestFile);
|
||||
var published = request is not null;
|
||||
var diskReached = File.Exists(Path.Combine(state, "disk-boundary"));
|
||||
if ((result.ExitCode == 0) != test.Success || diskReached != test.Success || published != test.Request)
|
||||
throw new InvalidOperationException("Native SystemVersion exact Bash producer/reply fixture failed: " + test.Name);
|
||||
var readonlyReached = File.Exists(Path.Combine(state, "readonly-boundary"));
|
||||
var candidateFile = Path.Combine(state, "candidate");
|
||||
var candidate = File.Exists(candidateFile) ? File.ReadAllText(candidateFile).TrimEnd('\n') : null;
|
||||
if ((result.ExitCode == 0) != test.Readonly || readonlyReached != test.Readonly || candidate != test.Candidate || published != test.Request)
|
||||
throw new InvalidOperationException("Native SystemVersion exact Bash candidate fixture failed: " + test.Name);
|
||||
if (published && !File.ReadAllBytes(Path.Combine(state, "native-system-version.plist")).SequenceEqual(test.Raw!)) throw new InvalidOperationException("Native version producer did not preserve exact bytes.");
|
||||
shellReceipts.Add(new { test.Name, result.ExitCode, requestPublished = published, diskReached, fixtureWaitSeconds = test.Name == "reply-timeout" ? 1 : 180 });
|
||||
string? qualifiedVersion = null; string? qualificationError = null;
|
||||
if (published)
|
||||
{
|
||||
try { qualifiedVersion = ParseNativeSystemVersion(test.Raw!, request!, token); }
|
||||
catch (Exception exception) when (exception is InvalidOperationException or XmlException or DecoderFallbackException) { qualificationError = exception.Message; }
|
||||
Save(Path.Combine(state, "native-system-version.json"), new NativeVersionEvidence(token, NativeVersionSource, test.Raw!.Length, Hash(test.Raw!), NativeVersionMethod, qualifiedVersion, qualificationError is null, 0, DateTimeOffset.UtcNow, qualificationError));
|
||||
}
|
||||
Save(Path.Combine(output, "native-system-version-fixtures.json"), new { success = true, parserCases = parserReceipts, bindingPositiveCases = 1, bindingNegativeCases = bindingCases.Length + 1, producerCases = shellReceipts, producerSha256 = Hash(Encoding.UTF8.GetBytes(producer)), sourcePathMappedOnlyForFixtureRead = true, actualBash = "/bin/bash", productionWaitSeconds = 180, timeoutFixtureWaitSeconds = 1, requestCommitLineRequired = true, dockerExecuted = false, guestExecuted = false });
|
||||
var eligible = Eligible(state, candidate ?? "14.6");
|
||||
if (eligible != test.Permit) throw new InvalidOperationException("Bash candidate bypassed mandatory host qualification: " + test.Name);
|
||||
shellReceipts.Add(new { test.Name, result.ExitCode, requestPublished = published, readonlyReached, candidate, qualifiedVersion, permitEligible = eligible });
|
||||
}
|
||||
Save(Path.Combine(output, "native-system-version-fixtures.json"), new { success = true, parserCases = parserReceipts, bindingPositiveCases = 1, bindingNegativeCases = bindingCases.Length + 1, earlyResultWithoutEvidenceRejected = true, producerCases = shellReceipts, sourceBoundHashes = new { controller = Hash(File.ReadAllBytes("tools/ci/MacOsNativeDiagnostic.cs")), readiness = Hash(Encoding.UTF8.GetBytes(readiness)), producer = Hash(Encoding.UTF8.GetBytes(producer)), actualCanonical603 = Hash(canonical) }, sourcePathMappedOnlyForFixtureRead = true, actualBash = "/bin/bash", candidateIsQualifiedReadiness = false, hostBindingRequiredBeforePermit = true, responseRequired = false, requestCommitLineRequired = true, dockerExecuted = false, guestExecuted = false });
|
||||
}
|
||||
|
||||
static void ReportCpuPreflight(string output, string logs)
|
||||
|
||||
@@ -101,21 +101,19 @@ read_scalar() {
|
||||
|
||||
# BEGIN native SystemVersion plist request helpers
|
||||
read_native_system_version() {
|
||||
# Recovery has Bash 3.2 before .NET. Read bytes here; XML is parsed by the
|
||||
# existing owned host controller, never by shell or VERSION metadata.
|
||||
# Recovery has Bash 3.2 before .NET. The numerical candidate is provisional;
|
||||
# only the owned host's complete XML/evidence binding can qualify readiness.
|
||||
local LC_ALL=C source="/System/Library/CoreServices/SystemVersion.plist"
|
||||
local value status owner reply started=$SECONDS invalid_bytes
|
||||
local value status owner candidate remainder started=$SECONDS invalid_bytes
|
||||
local raw="$STATE_DIR/native-system-version.plist"
|
||||
local ready="$STATE_DIR/native-system-version.request"
|
||||
local response="$STATE_DIR/native-system-version.reply"
|
||||
NATIVE_VERSION_ERROR=native_system_version_read_failed
|
||||
printf '[native-version-start] method=guest-file/host-xml source=%s seconds=%s\n' "$source" "$started" >&3
|
||||
IFS= read -r -n 81 -d '' owner < "$STATE_DIR/run.owner"; status=$?
|
||||
(( status == 1 && ${#owner} <= 80 )) &&
|
||||
[ "$owner" = "$PROOF_TOKEN"$'\n' ] || { NATIVE_VERSION_ERROR=native_system_version_foreign_owner; return 1; }
|
||||
[ ! -e "$ready" ] && [ ! -L "$ready" ] &&
|
||||
[ ! -e "$raw" ] && [ ! -L "$raw" ] &&
|
||||
[ ! -e "$response" ] && [ ! -L "$response" ] || { NATIVE_VERSION_ERROR=native_system_version_stale_exchange; return 1; }
|
||||
[ ! -e "$raw" ] && [ ! -L "$raw" ] || { NATIVE_VERSION_ERROR=native_system_version_stale_exchange; return 1; }
|
||||
[ -f "$source" ] || return 1
|
||||
IFS= read -r -n 4097 -d '' value < "$source"; status=$?
|
||||
# EOF is mandatory. NUL stops read with status 0; 4097 is the overbound sentinel.
|
||||
@@ -128,25 +126,19 @@ read_native_system_version() {
|
||||
# Publish this final marker only after the complete raw write has closed.
|
||||
printf '%s\n%s\n%s\nready:%s\n' "$PROOF_TOKEN" "$source" "${#value}" "$PROOF_TOKEN" > "$ready" || return 1
|
||||
printf '[native-version-request] method=guest-file/host-xml source=%s bytes=%s seconds=%s\n' "$source" "${#value}" "$SECONDS" >&3
|
||||
while (( SECONDS - started < 180 )); do
|
||||
if [ -e "$response" ] || [ -L "$response" ]; then
|
||||
[ -f "$response" ] && [ ! -L "$response" ] || { NATIVE_VERSION_ERROR=native_system_version_invalid_reply; return 1; }
|
||||
IFS= read -r -n 81 -d '' reply < "$response"; status=$?
|
||||
(( status == 1 && ${#reply} <= 80 )) && [[ "$reply" = *$'\n' ]] || { NATIVE_VERSION_ERROR=native_system_version_invalid_reply; return 1; }
|
||||
reply=${reply%$'\n'}
|
||||
if [[ "$reply" =~ ^([0-9a-f]{32}):([0-9]+\.[0-9]+(\.[0-9]+)?)$ ]] && [ "${BASH_REMATCH[1]}" = "$PROOF_TOKEN" ]; then
|
||||
SCALAR="${BASH_REMATCH[2]}"
|
||||
# This is a candidate from exactly these bytes, not an XML validity check.
|
||||
NATIVE_VERSION_ERROR=native_system_version_candidate_invalid
|
||||
[[ "$value" == *'<key>ProductVersion</key>'* ]] || return 1
|
||||
remainder=${value#*'<key>ProductVersion</key>'}
|
||||
remainder=${remainder#"${remainder%%[![:space:]]*}"}
|
||||
[[ "$remainder" == '<string>'* ]] || return 1
|
||||
remainder=${remainder#'<string>'}
|
||||
candidate=${remainder%%'</string>'*}
|
||||
[ "$candidate" != "$remainder" ] && [[ "$candidate" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]] || return 1
|
||||
SCALAR="$candidate"
|
||||
NATIVE_VERSION_ERROR=""
|
||||
printf '[native-version-result] method=guest-file/host-xml source=%s version=%s elapsed=%ss\n' "$source" "$SCALAR" "$((SECONDS - started))" >&3
|
||||
printf '[native-version-candidate] method=guest-file/host-xml source=%s candidate=%s qualified=false elapsed=%ss\n' "$source" "$SCALAR" "$((SECONDS - started))" >&3
|
||||
return 0
|
||||
fi
|
||||
NATIVE_VERSION_ERROR=native_system_version_rejected_reply
|
||||
return 1
|
||||
fi
|
||||
IFS= read -r -t 1 -u 9 unused || :
|
||||
done
|
||||
NATIVE_VERSION_ERROR=native_system_version_reply_timeout
|
||||
return 1
|
||||
}
|
||||
# END native SystemVersion plist request helpers
|
||||
# BEGIN disk IPC diagnostic
|
||||
|
||||
Reference in New Issue
Block a user