Skip to content

Commit 29b84d8

Browse files
committed
sbom: address Skoll review findings
Thread a new SBOM_DEP_VERSIONS make variable to gen-sbom's --dep-version so autotools packagers without pkg-config avoid NOASSERTION dep versions, resolve each dependency version once instead of per-format, drop a dead FIPS bucket branch in gen-advisory, and make the gen-advisory exec bit match gen-sbom. Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
1 parent a9c6055 commit 29b84d8

5 files changed

Lines changed: 90 additions & 4 deletions

File tree

Makefile.am

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,14 @@ WOLFSSL_LIB_DSO_BASENAMES = \
470470
# their own URL should set this to a URI they
471471
# actually serve (e.g.
472472
# https://example.com/sbom/wolfssl-X.Y.Z.spdx.json).
473+
# SBOM_DEP_VERSIONS Space-separated KEY=VERSION list forwarded to
474+
# gen-sbom as repeated --dep-version flags (KEY is
475+
# one of the known deps, e.g. liboqs / libz). Use
476+
# this on build/packaging hosts that lack the dep's
477+
# pkg-config .pc file, where version detection would
478+
# otherwise fall back to NOASSERTION (SPDX) / an
479+
# omitted version+purl (CycloneDX). Example:
480+
# make sbom SBOM_DEP_VERSIONS='liboqs=0.10.0 libz=1.3.1'.
473481
# SBOM_LIB_OVERRIDE Absolute path to the library artefact whose
474482
# SHA-256 should land in the SBOM, INSTEAD of
475483
# discovering one via a private staging install.
@@ -547,6 +555,7 @@ sbom:
547555
--lib "$$sbom_lib" \
548556
--dep-libz $(ENABLED_LIBZ) \
549557
--dep-liboqs $(ENABLED_LIBOQS) \
558+
$(foreach dv,$(SBOM_DEP_VERSIONS),--dep-version '$(dv)') \
550559
--cdx-out $(abs_builddir)/$(SBOM_CDX) \
551560
--spdx-out $(abs_builddir)/$(SBOM_SPDX); \
552561
$(PYSPDXTOOLS) --infile $(abs_builddir)/$(SBOM_SPDX) \

doc/SBOM.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,15 @@ its `.pc` file is missing):
498498
prints a warning to stderr.
499499

500500
For embedded / cross-compile builds without `pkg-config`, the standalone
501-
entry point exposes a `--dep-version libz=1.3.1` override (see § 1.2).
501+
entry point exposes a `--dep-version libz=1.3.1` override (see § 1.2). The
502+
autotools path exposes the same override through the `SBOM_DEP_VERSIONS`
503+
make variable (space-separated `KEY=VERSION` pairs), so a packaging host
504+
that lacks the dependency's `.pc` file can still record the version instead
505+
of `NOASSERTION`:
506+
507+
```sh
508+
make sbom SBOM_DEP_VERSIONS='liboqs=0.10.0 libz=1.3.1'
509+
```
502510

503511
### 2.5 Validating the SBOM manually
504512

scripts/gen-advisory

