1#!/usr/bin/env python3 2# 3# Copyright (C) 2024 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16"""Generate test data files for libgbl tests""" 17 18import argparse 19import os 20import pathlib 21import random 22import shutil 23import subprocess 24import tempfile 25from typing import List 26 27SCRIPT_DIR = pathlib.Path(os.path.dirname(os.path.realpath(__file__))) 28GPT_TOOL = pathlib.Path(SCRIPT_DIR.parents[1]) / "tools" / "gen_gpt_disk.py" 29AVB_DIR = pathlib.Path(SCRIPT_DIR.parents[4]) / "external" / "avb" 30AVB_TOOL = AVB_DIR / "avbtool.py" 31AVB_TEST_DATA_DIR = AVB_DIR / "test" / "data" 32SZ_KB = 1024 33 34# RNG seed values. Keep the same seed value for a given file to ensure 35# reproducibility as much as possible; this will prevent adding a bunch of 36# unnecessary test binaries to the git history. 37RNG_SEED_SPARSE_TEST_RAW = 1 38RNG_SEED_ZIRCON = {"a": 2, "b": 3, "r": 4, "slotless": 5} 39 40 41# A helper for writing bytes to a file at a given offset. 42def write_file(file, offset, data): 43 file.seek(offset, 0) 44 file.write(data) 45 46 47# Generates sparse image for flashing test 48def gen_sparse_test_file(): 49 out_file_raw = SCRIPT_DIR / "sparse_test_raw.bin" 50 random.seed(RNG_SEED_SPARSE_TEST_RAW) 51 with open(out_file_raw, "wb") as f: 52 # 4k filled with 0x78563412 53 write_file(f, 0, b"\x12\x34\x56\x78" * 1024) 54 # 8k file hole (will become dont-care with the "-s" option) 55 # 12k raw data 56 write_file(f, 12 * SZ_KB, random.randbytes(12 * SZ_KB)) 57 # 8k filled with 0x78563412 58 write_file(f, 24 * SZ_KB, b"\x12\x34\x56\x78" * 1024 * 2) 59 # 12k raw data 60 write_file(f, 32 * SZ_KB, random.randbytes(12 * SZ_KB)) 61 # 4k filled with 0x78563412 62 write_file(f, 44 * SZ_KB, b"\x12\x34\x56\x78" * 1024) 63 # 8k filled with 0xEFCDAB90 64 write_file(f, 48 * SZ_KB, b"\x90\xab\xcd\xef" * 1024 * 2) 65 66 # For now this requires that img2simg exists on $PATH. 67 # It can be built from an Android checkout via `m img2simg`; the resulting 68 # binary will be at out/host/linux-x86/bin/img2simg. 69 subprocess.run( 70 ["img2simg", "-s", out_file_raw, SCRIPT_DIR / "sparse_test.bin"] 71 ) 72 subprocess.run( 73 [ 74 "img2simg", 75 "-s", 76 out_file_raw, 77 SCRIPT_DIR / "sparse_test_blk1024.bin", 78 "1024", 79 ] 80 ) 81 82 83def gen_zircon_test_images(zbi_tool): 84 if not zbi_tool: 85 print( 86 "Warning: ZBI tool not provided. Skip regenerating zircon test images" 87 ) 88 return 89 90 PSK = AVB_TEST_DATA_DIR / "testkey_cert_psk.pem" 91 ATX_METADATA = AVB_TEST_DATA_DIR / "cert_metadata.bin" 92 TEST_ROLLBACK_INDEX_LOCATION = 1 93 TEST_ROLLBACK_INDEX = 2 94 with tempfile.TemporaryDirectory() as temp_dir: 95 for suffix in ["a", "b", "r", "slotless"]: 96 temp_dir = pathlib.Path(temp_dir) 97 random.seed(RNG_SEED_ZIRCON[suffix]) 98 out_kernel_bin_file = temp_dir / f"zircon_{suffix}.bin" 99 # The first 16 bytes are two u64 integers representing `entry` and 100 # `reserve_memory_size`. 101 # Set `entry` value to 2048 and `reserve_memory_size` to 1024. 102 kernel_bytes = int(2048).to_bytes(8, "little") + int(1024).to_bytes( 103 8, "little" 104 ) 105 kernel_bytes += random.randbytes(1 * SZ_KB - 16) 106 out_kernel_bin_file.write_bytes(kernel_bytes) 107 out_zbi_file = SCRIPT_DIR / f"zircon_{suffix}.zbi" 108 # Put image in a zbi container. 109 subprocess.run( 110 [ 111 zbi_tool, 112 "--output", 113 out_zbi_file, 114 "--type=KERNEL_X64", 115 out_kernel_bin_file, 116 ] 117 ) 118 119 # Generate vbmeta descriptor. 120 vbmeta_desc = f"{temp_dir}/zircon_{suffix}.vbmeta.desc" 121 subprocess.run( 122 [ 123 AVB_TOOL, 124 "add_hash_footer", 125 "--image", 126 out_zbi_file, 127 "--partition_name", 128 "zircon", 129 "--do_not_append_vbmeta_image", 130 "--output_vbmeta_image", 131 vbmeta_desc, 132 "--partition_size", 133 "209715200", 134 ] 135 ) 136 # Generate two cmdline ZBI items to add as property descriptors to 137 # vbmeta image for test. 138 vbmeta_prop_args = [] 139 for i in range(2): 140 prop_zbi_payload = f"{temp_dir}/prop_zbi_payload_{i}.bin" 141 subprocess.run( 142 [ 143 zbi_tool, 144 "--output", 145 prop_zbi_payload, 146 "--type=CMDLINE", 147 f"--entry=vb_prop_{i}=val", 148 ] 149 ) 150 vbmeta_prop_args += [ 151 "--prop_from_file", 152 f"zbi_vb_prop_{i}:{prop_zbi_payload}", 153 ] 154 # Also adds a property where the key name does not starts with 155 # "zbi". The item should not be processed. 156 vbmeta_prop_args += [ 157 "--prop_from_file", 158 f"vb_prop_{i}:{prop_zbi_payload}", 159 ] 160 # Generate vbmeta image 161 vbmeta_img = SCRIPT_DIR / f"vbmeta_{suffix}.bin" 162 subprocess.run( 163 [ 164 AVB_TOOL, 165 "make_vbmeta_image", 166 "--output", 167 vbmeta_img, 168 "--key", 169 PSK, 170 "--algorithm", 171 "SHA512_RSA4096", 172 "--public_key_metadata", 173 ATX_METADATA, 174 "--include_descriptors_from_image", 175 vbmeta_desc, 176 "--rollback_index", 177 f"{TEST_ROLLBACK_INDEX}", 178 "--rollback_index_location", 179 f"{TEST_ROLLBACK_INDEX_LOCATION}", 180 ] 181 + vbmeta_prop_args 182 ) 183 184 185# Generates test data for A/B slot Manager writeback test 186def gen_writeback_test_bin(): 187 subprocess.run( 188 [ 189 GPT_TOOL, 190 SCRIPT_DIR / "writeback_test_disk.bin", 191 "64K", 192 "--partition=test_partition,4k,/dev/zero", 193 ], 194 check=True, 195 ) 196 197 198def sha256_hash(path: pathlib.Path) -> bytes: 199 """Returns the SHA256 hash of the given file.""" 200 hash_hex = ( 201 subprocess.run( 202 ["sha256sum", path], 203 check=True, 204 capture_output=True, 205 text=True, 206 ) 207 .stdout.split()[0] # output is "<hash> <filename>". 208 .strip() 209 ) 210 return bytes.fromhex(hash_hex) 211 212 213def gen_vbmeta(): 214 """Creates the vbmeta keys and signs some images.""" 215 # Use the test vbmeta keys from libavb. 216 for name in [ 217 "testkey_rsa4096.pem", 218 "testkey_rsa4096_pub.pem", 219 "testkey_cert_psk.pem", 220 "cert_metadata.bin", 221 "cert_permanent_attributes.bin", 222 ]: 223 shutil.copyfile(AVB_TEST_DATA_DIR / name, SCRIPT_DIR / name) 224 225 # We need the permanent attribute SHA256 hash for libavb_cert callbacks. 226 hash_bytes = sha256_hash(SCRIPT_DIR / "cert_permanent_attributes.bin") 227 (SCRIPT_DIR / "cert_permanent_attributes.hash").write_bytes(hash_bytes) 228 229 # Also create a corrupted version of the permanent attributes to test failure. 230 # This is a little bit of a pain but we don't have an easy way to do a SHA256 in Rust 231 # at the moment so we can't generate it on the fly. 232 bad_attrs = bytearray( 233 (SCRIPT_DIR / "cert_permanent_attributes.bin").read_bytes() 234 ) 235 bad_attrs[4] ^= 0x01 # Bytes 0-3 = version, byte 4 starts the public key. 236 (SCRIPT_DIR / "cert_permanent_attributes.bad.bin").write_bytes(bad_attrs) 237 hash_bytes = sha256_hash(SCRIPT_DIR / "cert_permanent_attributes.bad.bin") 238 (SCRIPT_DIR / "cert_permanent_attributes.bad.hash").write_bytes(hash_bytes) 239 240 # Convert the public key to raw bytes for use in verification. 241 subprocess.run( 242 [ 243 AVB_TOOL, 244 "extract_public_key", 245 "--key", 246 SCRIPT_DIR / "testkey_rsa4096_pub.pem", 247 "--output", 248 SCRIPT_DIR / "testkey_rsa4096_pub.bin", 249 ], 250 check=True, 251 ) 252 253 with tempfile.TemporaryDirectory() as temp_dir: 254 temp_dir = pathlib.Path(temp_dir) 255 256 # Create the hash descriptor. We only need this temporarily until we add 257 # it into the final vbmeta image. 258 hash_descriptor_path = temp_dir / "hash_descriptor.bin" 259 subprocess.run( 260 [ 261 AVB_TOOL, 262 "add_hash_footer", 263 "--dynamic_partition_size", 264 "--do_not_append_vbmeta_image", 265 "--partition_name", 266 "zircon_a", 267 "--image", 268 SCRIPT_DIR / "zircon_a.zbi", 269 "--output_vbmeta_image", 270 hash_descriptor_path, 271 "--salt", 272 "2000", 273 ], 274 check=True, 275 ) 276 277 # Create the final signed vbmeta including the hash descriptor. 278 subprocess.run( 279 [ 280 AVB_TOOL, 281 "make_vbmeta_image", 282 "--key", 283 SCRIPT_DIR / "testkey_rsa4096.pem", 284 "--algorithm", 285 "SHA512_RSA4096", 286 "--include_descriptors_from_image", 287 hash_descriptor_path, 288 "--output", 289 SCRIPT_DIR / "zircon_a.vbmeta", 290 ], 291 check=True, 292 ) 293 294 # Also create a vbmeta using the libavb_cert extension. 295 subprocess.run( 296 [ 297 AVB_TOOL, 298 "make_vbmeta_image", 299 "--key", 300 SCRIPT_DIR / "testkey_cert_psk.pem", 301 "--public_key_metadata", 302 SCRIPT_DIR / "cert_metadata.bin", 303 "--algorithm", 304 "SHA512_RSA4096", 305 "--include_descriptors_from_image", 306 hash_descriptor_path, 307 "--output", 308 SCRIPT_DIR / "zircon_a.vbmeta.cert", 309 ] 310 ) 311 312 313def _parse_args() -> argparse.Namespace: 314 parser = argparse.ArgumentParser( 315 description=__doc__, 316 formatter_class=argparse.RawDescriptionHelpFormatter, 317 ) 318 319 parser.add_argument( 320 "--zbi_tool", default="", help="Path to the Fuchsia ZBI tool" 321 ) 322 323 return parser.parse_args() 324 325 326if __name__ == "__main__": 327 args = _parse_args() 328 gen_writeback_test_bin() 329 gen_sparse_test_file() 330 gen_zircon_test_images(args.zbi_tool) 331 gen_vbmeta() 332