From 3db21e81c99ae1e8bf8f24450046c18f53d78d74 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Thu, 16 Jul 2026 21:01:50 +0200 Subject: sbom: T9098: syft should run un squashfs instead of unpacked chroot lb (live-build) binary runs binary_rootfs first, then binary_grub-efi. binary_grub-efi temporarily installs EFI tooling and then runs: "apt remove --auto-remove --purge --allow-remove-essential" That cleanup is safe for the already-generated squashfs, but it mutates build/chroot and can remove vyos-1x, breaking subsequent work that expects chroot to remain intact for SBOM generation. But why unpacking the squashfs? In an ideal world we could call syft on the compressed squashfs file which is supported. In our world, we do use a BCJ pre-filter chained with LZMA2 for compressing the squashfs, which will increase the compression ratio without a decompression penalty. Difference: ~14.3 MB, ~2.7% smaller This is unsupported by fyft which means we do need to unpack the squashfs first before checking the files and generating the SBOM file. --- scripts/image-build/build-vyos-image | 51 ++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 19 deletions(-) (limited to 'scripts') diff --git a/scripts/image-build/build-vyos-image b/scripts/image-build/build-vyos-image index b181a6e4..a1458d99 100755 --- a/scripts/image-build/build-vyos-image +++ b/scripts/image-build/build-vyos-image @@ -31,6 +31,7 @@ import datetime import functools import string import subprocess +import tempfile class ImageBuildError(Exception): pass @@ -156,6 +157,7 @@ def build(): 'qemu-utils', 'gdisk', 'kpartx', + 'squashfs-tools', 'dosfstools' ], 'binaries': [] @@ -727,26 +729,37 @@ Pin-Priority: 600 manifest['artifacts'].append(iso_file) # Now create SBOM - syft_target_dir = 'chroot' - syft_base_path = os.getcwd() + f'/{syft_target_dir}' base_filename = iso_file.rstrip('.iso') - syft_cmd = [['syft', syft_target_dir, - '--source-name', 'VyOS', '--source-version', version, - '-o', f'cyclonedx-json={base_filename}.cdx.json', - '-o', f'spdx-json={base_filename}.spdx.json']] - - # syft bug for CycloneDX https://github.com/anchore/syft/issues/4592#issuecomment-4567247328 - syft_cmd.append(['sed', '-i', '-e', f's@{syft_base_path}@@g', f'{base_filename}.cdx.json']) - syft_cmd.append(['sed', '-i', '-e', f's@{syft_base_path}@//@g', f'{base_filename}.spdx.json']) - - for c in syft_cmd: - with subprocess.Popen(c, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - text=True, bufsize=1) as p: - for line in p.stdout: - sys.stdout.write(line) - sys.stdout.flush() - p.wait() - print("I: Finished SBOM generation") + syft_target_dir = tempfile.mkdtemp(prefix='unsquashfs_rootfs-', dir=os.getcwd()) + syft_base_path = syft_target_dir + try: + # lb config builds the amd64 squashfs as xz with -Xbcj x86 (a BCJ pre-filter + # chained with LZMA2. Real unsquashfs/mksquashfs fully support multi-filter + # xz streams; syft's own Go-based squashfs/xz decoder apparently only handles + # plain single-filter. Extract squashfs first + print("I: Unpack squashfs for SBOM generation") + syft_cmd = [['unsquashfs', '-quiet', '-no-progress', '-force', '-dest', syft_target_dir, 'binary/live/filesystem.squashfs']] + # run syft on extracted content + syft_cmd.append(['syft', syft_target_dir, + '--source-name', 'VyOS', '--source-version', version, + '-o', f'cyclonedx-json={base_filename}.cdx.json', + '-o', f'spdx-json={base_filename}.spdx.json']) + + # syft bug for CycloneDX https://github.com/anchore/syft/issues/4592#issuecomment-4567247328 + syft_cmd.append(['sed', '-i', '-e', f's@{syft_base_path}@@g', f'{base_filename}.cdx.json']) + syft_cmd.append(['sed', '-i', '-e', f's@{syft_base_path}@//@g', f'{base_filename}.spdx.json']) + + for c in syft_cmd: + with subprocess.Popen(c, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + text=True, bufsize=1) as p: + for line in p.stdout: + sys.stdout.write(line) + sys.stdout.flush() + p.wait() + print("I: Finished SBOM generation") + finally: + # remove temporary unpacked squashfs, even on failure/interruption + shutil.rmtree(syft_target_dir, ignore_errors=True) # If the flavor has `image_format = "iso"`, then the work is done. -- cgit v1.2.3