100644100755
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,10 +340,8 @@ def product_model(adv, ov):
340340
modver = fips.get('module_version')
341341
fbucket = _STATE_TO_BUCKET.get(fips.get('status', 'exploitable'),
342342
'known_affected')
343-
if fips.get('status') in _STATE_TO_BUCKET:
344-
fbucket = _STATE_TO_BUCKET[fips['status']]
345343
affected_ranges = []
346-
if fbucket != 'fixed' and modver:
344+
if modver:
347345
affected_ranges.append({
348346
'label': modver,
349347
'cpe': cpe_for('wolfcrypt', modver),

scripts/gen-sbom

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,21 @@ def _parse_dep_version_overrides(spec_list):
923923
return overrides
924924

925925

926+
def _resolve_dep_versions(enabled_deps, overrides):
927+
"""Resolve each enabled dependency's version exactly once, mutating and
928+
returning `overrides` so both the CDX and SPDX emitters reuse the same
929+
value instead of each re-invoking pkg-config. Caching the result
930+
(including None) means a later dep_version() lookup short-circuits on the
931+
membership check rather than re-shelling to `pkg-config --modversion`, so
932+
a default --with-libz --with-liboqs build calls pkg-config once per dep
933+
(not once per dep per output format) and the two documents can never
934+
disagree if pkg-config output were ever non-deterministic."""
935+
for key in enabled_deps:
936+
if key not in overrides:
937+
overrides[key] = dep_version(key, overrides)
938+
return overrides
939+
940+
926941
def main():
927942
parser = argparse.ArgumentParser(
928943
description='Generate CycloneDX and SPDX SBOMs for wolfssl. '
@@ -1061,6 +1076,10 @@ def main():
10611076
if flag.lower() == 'yes'
10621077
]
10631078
dep_version_overrides = _parse_dep_version_overrides(args.dep_version)
1079+
# Resolve each enabled dependency's version once, here, and feed the
1080+
# result to both the CDX and SPDX emitters via the overrides map (see
1081+
# _resolve_dep_versions for the once-per-dep pkg-config rationale).
1082+
_resolve_dep_versions(enabled_deps, dep_version_overrides)
10641083

10651084
if args.license_override:
10661085
license_id = args.license_override

scripts/test_gen_sbom.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,6 +1253,58 @@ def test_parse_overrides_accepts_known_keys(self):
12531253
self.assertEqual(out, {'libz': '1.3.1', 'liboqs': '0.10.0'})
12541254

12551255

1256+
class TestResolveDepVersionsSingleShot(unittest.TestCase):
1257+
"""Each enabled dependency's version must be resolved exactly once (in
1258+
main, via _resolve_dep_versions), not once per output format. Without
1259+
the precompute, generate_cdx and generate_spdx each call dep_version()
1260+
independently, so a default --with-libz --with-liboqs build would shell
1261+
out to `pkg-config --modversion` four times (2 deps x CDX+SPDX) instead
1262+
of twice -- and the two documents could disagree if pkg-config were ever
1263+
non-deterministic. These tests lock that single-resolution behaviour in."""
1264+
1265+
def test_pkgconfig_called_once_per_dep(self):
1266+
calls = []
1267+
original = gs.pkgconfig_version
1268+
try:
1269+
gs.pkgconfig_version = lambda pkg: (calls.append(pkg), '1.2.3')[1]
1270+
overrides = gs._resolve_dep_versions(['libz', 'liboqs'], {})
1271+
self.assertEqual(len(calls), 2)
1272+
self.assertEqual(overrides['libz'], '1.2.3')
1273+
self.assertEqual(overrides['liboqs'], '1.2.3')
1274+
# The emitters reuse the cached value: a later dep_version() for
1275+
# an already-resolved key must not re-invoke pkg-config.
1276+
gs.dep_version('libz', overrides)
1277+
gs.dep_version('liboqs', overrides)
1278+
self.assertEqual(len(calls), 2)
1279+
finally:
1280+
gs.pkgconfig_version = original
1281+
1282+
def test_user_override_skips_pkgconfig(self):
1283+
calls = []
1284+
original = gs.pkgconfig_version
1285+
try:
1286+
gs.pkgconfig_version = lambda pkg: (calls.append(pkg), '9.9.9')[1]
1287+
overrides = gs._resolve_dep_versions(['libz'], {'libz': '1.3.1'})
1288+
self.assertEqual(overrides['libz'], '1.3.1')
1289+
self.assertEqual(calls, [])
1290+
finally:
1291+
gs.pkgconfig_version = original
1292+
1293+
def test_none_is_cached_when_pkgconfig_missing(self):
1294+
calls = []
1295+
original = gs.pkgconfig_version
1296+
try:
1297+
gs.pkgconfig_version = lambda pkg: (calls.append(pkg), None)[1]
1298+
overrides = gs._resolve_dep_versions(['liboqs'], {})
1299+
self.assertIn('liboqs', overrides)
1300+
self.assertIsNone(overrides['liboqs'])
1301+
# A cached None must short-circuit later lookups too.
1302+
gs.dep_version('liboqs', overrides)
1303+
self.assertEqual(len(calls), 1)
1304+
finally:
1305+
gs.pkgconfig_version = original
1306+
1307+
12561308
class TestCliMutualExclusion(unittest.TestCase):
12571309
"""The two entry-point shapes (autotools / standalone) must be
12581310
mutually exclusive. Mixing them would produce a hash whose

0 commit comments

Comments
 (0)