From 16e5604f59f2b246f7a4a3356fca8d39ec1f178b Mon Sep 17 00:00:00 2001 From: Yan Date: Tue, 21 Jul 2026 17:34:06 -0700 Subject: [PATCH 1/3] Guard optional UEFI dispatch registration --- cle/backends/uefi_firmware.py | 57 +++++++++++++++++---------------- pyproject.toml | 2 +- tests/test_optional_backends.py | 18 +++++++++++ 3 files changed, 49 insertions(+), 28 deletions(-) create mode 100644 tests/test_optional_backends.py diff --git a/cle/backends/uefi_firmware.py b/cle/backends/uefi_firmware.py index 8301ab92..8bf7e2d7 100644 --- a/cle/backends/uefi_firmware.py +++ b/cle/backends/uefi_firmware.py @@ -108,36 +108,38 @@ def __init__(self, *args, **kwargs) -> None: def _load(self, uefi_obj): # pylint: disable=no-self-use raise CLEUnknownFormatError(f"Can't load firmware object: {uefi_obj}") - @_load.register - def _load_generic(self, uefi_obj: uefi_firmware.FirmwareObject): - for obj in uefi_obj.objects: - self._load(obj) - @_load.register def _load_none(self, uefi_obj: None): pass - @_load.register - def _load_firmwarefile(self, uefi_obj: uefi_firmware.uefi.FirmwareFile): - old_uuid = self._current_file - if uefi_obj.type == 7: # driver - uuid = UUID(bytes=uefi_obj.guid) - self._drivers_pending[uuid] = UefiModulePending() - self._current_file = uuid - self._load_generic(uefi_obj) - self._current_file = old_uuid - - @_load.register - def _load_firmwarefilesection(self, uefi_obj: uefi_firmware.uefi.FirmwareFileSystemSection): - pending = self._drivers_pending.get(self._current_file, None) - if pending is not None: - if uefi_obj.type == 16: # pe32 image - pending.pe_image = uefi_obj.content - elif uefi_obj.type == 18: # te image - pending.te_image = uefi_obj.content - elif uefi_obj.type == 21: # user interface name - pending.name = uefi_obj.content.decode("utf-16").strip("\0") - self._load_generic(uefi_obj) + if uefi_firmware is not None: + + @_load.register + def _load_generic(self, uefi_obj: uefi_firmware.FirmwareObject): + for obj in uefi_obj.objects: + self._load(obj) + + @_load.register + def _load_firmwarefile(self, uefi_obj: uefi_firmware.uefi.FirmwareFile): + old_uuid = self._current_file + if uefi_obj.type == 7: # driver + uuid = UUID(bytes=uefi_obj.guid) + self._drivers_pending[uuid] = UefiModulePending() + self._current_file = uuid + self._load_generic(uefi_obj) + self._current_file = old_uuid + + @_load.register + def _load_firmwarefilesection(self, uefi_obj: uefi_firmware.uefi.FirmwareFileSystemSection): + pending = self._drivers_pending.get(self._current_file, None) + if pending is not None: + if uefi_obj.type == 16: # pe32 image + pending.pe_image = uefi_obj.content + elif uefi_obj.type == 18: # te image + pending.te_image = uefi_obj.content + elif uefi_obj.type == 21: # user interface name + pending.name = uefi_obj.content.decode("utf-16").strip("\0") + self._load_generic(uefi_obj) @dataclass @@ -201,4 +203,5 @@ class UefiTE(UefiModuleMixin, TE): """ -register_backend("uefi", UefiFirmware) +if uefi_firmware is not None: + register_backend("uefi", UefiFirmware) diff --git a/pyproject.toml b/pyproject.toml index 0e3f8921..8118ce5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ py_limited_api = "cp312" [tool.uv.sources] archinfo = { git = "https://github.com/angr/archinfo.git", branch = "master" } -pyvex = { git = "https://github.com/angr/pyvex.git", branch = "master" } +pyvex = { git = "https://github.com/angr/pyvex.git", branch = "wasm-host-support" } [tool.black] line-length = 120 diff --git a/tests/test_optional_backends.py b/tests/test_optional_backends.py new file mode 100644 index 00000000..5ca3bf9f --- /dev/null +++ b/tests/test_optional_backends.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import subprocess +import sys + + +def test_import_without_uefi_firmware(): + script = """ +import sys + +sys.modules["uefi_firmware"] = None + +import cle +from cle.backends import ALL_BACKENDS + +assert "uefi" not in ALL_BACKENDS +""" + subprocess.run([sys.executable, "-c", script], check=True) From 3a0fde32162ae4609131752c511723c4c8263feb Mon Sep 17 00:00:00 2001 From: Yan Date: Tue, 21 Jul 2026 18:24:59 -0700 Subject: [PATCH 2/3] Use merged PyVEX source --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8118ce5a..0e3f8921 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ py_limited_api = "cp312" [tool.uv.sources] archinfo = { git = "https://github.com/angr/archinfo.git", branch = "master" } -pyvex = { git = "https://github.com/angr/pyvex.git", branch = "wasm-host-support" } +pyvex = { git = "https://github.com/angr/pyvex.git", branch = "master" } [tool.black] line-length = 120 From 6674ce434302d02836b80b9d248896e573316dba Mon Sep 17 00:00:00 2001 From: Yan Date: Tue, 21 Jul 2026 18:57:14 -0700 Subject: [PATCH 3/3] Simplify optional UEFI backend loading --- cle/backends/uefi_firmware.py | 55 ++++++++++++++------------------- tests/test_optional_backends.py | 2 +- 2 files changed, 25 insertions(+), 32 deletions(-) diff --git a/cle/backends/uefi_firmware.py b/cle/backends/uefi_firmware.py index 8bf7e2d7..6eac02a8 100644 --- a/cle/backends/uefi_firmware.py +++ b/cle/backends/uefi_firmware.py @@ -4,7 +4,7 @@ import logging import mmap from dataclasses import dataclass -from functools import singledispatchmethod +from typing import cast from uuid import UUID import archinfo @@ -104,42 +104,36 @@ def __init__(self, *args, **kwargs) -> None: if self.loader._main_object is self: self.loader._main_object = None - @singledispatchmethod - def _load(self, uefi_obj): # pylint: disable=no-self-use - raise CLEUnknownFormatError(f"Can't load firmware object: {uefi_obj}") - - @_load.register - def _load_none(self, uefi_obj: None): - pass - - if uefi_firmware is not None: - - @_load.register - def _load_generic(self, uefi_obj: uefi_firmware.FirmwareObject): - for obj in uefi_obj.objects: - self._load(obj) + def _load(self, uefi_obj): + if uefi_obj is None: + return + if uefi_firmware is None: + raise ImportError("The UEFI backend requires the uefi-firmware package") - @_load.register - def _load_firmwarefile(self, uefi_obj: uefi_firmware.uefi.FirmwareFile): - old_uuid = self._current_file + is_firmware_file = isinstance(uefi_obj, uefi_firmware.uefi.FirmwareFile) + old_uuid = self._current_file + if is_firmware_file: if uefi_obj.type == 7: # driver uuid = UUID(bytes=uefi_obj.guid) self._drivers_pending[uuid] = UefiModulePending() self._current_file = uuid - self._load_generic(uefi_obj) - self._current_file = old_uuid - - @_load.register - def _load_firmwarefilesection(self, uefi_obj: uefi_firmware.uefi.FirmwareFileSystemSection): - pending = self._drivers_pending.get(self._current_file, None) + elif isinstance(uefi_obj, uefi_firmware.uefi.FirmwareFileSystemSection): + pending = self._drivers_pending.get(self._current_file) if self._current_file is not None else None if pending is not None: if uefi_obj.type == 16: # pe32 image - pending.pe_image = uefi_obj.content + pending.pe_image = cast(bytes, uefi_obj.content) elif uefi_obj.type == 18: # te image - pending.te_image = uefi_obj.content + pending.te_image = cast(bytes, uefi_obj.content) elif uefi_obj.type == 21: # user interface name - pending.name = uefi_obj.content.decode("utf-16").strip("\0") - self._load_generic(uefi_obj) + pending.name = cast(bytes, uefi_obj.content).decode("utf-16").strip("\0") + elif not isinstance(uefi_obj, uefi_firmware.FirmwareObject): + raise CLEUnknownFormatError(f"Can't load firmware object: {uefi_obj}") + + for obj in uefi_obj.objects: + self._load(obj) + + if is_firmware_file: + self._current_file = old_uuid @dataclass @@ -186,7 +180,7 @@ def __init__(self, *args, guid: UUID, name: str | None, **kwargs): def __repr__(self): return ( f"<{type(self).__name__} Object " - f'{self.guid}{f" {self.user_interface_name}" if self.user_interface_name else ""}, ' + f"{self.guid}{f' {self.user_interface_name}' if self.user_interface_name else ''}, " f"maps [{self.min_addr:#x}:{self.max_addr:#x}]>" ) @@ -203,5 +197,4 @@ class UefiTE(UefiModuleMixin, TE): """ -if uefi_firmware is not None: - register_backend("uefi", UefiFirmware) +register_backend("uefi", UefiFirmware) diff --git a/tests/test_optional_backends.py b/tests/test_optional_backends.py index 5ca3bf9f..9f14ab0e 100644 --- a/tests/test_optional_backends.py +++ b/tests/test_optional_backends.py @@ -13,6 +13,6 @@ def test_import_without_uefi_firmware(): import cle from cle.backends import ALL_BACKENDS -assert "uefi" not in ALL_BACKENDS +assert "uefi" in ALL_BACKENDS """ subprocess.run([sys.executable, "-c", script], check=True)