Skip to content

fix: recover SBOM components from monolithic non-ELF firmware - #34

Merged
nmatt0 merged 1 commit into
masterfrom
fix/sbom-binver-monolithic-nonelf
Oct 16, 2026
Merged

nmatt0 merged 1 commit into
masterfrom
fix/sbom-binver-monolithic-nonelf

Conversation

@nmatt0

@nmatt0 nmatt0 commented Oct 16, 2026

Copy link
Copy Markdown
Owner

Fixes #33

Problem

scan_binver returned immediately on non-ELF input, so the component version-banner matcher (openssl, busybox, dropbear, the TLS libs, ...) only ran on ELF files. Firmware shipped as a single statically-linked non-ELF image (proprietary-OS / RTOS routers, monolithic MCU images) got an empty SBOM, and the CVE pass reported nothing downstream of it, even when the image carried a clear version banner.

Fix

Scan non-ELF bytes too, gated two ways so filesystem-based images are untouched:

  1. Monolithic only. scan_binver takes a scan_nonelf flag; walk.cpp sets it only when the tree has no ELF member anywhere (a cheap first-4-bytes peek, short-circuited on the first ELF). A tree with any ELF keeps the current ELF-only behavior.
  2. Distinctive anchors only. A per-signature raw_safe flag; on raw bytes only the distinctive-anchor banners run. zlib (inflate /deflate , ordinary words) stays ELF-gated until it gets its own raw-bytes FP check.

The strict version patterns are unchanged, including the openssl build-date requirement (issue #7): a corrupted-date banner is still rejected, not emitted with a bogus version. This change only lifts the ELF-only restriction on where the existing patterns run.

Result

On a monolithic router image carrying OpenSSL 0.9.7b 10 Apr 2003, the SBOM goes from 0 components to openssl 0.9.7b, and the CVE pass surfaces 75 matched CVEs (9 high-signal). A second monolithic image (OpenSSL 1.0.1j 15 Oct 2014) recovers likewise.

Across a set of filesystem-based firmware images (jffs2 / ubi / full-flash, including one with 263 components), the component / CVE / secret / license / boot / key counts are byte-for-byte identical before and after, confirming filesystem trees are unaffected.

Tests

  • Unit: the raw_safe subset (zlib stays ELF-gated on raw bytes, recovered inside an ELF), strict-date rejection on the raw path, the default (filesystem) path unchanged.
  • Integration (test_sbom.py): a synthetic monolithic image recovers busybox + openssl; the same blob next to an ELF is not raw-scanned; malformed-date and zlib banners are rejected.
  • Full suite (1413 unit checks + integration) green; ASan/UBSan clean.

scan_binver was gated behind is_elf(), so a statically-linked image with no
ELF member (proprietary-OS / RTOS single-blob firmware) produced an empty
SBOM and empty CVE result even with a clear component banner present.

Scan non-ELF bytes too, but only when the whole tree is monolithic (no ELF
anywhere) and only with the distinctive-anchor signatures (raw_safe). A tree
with any ELF keeps the ELF-only behavior, so filesystem-based images are
unchanged, and zlib's ordinary-word anchors ("inflate "/"deflate ") stay
ELF-gated. The strict version patterns, including the openssl build-date
requirement (issue #7), are unchanged; this only lifts the restriction on
where they run.

Recovers e.g. OpenSSL from a monolithic router image (0 components -> openssl
plus its CVEs) while leaving every filesystem-based image byte-identical
across all passes. Covered by unit checks (the raw_safe subset, strict-date
rejection, ELF-path unchanged) and an integration test (monolithic recovery,
ELF-present skip, malformed/zlib rejection).
@nmatt0
nmatt0 merged commit 470e4e7 into master Oct 16, 2026
4 checks passed
@nmatt0
nmatt0 deleted the fix/sbom-binver-monolithic-nonelf branch October 16, 2026 19:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SBOM: component version-banner scan is ELF-gated, missing statically-linked components in monolithic (non-ELF) firmware

1 participant