xref: /aosp_15_r20/bootable/libbootloader/gbl/libgbl/testdata/gen_test_data.py (revision 5225e6b173e52d2efc6bcf950c27374fd72adabc)
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