anchore / anchore/syft

Binary catalogers load full ELF symbol tables for every executable — excessive allocations for non-matching files

Open
#4,777 1 comment 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

performance
Dominant language
Go
Stars
9.6k
Forks
962
Avg merge
23h 27m
Merged PRs (30d)
48

Description

Problem

Several binary-scanning catalogers open every executable in the image and load large data structures (ELF symbol tables, full file contents) before determining if the file is relevant. The vast majority of executables are not Go binaries, GraalVM native images, or Rust auditable binaries — but they all get expensive processing regardless.

The main offenders:

  • graalvm-native-image-cataloger — calls elf.File.Symbols() + DynamicSymbols() on every ELF binary, loading the full .symtab/.dynsym sections and .strtab/.dynstr string tables into memory. On alpine (8.7 MB, zero GraalVM binaries), this allocates 883 MB cumulatively.

  • go-module-binary-cataloger — uses debug/buildinfo.Read() which calls searchMagic, scanning the entire binary file in memory looking for the \xff Go buildinf: magic sequence. Also uses goversion library which loads ELF symbols via (*elfExe).Symbols. On alpine (zero Go binaries), this allocates 529 MB.

  • binary-classifier-cataloger, elf-binary-package-cataloger, cargo-auditable-binary-cataloger — similar patterns of loading substantial data from every executable.

Profiling Data

Profiled syft v1.32.0 with pprof and per-cataloger ReadMemStats attribution:

Per-cataloger TotalAlloc (cumulative allocations, includes GC'd objects)
Cataloger alpine (8.7 MB) jenkins (481 MB) gitlab-ee (4.5 GB)
graalvm-native-image-cataloger 883 MB 118 MB
go-module-binary-cataloger 529 MB 885 MB 134 MB
binary-classifier-cataloger 45 MB 291 MB
file-executable-cataloger 1,930 MB 3,824 MB 4,798 MB
elf-binary-package-cataloger 27 MB 165 MB
pprof top allocations (alpine, alloc_space at peak)
Function Cumulative
debug/elf.(*File).getSymbols64 1,262 MB
internal/saferio.ReadData 713 MB
java.nativeImageElf.getSymbols 858 MB
debug/buildinfo.searchMagic 138 MB
goversion.(*elfExe).ReadData 239 MB

These are cumulative allocations — most get GC'd — but they drive peak RSS through GC timing (heap grows to 2x live set before collection). With parallel execution (default NumCPU * 4), multiple catalogers process binaries simultaneously, stacking live allocations.

Context

We use syft as a library in Kubescape for in-cluster SBOM scanning of container images. Memory-constrained sidecars (1400 Mi) frequently OOM on medium-to-large images. #3800 reports a similar finding — disabling the Go binary cataloger resolved OOM on a 330 MB image.

Suggested Approach: Cheap Pre-Filtering

Each binary cataloger could do a cheap check before loading heavy data:

  • GraalVM cataloger: Check for specific ELF section names or do a byte scan for graal_create_isolate before calling .Symbols() / .DynamicSymbols()
  • Go module cataloger: Check for .go.buildinfo ELF section existence before calling buildinfo.Read() (a section header lookup, no data loading)
  • Cargo auditable cataloger: Check for .dep-v0 section before parsing
  • Binary classifier / ELF package cataloger: Lighter initial pass before loading full binary content

The section header check is essentially free — debug/elf.NewFile already parses section headers when opening the ELF file. Checking section names is a lookup on already-parsed data, adding zero allocations.

This would eliminate the vast majority of allocations since <1% of ELF files in typical images are Go/GraalVM/Rust binaries.

We're happy to contribute a PR for this.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the graalvm-native-image-cataloger, go-module-binary-cataloger, cargo-auditable-binary-cataloger, binary-classifier-cataloger, and elf-binary-package-cataloger entry points, focusing on their ELF, buildinfo, and symbol-loading paths. Compare the suggested section or byte pre-filters with the existing profiling data, then verify that non-matching executables avoid heavy allocations without changing cataloging results.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
devtools, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.