blob: 71f11b311d6dd1c07fafe1ca2e42301d3370dfe6 [file] [log] [blame]
Slava Andrianov192ee172025-06-11 15:40:43 -05001#!/usr/bin/env python3
2#
3# Copyright (c) 2025 Arm Limited. All rights reserved.
4#
5# SPDX-License-Identifier: BSD-3-Clause
6#
7
8# This script compares the hashes in the TFA event log against the files
9# generated by the build system, or in the case of the startup locality and
10# critical data, the default values.
11
12import os
13import hashlib
14from enum import Enum
15from dataclasses import dataclass
16
17# Stores identifiers in the log associated with the different images
18class ImageType(Enum):
19 UNKNOWN = "UNKNOWN"
20 BL2 = "BL_2"
21 BL31 = "SECURE_RT_EL3"
22 NT_FW_CONFIG = "NT_FW_CONFIG"
23 TB_FW_CONFIG = "TB_FW_CONFIG"
24 SOC_FW_CONFIG = "SOC_FW_CONFIG"
25 FW_CONFIG = "FW_CONFIG"
26 BL33 = "BL_33"
27 BL32 = "SECURE_RT_EL1"
28 BL32_EXTRA1 = "EXTRA1"
29 BL32_EXTRA2 = "EXTRA2"
30 STARTUP_LOCALITY = "StartupLocality"
31 CRITICAL_DATA = "CRITICAL DATA"
32
33marker_to_image_type = {
34 "BL_2" : ImageType.BL2,
35 "SECURE_RT_EL3" : ImageType.BL31,
36 "NT_FW_CONFIG" : ImageType.NT_FW_CONFIG,
37 "TB_FW_CONFIG" : ImageType.TB_FW_CONFIG,
38 "SOC_FW_CONFIG" : ImageType.SOC_FW_CONFIG,
39 "FW_CONFIG" : ImageType.FW_CONFIG,
40 "BL_33" : ImageType.BL33,
41 "StartupLocality" : ImageType.STARTUP_LOCALITY,
42 "CRITICAL DATA" : ImageType.CRITICAL_DATA,
43}
44
45class HashType(Enum):
46 UNKNOWN = "UNKNOWN"
47 SHA256 = "SHA256"
48 SHA384 = "SHA384"
49
50PCR_EVENT_MARKER = "PCR_Event2"
51ALGORITHM_MARKER = "AlgorithmId"
52DIGEST_MARKER = "Digest "
53EVENT_SIZE_MARKER = "EventSize"
54EVENT_TYPE_MARKER = "Event "
55
56BUF_SIZE = 65536
57
58# On FVPs, critical data is a hash of the non volatile registers which FVPs
59# do not alter. These registers have the below default values. They are stored
60# together in a struct which is then hashed, so this is replicated here
61TFW_NVCTR_VAL = 31
62NTFW_NVCTR_VAL = 223
63COMBINED_NVCTRS = (NTFW_NVCTR_VAL << 32) | TFW_NVCTR_VAL
64COMBINED_NVCTR_BYTES = COMBINED_NVCTRS.to_bytes(8, byteorder="little")
65
66# Need to know the location of the built files to verify their hashes
67artefacts_dir = os.environ["artefacts_dir"]
68out_file_path = f"{artefacts_dir}/tfa_event_log"
69
70# This is needed to correctly identify the files associated with BL32 and BL33
71# as the names of these can vary by test
72build_args_path = f"{artefacts_dir}/fip_build_args"
73
74# Structure:
75# - boolean for if an entry for this image has been found in the event log
76# - path to the built file
77# - hash type collected from the event log
78# - hash of the file from the event log
79@dataclass
80class ImageData:
81 found: bool
82 path: str
83 hash_type: HashType
84 event_log_hash: str
85
86 def __init__(self, initial_path: str):
87 self.found = False
88 self.path = initial_path
89 self.hash_type = HashType.UNKNOWN
90 self.event_log_hash = ""
91
92 # For convenience
93 def as_tuple(self):
94 return (self.found, self.path, self.hash_type, self.event_log_hash)
95
96# As event log entries for these images are found, their data will be stored
97# inside of the objects in this dictionary
98image_data = {
99 ImageType.BL2 : ImageData(f"{artefacts_dir}/bl2.bin"),
100 ImageType.BL31 : ImageData(f"{artefacts_dir}/bl31.bin"),
101 ImageType.FW_CONFIG : ImageData(f"{artefacts_dir}/fvp_fw_config.dtb"),
102 ImageType.TB_FW_CONFIG : ImageData(f"{artefacts_dir}/fvp_tb_fw_config.dtb"),
103 ImageType.NT_FW_CONFIG : ImageData(f"{artefacts_dir}/fvp_nt_fw_config.dtb"),
104 ImageType.SOC_FW_CONFIG : ImageData(f"{artefacts_dir}/fvp_soc_fw_config.dtb"),
105 ImageType.BL33 : ImageData(""),
106 ImageType.BL32 : ImageData(""),
107 ImageType.BL32_EXTRA1 : ImageData(""),
108 ImageType.BL32_EXTRA2 : ImageData(""),
109 ImageType.STARTUP_LOCALITY : ImageData(""),
110 ImageType.CRITICAL_DATA : ImageData(""),
111}
112
113# Sometimes alternate paths are provided for some of the images used in the
114# FIP, so these need to be checked for and stored in the image_data dictionary
115def get_build_arg_paths():
116 build_data = ""
117 with open(build_args_path, 'r') as f:
118 build_data = f.read()
119
120 components = build_data.split()
121 for comp in components:
122 split_point = comp.find('=')
123 name = comp[0:split_point]
124 path_value = comp[(split_point + 1):]
125 image_type = ImageType.UNKNOWN
126 if name == "BL33":
127 image_type = ImageType.BL33
128 elif "BL32" in name:
129 if "EXTRA1" in name:
130 image_type = ImageType.BL32_EXTRA1
131 elif "EXTRA2" in name:
132 image_type = ImageType.BL32_EXTRA2
133 else:
134 image_type = ImageType.BL32
135
136 if image_type != ImageType.UNKNOWN:
137 image_data[image_type].path = path_value
138
139 # BL32 can show up as its own binary if it is not diven a different name in
140 # the build file
141 if (image_data[ImageType.BL32].path == "") and os.path.exists(f"{artefacts_dir}/bl32.bin"):
142 image_data[ImageType.BL32].path = f"{artefacts_dir}/bl32.bin"
143
144
145# Only found images should have their hashes compared
146found_images = []
147
148# Get the hash of the file stored at the given path with the specified hash
149# algorithm
150def calc_file_hash(path: str, hash_type: HashType) -> str:
151
152 if hash_type == HashType.UNKNOWN:
153 return ""
154
155 if (path == ""):
156 return "No path provided"
157
158 if not os.path.exists(path):
159 return f"No file available at path: {path}"
160
161 # Need to use this because the Docker image used for CI uses Python 3.10
162 # so hashlib.file_digest() can't be used
163 hasher = hashlib.new(hash_type.value.lower())
164 with open(path, "rb") as bin_file:
165 while True:
166 file_data = bin_file.read(BUF_SIZE)
167 if not file_data: # EOF
168 break;
169 hasher.update(file_data)
170
171 return hasher.hexdigest()
172
173# For a event log entry, extract the hash algorithm used and the hash for this# entry
174def extract_hash(line: str, tfa_event_log_file) -> (str, HashType, ImageType):
175
176 # This skips over the PCR index and event type later these lines should be
177 # parsed and used to calculate the PCR value
178 while not ALGORITHM_MARKER in line:
179 line = tfa_event_log_file.readline()
180
181 hash_type = HashType.UNKNOWN
182 for ht in HashType:
183 if ht.value in line:
184 hash_type = ht
185 break
186
187 # Early return for now if other hash type
188 if hash_type == HashType.UNKNOWN:
189 return ("", hash_type, ImageType.UNKNOWN)
190
191 # Storing lines which contain the hash characters
192 digest_lines = []
193 line = tfa_event_log_file.readline()
194 if not DIGEST_MARKER in line:
195 return ("", hash_type, ImageType.UNKNOWN)
196
197 while not EVENT_SIZE_MARKER in line:
198 digest_lines.append(line)
199 line = tfa_event_log_file.readline()
200
201 # This line contains the event type
202 line = tfa_event_log_file.readline()
203 # This will get to the first char of the name of the image
204 sep_ind = line.find(':') + 2
205 event_substr = line[sep_ind:-1]
206 image_type = ImageType.UNKNOWN
207
208 if event_substr in marker_to_image_type:
209 image_type = marker_to_image_type[event_substr]
210 elif ImageType.BL32.value in event_substr:
211 if ImageType.BL32_EXTRA1.value in event_substr:
212 image_type = ImageType.BL32_EXTRA1
213 elif ImageType.BL32_EXTRA2.value in event_substr:
214 image_type = ImageType.BL32_EXTRA2
215 else:
216 image_type = ImageType.BL32
217
218 if image_type == ImageType.UNKNOWN:
219 return ("", hash_type, ImageType.UNKNOWN)
220
221 # Know that its one of the images that we want to know the hash of so can
222 # proceed with extracting the hash
223 hash = ""
224 for digest_line in digest_lines:
225 sep_ind = digest_line.find(" : ")
226 # + 3 to skip past the separator
227 component = digest_line[sep_ind + 3:].strip().replace(' ', '')
228 hash += component
229
230 return (hash, hash_type, image_type)
231
232
233# Update image data map with paths to BL33 and BL32 binaries
234get_build_arg_paths()
235
236with open(out_file_path, "r") as tfa_event_log_file:
237 line = tfa_event_log_file.readline()
238 while len(line) > 0:
239 # Found at the start of a event log entry
240 if PCR_EVENT_MARKER in line:
241 hash, hash_type, image_type = extract_hash(line, tfa_event_log_file)
242
243 if image_type != ImageType.UNKNOWN:
244 image_data[image_type].found = True
245 image_data[image_type].hash_type = hash_type
246 image_data[image_type].event_log_hash = hash
247 found_images.append(image_type)
248
249 line = tfa_event_log_file.readline()
250
251all_match = True
252for image_type in found_images:
253 present, file_path, hash_type, event_log_hash = image_data[image_type].as_tuple()
254 comparison_hash = ""
255 if image_type == ImageType.STARTUP_LOCALITY:
256 if int(event_log_hash) == 0:
257 comparison_hash = event_log_hash
258 else:
259 comparison_hash = "0"
260
261 elif image_type == ImageType.CRITICAL_DATA:
262 hasher = hashlib.new(hash_type.value.lower())
263 hasher.update(COMBINED_NVCTR_BYTES)
264 comparison_hash = hasher.hexdigest()
265 else:
266 comparison_hash = calc_file_hash(file_path, hash_type)
267
268 print(f"{image_type.name} hash algo: {hash_type.value}")
269 print(f"Event log hash: {event_log_hash}\nComparison hash: {comparison_hash}")
270 if comparison_hash != event_log_hash:
271 print("Mismatched hashes")
272 all_match = False
273
274# These two must always be present
275if not image_data[ImageType.BL2].found:
276 print("BL2 hash not found")
277
278if not image_data[ImageType.BL31].found:
279 print("BL31 hash not found")
280
281if all_match:
282 print("All found hashes match")