From d6bc5027659d6661bf5c71ba9c52cdf8ffc5eb1c Mon Sep 17 00:00:00 2001 From: MrMha <114929652+MrMha@users.noreply.github.com> Date: Sun, 11 Oct 2026 18:20:18 +0400 Subject: [PATCH] fix(import-audit): validate ELF and PE exports before reporting --- tools/import_audit.py | 96 +++++++++++++--- tools/tests/test_import_audit.py | 184 ++++++++++++++++++++++++++++++- 2 files changed, 256 insertions(+), 24 deletions(-) diff --git a/tools/import_audit.py b/tools/import_audit.py index c7ebf9dc8..a6c0f6b0d 100644 --- a/tools/import_audit.py +++ b/tools/import_audit.py @@ -1,4 +1,5 @@ import argparse +import hashlib import json import re import struct @@ -33,14 +34,14 @@ def unpack(layout, data, offset, path, what): return struct.unpack(layout, take(data, offset, struct.calcsize(layout), path, what)) -def cstring(data, offset, limit, path, what): +def cstring(data, offset, limit, path, what, encoding="utf-8", errors="surrogateescape"): end = data.find(b"\0", offset, limit) if offset < 0 or offset >= limit or end < 0: raise AuditError(f"{path}: {what} at 0x{offset:x} is not a NUL-terminated string inside 0x{limit:x} bytes") try: - return data[offset:end].decode("utf-8") + return data[offset:end].decode(encoding, errors=errors) except UnicodeDecodeError as error: - raise AuditError(f"{path}: {what} at 0x{offset:x} is not UTF-8: {error}") from error + raise AuditError(f"{path}: {what} at 0x{offset:x} is not {encoding}: {error}") from error def elf_exports(path, data): @@ -48,9 +49,14 @@ def elf_exports(path, data): ident, section_offset, section_size, section_count = header[0], header[6], header[11], header[12] if ident[4] != 2 or ident[5] != 1 or section_size != 64: raise AuditError(f"{path}: only little-endian ELF64 with 64-byte section headers is supported (class {ident[4]}, data {ident[5]}, shentsize {section_size})") + if not section_offset: + raise AuditError(f"{path}: ELF exports without a section header table are not supported") + if not section_count: + section_count = unpack(" 1: + raise AuditError(f"{path}: multiple extended section index tables for section {section_index}") for position in range(0, size, 24): name, info, other, index, _, _ = unpack("= len(sections): + raise AuditError(f"{path}: extended symbol section index {index} lies outside the section table") + elif 0 < index < 0xff00 and index >= len(sections): + raise AuditError(f"{path}: symbol section index {index} lies outside the section table") binding, visibility = info >> 4, other & 3 - if index == 0 or binding not in (1, 2, 10) or visibility not in (0, 3): + if name == 0 or index == 0 or binding not in (1, 2, 10) or visibility not in (0, 3): continue names.add(cstring(data, strings_offset + name, strings_offset + strings_size, path, "export name")) return names @@ -79,12 +96,19 @@ def pe_exports(path, data): directory = PE_DIRECTORY_OFFSET[magic] if optional_size < directory + 8: raise AuditError(f"{path}: PE optional header of {optional_size} bytes has no export directory") + take(data, optional, optional_size, path, "PE optional header") + if unpack("= raw_size or length > raw_size - relative: continue take(data, raw_offset + relative, length, path, f"data at RVA 0x{address:x}") - return raw_offset + relative + return raw_offset + relative, min(raw_offset + raw_size, len(data)) raise AuditError(f"{path}: RVA 0x{address:x} is not inside any section") - table = unpack("= function_count: + raise AuditError(f"{path}: export ordinal {ordinal} lies outside the address table ({function_count} entries)") + function_rva = unpack("