blob: 71f11b311d6dd1c07fafe1ca2e42301d3370dfe6 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (c) 2025 Arm Limited. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
# This script compares the hashes in the TFA event log against the files
# generated by the build system, or in the case of the startup locality and
# critical data, the default values.
import os
import hashlib
from enum import Enum
from dataclasses import dataclass
# Stores identifiers in the log associated with the different images
class ImageType(Enum):
UNKNOWN = "UNKNOWN"
BL2 = "BL_2"
BL31 = "SECURE_RT_EL3"
NT_FW_CONFIG = "NT_FW_CONFIG"
TB_FW_CONFIG = "TB_FW_CONFIG"
SOC_FW_CONFIG = "SOC_FW_CONFIG"
FW_CONFIG = "FW_CONFIG"
BL33 = "BL_33"
BL32 = "SECURE_RT_EL1"
BL32_EXTRA1 = "EXTRA1"
BL32_EXTRA2 = "EXTRA2"
STARTUP_LOCALITY = "StartupLocality"
CRITICAL_DATA = "CRITICAL DATA"
marker_to_image_type = {
"BL_2" : ImageType.BL2,
"SECURE_RT_EL3" : ImageType.BL31,
"NT_FW_CONFIG" : ImageType.NT_FW_CONFIG,
"TB_FW_CONFIG" : ImageType.TB_FW_CONFIG,
"SOC_FW_CONFIG" : ImageType.SOC_FW_CONFIG,
"FW_CONFIG" : ImageType.FW_CONFIG,
"BL_33" : ImageType.BL33,
"StartupLocality" : ImageType.STARTUP_LOCALITY,
"CRITICAL DATA" : ImageType.CRITICAL_DATA,
}
class HashType(Enum):
UNKNOWN = "UNKNOWN"
SHA256 = "SHA256"
SHA384 = "SHA384"
PCR_EVENT_MARKER = "PCR_Event2"
ALGORITHM_MARKER = "AlgorithmId"
DIGEST_MARKER = "Digest "
EVENT_SIZE_MARKER = "EventSize"
EVENT_TYPE_MARKER = "Event "
BUF_SIZE = 65536
# On FVPs, critical data is a hash of the non volatile registers which FVPs
# do not alter. These registers have the below default values. They are stored
# together in a struct which is then hashed, so this is replicated here
TFW_NVCTR_VAL = 31
NTFW_NVCTR_VAL = 223
COMBINED_NVCTRS = (NTFW_NVCTR_VAL << 32) | TFW_NVCTR_VAL
COMBINED_NVCTR_BYTES = COMBINED_NVCTRS.to_bytes(8, byteorder="little")
# Need to know the location of the built files to verify their hashes
artefacts_dir = os.environ["artefacts_dir"]
out_file_path = f"{artefacts_dir}/tfa_event_log"
# This is needed to correctly identify the files associated with BL32 and BL33
# as the names of these can vary by test
build_args_path = f"{artefacts_dir}/fip_build_args"
# Structure:
# - boolean for if an entry for this image has been found in the event log
# - path to the built file
# - hash type collected from the event log
# - hash of the file from the event log
@dataclass
class ImageData:
found: bool
path: str
hash_type: HashType
event_log_hash: str
def __init__(self, initial_path: str):
self.found = False
self.path = initial_path
self.hash_type = HashType.UNKNOWN
self.event_log_hash = ""
# For convenience
def as_tuple(self):
return (self.found, self.path, self.hash_type, self.event_log_hash)
# As event log entries for these images are found, their data will be stored
# inside of the objects in this dictionary
image_data = {
ImageType.BL2 : ImageData(f"{artefacts_dir}/bl2.bin"),
ImageType.BL31 : ImageData(f"{artefacts_dir}/bl31.bin"),
ImageType.FW_CONFIG : ImageData(f"{artefacts_dir}/fvp_fw_config.dtb"),
ImageType.TB_FW_CONFIG : ImageData(f"{artefacts_dir}/fvp_tb_fw_config.dtb"),
ImageType.NT_FW_CONFIG : ImageData(f"{artefacts_dir}/fvp_nt_fw_config.dtb"),
ImageType.SOC_FW_CONFIG : ImageData(f"{artefacts_dir}/fvp_soc_fw_config.dtb"),
ImageType.BL33 : ImageData(""),
ImageType.BL32 : ImageData(""),
ImageType.BL32_EXTRA1 : ImageData(""),
ImageType.BL32_EXTRA2 : ImageData(""),
ImageType.STARTUP_LOCALITY : ImageData(""),
ImageType.CRITICAL_DATA : ImageData(""),
}
# Sometimes alternate paths are provided for some of the images used in the
# FIP, so these need to be checked for and stored in the image_data dictionary
def get_build_arg_paths():
build_data = ""
with open(build_args_path, 'r') as f:
build_data = f.read()
components = build_data.split()
for comp in components:
split_point = comp.find('=')
name = comp[0:split_point]
path_value = comp[(split_point + 1):]
image_type = ImageType.UNKNOWN
if name == "BL33":
image_type = ImageType.BL33
elif "BL32" in name:
if "EXTRA1" in name:
image_type = ImageType.BL32_EXTRA1
elif "EXTRA2" in name:
image_type = ImageType.BL32_EXTRA2
else:
image_type = ImageType.BL32
if image_type != ImageType.UNKNOWN:
image_data[image_type].path = path_value
# BL32 can show up as its own binary if it is not diven a different name in
# the build file
if (image_data[ImageType.BL32].path == "") and os.path.exists(f"{artefacts_dir}/bl32.bin"):
image_data[ImageType.BL32].path = f"{artefacts_dir}/bl32.bin"
# Only found images should have their hashes compared
found_images = []
# Get the hash of the file stored at the given path with the specified hash
# algorithm
def calc_file_hash(path: str, hash_type: HashType) -> str:
if hash_type == HashType.UNKNOWN:
return ""
if (path == ""):
return "No path provided"
if not os.path.exists(path):
return f"No file available at path: {path}"
# Need to use this because the Docker image used for CI uses Python 3.10
# so hashlib.file_digest() can't be used
hasher = hashlib.new(hash_type.value.lower())
with open(path, "rb") as bin_file:
while True:
file_data = bin_file.read(BUF_SIZE)
if not file_data: # EOF
break;
hasher.update(file_data)
return hasher.hexdigest()
# For a event log entry, extract the hash algorithm used and the hash for this# entry
def extract_hash(line: str, tfa_event_log_file) -> (str, HashType, ImageType):
# This skips over the PCR index and event type later these lines should be
# parsed and used to calculate the PCR value
while not ALGORITHM_MARKER in line:
line = tfa_event_log_file.readline()
hash_type = HashType.UNKNOWN
for ht in HashType:
if ht.value in line:
hash_type = ht
break
# Early return for now if other hash type
if hash_type == HashType.UNKNOWN:
return ("", hash_type, ImageType.UNKNOWN)
# Storing lines which contain the hash characters
digest_lines = []
line = tfa_event_log_file.readline()
if not DIGEST_MARKER in line:
return ("", hash_type, ImageType.UNKNOWN)
while not EVENT_SIZE_MARKER in line:
digest_lines.append(line)
line = tfa_event_log_file.readline()
# This line contains the event type
line = tfa_event_log_file.readline()
# This will get to the first char of the name of the image
sep_ind = line.find(':') + 2
event_substr = line[sep_ind:-1]
image_type = ImageType.UNKNOWN
if event_substr in marker_to_image_type:
image_type = marker_to_image_type[event_substr]
elif ImageType.BL32.value in event_substr:
if ImageType.BL32_EXTRA1.value in event_substr:
image_type = ImageType.BL32_EXTRA1
elif ImageType.BL32_EXTRA2.value in event_substr:
image_type = ImageType.BL32_EXTRA2
else:
image_type = ImageType.BL32
if image_type == ImageType.UNKNOWN:
return ("", hash_type, ImageType.UNKNOWN)
# Know that its one of the images that we want to know the hash of so can
# proceed with extracting the hash
hash = ""
for digest_line in digest_lines:
sep_ind = digest_line.find(" : ")
# + 3 to skip past the separator
component = digest_line[sep_ind + 3:].strip().replace(' ', '')
hash += component
return (hash, hash_type, image_type)
# Update image data map with paths to BL33 and BL32 binaries
get_build_arg_paths()
with open(out_file_path, "r") as tfa_event_log_file:
line = tfa_event_log_file.readline()
while len(line) > 0:
# Found at the start of a event log entry
if PCR_EVENT_MARKER in line:
hash, hash_type, image_type = extract_hash(line, tfa_event_log_file)
if image_type != ImageType.UNKNOWN:
image_data[image_type].found = True
image_data[image_type].hash_type = hash_type
image_data[image_type].event_log_hash = hash
found_images.append(image_type)
line = tfa_event_log_file.readline()
all_match = True
for image_type in found_images:
present, file_path, hash_type, event_log_hash = image_data[image_type].as_tuple()
comparison_hash = ""
if image_type == ImageType.STARTUP_LOCALITY:
if int(event_log_hash) == 0:
comparison_hash = event_log_hash
else:
comparison_hash = "0"
elif image_type == ImageType.CRITICAL_DATA:
hasher = hashlib.new(hash_type.value.lower())
hasher.update(COMBINED_NVCTR_BYTES)
comparison_hash = hasher.hexdigest()
else:
comparison_hash = calc_file_hash(file_path, hash_type)
print(f"{image_type.name} hash algo: {hash_type.value}")
print(f"Event log hash: {event_log_hash}\nComparison hash: {comparison_hash}")
if comparison_hash != event_log_hash:
print("Mismatched hashes")
all_match = False
# These two must always be present
if not image_data[ImageType.BL2].found:
print("BL2 hash not found")
if not image_data[ImageType.BL31].found:
print("BL31 hash not found")
if all_match:
print("All found hashes match")