t8y2/dbx · error · PeFormatError

truncated PE import descriptor

Error message

truncated PE import descriptor

What it means

Each IMAGE_IMPORT_DESCRIPTOR is a 20-byte structure walked sequentially from the import directory. This error is raised when fewer than 20 bytes remain in the file at the current descriptor offset, i.e. the import descriptor array runs off the end of the file before the terminating all-zero descriptor is found. It indicates a truncated or corrupt import table rather than a legitimate terminator.

Source

Thrown at agents/scripts/validate_windows_pe_dependencies.py:91

        )

    def rva_to_offset(rva: int) -> int:
        if rva < size_of_headers:
            return rva
        for virtual_address, virtual_size, raw_offset, raw_size in sections:
            span = max(virtual_size, raw_size)
            if virtual_address <= rva < virtual_address + span:
                delta = rva - virtual_address
                if delta >= raw_size:
                    break
                return raw_offset + delta
        raise PeFormatError(f"PE RVA 0x{rva:x} is not backed by file data")

    descriptor_offset = rva_to_offset(import_directory_rva)
    imports = []
    for _ in range(4096):
        if descriptor_offset + 20 > len(data):
            raise PeFormatError("truncated PE import descriptor")
        descriptor = data[descriptor_offset : descriptor_offset + 20]
        if descriptor == b"\0" * 20:
            return sorted(set(imports), key=str.casefold)
        name_rva = _read_u32(data, descriptor_offset + 12)
        if name_rva == 0:
            raise PeFormatError("PE import descriptor has no DLL name")
        imports.append(_read_c_string(data, rva_to_offset(name_rva)))
        descriptor_offset += 20

    raise PeFormatError("PE import descriptor table is not terminated")


def forbidden_msvc_runtime_dlls(imports: list[str]) -> list[str]:
    return sorted(
        {name for name in imports if name.casefold().startswith(("msvcp", "vcruntime"))},
        key=str.casefold,
    )

View on GitHub (pinned to c0390bff16)

Solutions

  1. Re-download or rebuild the binary and compare its size/hash against the original artifact; truncation is the usual cause.
  2. Validate the import directory RVA/size against the section bounds with pefile before running the script.
  3. Wrap the call in try/except PeFormatError and report the binary as malformed instead of aborting the whole validation batch.
  4. If you produce the binaries, check that your linker/packager emits a complete, zero-terminated import descriptor table.

Example fix

// before
imports = imported_dlls(Path(binary_path))
// after
try:
    imports = imported_dlls(Path(binary_path))
except PeFormatError as err:
    print(f"rejecting {binary_path}: {err}")
    sys.exit(1)
Defensive patterns

Strategy: try-catch

Validate before calling

import os

def file_size_at_least(path, min_bytes: int) -> bool:
    return os.path.getsize(path) >= min_bytes
# Also compare against expected build-artifact size/hash to catch truncation early.

Try / catch

try:
    imports = imported_dlls(path)
except PeFormatError as err:
    if "truncated" in str(err):
        quarantine(path, reason=err)
    else:
        raise

Prevention

When it happens

Trigger: imported_dlls(path) on a PE where descriptor_offset + 20 exceeds len(data) during the descriptor walk — e.g. the import directory RVA resolves near end-of-file, the file was truncated, or the import directory size field disagrees with the actual descriptor array.

Common situations: Incomplete download or failed copy of the binary; a build artifact sliced by packaging tooling; fuzzed/malicious PE samples with truncated sections; disk corruption on build machines.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/6b89e2e6e39bcbda. Report an issue: GitHub.