From 99c3e82d28daba8317c92e2cd41ac87d840bb911 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Tue, 10 Nov 2026 17:37:25 -0600 Subject: [PATCH] Preserve guard interfaces and make runtime types explicit --- .../longitudinal/tests/test_force_stop.py | 2 + .../models/runtime_bridge/dom_usb.py | 4 +- .../models/runtime_bridge/guard-policy.json | 6 +- .../models/runtime_bridge/runtime.py | 2 +- .../models/tests/test_usb_source_modes.py | 62 +++++++++++++++++++ openpilot/starpilot/speed_limits/runtime.py | 2 +- .../vision/tests/test_stop_observation.py | 1 + prebuilt.json | 2 +- 8 files changed, 73 insertions(+), 8 deletions(-) diff --git a/openpilot/starpilot/longitudinal/tests/test_force_stop.py b/openpilot/starpilot/longitudinal/tests/test_force_stop.py index 6c87028c1a..e7900b87ff 100644 --- a/openpilot/starpilot/longitudinal/tests/test_force_stop.py +++ b/openpilot/starpilot/longitudinal/tests/test_force_stop.py @@ -374,6 +374,7 @@ class ForceStopTests(unittest.TestCase): def test_sign_behind_stopped_lead_stops_at_line_after_lead_leaves(self): self.queue_behind_lead() + assert self.owner.queue_line is not None self.assertAlmostEqual(self.owner.queue_line, 9.5) for tick in range(30, 35): plan = self.owner.step(frame(tick, speed_mps=0., horizon_m=12., light=False, standstill=True, lead_present=True, @@ -408,6 +409,7 @@ class ForceStopTests(unittest.TestCase): plan = self.owner.step(frame(60, speed_mps=0., horizon_m=5., light=False, standstill=True, lead_present=True, lead_distance_m=4., lead_speed_mps=0.), self.tune) self.assertFalse(plan.forcing) + assert self.owner.queue_line is not None self.assertAlmostEqual(self.owner.queue_line, 8.5) def test_driver_gas_while_queued_forgets_the_sign(self): diff --git a/openpilot/starpilot/models/runtime_bridge/dom_usb.py b/openpilot/starpilot/models/runtime_bridge/dom_usb.py index 3f6acbc23a..7d311be85e 100644 --- a/openpilot/starpilot/models/runtime_bridge/dom_usb.py +++ b/openpilot/starpilot/models/runtime_bridge/dom_usb.py @@ -7,7 +7,7 @@ from openpilot.starpilot.models.runtime_bridge.usb import USBProtection class DomUSBProtection(USBProtection): - def protect_call(self, call): + def protect_call(self, call, *, fresh_source=False): if isinstance(call.arg.aux, HCQInfo) and call.arg.aux.device == ('QCOM',) and call.body.op is Ops.SINK: return Protection.protect_call(self, call, fresh_source=True) - return super().protect_call(call) + return super().protect_call(call, fresh_source=fresh_source) diff --git a/openpilot/starpilot/models/runtime_bridge/guard-policy.json b/openpilot/starpilot/models/runtime_bridge/guard-policy.json index 128fd549f5..c2b743ff07 100644 --- a/openpilot/starpilot/models/runtime_bridge/guard-policy.json +++ b/openpilot/starpilot/models/runtime_bridge/guard-policy.json @@ -5,14 +5,14 @@ "bootstrap.py": "2efd3aaffb98d30e316fb950dac77b16f0866dfeed1c9767b97dad915e4d0c63", "bounded_wait.c": "a9e1a82e50ebefd983b3dbc6f784adc465cbd06848debed11dfff80cc8a832fe", "capture.py": "834cf5d83725729f30aec68a7ba5ca4897701dd4aa34147f0b49d8b0a9b9e81a", - "dom_admission.py": "45ca0ad26271b6606af258af040ded846b19b74f693f837dd802fa71b9e9a284", + "dom_admission.py": "f02dd5d30a2a38bd9ca03481c2d4fb42982453da052d3551bdd6f05a5ada1a92", "dom_loader.py": "6a51233e641a42a093d8c9412f4bba809133d7e9b9aeb212dcdeeb102c12a49b", - "dom_usb.py": "f57983bac65f61873d67fa644e9eedde6eecec9d026130c31d8e55efe2185c0d", + "dom_usb.py": "b329c614eeef71698e8acee8acafa727cc7e2635f9a595bfac1b50e668747aaf", "libbounded_wait.so": "24ed559861c8636c1026cad9a244854bb2236b5ee8b91bf21c5efbf688fa4a69", "libusb_guards.so": "66a3dfe48ac17899862ab80160cf6dac2c9b9707234527ba0d808f3b68404114", "ops-mapping.json": "8acf8d759ffd597684fce9e0879b488cf2b32c3f05a5ca355a77ba0026c6bb95", "qcom.py": "da93aa4b1929fbb881056669a78e765d113a2ace2d766e1cf7cce40ddba8ab95", - "usb.py": "a2e84aa6d6be4cd7b8db7bf9dae85f67a6008de5c6e93ecb288fefede2b46e23", + "usb.py": "3de547d0b92b75333593fca8f985406894e3be2c4e02b34a5fd52205ddecd890", "usb_guards.c": "8b3c207de74f92b74980da35428cff6f4a198ce97f9f8b6e64aded74e0110316" }, "native_abi": "linux-aarch64-64bit", diff --git a/openpilot/starpilot/models/runtime_bridge/runtime.py b/openpilot/starpilot/models/runtime_bridge/runtime.py index 3941336e6f..1652e71080 100644 --- a/openpilot/starpilot/models/runtime_bridge/runtime.py +++ b/openpilot/starpilot/models/runtime_bridge/runtime.py @@ -17,7 +17,7 @@ _GUARD_HASHES = { 'libbounded_wait.so': '24ed559861c8636c1026cad9a244854bb2236b5ee8b91bf21c5efbf688fa4a69', 'libusb_guards.so': '66a3dfe48ac17899862ab80160cf6dac2c9b9707234527ba0d808f3b68404114', } -_POLICY_SHA256 = '2eec2cf23b56e46e25adea32dd500cd37da53905ad9391dc6b020cb905b65f42' +_POLICY_SHA256 = '06b6304d889166a448c4e48bb737933cbdeeedfe49a4c629af11ba06bd30e46b' _selection = None _protection = None _laboratory_request_json = None diff --git a/openpilot/starpilot/models/tests/test_usb_source_modes.py b/openpilot/starpilot/models/tests/test_usb_source_modes.py index 83fb7aa050..a65da2d800 100644 --- a/openpilot/starpilot/models/tests/test_usb_source_modes.py +++ b/openpilot/starpilot/models/tests/test_usb_source_modes.py @@ -14,3 +14,65 @@ def test_usb_guard_rejects_explicit_fresh_qcom_source_before_access(): exec(compile(ast.Module(body=[method], type_ignores=[]), str(source), 'exec'), namespace) with pytest.raises(ValueError, match='not qualified'): namespace['protect_call'](object(), object(), fresh_source=True) + + +@pytest.mark.parametrize(('fresh_source', 'qualified'), [(False, True), (True, True), (True, False)]) +def test_dom_guard_preserves_qualified_qcom_dispatch(fresh_source, qualified): + from types import SimpleNamespace + + calls = [] + class Protection: + def protect_call(self, call, *, fresh_source=False): + calls.append((call, fresh_source)) + return call + + class HCQInfo: + device = ('QCOM',) + + sink = object() + namespace = {} + namespace.update(Protection=Protection, HCQInfo=HCQInfo, Ops=SimpleNamespace(SINK=sink)) + root = Path(__file__).resolve().parents[1] / 'runtime_bridge' + for filename, classname in [('usb.py', 'USBProtection'), ('dom_usb.py', 'DomUSBProtection')]: + source = root / filename + tree = ast.parse(source.read_text()) + owner = next(node for node in tree.body if isinstance(node, ast.ClassDef) and node.name == classname) + exec(compile(ast.Module(body=[owner], type_ignores=[]), str(source), 'exec'), namespace) + + guard_type = namespace['DomUSBProtection'] + assert isinstance(guard_type, type) + guard = guard_type.__new__(guard_type) + call = SimpleNamespace(arg=SimpleNamespace(aux=HCQInfo() if qualified else object()), body=SimpleNamespace(op=sink)) + if qualified: + assert guard.protect_call(call, fresh_source=fresh_source) is call + assert calls == [(call, True)] + else: + with pytest.raises(ValueError, match='not qualified'): + guard.protect_call(call, fresh_source=True) + assert calls == [] + + +def test_runtime_policy_matches_packaged_guard_sources(): + from openpilot.starpilot.models.runtime_bridge import runtime + runtime._verify_policy() + + +def test_runtime_policy_still_rejects_each_modified_guard(tmp_path, monkeypatch): + import json + import shutil + from openpilot.starpilot.models.runtime_bridge import runtime + + root = Path(runtime.__file__).parent + policy = json.loads((root / 'guard-policy.json').read_text()) + for name in ['guard-policy.json', *policy['files']]: + shutil.copyfile(root / name, tmp_path / name) + monkeypatch.setattr(runtime, '__file__', str(tmp_path / 'runtime.py')) + runtime._verify_policy() + for name in ['guard-policy.json', *policy['files']]: + path = tmp_path / name + original = path.read_bytes() + path.write_bytes(original + b'\n') + with pytest.raises(RuntimeError, match='changed'): + runtime._verify_policy() + path.write_bytes(original) + runtime._verify_policy() diff --git a/openpilot/starpilot/speed_limits/runtime.py b/openpilot/starpilot/speed_limits/runtime.py index 98ce95d4aa..75d449f45e 100644 --- a/openpilot/starpilot/speed_limits/runtime.py +++ b/openpilot/starpilot/speed_limits/runtime.py @@ -356,7 +356,7 @@ class Runtime: ego_speeds = (car_state.vEgo, car_state.vEgoCluster) ego_valid = car_fresh and all(type(speed) in (int, float) and math.isfinite(speed) and (car_state.standstill or speed >= 0.) for speed in ego_speeds) - ego = (sd.SpeedPair(sd.DomainStatus.VALID, *(max(0., speed) for speed in ego_speeds), self.session_id, now_ns) + ego = (sd.SpeedPair(sd.DomainStatus.VALID, max(0., ego_speeds[0]), max(0., ego_speeds[1]), self.session_id, now_ns) if ego_valid else sd.SpeedPair(sd.DomainStatus.STALE if sm.logMonoTime.get("carState", 0) else sd.DomainStatus.UNKNOWN)) host = comp.HostState(bool(CP.openpilotLongitudinalControl), host_long, bool(selfdrive.enabled and selfdrive_fresh), bool(controls.forceDecel) if controls_fresh else True, selected, selected_cluster) diff --git a/openpilot/starpilot/speed_limits/vision/tests/test_stop_observation.py b/openpilot/starpilot/speed_limits/vision/tests/test_stop_observation.py index a126212894..050ad54221 100644 --- a/openpilot/starpilot/speed_limits/vision/tests/test_stop_observation.py +++ b/openpilot/starpilot/speed_limits/vision/tests/test_stop_observation.py @@ -132,6 +132,7 @@ class TestStopObservation(unittest.TestCase): if expected is None: self.assertIsNone(owner.distance_m) else: + assert owner.distance_m is not None self.assertAlmostEqual(owner.distance_m, expected, places=2) owner.observe(None, now_ns=BASE + 900_000_000) self.assertIsNone(owner.distance_m) diff --git a/prebuilt.json b/prebuilt.json index d52ba6533f..9d1b8f36a6 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "ae58f86d7b25e48aa0d608b5d95a87e0cf59caaa0c223af2d5abe5f56c260be3", "schema_version": 1} +{"protected_inventory_sha256": "5708a3cd319cd5c96ad0fa1679a47712342bb2b70ff983b410bc308aad184d47", "schema_version": 1}