Binary composition & provenance

Where the SBOM answers what packages are installed, --binaries <dir> answers what the binaries are actually made of. It walks a directory, parses every ELF file it finds, and reports — per binary and aggregated over the whole tree — architecture, libc, shared-library dependencies, the toolchain that built it, and its exploit-mitigation posture.

It is opt-in (it reads file contents, so it is not part of a default run) and entirely path-based: point it at / on a live host, or at a mounted image under --root to analyze a firmware rootfs offline. The parser is dependency-free (no libelf) and fully bounds-checked, so it is safe to run against untrusted images — a malformed or truncated file is reported as “not ELF” rather than crashing the scan.

delta --binaries /usr/bin
delta --root /mnt/image --binaries / --json

Per binary

For each ELF, Delta records:

  • architecture / class / endianness — from the ELF header (e_machine, ELF32/64, LE/BE). A mix of architectures in one image is itself notable.

  • libc flavor — derived from the program interpreter (PT_INTERP): glibc (ld-linux*), musl (ld-musl*), uclibc, or the raw loader name. This distinguishes an embedded musl/uClibc rootfs that /etc/os-release would not reveal.

  • dependencies — the DT_NEEDED shared libraries the binary pulls in, plus DT_SONAME and RUNPATH/RPATH.

  • toolchain — the producer string(s) from the .comment section (GCC: (…) 12.2.0, clang version …) and the .note.gnu.build-id.

  • linkage — dynamic, static, or static-PIE.

  • exploit mitigations — NX (non-executable stack), PIE, RELRO (partial/full), stack canary, _FORTIFY_SOURCE, and whether the binary is stripped.

In --json these appear under binaries.items:

{ "path": "/bin/ls", "arch": "aarch64", "class": 64, "endian": "LE",
  "pie": true, "static": false, "libc": "glibc",
  "compiler": "GCC: (Ubuntu 13.2.0) 13.2.0",
  "build_id": "14aaed12…", "nx": true, "relro": "full",
  "canary": true, "fortify": true, "stripped": true,
  "needed": ["libc.so.6", "libselinux.so.1"] }

Distribution summary

The interesting signal is usually the aggregate over the whole image, emitted as binaries.summary:

"summary": {
  "total": 235,
  "architectures": ["aarch64"],
  "libc": ["glibc"],
  "compilers": ["GCC: (Ubuntu 13.2.0) 13.2.0"],
  "hardening": { "nx": 235, "pie": 234, "relro": 235, "full_relro": 234,
                 "canary": 232, "fortify": 228, "static": 0, "stripped": 235 }
}

so at a glance you see: a single-architecture glibc image, one toolchain, and a hardening-coverage count per mitigation (how many of the 235 binaries carry NX, PIE, full RELRO, a canary, fortify). A stray second architecture, an unexpected compiler, or a dip in mitigation coverage all stand out.

Hardening policy

The composition above is descriptive; a norm.binaries block makes the exploit-mitigation posture enforceable. Each require_* demands that mitigation on every scanned executable and emits a hardening violation where it is missing:

{ "norm": { "binaries": {
    "require_nx": true, "require_pie": true, "require_relro": true,
    "require_canary": true, "require_fortify": false } } }
delta --binaries /usr/bin --cfg example/config_binaries.json --json

Shared libraries are exempt from require_pie (they are always position- independent) but are still held to the memory-protection requirements. Like the other policy checks, this is inert unless the binaries block is present (see Configuration), so an unconfigured --binaries run only reports, never fails.

Notes

  • Static binaries are typically stripped, so canary/fortify (detected from the dynamic symbol names) are not visible for them and read as absent.

  • Non-ELF files (scripts, data, PE/Mach-O) are silently skipped.