fix(update): drop the GetNativeSystemInfo probe that lies under WoA emulation

The residual Windows-on-ARM integrity-gate fix added GetNativeSystemInfo as a
second "OS-native" probe behind IsWow64Process2. Microsoft documents the
opposite behavior: "the API GetNativeSystemInfo also returns emulated
processor details when run from an app under emulation."

On the exact hosts the probe was added to rescue -- an x64 hermes-setup.exe
emulated on an ARM64 Surface -- it therefore reports AMD64, making it a
duplicate of the PROCESSOR_ARCHITECTURE rung already below it rather than a
fallback for it. Its test only passed because the kernel32 fake was hand-fed
ARM64, asserting behavior real Windows does not exhibit.

Replace it with GetMachineTypeAttributes, which answers the question the gate
actually asks -- "can this host load a PE of machine X?" -- instead of
inferring it from an architecture name. It is also the only documented API
that reports AMD64-on-ARM64 emulation support. The gate prefers it and falls
back to the existing name-based mapping on pre-Windows-11 hosts.

The load-bearing half of the previous fix (typing GetCurrentProcess as HANDLE
so IsWow64Process2 stops failing ERROR_INVALID_HANDLE) is unchanged.

Co-authored-by: xxxigm <xxxigm@users.noreply.github.com>
Co-authored-by: Teknium <teknium1@users.noreply.github.com>
This commit is contained in:
Brooklyn Nicholson 2026-07-25 21:13:15 -05:00
parent a606d24cf2
commit c03a977a96
2 changed files with 106 additions and 87 deletions

View file

