Binary catalogers load full ELF symbol tables for every executable — excessive allocations for non-matching files
Nobody has claimed this yet.
- 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— callself.File.Symbols()+DynamicSymbols()on every ELF binary, loading the full.symtab/.dynsymsections and.strtab/.dynstrstring tables into memory. On alpine (8.7 MB, zero GraalVM binaries), this allocates 883 MB cumulatively. -
go-module-binary-cataloger— usesdebug/buildinfo.Read()which callssearchMagic, scanning the entire binary file in memory looking for the\xff Go buildinf:magic sequence. Also usesgoversionlibrary 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_isolatebefore calling.Symbols()/.DynamicSymbols() - Go module cataloger: Check for
.go.buildinfoELF section existence before callingbuildinfo.Read()(a section header lookup, no data loading) - Cargo auditable cataloger: Check for
.dep-v0section 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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