fix(update): decide SCM service ownership by name before asking binpath; AccessDenied is "not ours"
Review follow-up: `find_windows_gateway_services` queried binpath (QueryServiceConfig, SERVICE_QUERY_CONFIG) for EVERY enumerated service before the ownership gate, where main only ever asked status/pid. One hardened third-party service denying that query to the non-elevated user raised psutil.AccessDenied -> "SCM service inspection failed" -> the whole update aborted (the #96860 fragility class on a new query). Hermes-named services are now settled by name alone; binpath is asked only for the rest, and a service this user cannot even query-config is one it could not `sc stop` either -> skipped, never an abort.
This commit is contained in:
@@ -822,8 +822,17 @@ def find_windows_gateway_services(
|
||||
# Ownership before state: an OS service above the gateway (Task Scheduler's svchost for a
|
||||
# task-launched gateway, BITS mid-transition) is never its supervisor, so neither its
|
||||
# PID nor its status may steer the pause. Only Hermes-owned services reach the guards below.
|
||||
service_binpath = str(_scm_service_field(service, "binpath") or "")
|
||||
if not hermes_owns_windows_service(service_name, service_binpath, hermes_roots):
|
||||
# The name alone settles Hermes-named services; binpath (QueryServiceConfig) is asked only
|
||||
# for the rest, and a service that refuses even that to this user is one this user could
|
||||
# not `sc stop` either — never Hermes's, never a reason to abort the enumeration.
|
||||
owned = hermes_owns_windows_service(service_name, "", hermes_roots)
|
||||
if not owned:
|
||||
try:
|
||||
service_binpath = str(_scm_service_field(service, "binpath") or "")
|
||||
except psutil_module.AccessDenied:
|
||||
continue
|
||||
owned = hermes_owns_windows_service(service_name, service_binpath, hermes_roots)
|
||||
if not owned:
|
||||
continue
|
||||
service_status = _scm_service_field(service, "status")
|
||||
service_pid = int(_scm_service_field(service, "pid") or 0)
|
||||
|
||||
@@ -1327,6 +1327,7 @@ def test_find_windows_gateway_services_ignores_task_scheduler_ancestor(monkeypat
|
||||
"""gateway <- cmd.exe <- svchost.exe(Schedule) <- services.exe: the Task Scheduler host is not the
|
||||
gateway's supervisor, so a task-launched gateway is a plain process (#97208); the same tree under a
|
||||
Hermes-owned service (by binary path) stays SCM-supervised."""
|
||||
import psutil
|
||||
import hermes_cli.gateway_windows as gateway_windows
|
||||
|
||||
monkeypatch.setattr(gateway_windows, "hermes_service_roots", lambda: (r"C:\hermes\hermes-agent",))
|
||||
@@ -1355,7 +1356,8 @@ def test_find_windows_gateway_services_ignores_task_scheduler_ancestor(monkeypat
|
||||
|
||||
def run(service):
|
||||
return gateway.find_windows_gateway_services(
|
||||
psutil_module=SimpleNamespace(win_service_iter=lambda: [service], Process=FakeProcess),
|
||||
psutil_module=SimpleNamespace(
|
||||
win_service_iter=lambda: [service], Process=FakeProcess, AccessDenied=psutil.AccessDenied),
|
||||
profile_processes=[profile],
|
||||
)
|
||||
|
||||
@@ -1363,6 +1365,16 @@ def test_find_windows_gateway_services_ignores_task_scheduler_ancestor(monkeypat
|
||||
owned = run(FakeService("gw", r'"C:\hermes\hermes-agent\venv\Scripts\hermes.exe" gateway run'))
|
||||
assert [(s.name, s.service_pid, s.gateway_pid) for s in owned] == [("gw", 2360, 18480)]
|
||||
|
||||
# QueryServiceConfig denied to this user (hardened third-party service): not Hermes's, and never a
|
||||
# reason to abort the whole enumeration; a Hermes-NAMED service is settled without asking binpath.
|
||||
class DeniedConfigService(FakeService):
|
||||
def binpath(self):
|
||||
raise psutil.AccessDenied(2360, self._name)
|
||||
|
||||
assert run(DeniedConfigService("Hardened", "")) == []
|
||||
named = run(DeniedConfigService("HermesGateway", ""))
|
||||
assert [(s.name, s.service_pid, s.gateway_pid) for s in named] == [("HermesGateway", 2360, 18480)]
|
||||
|
||||
|
||||
def test_find_windows_gateway_services_rejects_shared_service_host_pid(monkeypatch):
|
||||
"""A shared host PID cannot prove which service owns the gateway subtree."""
|
||||
|
||||
Reference in New Issue
Block a user