From 5218e54ebbb290693881fcf047a81a56e2fc65e3 Mon Sep 17 00:00:00 2001 From: Yan Date: Thu, 1 Oct 2026 01:16:41 +0000 Subject: [PATCH] Reject malformed ELF symbol metadata --- cle/backends/elf/elf.py | 7 +++- tests/test_elf.py | 77 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 tests/test_elf.py diff --git a/cle/backends/elf/elf.py b/cle/backends/elf/elf.py index 6bfbf7d11..4f751de46 100644 --- a/cle/backends/elf/elf.py +++ b/cle/backends/elf/elf.py @@ -438,6 +438,11 @@ def get_symbol(self, symid, symbol_table=None): # pylint: disable=arguments-dif return self._nullsymbol if symbol_table is None: raise TypeError("Must specify the symbol table to look up symbols by index") + # pyelftools does not bounds-check indices for section-backed symbol tables. The synthetic table built + # from PT_DYNAMIC has no section type and its inferred size is not reliable. + symbol_table_type = symbol_table.header.get("sh_type") + if symid < 0 or (symbol_table_type in ("SHT_SYMTAB", "SHT_DYNSYM") and symid >= symbol_table.num_symbols()): + return None try: re_sym = symbol_table.get_symbol(symid) except Exception: # pylint: disable=broad-except @@ -476,7 +481,7 @@ def get_symbol(self, symid, symbol_table=None): # pylint: disable=arguments-dif symbol = ELFSymbol(self, re_sym) if version is not None and self._versions is not None: version = enums.ENUM_VERSYM.get(version, version) & 0x7FFF - symbol.version = self._versions[version] + symbol.version = self._versions.get(version) self._symbol_cache[cache_key] = symbol self._cache_symbol_name(symbol) return symbol diff --git a/tests/test_elf.py b/tests/test_elf.py new file mode 100644 index 000000000..8d01e3c99 --- /dev/null +++ b/tests/test_elf.py @@ -0,0 +1,77 @@ +from __future__ import annotations + +from types import SimpleNamespace +from typing import Any, cast + +import pytest + +from cle.backends.elf import elf as elf_module +from cle.backends.elf.elf import ELF + + +class _SymbolTable: + """Minimal pyelftools symbol table used to isolate ``ELF.get_symbol``.""" + + def __init__(self, count: int, symbol: Any = None, sh_type: str | None = "SHT_SYMTAB"): + self.count = count + self.symbol = symbol + self.lookups = [] + self.header = {} if sh_type is None else {"sh_type": sh_type} + + def num_symbols(self): + return self.count + + def get_symbol(self, index): + self.lookups.append(index) + return self.symbol + + +@pytest.mark.parametrize("index", (-1, 3)) +def test_out_of_range_symbol_index(index): + elf = cast(Any, ELF.__new__(ELF)) + symbol_table = _SymbolTable(3) + + assert elf.get_symbol(index, symbol_table) is None + assert not symbol_table.lookups + + +def test_unknown_symbol_version(monkeypatch): + elf = cast(Any, ELF.__new__(ELF)) + symbol_table = _SymbolTable(3, object()) + elf.hashtable = SimpleNamespace(symtab=symbol_table) + elf._vertable = SimpleNamespace(get_symbol=lambda _: SimpleNamespace(entry=SimpleNamespace(ndx=120))) + elf._versions = {0: "*local*", 1: "*global*"} + elf._symbol_cache = {} + elf._symbols_by_name = {} + elf._desperate_for_symbols = False + monkeypatch.setattr(ELF, "_symbol_to_tuple", staticmethod(lambda _: ("symbol",))) + monkeypatch.setattr( + elf_module, + "ELFSymbol", + lambda *_: SimpleNamespace(name="", version=None), + ) + + symbol = elf.get_symbol(2, symbol_table) + + assert symbol.version is None + assert symbol_table.lookups == [2] + + +def test_synthetic_symbol_table_does_not_use_unreliable_count(monkeypatch): + elf = cast(Any, ELF.__new__(ELF)) + symbol_table = _SymbolTable(1, object(), sh_type=None) + elf.hashtable = SimpleNamespace(symtab=symbol_table) + elf._vertable = None + elf._versions = None + elf._symbol_cache = {} + elf._symbols_by_name = {} + elf._desperate_for_symbols = False + monkeypatch.setattr(ELF, "_symbol_to_tuple", staticmethod(lambda _: ("symbol",))) + monkeypatch.setattr( + elf_module, + "ELFSymbol", + lambda *_: SimpleNamespace(name="", version=None), + ) + + assert elf.get_symbol(2, symbol_table) is not None + assert symbol_table.lookups == [2]