#!/usr/bin/env python3 # # Copyright (C) 2023 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.
"""
Generate the SBOM of the current target product in SPDX format.
Usage example:
generate-sbom.py --output_file out/target/product/vsoc_x86_64/sbom.spdx \
--metadata out/target/product/vsoc_x86_64/sbom-metadata.csv \
--build_version $(cat out/target/product/vsoc_x86_64/build_fingerprint-${TARGET_PRODUCT}.txt) \
--product_mfr=Google """
import argparse import csv import datetime import google.protobuf.text_format as text_format import hashlib import os import metadata_file_pb2 import sbom_data import sbom_writers
def checksum(file_path):
h = hashlib.sha1() if os.path.islink(file_path):
h.update(os.readlink(file_path).encode('utf-8')) else: with open(file_path, 'rb') as f:
h.update(f.read()) return f'SHA1: {h.hexdigest()}'
def is_soong_prebuilt_module(file_metadata): return (file_metadata['soong_module_type'] and
file_metadata['soong_module_type'] in SOONG_PREBUILT_MODULE_TYPES)
def is_prebuilt_package(file_metadata):
module_path = file_metadata['module_path'] if module_path: return (module_path.startswith('prebuilts/') or
is_soong_prebuilt_module(file_metadata) or
file_metadata['is_prebuilt_make_module'])
kernel_module_copy_files = file_metadata['kernel_module_copy_files'] if kernel_module_copy_files andnot kernel_module_copy_files.startswith('ANDROID-GEN:'): returnTrue
returnFalse
def get_source_package_info(file_metadata, metadata_file_path): """Return source package info exists in its METADATA file, currently including name, security tag and external SBOM reference.
See go/android-spdx and go/android-sbom-gen for more details. """ ifnot metadata_file_path: return file_metadata['module_path'], []
metadata_proto = metadata_file_protos[metadata_file_path]
external_refs = [] for tag in metadata_proto.third_party.security.tag: if tag.lower().startswith((NVD_CPE23 + 'cpe:2.3:').lower()):
external_refs.append(
sbom_data.PackageExternalRef(category=sbom_data.PackageExternalRefCategory.SECURITY,
type=sbom_data.PackageExternalRefType.cpe23Type,
locator=tag.removeprefix(NVD_CPE23))) elif tag.lower().startswith((NVD_CPE23 + 'cpe:/').lower()):
external_refs.append(
sbom_data.PackageExternalRef(category=sbom_data.PackageExternalRefCategory.SECURITY,
type=sbom_data.PackageExternalRefType.cpe22Type,
locator=tag.removeprefix(NVD_CPE23)))
if metadata_proto.name: return metadata_proto.name, external_refs else: return os.path.basename(metadata_file_path), external_refs # return the directory name only as package name
def get_prebuilt_package_name(file_metadata, metadata_file_path): """Return name of a prebuilt package, which can be from the METADATA file, metadata file path,
module path or kernel module's source path if the installed file is a kernel module.
See go/android-spdx and go/android-sbom-gen for more details. """
name = None if metadata_file_path:
metadata_proto = metadata_file_protos[metadata_file_path] if metadata_proto.name:
name = metadata_proto.name else:
name = metadata_file_path elif file_metadata['module_path']:
name = file_metadata['module_path'] elif file_metadata['kernel_module_copy_files']:
src_path = file_metadata['kernel_module_copy_files'].split(':')[0]
name = os.path.dirname(src_path)
def get_metadata_file_path(file_metadata): """Search for METADATA file of a package and return its path."""
metadata_path = '' if file_metadata['module_path']:
metadata_path = file_metadata['module_path'] elif file_metadata['kernel_module_copy_files']:
metadata_path = os.path.dirname(file_metadata['kernel_module_copy_files'].split(':')[0])
while metadata_path andnot os.path.exists(metadata_path + '/METADATA'):
metadata_path = os.path.dirname(metadata_path)
return metadata_path
def get_package_version(metadata_file_path): """Return a package's version in its METADATA file.""" ifnot metadata_file_path: returnNone
metadata_proto = metadata_file_protos[metadata_file_path] return metadata_proto.third_party.version
def get_package_homepage(metadata_file_path): """Return a package's homepage URL in its METADATA file.""" ifnot metadata_file_path: returnNone
metadata_proto = metadata_file_protos[metadata_file_path] if metadata_proto.third_party.homepage: return metadata_proto.third_party.homepage for url in metadata_proto.third_party.url: if url.type == metadata_file_pb2.URL.Type.HOMEPAGE: return url.value
returnNone
def get_package_download_location(metadata_file_path): """Return a package's code repository URL in its METADATA file.""" ifnot metadata_file_path: returnNone
metadata_proto = metadata_file_protos[metadata_file_path] if metadata_proto.third_party.url:
urls = sorted(metadata_proto.third_party.url, key=lambda url: url.type) if urls[0].type != metadata_file_pb2.URL.Type.HOMEPAGE: return urls[0].value elif len(urls) > 1: return urls[1].value
returnNone
def get_sbom_fragments(installed_file_metadata, metadata_file_path): """Return SPDX fragment of source/prebuilt packages, which usually contains a SOURCE/PREBUILT
package, a UPSTREAM package and an external SBOM document reference if sbom_ref defined in its
METADATA file.
See go/android-spdx and go/android-sbom-gen for more details. """
external_doc_ref = None
packages = []
relationships = []
# Info from METADATA file
homepage = get_package_homepage(metadata_file_path)
version = get_package_version(metadata_file_path)
download_location = get_package_download_location(metadata_file_path)
if metadata_file_path:
metadata_proto = metadata_file_protos[metadata_file_path] if metadata_proto.third_party.WhichOneof('sbom') == 'sbom_ref':
sbom_url = metadata_proto.third_party.sbom_ref.url
sbom_checksum = metadata_proto.third_party.sbom_ref.checksum
upstream_element_id = metadata_proto.third_party.sbom_ref.element_id if sbom_url and sbom_checksum and upstream_element_id:
doc_ref_id = f'DocumentRef-{PKG_UPSTREAM}-{sbom_data.encode_for_spdxid(name)}'
external_doc_ref = sbom_data.DocumentExternalReference(id=doc_ref_id,
uri=sbom_url,
checksum=sbom_checksum)
relationships.append(
sbom_data.Relationship(id1=upstream_package_id,
relationship=sbom_data.RelationshipType.VARIANT_OF,
id2=doc_ref_id + ':' + upstream_element_id))
return external_doc_ref, packages, relationships
def save_report(report_file_path, report): with open(report_file_path, 'w', encoding='utf-8') as report_file: for type, issues in report.items():
report_file.write(type + '\n') for issue in issues:
report_file.write('\t' + issue + '\n')
report_file.write('\n')
# Validate the metadata generated by Make for installed files and report if there is no metadata. def installed_file_has_metadata(installed_file_metadata, report):
installed_file = installed_file_metadata['installed_file']
module_path = installed_file_metadata['module_path']
product_copy_files = installed_file_metadata['product_copy_files']
kernel_module_copy_files = installed_file_metadata['kernel_module_copy_files']
is_platform_generated = installed_file_metadata['is_platform_generated']
if (not module_path and not product_copy_files and not kernel_module_copy_files and not is_platform_generated and not installed_file.endswith('.fsv_meta')):
report[ISSUE_NO_METADATA].append(installed_file) returnFalse
returnTrue
# Validate identifiers in a package's METADATA. # 1) Only known identifier type is allowed # 2) Only one identifier's primary_source can be true def validate_package_metadata(metadata_file_path, package_metadata):
primary_source_found = False for identifier in package_metadata.third_party.identifier: if identifier.type notin THIRD_PARTY_IDENTIFIER_TYPES:
sys.exit(f'Unknown value of third_party.identifier.type in {metadata_file_path}/METADATA: {identifier.type}.') if primary_source_found and identifier.primary_source:
sys.exit(
f'Field "primary_source" is set to true in multiple third_party.identifier in {metadata_file_path}/METADATA.')
primary_source_found = identifier.primary_source
ifnot metadata_file_path in metadata_file_protos:
metadata_file_protos[metadata_file_path] = package_metadata ifnot package_metadata.name:
report[ISSUE_METADATA_FILE_INCOMPLETE].append(f'{metadata_file_path}/METADATA does not has "name"')
ifnot package_metadata.third_party.version:
report[ISSUE_METADATA_FILE_INCOMPLETE].append(
f'{metadata_file_path}/METADATA does not has "third_party.version"')
for tag in package_metadata.third_party.security.tag: ifnot tag.startswith(NVD_CPE23):
report[ISSUE_UNKNOWN_SECURITY_TAG_TYPE].append(
f'Unknown security tag type: {tag} in {metadata_file_path}/METADATA') else:
report[ISSUE_NO_METADATA_FILE].append( "installed_file: {}, module_path: {}".format(
installed_file_metadata['installed_file'], installed_file_metadata['module_path']))
def generate_sbom_for_unbundled_apk(): with open(args.metadata, newline='') as sbom_metadata_file:
reader = csv.DictReader(sbom_metadata_file)
doc = sbom_data.Document(name=args.build_version,
namespace=f'https://www.google.com/sbom/spdx/android/{args.build_version}',
creators=['Organization: ' + args.product_mfr]) for installed_file_metadata in reader:
installed_file = installed_file_metadata['installed_file'] if args.output_file != installed_file_metadata['build_output_path'] + '.spdx.json': continue
# Report on some issues and information
report = {
ISSUE_NO_METADATA: [],
ISSUE_NO_METADATA_FILE: [],
ISSUE_METADATA_FILE_INCOMPLETE: [],
ISSUE_UNKNOWN_SECURITY_TAG_TYPE: [],
ISSUE_INSTALLED_FILE_NOT_EXIST: [],
INFO_METADATA_FOUND_FOR_PACKAGE: [],
}
# Scan the metadata in CSV file and create the corresponding package and file records in SPDX with open(args.metadata, newline='') as sbom_metadata_file:
reader = csv.DictReader(sbom_metadata_file) for installed_file_metadata in reader:
installed_file = installed_file_metadata['installed_file']
module_path = installed_file_metadata['module_path']
product_copy_files = installed_file_metadata['product_copy_files']
kernel_module_copy_files = installed_file_metadata['kernel_module_copy_files']
build_output_path = installed_file_metadata['build_output_path']
is_static_lib = installed_file_metadata['is_static_lib']
ifnot installed_file_has_metadata(installed_file_metadata, report): continue ifnot is_static_lib andnot (os.path.islink(build_output_path) or os.path.isfile(build_output_path)): # Ignore non-existing static library files for now since they are not shipped on devices.
report[ISSUE_INSTALLED_FILE_NOT_EXIST].append(installed_file) continue
file_id = new_file_id(installed_file) # TODO(b/285453664): Soong should report the information of statically linked libraries to Make. # This happens when a different sanitized version of static libraries is used in linking. # As a workaround, use the following SHA1 checksum for static libraries created by Soong, if .a files could not be # located correctly because Soong doesn't report the information to Make.
sha1 = 'SHA1: da39a3ee5e6b4b0d3255bfef95601890afd80709'# SHA1 of empty string if os.path.islink(build_output_path) or os.path.isfile(build_output_path):
sha1 = checksum(build_output_path)
doc.files.append(sbom_data.File(id=file_id,
name=installed_file,
checksum=sha1))
if is_source_package(installed_file_metadata) or is_prebuilt_package(installed_file_metadata):
metadata_file_path = get_metadata_file_path(installed_file_metadata)
report_metadata_file(metadata_file_path, installed_file_metadata, report)
# File from source fork packages or prebuilt fork packages
external_doc_ref, pkgs, rels = get_sbom_fragments(installed_file_metadata, metadata_file_path) if len(pkgs) > 0: if external_doc_ref:
doc.add_external_ref(external_doc_ref) for p in pkgs:
doc.add_package(p) for rel in rels:
doc.add_relationship(rel)
fork_package_id = pkgs[0].id # The first package should be the source/prebuilt fork package
doc.add_relationship(sbom_data.Relationship(id1=file_id,
relationship=sbom_data.RelationshipType.GENERATED_FROM,
id2=fork_package_id)) elif module_path or installed_file_metadata['is_platform_generated']: # File from PLATFORM package
doc.add_relationship(sbom_data.Relationship(id1=file_id,
relationship=sbom_data.RelationshipType.GENERATED_FROM,
id2=sbom_data.SPDXID_PLATFORM)) elif product_copy_files: # Format of product_copy_files: <source path>:<dest path>
src_path = product_copy_files.split(':')[0] # So far product_copy_files are copied from directory system, kernel, hardware, frameworks and device, # so process them as files from PLATFORM package
doc.add_relationship(sbom_data.Relationship(id1=file_id,
relationship=sbom_data.RelationshipType.GENERATED_FROM,
id2=sbom_data.SPDXID_PLATFORM)) elif installed_file.endswith('.fsv_meta'): # See build/make/core/Makefile:2988
doc.add_relationship(sbom_data.Relationship(id1=file_id,
relationship=sbom_data.RelationshipType.GENERATED_FROM,
id2=sbom_data.SPDXID_PLATFORM)) elif kernel_module_copy_files.startswith('ANDROID-GEN'): # For the four files generated for _dlkm, _ramdisk partitions # See build/make/core/Makefile:323
doc.add_relationship(sbom_data.Relationship(id1=file_id,
relationship=sbom_data.RelationshipType.GENERATED_FROM,
id2=sbom_data.SPDXID_PLATFORM))
# Process static libraries and whole static libraries the installed file links to
static_libs = installed_file_metadata['static_libraries']
whole_static_libs = installed_file_metadata['whole_static_libraries']
all_static_libs = (static_libs + ' ' + whole_static_libs).strip() if all_static_libs: for lib in all_static_libs.split(' '):
doc.add_relationship(sbom_data.Relationship(id1=file_id,
relationship=sbom_data.RelationshipType.STATIC_LINK,
id2=new_file_id(lib + '.a')))
if args.unbundled_apex:
doc.describes = doc.files[0].id
# Save SBOM records to output file
doc.generate_packages_verification_code()
doc.created = datetime.datetime.now(tz=datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
prefix = args.output_file if prefix.endswith('.spdx'):
prefix = prefix.removesuffix('.spdx') elif prefix.endswith('.spdx.json'):
prefix = prefix.removesuffix('.spdx.json')
output_file = prefix + '.spdx' if args.unbundled_apex:
output_file = prefix + '-fragment.spdx' with open(output_file, 'w', encoding="utf-8") as file:
sbom_writers.TagValueWriter.write(doc, file, fragment=args.unbundled_apex) if args.json: with open(prefix + '.spdx.json', 'w', encoding="utf-8") as file:
sbom_writers.JSONWriter.write(doc, file)
save_report(prefix + '-gen-report.txt', report)
if __name__ == '__main__':
main()
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.17 Sekunden
(vorverarbeitet am 2026-06-28)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.