From e900ee169a115cef921e261fb07bb1b3f1e57abe Mon Sep 17 00:00:00 2001 From: Dylan Van Assche Date: Sun, 13 Jun 2021 12:08:14 +0200 Subject: [PATCH] install: refactor embed_firmware and add test (MR 2069) --- pmb/install/_install.py | 92 +++++++++++++++----------- test/test_install.py | 56 ++++++++++++++++ test/testdata/pmb_install/big.bin | Bin 0 -> 7000 bytes test/testdata/pmb_install/binary2.bin | Bin 0 -> 100 bytes test/testdata/pmb_install/full.bin | Bin 0 -> 5120 bytes test/testdata/pmb_install/overrun.bin | Bin 0 -> 15000 bytes test/testdata/pmb_install/small.bin | Bin 0 -> 600 bytes 7 files changed, 108 insertions(+), 40 deletions(-) create mode 100644 test/testdata/pmb_install/big.bin create mode 100644 test/testdata/pmb_install/binary2.bin create mode 100644 test/testdata/pmb_install/full.bin create mode 100644 test/testdata/pmb_install/overrun.bin create mode 100644 test/testdata/pmb_install/small.bin diff --git a/pmb/install/_install.py b/pmb/install/_install.py index 748d9edf..89dd98df 100644 --- a/pmb/install/_install.py +++ b/pmb/install/_install.py @@ -425,6 +425,57 @@ def print_firewall_info(args): logging.info("For more information: https://postmarketos.org/firewall") +def generate_binary_list(args, suffix, step): + """ + Perform three checks prior to writing binaries to disk: 1) that binaries + exist, 2) that binaries do not extend into the first partition, 3) that + binaries do not overlap each other. + + :param suffix: of the chroot, which holds the firmware files (either the + f"rootfs_{args.device}", or f"installer_{args.device}") + :param step: partition step size in bytes + """ + binary_ranges = {} + binary_list = [] + binaries = args.deviceinfo["sd_embed_firmware"].split(",") + + for binary_offset in binaries: + binary, offset = binary_offset.split(':') + try: + offset = int(offset) + except ValueError: + raise RuntimeError("Value for firmware binary offset is " + f"not valid: {offset}") + binary_path = os.path.join(args.work, f"chroot_{suffix}", "usr/share", + binary) + if not os.path.exists(binary_path): + raise RuntimeError("The following firmware binary does not " + f"exist in the {suffix} chroot: " + f"/usr/share/{binary}") + # Insure that embedding the firmware will not overrun the + # first partition + boot_part_start = args.deviceinfo["boot_part_start"] or "2048" + max_size = (int(boot_part_start) * 512) - (offset * step) + binary_size = os.path.getsize(binary_path) + if binary_size > max_size: + raise RuntimeError("The firmware is too big to embed in the " + f"disk image {binary_size}B > {max_size}B") + # Insure that the firmware does not conflict with any other firmware + # that will be embedded + binary_start = offset * step + binary_end = binary_start + binary_size + for start, end in binary_ranges.items(): + if ((binary_start >= start and binary_start <= end) or + (binary_end >= start and binary_end <= end)): + raise RuntimeError("The firmware overlaps with at least one " + f"other firmware image: {binary}") + + binary_ranges[binary_start] = binary_end + binary_list.append((binary, offset)) + + return binary_list + + def embed_firmware(args, suffix): """ This method will embed firmware, located at /usr/share, that are specified @@ -449,46 +500,7 @@ def embed_firmware(args, suffix): "is not valid: {}".format(step)) device_rootfs = mount_device_rootfs(args, suffix) - binaries = args.deviceinfo["sd_embed_firmware"].split(",") - - # Perform three checks prior to writing binaries to disk: 1) that binaries - # exist, 2) that binaries do not extend into the first partition, 3) that - # binaries do not overlap each other - binary_ranges = {} - binary_list = [] - for binary_offset in binaries: - binary, offset = binary_offset.split(':') - try: - offset = int(offset) - except ValueError: - raise RuntimeError("Value for firmware binary offset is " - "not valid: {}".format(offset)) - binary_path = os.path.join(args.work, f"chroot_{suffix}", "usr/share", - binary) - if not os.path.exists(binary_path): - raise RuntimeError("The following firmware binary does not " - f"exist in the {suffix} chroot: " - f"/usr/share/{binary}") - # Insure that embedding the firmware will not overrun the - # first partition - boot_part_start = args.deviceinfo["boot_part_start"] or "2048" - max_size = (int(boot_part_start) * 512) - (offset * step) - binary_size = os.path.getsize(binary_path) - if binary_size > max_size: - raise RuntimeError("The firmware is too big to embed in the " - "disk image {}B > {}B".format(binary_size, - max_size)) - # Insure that the firmware does not conflict with any other firmware - # that will be embedded - binary_start = offset * step - binary_end = binary_start + binary_size - for start, end in binary_ranges.items(): - if ((binary_start >= start and binary_start <= end) or - (binary_end >= start and binary_end <= end)): - raise RuntimeError("The firmware overlaps with at least one " - "other firmware image: {}".format(binary)) - binary_ranges[binary_start] = binary_end - binary_list.append((binary, offset)) + binary_list = generate_binary_list(args, suffix, step) # Write binaries to disk for binary, offset in binary_list: diff --git a/test/test_install.py b/test/test_install.py index e0f1906e..057acfed 100644 --- a/test/test_install.py +++ b/test/test_install.py @@ -2,6 +2,8 @@ # SPDX-License-Identifier: GPL-3.0-or-later import pytest import sys +import os +import shutil import pmb_test import pmb_test.const @@ -104,3 +106,57 @@ def test_get_groups(args): with pytest.raises(RuntimeError) as e: func(args) assert str(e.value).startswith("Could not find aport for package") + + +def test_generate_binary_list(args): + suffix = "mysuffix" + args.work = "/tmp" + func = pmb.install._install.generate_binary_list + binary_dir = os.path.join(args.work, f"chroot_{suffix}", "usr/share") + os.makedirs(binary_dir, exist_ok=True) + step = 1024 + binaries = [f"{pmb_test.const.testdata}/pmb_install/small.bin", + f"{pmb_test.const.testdata}/pmb_install/full.bin", + f"{pmb_test.const.testdata}/pmb_install/big.bin", + f"{pmb_test.const.testdata}/pmb_install/overrun.bin", + f"{pmb_test.const.testdata}/pmb_install/binary2.bin"] + for b in binaries: + shutil.copy(b, binary_dir) + + # Binary that is small enough to fit the partition of 10 blocks + # of 512 bytes each + binaries = "small.bin:1,binary2.bin:11" + args.deviceinfo = {"sd_embed_firmware": binaries, + "boot_part_start": "128"} + assert func(args, suffix, step) == [('small.bin', 1), ('binary2.bin', 11)] + + # Binary that is fully filling the partition of 10 blocks of 512 bytes each + binaries = "full.bin:1,binary2.bin:11" + args.deviceinfo = {"sd_embed_firmware": binaries, + "boot_part_start": "128"} + assert func(args, suffix, step) == [('full.bin', 1), ('binary2.bin', 11)] + + # Binary that is too big to fit the partition of 10 blocks + # of 512 bytes each + binaries = "big.bin:1,binary2.bin:2" + args.deviceinfo = {"sd_embed_firmware": binaries, + "boot_part_start": "128"} + with pytest.raises(RuntimeError) as e: + func(args, suffix, step) + assert str(e.value).startswith("The firmware overlaps with at least one") + + # Binary that overruns the first partition + binaries = "overrun.bin:1" + args.deviceinfo = {"sd_embed_firmware": binaries, + "boot_part_start": "1"} + with pytest.raises(RuntimeError) as e: + func(args, suffix, step) + assert str(e.value).startswith("The firmware is too big to embed in") + + # Binary does not exist + binaries = "does-not-exist.bin:1,binary2.bin:11" + args.deviceinfo = {"sd_embed_firmware": binaries, + "boot_part_start": "128"} + with pytest.raises(RuntimeError) as e: + func(args, suffix, step) + assert str(e.value).startswith("The following firmware binary does not") diff --git a/test/testdata/pmb_install/big.bin b/test/testdata/pmb_install/big.bin new file mode 100644 index 0000000000000000000000000000000000000000..a5e5b321ab45b2d8ab73b64fda701f8166f660bc GIT binary patch literal 7000 zcmeIufdBvi0K=g9Qy<|1g-~I@fB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkryI8yFi{ B00961 literal 0 HcmV?d00001 diff --git a/test/testdata/pmb_install/binary2.bin b/test/testdata/pmb_install/binary2.bin new file mode 100644 index 0000000000000000000000000000000000000000..eeb576070df6ab6d3f9dfdf278414e6c3f3ca6b7 GIT binary patch literal 100 LcmZQzpgjNp0Av6G literal 0 HcmV?d00001 diff --git a/test/testdata/pmb_install/full.bin b/test/testdata/pmb_install/full.bin new file mode 100644 index 0000000000000000000000000000000000000000..e7f3c2d40ecc31921643a456cda2de3a907b680a GIT binary patch literal 5120 scmeIu0Sy2E0K%a6Pi+qe5hx58Fkrxd0RsjM7%*VKfB^#r3>bJF7!&{i0RR91 literal 0 HcmV?d00001 diff --git a/test/testdata/pmb_install/overrun.bin b/test/testdata/pmb_install/overrun.bin new file mode 100644 index 0000000000000000000000000000000000000000..be62991f1c1d0b8e8a8c7c8213f56e25ade61d81 GIT binary patch literal 15000 zcmeIufdBvi0K=g9Qy<|1g-~I@fB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM k7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFks*h3_6$q0RR91 literal 0 HcmV?d00001 diff --git a/test/testdata/pmb_install/small.bin b/test/testdata/pmb_install/small.bin new file mode 100644 index 0000000000000000000000000000000000000000..9242370ab5b60e44c5a0523176b1b9889f011a34 GIT binary patch literal 600 QcmZQz7zLvtK>ZK^00LM50RR91 literal 0 HcmV?d00001