fix(update): detect the OS-native machine for the Windows exe integrity gate

The #71119 integrity gate rejected CORRECT ARM64 desktop rebuilds on
Windows-on-ARM (#69179 follow-up): platform.machine() reports the PROCESS
architecture, and the update chain runs an x64 hermes-setup.exe / x64
Python under ARM64 emulation, so the gate compared the ARM64 Hermes.exe
against a phantom 'AMD64 host' and aborted the update.

_windows_native_machine() now asks the OS via IsWow64Process2 (whose
nativeMachine is truthful from emulated processes), falling back to
PROCESSOR_ARCHITEW6432/PROCESSOR_ARCHITECTURE (pre-1511 Win10) and then
platform.machine(). All three consumers — the integrity gate, the
packaged-exe arch preference, and backup validation — go through it.

Sabotage-verified: the three new regression tests fail against the old
process-arch behavior and pass with the fix.
This commit is contained in:
teknium1 2026-07-24 22:44:54 -07:00 committed by Teknium
parent ce44c3413e
commit d9c41b5178
2 changed files with 136 additions and 18 deletions

View file

@ -5557,17 +5557,63 @@ _PE_MACHINE_NAMES = {
}
def _windows_native_machine() -> str:
"""The Windows host OS's NATIVE machine architecture, normalized upper.
``platform.machine()`` reports the PROCESS architecture, which lies under
emulation: the desktop update chain runs an x64 hermes-setup.exe (and thus
x64 Python) on Windows-on-ARM devices, where ``platform.machine()``
returns ``AMD64`` even though the OS is ARM64. The #71119 integrity gate
then rejected the CORRECT ARM64 rebuild as an "architecture mismatch"
(#69179 follow-up report). ``IsWow64Process2`` asks the OS for the true
native machine and works from emulated processes; the classic
``PROCESSOR_ARCHITEW6432`` fallback only covers WOW64 (32-bit processes)
and is NOT set under x64-on-ARM64 emulation, so it cannot replace the API
probe it is kept only for pre-1511 Windows 10 hosts without the API.
"""
if sys.platform == "win32":
try:
import ctypes
kernel32 = ctypes.windll.kernel32
process_machine = ctypes.c_ushort(0)
native_machine = ctypes.c_ushort(0)
if kernel32.IsWow64Process2(
kernel32.GetCurrentProcess(),
ctypes.byref(process_machine),
ctypes.byref(native_machine),
):
name = {
_PE_MACHINE_ARM64: "ARM64",
_PE_MACHINE_AMD64: "AMD64",
_PE_MACHINE_I386: "X86",
}.get(native_machine.value)
if name:
return name
except (OSError, AttributeError):
# No IsWow64Process2 (pre-1511 Windows 10) — fall through to the
# documented WOW64 env vars, then the process architecture.
pass
env_arch = os.environ.get("PROCESSOR_ARCHITEW6432") or os.environ.get(
"PROCESSOR_ARCHITECTURE"
)
if env_arch:
return env_arch.upper()
import platform as _platform
return (_platform.machine() or "").upper()
def _expected_windows_pe_machines() -> set:
"""PE machine values the current Windows host can natively load.
AMD64 hosts run x64 and (via WOW64) x86. ARM64 hosts run ARM64 and
(Windows 11 emulation) x64. 32-bit x86 hosts run only x86. Unknown
machines return the permissive full set so the integrity gate can never
brick launch on exotic hosts.
brick launch on exotic hosts. Host detection uses the OS-native machine
(see ``_windows_native_machine``), not the process architecture.
"""
import platform as _platform
machine = (_platform.machine() or "").upper()
machine = _windows_native_machine().upper()
if machine in ("AMD64", "X86_64", "X64"):
return {_PE_MACHINE_AMD64, _PE_MACHINE_I386}
if machine in ("ARM64", "AARCH64"):
@ -5645,12 +5691,10 @@ def _desktop_exe_integrity_error(path: Path) -> Optional[str]:
return str(exc)
expected = _expected_windows_pe_machines()
if machine not in expected:
import platform as _platform
got = _PE_MACHINE_NAMES.get(machine, f"unknown machine 0x{machine:04X}")
return (
f"architecture mismatch: built a {got} executable but this is a "
f"{_platform.machine()} Windows host"
f"{_windows_native_machine()} Windows host"
)
return None

View file

@ -107,7 +107,7 @@ def test_parse_pe_machine_rejects_bad_pe_signature(tmp_path):
],
)
def test_expected_machines_per_host(host, loadable, not_loadable):
with patch("platform.machine", return_value=host):
with patch("hermes_cli.main._windows_native_machine", return_value=host):
expected = cli_main._expected_windows_pe_machines()
assert loadable <= expected
assert not (not_loadable & expected)
@ -115,17 +115,91 @@ def test_expected_machines_per_host(host, loadable, not_loadable):
def test_expected_machines_unknown_host_is_permissive():
"""The gate must never brick launch on hosts we don't recognize."""
with patch("platform.machine", return_value="RISCV64"):
with patch("hermes_cli.main._windows_native_machine", return_value="RISCV64"):
expected = cli_main._expected_windows_pe_machines()
assert {PE_AMD64, PE_ARM64, PE_I386} <= expected
# ─── _windows_native_machine ────────────────────────────────────────────────
class _FakeKernel32:
"""Stands in for kernel32 so the IsWow64Process2 path runs on any CI OS."""
def __init__(self, native_machine: int):
self._native = native_machine
def GetCurrentProcess(self):
return -1
def IsWow64Process2(self, _handle, process_ref, native_ref):
process_ref._obj.value = PE_AMD64 # emulated x64 process
native_ref._obj.value = self._native
return 1
def _fake_windll(native_machine: int):
class _WinDLL:
kernel32 = _FakeKernel32(native_machine)
return _WinDLL()
def test_native_machine_reports_os_arch_not_process_arch(monkeypatch):
"""The #69179 WoA regression: x64 Python under ARM64 emulation must report
ARM64 (the OS), not AMD64 (the process) otherwise the integrity gate
rejects the correct ARM64 rebuild."""
import ctypes
monkeypatch.setattr(cli_main.sys, "platform", "win32")
with patch.object(ctypes, "windll", _fake_windll(PE_ARM64), create=True), \
patch("platform.machine", return_value="AMD64"):
assert cli_main._windows_native_machine() == "ARM64"
def test_native_machine_env_fallback_without_api(monkeypatch):
"""Pre-1511 Windows 10: no IsWow64Process2 → the WOW64 env var wins."""
monkeypatch.setattr(cli_main.sys, "platform", "win32")
monkeypatch.setenv("PROCESSOR_ARCHITEW6432", "AMD64")
# On non-Windows interpreters ctypes has no `windll`, which is exactly the
# AttributeError shape a missing API produces.
with patch("platform.machine", return_value="x86"):
assert cli_main._windows_native_machine() == "AMD64"
def test_native_machine_platform_fallback(monkeypatch):
"""No API, no env vars → the historical platform.machine() answer."""
monkeypatch.setattr(cli_main.sys, "platform", "win32")
monkeypatch.delenv("PROCESSOR_ARCHITEW6432", raising=False)
monkeypatch.delenv("PROCESSOR_ARCHITECTURE", raising=False)
with patch("platform.machine", return_value="AMD64"):
assert cli_main._windows_native_machine() == "AMD64"
def test_native_machine_non_windows_uses_platform(monkeypatch):
monkeypatch.setattr(cli_main.sys, "platform", "linux")
with patch("platform.machine", return_value="aarch64"):
assert cli_main._windows_native_machine() == "AARCH64"
def test_integrity_gate_accepts_arm64_exe_from_emulated_x64_process(monkeypatch, tmp_path):
"""End-to-end shape of the reporter's failure: ARM64 host, x64 updater
process, correctly-built ARM64 Hermes.exe. The gate must pass it."""
import ctypes
monkeypatch.setattr(cli_main.sys, "platform", "win32")
exe = make_pe(tmp_path / "Hermes.exe", PE_ARM64)
with patch.object(ctypes, "windll", _fake_windll(PE_ARM64), create=True), \
patch("platform.machine", return_value="AMD64"):
assert cli_main._desktop_exe_integrity_error(exe) is None
# ─── _desktop_exe_integrity_error ───────────────────────────────────────────
def test_integrity_error_none_for_matching_arch(tmp_path):
exe = make_pe(tmp_path / "Hermes.exe", PE_AMD64)
with patch("platform.machine", return_value="AMD64"):
with patch("hermes_cli.main._windows_native_machine", return_value="AMD64"):
assert cli_main._desktop_exe_integrity_error(exe) is None
@ -133,7 +207,7 @@ def test_integrity_error_reports_arch_mismatch(tmp_path):
"""ARM64 exe on the reporter's 'Windows 10 AMD64' host — the wrong-arch
flavor of 'This app can't run on your computer'."""
exe = make_pe(tmp_path / "Hermes.exe", PE_ARM64)
with patch("platform.machine", return_value="AMD64"):
with patch("hermes_cli.main._windows_native_machine", return_value="AMD64"):
error = cli_main._desktop_exe_integrity_error(exe)
assert error is not None and "architecture mismatch" in error
assert "ARM64" in error
@ -159,7 +233,7 @@ def test_packaged_executable_prefers_host_arch_over_mtime(tmp_path, monkeypatch)
os.utime(bad, (bad.stat().st_atime + 1000, bad.stat().st_mtime + 1000))
with patch("platform.machine", return_value="AMD64"):
with patch("hermes_cli.main._windows_native_machine", return_value="AMD64"):
assert cli_main._desktop_packaged_executable(desktop_dir) == good
@ -175,7 +249,7 @@ def test_packaged_executable_falls_back_to_mtime_when_unparseable(tmp_path, monk
import os
os.utime(b, (b.stat().st_atime + 1000, b.stat().st_mtime + 1000))
with patch("platform.machine", return_value="AMD64"):
with patch("hermes_cli.main._windows_native_machine", return_value="AMD64"):
assert cli_main._desktop_packaged_executable(desktop_dir) == b
@ -194,7 +268,7 @@ def test_rollback_restores_backup_and_keeps_corrupt_copy(tmp_path):
backup_exe = desktop_dir / "release" / "win-unpacked.bak" / "Hermes.exe"
make_pe(backup_exe, PE_AMD64) # valid old build
with patch("platform.machine", return_value="AMD64"):
with patch("hermes_cli.main._windows_native_machine", return_value="AMD64"):
restored = cli_main._rollback_desktop_from_backup(exe)
assert restored == exe
@ -232,7 +306,7 @@ def test_gate_passes_valid_exe(tmp_path, monkeypatch):
monkeypatch.setattr(cli_main.sys, "platform", "win32")
desktop_dir, exe = _win_tree(tmp_path)
make_pe(exe, PE_AMD64)
with patch("platform.machine", return_value="AMD64"):
with patch("hermes_cli.main._windows_native_machine", return_value="AMD64"):
verified, rolled_back = cli_main._ensure_desktop_exe_launchable(desktop_dir, exe)
assert verified == exe
assert rolled_back is False
@ -259,7 +333,7 @@ def test_gate_rolls_back_corrupt_exe_and_purges_cache(tmp_path, monkeypatch, cap
stamp.parent.mkdir(parents=True, exist_ok=True)
stamp.write_text("{}", encoding="utf-8")
with patch("platform.machine", return_value="AMD64"), \
with patch("hermes_cli.main._windows_native_machine", return_value="AMD64"), \
patch("hermes_cli.main._purge_electron_build_cache", return_value=[]) as mock_purge, \
patch("hermes_cli.main._desktop_stamp_path", return_value=stamp):
verified, rolled_back = cli_main._ensure_desktop_exe_launchable(desktop_dir, exe)
@ -336,7 +410,7 @@ def test_build_only_fails_when_pack_produces_corrupt_exe(tmp_path, monkeypatch,
patch("hermes_cli.main._purge_electron_build_cache", return_value=[]), \
patch("hermes_cli.main._desktop_stamp_path", return_value=tmp_path / "stamp.json"), \
patch("hermes_cli.main._write_desktop_build_stamp") as mock_stamp, \
patch("platform.machine", return_value="AMD64"), \
patch("hermes_cli.main._windows_native_machine", return_value="AMD64"), \
patch("hermes_cli.main.subprocess.run", return_value=pack_ok), \
pytest.raises(SystemExit) as exc:
cli_main.cmd_gui(_ns())
@ -368,7 +442,7 @@ def test_build_only_succeeds_with_valid_exe(tmp_path, monkeypatch, capsys):
patch("hermes_cli.main._desktop_build_needed", return_value=True), \
patch("hermes_cli.main._stop_desktop_processes_locking_build", return_value=[]), \
patch("hermes_cli.main._write_desktop_build_stamp") as mock_stamp, \
patch("platform.machine", return_value="AMD64"), \
patch("hermes_cli.main._windows_native_machine", return_value="AMD64"), \
patch("hermes_cli.main.subprocess.run", return_value=pack_ok):
cli_main.cmd_gui(_ns())