Read Recovery version from current guest SystemVersion evidence

This commit is contained in:
dh
2026-10-04 15:00:11 +02:00
parent 9735db1cdc
commit 6122be2cef
3 changed files with 327 additions and 42 deletions
+51 -36
View File
@@ -99,26 +99,56 @@ read_scalar() {
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]}"
# 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.
local LC_ALL=C source="/System/Library/CoreServices/SystemVersion.plist"
local value status owner reply 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; }
[ -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.
(( status == 1 && ${#value} > 0 && ${#value} <= 4096 )) || { NATIVE_VERSION_ERROR=native_system_version_invalid_bytes; return 1; }
invalid_bytes=${value//$'\t'/}
invalid_bytes=${invalid_bytes//$'\r'/}
invalid_bytes=${invalid_bytes//$'\n'/}
[[ "$invalid_bytes" =~ [[:cntrl:]] ]] && { NATIVE_VERSION_ERROR=native_system_version_binary; return 1; }
printf '%s' "$value" > "$raw" || return 1
# 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]}"
NATIVE_VERSION_ERROR=""
printf '[native-version-result] method=guest-file/host-xml source=%s version=%s elapsed=%ss\n' "$source" "$SCALAR" "$((SECONDS - started))" >&3
return 0
fi
NATIVE_VERSION_ERROR=native_system_version_rejected_reply
return 1
fi
done <<< "$value"
(( fields == 1 )) || return 1
SCALAR="$version"
IFS= read -r -t 1 -u 9 unused || :
done
NATIVE_VERSION_ERROR=native_system_version_reply_timeout
return 1
}
# END successful sw_vers version parser
# END native SystemVersion plist request helpers
# 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.
@@ -283,24 +313,9 @@ run_command uid /usr/bin/id -u
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
# BEGIN native SystemVersion plist getter
read_native_system_version || fail_probe "$NATIVE_VERSION_ERROR"
# END native SystemVersion plist getter
os_version="$SCALAR"
[[ "$os_version" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]] || fail_probe product_version_invalid
(( ${os_version%%.*} >= 14 )) || fail_probe unsupported_macos_version