Close the free headroom in the clean-machine simulation
Assert arch and signature on every downloaded Mach-O. This is the one genuine gap the simulation had: Rosetta 2 is preinstalled on hosted runners and absent from a factory-fresh Mac, so an x86_64-only llama.cpp, whisper.cpp, Node or uv payload runs green here and dies with "bad CPU type in executable" for the user. llama-server launching under `assert-llama-loads.sh` does not rule that out, because Rosetta makes it launch. The new `macho` check reads `file -b` (`lipo` is an xcrun shim and is gone after masking, as the desktop lane already notes) and keys the expected arch off `uname -m`, so macos-15-intel expects x86_64. It also requires at least an ad-hoc signature on arm64, which closes the AMFI "Killed: 9" class that uv has already been bitten by; the check is skipped on x86_64, where unsigned code loads fine and so is not the same defect. It fails when the scan finds nothing, since an empty scan reads exactly like a clean one. Make absence real rather than PATH-hidden. uv probes well-known interpreter locations and the framework loader ignores PATH entirely, so hiding the toolcache only hid it from `command -v`. Empty /usr/local (it EXISTS on a factory-fresh Mac as a SIP-exempt firmlink, and is empty; it is /usr/local/bin that is absent, so the directory itself stays), move the hosted toolcache and /Library/Frameworks/Python.framework aside, and clear the developer dotdirs and caches. A populated uv or pip cache can also satisfy a resolution that would fail on a user's machine. Every removal goes through --remove and is recorded in the generated restore.sh, guarded so a path the install recreated is not buried inside its own restore. Unset CI, GITHUB_* and RUNNER_* for the installer process only. An installer branching on CI=true is a hidden dependency no consumer exercises. Scoped to the child so the step's own $GITHUB_OUTPUT still resolves. Record spctl --status and csrutil status. Neither is documented for these images and both change what a binary is allowed to do.
This commit is contained in:
parent
7d4311fe58
commit
83c3dba5ab
3 changed files with 116 additions and 7 deletions
49
.github/scripts/clean-machine-assert.sh
vendored
49
.github/scripts/clean-machine-assert.sh
vendored
|
|
@ -12,8 +12,10 @@
|
|||
# "Building <pkg>==<ver>" from uv. Needs UNSLOTH_VERBOSE=1, or
|
||||
# run_install_cmd (install.sh:193-243) discards uv's output on success
|
||||
# and there is nothing to read.
|
||||
# macho Every Mach-O under $MACHO_ROOT is the host architecture and is signed.
|
||||
# Closes the Rosetta 2 gap, the one divergence masking cannot reproduce.
|
||||
#
|
||||
# Usage: bash .github/scripts/clean-machine-assert.sh absent notools nobuild
|
||||
# Usage: bash .github/scripts/clean-machine-assert.sh absent notools nobuild macho
|
||||
set -uo pipefail
|
||||
|
||||
LOG="${INSTALL_LOG:-logs/install.log}"
|
||||
|
|
@ -151,6 +153,51 @@ for check in "$@"; do
|
|||
fi
|
||||
;;
|
||||
|
||||
macho)
|
||||
# The one thing masking cannot reproduce: Rosetta 2 is preinstalled on hosted
|
||||
# runners and absent from a factory-fresh Mac, so an x86_64-only payload runs
|
||||
# green here and dies with "bad CPU type in executable" for the user. Assert the
|
||||
# architecture rather than hope the runner lacks Rosetta.
|
||||
# `lipo` is an xcrun shim and is gone after masking, so read `file -b`, exactly
|
||||
# as the desktop lane does. Keyed off `uname -m`, since macos-15-intel is x86_64.
|
||||
root="${MACHO_ROOT:-${UNSLOTH_STUDIO_HOME:-$HOME/.unsloth}}"
|
||||
want="$(uname -m)"
|
||||
[ "$want" = "aarch64" ] && want=arm64
|
||||
if [ ! -d "$root" ]; then
|
||||
fail "macho requested but $root does not exist"
|
||||
else
|
||||
n=0 bad_arch="" unsigned=""
|
||||
while IFS= read -r f; do
|
||||
desc="$(file -b "$f" 2>/dev/null || true)"
|
||||
case "$desc" in *Mach-O*) ;; *) continue ;; esac
|
||||
n=$((n + 1))
|
||||
# Substring, not equality: a universal binary lists every slice it carries,
|
||||
# and one that includes the host arch is fine.
|
||||
case "$desc" in
|
||||
*"$want"*) ;;
|
||||
*) bad_arch="$bad_arch $f [$desc]" ;;
|
||||
esac
|
||||
# arm64 only: AMFI SIGKILLs unsigned code there ("Killed: 9"), while x86_64
|
||||
# loads it happily, so an unsigned x86_64 payload is not the same defect.
|
||||
# Ad-hoc is enough, which is what the linker emits by default.
|
||||
if [ "$want" = "arm64" ] && ! codesign -v "$f" >/dev/null 2>&1; then
|
||||
unsigned="$unsigned $f"
|
||||
fi
|
||||
done < <(find "$root" -type f \( -perm -u+x -o -name '*.dylib' -o -name '*.so' -o -name '*.node' \) 2>/dev/null)
|
||||
if [ "$n" = "0" ]; then
|
||||
# An empty scan reads exactly like a clean one, so the check would pass on a
|
||||
# wrong root and prove nothing.
|
||||
fail "no Mach-O found under $root; the arch/signature assertion proved nothing"
|
||||
elif [ -n "$bad_arch" ]; then
|
||||
fail "Mach-O is not $want, so it runs here only under Rosetta 2, which a fresh Mac does not have:$bad_arch"
|
||||
elif [ -n "$unsigned" ]; then
|
||||
fail "unsigned Mach-O, which AMFI kills on arm64:$unsigned"
|
||||
else
|
||||
ok "$n Mach-O files under $root are $want$([ "$want" = arm64 ] && echo ' and signed')"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
fail "unknown check '$check'"
|
||||
;;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue