The window host gate (0.3.22): packaging/windows/host-gate.py refuses a payload whose Igneum Miner.exe version resource is not the cut's version, carries a mingw-w64 signature, or is not the pinned MSVC host (packaging/windows/host.sha256, e223db18 for 0.3.21); wired into make-payload.sh before the host is copied, its self-test in the pre-push gate (PC 2, 7 October 2026: a 0.3.22.0 mingw host inside a 0.3.21 installer crashed in ntdll seconds after starting its engine)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
6c6bd1f3e6
commit
85eb90eaa9
4 changed files with 140 additions and 1 deletions
126
packaging/windows/host-gate.py
Normal file
126
packaging/windows/host-gate.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
#!/usr/bin/env python3
|
||||
"""The window host gate (0.3.22; PC 2, 7 October 2026: a 0.3.21 installer carried a mingw-built "Igneum Miner.exe" whose
|
||||
version resource read 0.3.22.0 and which crashed in ntdll seconds after starting its engine). Before a host enters a
|
||||
payload, three facts must hold or the payload is refused:
|
||||
1. the exe's version resource (FileVersion and ProductVersion) equals the installer's version (as "a.b.c" or "a.b.c.0");
|
||||
2. the exe carries no mingw-w64 signature (the GNU runtime strings "Mingw-w64", "libgcc", "GCC: (GNU", "libstdc++-6.dll",
|
||||
"libwinpthread-1.dll"): the host is the MSVC build (app\\windows\\BUILD-APP.bat) or nothing;
|
||||
3. when a pin file is given (packaging/windows/host.sha256: one sha256 per line, the cut's MSVC host), the exe's sha256
|
||||
is in it.
|
||||
host-gate.py <Igneum Miner.exe> <version> [<pin file>] exit 0 = pass, 1 = refused (every reason printed)
|
||||
host-gate.py --self-test known-bad and known-good blobs
|
||||
No dependency; reads the version strings straight from the resource's UTF-16LE text, which is how every PE carries them."""
|
||||
import hashlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
MINGW_MARKS = [b"Mingw-w64", b"mingw-w64", b"libgcc", b"GCC: (GNU", b"libstdc++-6.dll", b"libwinpthread-1.dll"]
|
||||
|
||||
|
||||
def utf16(s):
|
||||
return s.encode("utf-16-le")
|
||||
|
||||
|
||||
def version_strings(blob):
|
||||
"""FileVersion and ProductVersion from the VS_VERSIONINFO StringFileInfo block: the UTF-16 key, 0 to 3 NUL words of
|
||||
padding, then the UTF-16 value up to its NUL."""
|
||||
out = {}
|
||||
for key in ("FileVersion", "ProductVersion"):
|
||||
m = re.search(re.escape(utf16(key)) + rb"\x00\x00(?:\x00\x00){0,3}((?:[^\x00]\x00|\x00[^\x00]){1,40}?)\x00\x00", blob)
|
||||
if m:
|
||||
try:
|
||||
out[key] = m.group(1).decode("utf-16-le").strip()
|
||||
except UnicodeDecodeError:
|
||||
out[key] = ""
|
||||
return out
|
||||
|
||||
|
||||
def norm(v):
|
||||
parts = [p for p in re.split(r"[.,\s]+", v.strip()) if p != ""]
|
||||
while len(parts) < 4:
|
||||
parts.append("0")
|
||||
return ".".join(parts[:4])
|
||||
|
||||
|
||||
def check(blob, version, pins=None):
|
||||
reasons = []
|
||||
vs = version_strings(blob)
|
||||
want = norm(version)
|
||||
for key in ("FileVersion", "ProductVersion"):
|
||||
have = vs.get(key)
|
||||
if have is None:
|
||||
reasons.append(f"no {key} in the version resource")
|
||||
elif norm(have) != want:
|
||||
reasons.append(f"{key} reads {have}, the installer is {version}")
|
||||
marks = [m.decode() for m in MINGW_MARKS if m in blob]
|
||||
if marks:
|
||||
reasons.append("a mingw-w64 build (" + ", ".join(marks) + "): the host must be the MSVC build")
|
||||
if pins is not None:
|
||||
sha = hashlib.sha256(blob).hexdigest()
|
||||
if sha not in pins:
|
||||
reasons.append(f"sha256 {sha[:12]} is not the cut's pinned MSVC host ({', '.join(p[:12] for p in pins) or 'no pin'})")
|
||||
return reasons
|
||||
|
||||
|
||||
def read_pins(path):
|
||||
pins = []
|
||||
with open(path, encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.split("#", 1)[0].strip().lower()
|
||||
if re.fullmatch(r"[0-9a-f]{64}", line):
|
||||
pins.append(line)
|
||||
return pins
|
||||
|
||||
|
||||
def fake_pe(file_version, product_version, extra=b""):
|
||||
"""A blob with a version resource shaped like a real one (the strings, the padding, the NULs)."""
|
||||
return (b"MZ" + b"\x00" * 64 + utf16("FileVersion") + b"\x00\x00\x00\x00" + utf16(file_version) + b"\x00\x00"
|
||||
+ utf16("ProductVersion") + b"\x00\x00\x00\x00" + utf16(product_version) + b"\x00\x00" + extra)
|
||||
|
||||
|
||||
def self_test():
|
||||
# known-bad first: the take-4 host's shape, 0.3.22.0 inside a 0.3.21 installer, mingw-built
|
||||
bad = fake_pe("0.3.22.0", "0.3.22.0", b"...Mingw-w64 runtime failure:...libgcc_s_seh-1.dll...")
|
||||
r = check(bad, "0.3.21", pins=[])
|
||||
assert any("FileVersion reads 0.3.22.0" in x for x in r), r
|
||||
assert any("mingw-w64 build" in x for x in r), r
|
||||
assert any("not the cut's pinned" in x for x in r), r
|
||||
# a right version but a mingw build is still refused
|
||||
r = check(fake_pe("0.3.21.0", "0.3.21.0", b"GCC: (GNU) 13-posix"), "0.3.21")
|
||||
assert r == ["a mingw-w64 build (GCC: (GNU): the host must be the MSVC build"], r
|
||||
# known-good: the version equal in both forms, no GNU strings, the sha pinned
|
||||
good = fake_pe("0.3.21.0", "0.3.21", b"Microsoft (R) C/C++ Optimizing Compiler")
|
||||
assert check(good, "0.3.21") == [], check(good, "0.3.21")
|
||||
assert check(good, "0.3.21", pins=[hashlib.sha256(good).hexdigest()]) == []
|
||||
assert check(good, "0.3.21", pins=["0" * 64]) != [], "a pin that is not this exe refuses"
|
||||
# no resource at all is refused (nothing to compare)
|
||||
r = check(b"MZ" + b"\x00" * 200, "0.3.21")
|
||||
assert r == ["no FileVersion in the version resource", "no ProductVersion in the version resource"], r
|
||||
print("self-test passed: a 0.3.22.0 mingw host fails a 0.3.21 installer on all three counts, a right-version mingw host fails, a pinned MSVC host passes, a resource-less blob fails")
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) >= 2 and argv[1] == "--self-test":
|
||||
self_test()
|
||||
return 0
|
||||
if len(argv) < 3:
|
||||
print(__doc__)
|
||||
return 2
|
||||
exe, version = argv[1], argv[2]
|
||||
pins = read_pins(argv[3]) if len(argv) > 3 else None
|
||||
with open(exe, "rb") as f:
|
||||
blob = f.read()
|
||||
reasons = check(blob, version, pins)
|
||||
vs = version_strings(blob)
|
||||
sha = hashlib.sha256(blob).hexdigest()
|
||||
if reasons:
|
||||
print(f"host-gate: REFUSED {exe} for {version} (FileVersion {vs.get('FileVersion')}, ProductVersion {vs.get('ProductVersion')}, sha256 {sha[:12]}):")
|
||||
for r in reasons:
|
||||
print(f" - {r}")
|
||||
return 1
|
||||
print(f"host-gate: ok {exe} is {version} (FileVersion {vs.get('FileVersion')}, ProductVersion {vs.get('ProductVersion')}), no mingw signature, sha256 {sha[:12]}" + (" pinned" if pins is not None else ""))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
4
packaging/windows/host.sha256
Normal file
4
packaging/windows/host.sha256
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# The cut's MSVC window host (app\windows\BUILD-APP.bat), one sha256 per line; packaging/windows/host-gate.py refuses a payload
|
||||
# whose "Igneum Miner.exe" is not one of these. The shipper replaces the line at every cut from the CI artifact igneum-windows-host
|
||||
# (or PC 1's BUILD-APP.bat run). 7 October 2026: PC 1's MSVC 0.3.21 host, the one running on PC 2 since 19:14 BST.
|
||||
e223db18bcfa74a1f60335de62390729a19975e6a6bddd7547bf9d6f0676df17
|
||||
|
|
@ -84,7 +84,15 @@ cp "$ROOT/proto-opencl/host.c" "$ROOT/proto-opencl/build.bat" "$ROOT/proto-openc
|
|||
# the window host sources, built on the PC or by CI; the built host when BUILD-APP.bat already ran here
|
||||
cp "$ROOT/app/windows/host.cpp" "$ROOT/app/windows/host.rc" "$ROOT/app/windows/version.h" "$ROOT/app/windows/BUILD-APP.bat" "$STAGE/app/windows/"
|
||||
mkdir -p "$STAGE/app/windows/art" && cp "$ROOT/brand/icons/igneum.ico" "$STAGE/app/windows/art/"
|
||||
if [ -f "$ROOT/app/windows/dist/Igneum Miner.exe" ]; then cp "$ROOT/app/windows/dist/Igneum Miner.exe" "$STAGE/"; echo "window host: Igneum Miner.exe from app/windows/dist"; fi
|
||||
if [ -f "$ROOT/app/windows/dist/Igneum Miner.exe" ]; then
|
||||
# the host gate (0.3.22): the version resource equals this payload's version, no mingw-w64 signature, and the sha pinned
|
||||
# in packaging/windows/host.sha256 when that file exists (PC 2, 7 October 2026: a 0.3.22.0 mingw host in a 0.3.21 installer
|
||||
# crashed in ntdll and left the window on "starting the engine")
|
||||
HOST_VERSION="${APP_VERSION:-$(sed -n 's/^version = "\(.*\)"/\1/p' "$ROOT/app/igneum-app/Cargo.toml" | head -1)}"
|
||||
PIN_ARG=""; [ -s "$ROOT/packaging/windows/host.sha256" ] && PIN_ARG="$ROOT/packaging/windows/host.sha256"
|
||||
python3 "$ROOT/packaging/windows/host-gate.py" "$ROOT/app/windows/dist/Igneum Miner.exe" "$HOST_VERSION" $PIN_ARG || { echo "make-payload: the window host is refused (host-gate.py above); no host rides this payload" >&2; exit 1; }
|
||||
cp "$ROOT/app/windows/dist/Igneum Miner.exe" "$STAGE/"; echo "window host: Igneum Miner.exe from app/windows/dist (host-gate ok)"
|
||||
fi
|
||||
cp "$HERE/stop-igneum.ps1" "$STAGE/stop-igneum.ps1"
|
||||
|
||||
# proving v0 (spec 7.7, app/igneum-app/src/prover.rs): the SP1 host runs inside WSL2 on a PC, so the payload ships the
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ tree_checks() {
|
|||
run "bash bodies in PowerShell job scripts pass bash -n" bash -c 'bash tools/ci/bash-body-check.sh --self-test && bash tools/ci/bash-body-check.sh'
|
||||
run "run jobs test their fetched kit before use" bash -c 'bash tools/ci/kit-path-check.sh --self-test && bash tools/ci/kit-path-check.sh'
|
||||
run "every Windows spawn of the app runs with a hidden console" bash -c 'node tools/ci/windows-spawn-check.mjs --self-test && node tools/ci/windows-spawn-check.mjs'
|
||||
run "the window host gate refuses a wrong-version or mingw host (host-gate.py self-test)" python3 packaging/windows/host-gate.py --self-test
|
||||
run "pinned guest programs match their manifest" bash tools/ci/pinned-guests-check.sh
|
||||
run "no permanent miner fault in the engine (miner-faults.md MF-2)" bash -c 'bash tools/ci/permanent-fault-check.sh --self-test && bash tools/ci/permanent-fault-check.sh'
|
||||
run "root prover playbooks kill the GPU server and unlink its socket" bash tools/ci/prover-socket-check.sh
|
||||
|
|
|
|||
Loading…
Reference in a new issue