@ -5556,26 +5556,15 @@ _PE_MACHINE_NAMES = {
_PE_MACHINE_ARM64: "ARM64",
}
# SYSTEM_INFO.wProcessorArchitecture values (winnt.h). Used by
# GetNativeSystemInfo as a second OS-native probe when IsWow64Process2 is
# unavailable or fails under a mistyped ctypes HANDLE.
_PROCESSOR_ARCHITECTURE_INTEL = 0
_PROCESSOR_ARCHITECTURE_ARM = 5
_PROCESSOR_ARCHITECTURE_AMD64 = 9
_PROCESSOR_ARCHITECTURE_ARM64 = 12
_PE_MACHINE_TO_NAME = {
_PE_MACHINE_ARM64: "ARM64",
_PE_MACHINE_AMD64: "AMD64",
_PE_MACHINE_I386: "X86",
}
_SYSTEM_INFO_ARCH_TO_NAME = {
_PROCESSOR_ARCHITECTURE_ARM64: "ARM64",
_PROCESSOR_ARCHITECTURE_AMD64: "AMD64",
_PROCESSOR_ARCHITECTURE_INTEL: "X86",
_PROCESSOR_ARCHITECTURE_ARM: "ARM",
}
# MACHINE_ATTRIBUTES bits (processthreadsapi.h). UserEnabled means the host
# can run user-mode code of that machine type — natively or under emulation.
_MACHINE_ATTRIBUTE_USER_ENABLED = 0x00000001
def _windows_native_machine_from_iswow64() -> Optional[str]:
@ -5615,39 +5604,37 @@ def _windows_native_machine_from_iswow64() -> Optional[str]:
return _PE_MACHINE_TO_NAME.get(native_machine.value)
def _windows_native_machine_from_native_system_info() -> Optional[str]:
"""GetNativeSystemInfo — truthful under x64-on-ARM64 when IsWow64 fails.
def _windows_user_runnable_pe_machines() -> Optional[set]:
"""PE machines this host can run in user mode, via GetMachineTypeAttributes.
Unlike ``PROCESSOR_ARCHITECTURE`` (process/emulation view) this reports
the OS-native architecture, so it covers the residual #71218 failure mode
where ``IsWow64Process2`` returns FALSE and the env-var fallback would
otherwise lie as AMD64.
This asks the question the integrity gate actually cares about "can this
Windows host load a PE of machine X?" — instead of inferring it from a
host-architecture name. It is also the only documented API that reports
AMD64-on-ARM64 emulation support; ``IsWow64GuestMachineSupported`` only
answers for 32-bit guests.
Returns None when the API is unavailable (pre-Windows-11 build 22000) or
reports nothing runnable, so callers fall back to name-based detection.
"""
import ctypes
from ctypes import wintypes
class _SYSTEM_INFO(ctypes.Structure):
_fields_ = [
("wProcessorArchitecture", wintypes.WORD),
("wReserved", wintypes.WORD),
("dwPageSize", wintypes.DWORD),
("lpMinimumApplicationAddress", ctypes.c_void_p),
("lpMaximumApplicationAddress", ctypes.c_void_p),
("dwActiveProcessorMask", ctypes.c_size_t),
("dwNumberOfProcessors", wintypes.DWORD),
("dwProcessorType", wintypes.DWORD),
("dwAllocationGranularity", wintypes.DWORD),
("wProcessorLevel", wintypes.WORD),
("wProcessorRevision", wintypes.WORD),
]
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
kernel32.GetNativeSystemInfo.argtypes = [ctypes.POINTER(_SYSTEM_INFO)]
kernel32.GetNativeSystemInfo.restype = None
kernel32.GetMachineTypeAttributes.argtypes = [
wintypes.USHORT,
ctypes.POINTER(ctypes.c_int),
]
kernel32.GetMachineTypeAttributes.restype = ctypes.c_long
info = _SYSTEM_INFO()
kernel32.GetNativeSystemInfo(ctypes.byref(info))
return _SYSTEM_INFO_ARCH_TO_NAME.get(info.wProcessorArchitecture)
runnable = set()
for machine in (_PE_MACHINE_ARM64, _PE_MACHINE_AMD64, _PE_MACHINE_I386):
attributes = ctypes.c_int(0)
# HRESULT: zero is success, any nonzero value is a failure.
if kernel32.GetMachineTypeAttributes(machine, ctypes.byref(attributes)):
continue
if attributes.value & _MACHINE_ATTRIBUTE_USER_ENABLED:
runnable.add(machine)
return runnable or None
def _windows_native_machine() -> str:
@ -5661,26 +5648,26 @@ def _windows_native_machine() -> str:
(#69179 follow-up report). Probe order:
1. ``IsWow64Process2`` with a correctly-typed current-process HANDLE
(#71218 + HANDLE-truncation fix).
2. ``GetNativeSystemInfo`` still OS-native when (1) fails under
x64-on-ARM64 emulation (env vars alone cannot cover that case).
3. ``PROCESSOR_ARCHITEW6432`` / ``PROCESSOR_ARCHITECTURE`` WOW64
(32-bit) hosts and pre-1511 Windows 10 without the newer APIs.
4. ``platform.machine()``.
(#71218 + HANDLE-truncation fix). This is the only API that tells the
truth from an x64 process emulated on ARM64.
2. ``PROCESSOR_ARCHITEW6432`` / ``PROCESSOR_ARCHITECTURE`` WOW64
(32-bit) hosts and pre-1511 Windows 10 without the newer API.
3. ``platform.machine()``.
Note ``GetNativeSystemInfo`` is deliberately NOT used: Microsoft documents
that it "also returns emulated processor details when run from an app
under emulation", so on the very WoA hosts this function exists to serve
it reports AMD64 no better than the env-var rung below it.
"""
if sys.platform == "win32":
for probe in (
_windows_native_machine_from_iswow64,
_windows_native_machine_from_native_system_info,
):
try:
name = probe()
except (OSError, AttributeError, TypeError, ValueError):
# API missing (pre-1511), DLL load failure in tests, or a
# mistyped ctypes binding — try the next probe.
name = None
if name:
return name
try:
name = _windows_native_machine_from_iswow64()
except (OSError, AttributeError, TypeError, ValueError):
# API missing (pre-1511), DLL load failure in tests, or a
# mistyped ctypes binding — fall through to the env vars.
name = None
if name:
return name
env_arch = os.environ.get("PROCESSOR_ARCHITEW6432") or os.environ.get(
"PROCESSOR_ARCHITECTURE"
)
@ -5694,12 +5681,23 @@ def _windows_native_machine() -> str:
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. Host detection uses the OS-native machine
(see ``_windows_native_machine``), not the process architecture.
Preferred source is ``GetMachineTypeAttributes``, which answers this
question directly (including AMD64-on-ARM64 emulation) instead of
inferring it from an architecture name.
Fallback is name-based: 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. Host detection uses the OS-native
machine (see ``_windows_native_machine``), not the process architecture.
"""
if sys.platform == "win32":
try:
runnable = _windows_user_runnable_pe_machines()
except (OSError, AttributeError, TypeError, ValueError):
runnable = None
if runnable:
return runnable
machine = _windows_native_machine().upper()
if machine in ("AMD64", "X86_64", "X64"):
return {_PE_MACHINE_AMD64, _PE_MACHINE_I386}

View file

@ -122,9 +122,8 @@ def test_expected_machines_unknown_host_is_permissive():
# ─── _windows_native_machine ────────────────────────────────────────────────
# winnt.h PROCESSOR_ARCHITECTURE_* — mirrors hermes_cli.main constants.
_SYSINFO_AMD64 = 9
_SYSINFO_ARM64 = 12
# MACHINE_ATTRIBUTES.UserEnabled — mirrors hermes_cli.main.
_USER_ENABLED = 0x00000001
class _SettableFunc:
@ -147,14 +146,16 @@ class _FakeKernel32:
native_pe_machine: int,
*,
wow64_ok: bool = True,
system_info_arch: int | None = None,
user_runnable: set | None = None,
):
self._native_pe = native_pe_machine
self._wow64_ok = wow64_ok
self._system_info_arch = system_info_arch
self._user_runnable = user_runnable
self.GetCurrentProcess = _SettableFunc(lambda: -1)
self.IsWow64Process2 = _SettableFunc(self._is_wow64_process2)
self.GetNativeSystemInfo = _SettableFunc(self._get_native_system_info)
self.GetMachineTypeAttributes = _SettableFunc(
self._get_machine_type_attributes
)
def _is_wow64_process2(self, _handle, process_ref, native_ref):
if not self._wow64_ok:
@ -163,22 +164,26 @@ class _FakeKernel32:
native_ref._obj.value = self._native_pe
return 1
def _get_native_system_info(self, info_ref):
if self._system_info_arch is None:
raise AttributeError("GetNativeSystemInfo unavailable in this fake")
info_ref._obj.wProcessorArchitecture = self._system_info_arch
def _get_machine_type_attributes(self, machine, attributes_ref):
if self._user_runnable is None:
raise AttributeError("GetMachineTypeAttributes unavailable in this fake")
if machine not in self._user_runnable:
attributes_ref._obj.value = 0
return 0
attributes_ref._obj.value = _USER_ENABLED
return 0
def _fake_windll(
native_pe_machine: int,
*,
wow64_ok: bool = True,
system_info_arch: int | None = None,
user_runnable: set | None = None,
):
kernel32 = _FakeKernel32(
native_pe_machine,
wow64_ok=wow64_ok,
system_info_arch=system_info_arch,
user_runnable=user_runnable,
)
def _windll(name, *args, **kwargs):
@ -218,11 +223,10 @@ def test_native_machine_binds_current_process_handle_restype(monkeypatch):
assert kernel32.IsWow64Process2.argtypes is not None
def test_native_machine_system_info_fallback_when_iswow64_fails(monkeypatch):
"""Residual #71218 failure: IsWow64Process2 returns FALSE (e.g. invalid
handle) while PROCESSOR_ARCHITECTURE still lies as AMD64. GetNativeSystemInfo
must still report the real ARM64 host so the integrity gate accepts the
correctly-built ARM64 Hermes.exe."""
def test_expected_machines_prefers_user_runnable_api_over_arch_name(monkeypatch):
"""GetMachineTypeAttributes answers "can this host load PE machine X?"
directly, so a WoA host that reports AMD64 everywhere else still accepts an
ARM64 exe."""
import ctypes
monkeypatch.setattr(cli_main.sys, "platform", "win32")
@ -231,14 +235,28 @@ def test_native_machine_system_info_fallback_when_iswow64_fails(monkeypatch):
with patch.object(
ctypes,
"WinDLL",
_fake_windll(PE_ARM64, wow64_ok=False, system_info_arch=_SYSINFO_ARM64),
_fake_windll(
PE_ARM64, wow64_ok=False, user_runnable={PE_ARM64, PE_AMD64}
),
create=True,
), patch("platform.machine", return_value="AMD64"):
assert cli_main._windows_native_machine() == "ARM64"
assert cli_main._expected_windows_pe_machines() == {PE_ARM64, PE_AMD64}
def test_expected_machines_falls_back_when_attributes_api_missing(monkeypatch):
"""Pre-Windows-11 hosts have no GetMachineTypeAttributes — the name-based
mapping must still drive the gate."""
import ctypes
monkeypatch.setattr(cli_main.sys, "platform", "win32")
with patch.object(
ctypes, "WinDLL", _fake_windll(PE_ARM64, user_runnable=None), create=True
), patch("platform.machine", return_value="AMD64"):
assert cli_main._expected_windows_pe_machines() == {PE_ARM64, PE_AMD64}
def test_native_machine_env_fallback_without_api(monkeypatch):
"""Pre-1511 Windows 10: no IsWow64Process2 / GetNativeSystemInfo → env."""
"""Pre-1511 Windows 10: no IsWow64Process2 → env."""
import ctypes
monkeypatch.setattr(cli_main.sys, "platform", "win32")
@ -286,11 +304,12 @@ def test_integrity_gate_accepts_arm64_exe_from_emulated_x64_process(monkeypatch,
assert cli_main._desktop_exe_integrity_error(exe) is None
def test_integrity_gate_accepts_arm64_when_iswow64_fails_but_system_info_ok(
def test_integrity_gate_accepts_arm64_when_iswow64_fails_but_attributes_ok(
monkeypatch, tmp_path
):
"""End-to-end residual WoA shape: IsWow64Process2 fails, env lies as AMD64,
GetNativeSystemInfo reports ARM64, ARM64 Hermes.exe must pass the gate."""
"""End-to-end residual WoA shape: IsWow64Process2 fails and env lies as
AMD64, but GetMachineTypeAttributes reports ARM64 as user-runnable, so the
ARM64 Hermes.exe must pass the gate."""
import ctypes
monkeypatch.setattr(cli_main.sys, "platform", "win32")
@ -300,7 +319,9 @@ def test_integrity_gate_accepts_arm64_when_iswow64_fails_but_system_info_ok(
with patch.object(
ctypes,
"WinDLL",
_fake_windll(PE_ARM64, wow64_ok=False, system_info_arch=_SYSINFO_ARM64),
_fake_windll(
PE_ARM64, wow64_ok=False, user_runnable={PE_ARM64, PE_AMD64}
),
create=True,
), patch("platform.machine", return_value="AMD64"):
assert cli_main._desktop_exe_integrity_error(exe) is None