diff options
-rw-r--r-- | core/Makefile | 77 | ||||
-rw-r--r-- | core/combo/TARGET_linux-arm.mk | 2 | ||||
-rw-r--r-- | core/config_sanitizers.mk | 18 | ||||
-rw-r--r-- | core/dex_preopt_libart_boot.mk | 2 | ||||
-rwxr-xr-x | tools/releasetools/build_image.py | 355 | ||||
-rw-r--r-- | tools/releasetools/common.py | 3 | ||||
-rwxr-xr-x | tools/releasetools/ota_from_target_files.py | 31 | ||||
-rw-r--r-- | tools/releasetools/test_add_img_to_target_files.py | 6 | ||||
-rw-r--r-- | tools/releasetools/test_blockimgdiff.py | 15 | ||||
-rw-r--r-- | tools/releasetools/test_build_image.py | 67 | ||||
-rw-r--r-- | tools/releasetools/test_common.py | 19 | ||||
-rw-r--r-- | tools/releasetools/test_ota_from_target_files.py | 32 | ||||
-rw-r--r-- | tools/releasetools/test_rangelib.py | 10 | ||||
-rw-r--r-- | tools/releasetools/test_sign_target_files_apks.py | 8 | ||||
-rw-r--r-- | tools/releasetools/test_utils.py | 8 | ||||
-rw-r--r-- | tools/releasetools/test_validate_target_files.py | 14 | ||||
-rw-r--r-- | tools/releasetools/test_verity_utils.py | 81 | ||||
-rw-r--r-- | tools/releasetools/verity_utils.py | 345 |
18 files changed, 535 insertions, 558 deletions
diff --git a/core/Makefile b/core/Makefile index 517410a67f..2df67e96f9 100644 --- a/core/Makefile +++ b/core/Makefile @@ -3045,33 +3045,42 @@ endif # ----------------------------------------------------------------- # host tools needed to build dist and OTA packages -build_ota_package := true -ifeq ($(TARGET_SKIP_OTA_PACKAGE),true) -build_ota_package := false -endif ifeq ($(BUILD_OS),darwin) -build_ota_package := false -endif -ifneq ($(strip $(SANITIZE_TARGET)),) -build_ota_package := false -endif -ifeq ($(TARGET_PRODUCT),sdk) -build_ota_package := false -endif -ifneq ($(filter generic%,$(TARGET_DEVICE)),) -build_ota_package := false -endif -ifeq ($(TARGET_NO_KERNEL),true) -build_ota_package := false -endif -ifeq ($(recovery_fstab),) -build_ota_package := false -endif -ifeq ($(TARGET_BUILD_PDK),true) -build_ota_package := false + build_ota_package := false + build_otatools_package := false +else + # set build_ota_package, and allow opt-out below + build_ota_package := true + ifeq ($(TARGET_SKIP_OTA_PACKAGE),true) + build_ota_package := false + endif + ifneq ($(strip $(SANITIZE_TARGET)),) + build_ota_package := false + endif + ifeq ($(TARGET_PRODUCT),sdk) + build_ota_package := false + endif + ifneq ($(filter generic%,$(TARGET_DEVICE)),) + build_ota_package := false + endif + ifeq ($(TARGET_NO_KERNEL),true) + build_ota_package := false + endif + ifeq ($(recovery_fstab),) + build_ota_package := false + endif + ifeq ($(TARGET_BUILD_PDK),true) + build_ota_package := false + endif + + # set build_otatools_package, and allow opt-out below + build_otatools_package := true + ifeq ($(TARGET_SKIP_OTATOOLS_PACKAGE),true) + build_otatools_package := false + endif endif -ifeq ($(build_ota_package),true) +ifeq ($(build_otatools_package),true) OTATOOLS := $(HOST_OUT_EXECUTABLES)/minigzip \ $(HOST_OUT_EXECUTABLES)/aapt \ $(HOST_OUT_EXECUTABLES)/checkvintf \ @@ -3160,13 +3169,23 @@ $(BUILT_OTATOOLS_PACKAGE): zip_root := $(call intermediates-dir-for,PACKAGING,ot OTATOOLS_DEPS := \ system/extras/ext4_utils/mke2fs.conf \ - $(sort $(shell find external/avb/test/data -type f -name "testkey_*.pem" -o \ - -name "atx_metadata.bin")) \ - $(sort $(shell find system/update_engine/scripts -name "*.pyc" -prune -o -type f -print)) \ $(sort $(shell find build/target/product/security -type f -name "*.x509.pem" -o -name "*.pk8" -o \ - -name verity_key)) \ + -name verity_key)) + +ifneq (,$(wildcard device)) +OTATOOLS_DEPS += \ $(sort $(shell find device $(wildcard vendor) -type f -name "*.pk8" -o -name "verifiedboot*" -o \ -name "*.x509.pem" -o -name "oem*.prop")) +endif +ifneq (,$(wildcard external/avb)) +OTATOOLS_DEPS += \ + $(sort $(shell find external/avb/test/data -type f -name "testkey_*.pem" -o \ + -name "atx_metadata.bin")) +endif +ifneq (,$(wildcard system/update_engine)) +OTATOOLS_DEPS += \ + $(sort $(shell find system/update_engine/scripts -name "*.pyc" -prune -o -type f -print)) +endif OTATOOLS_RELEASETOOLS := \ $(sort $(shell find build/make/tools/releasetools -name "*.pyc" -prune -o -type f)) @@ -3189,7 +3208,7 @@ $(BUILT_OTATOOLS_PACKAGE): $(OTATOOLS) $(OTATOOLS_DEPS) $(OTATOOLS_RELEASETOOLS) .PHONY: otatools-package otatools-package: $(BUILT_OTATOOLS_PACKAGE) -endif # build_ota_package +endif # build_otatools_package # ----------------------------------------------------------------- # A zip of the directories that map to the target filesystem. diff --git a/core/combo/TARGET_linux-arm.mk b/core/combo/TARGET_linux-arm.mk index 3ce64f984e..ffb6021c34 100644 --- a/core/combo/TARGET_linux-arm.mk +++ b/core/combo/TARGET_linux-arm.mk @@ -33,7 +33,7 @@ ifeq ($(strip $(TARGET_$(combo_2nd_arch_prefix)CPU_VARIANT)),) TARGET_$(combo_2nd_arch_prefix)CPU_VARIANT := generic endif -KNOWN_ARMv8_CORES := cortex-a53 cortex-a53.a57 cortex-a55 cortex-a73 cortex-a75 +KNOWN_ARMv8_CORES := cortex-a53 cortex-a53.a57 cortex-a55 cortex-a73 cortex-a75 cortex-a76 KNOWN_ARMv8_CORES += kryo denver64 exynos-m1 exynos-m2 # Many devices (incorrectly) use armv7-a-neon as the 2nd architecture variant diff --git a/core/config_sanitizers.mk b/core/config_sanitizers.mk index e58f676845..be1b124f0a 100644 --- a/core/config_sanitizers.mk +++ b/core/config_sanitizers.mk @@ -212,10 +212,6 @@ ifneq ($(filter address thread hwaddress,$(my_sanitize)),) my_sanitize := $(filter-out scudo,$(my_sanitize)) endif -ifneq ($(filter scudo,$(my_sanitize)),) - my_shared_libraries += $($(LOCAL_2ND_ARCH_VAR_PREFIX)SCUDO_RUNTIME_LIBRARY) -endif - # Undefined symbols can occur if a non-sanitized library links # sanitized static libraries. That's OK, because the executable # always depends on the ASan runtime library, which defines these @@ -375,7 +371,7 @@ ifeq ($(LOCAL_IS_HOST_MODULE)$(LOCAL_IS_AUX_MODULE),) endif endif ifneq ($(filter unsigned-integer-overflow signed-integer-overflow integer,$(my_sanitize)),) - ifeq ($(filter unsigned-integer-overflow signed-integer overflow integer,$(my_sanitize_diag)),) + ifeq ($(filter unsigned-integer-overflow signed-integer-overflow integer,$(my_sanitize_diag)),) ifeq ($(filter cfi,$(my_sanitize_diag)),) ifeq ($(filter address hwaddress,$(my_sanitize)),) my_cflags += -fsanitize-minimal-runtime @@ -387,6 +383,18 @@ ifeq ($(LOCAL_IS_HOST_MODULE)$(LOCAL_IS_AUX_MODULE),) endif endif +# For Scudo, we opt for the minimal runtime, unless some diagnostics are enabled. +ifneq ($(filter scudo,$(my_sanitize)),) + ifeq ($(filter unsigned-integer-overflow signed-integer-overflow integer cfi,$(my_sanitize_diag)),) + my_cflags += -fsanitize-minimal-runtime + endif + ifneq ($(filter -fsanitize-minimal-runtime,$(my_cflags)),) + my_shared_libraries += $($(LOCAL_2ND_ARCH_VAR_PREFIX)SCUDO_MINIMAL_RUNTIME_LIBRARY) + else + my_shared_libraries += $($(LOCAL_2ND_ARCH_VAR_PREFIX)SCUDO_RUNTIME_LIBRARY) + endif +endif + ifneq ($(strip $(LOCAL_SANITIZE_RECOVER)),) recover_arg := $(subst $(space),$(comma),$(LOCAL_SANITIZE_RECOVER)), my_cflags += -fsanitize-recover=$(recover_arg) diff --git a/core/dex_preopt_libart_boot.mk b/core/dex_preopt_libart_boot.mk index a56fd5ef55..14955f01b8 100644 --- a/core/dex_preopt_libart_boot.mk +++ b/core/dex_preopt_libart_boot.mk @@ -108,7 +108,7 @@ $($(my_2nd_arch_prefix)DEFAULT_DEX_PREOPT_BUILT_IMAGE_FILENAME) : $(LIBART_TARGE --instruction-set-variant=$($(PRIVATE_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_CPU_VARIANT) \ --instruction-set-features=$($(PRIVATE_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) \ --android-root=$(PRODUCT_OUT)/system \ - --multi-image --no-inline-from=core-oj.jar \ + --no-inline-from=core-oj.jar \ --abort-on-hard-verifier-error \ --abort-on-soft-verifier-error \ $(PRODUCT_DEX_PREOPT_BOOT_FLAGS) $(GLOBAL_DEXPREOPT_FLAGS) $(ART_BOOT_IMAGE_EXTRA_ARGS) \ diff --git a/tools/releasetools/build_image.py b/tools/releasetools/build_image.py index 42f05a7aca..43c91dac7c 100755 --- a/tools/releasetools/build_image.py +++ b/tools/releasetools/build_image.py @@ -29,18 +29,14 @@ from __future__ import print_function import os import os.path import re -import shlex import shutil import sys import common -import sparse_img - +import verity_utils OPTIONS = common.OPTIONS - -FIXED_SALT = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7" -BLOCK_SIZE = 4096 +BLOCK_SIZE = common.BLOCK_SIZE BYTES_IN_MB = 1024 * 1024 @@ -51,34 +47,6 @@ class BuildImageError(Exception): Exception.__init__(self, message) -def GetVerityFECSize(partition_size): - cmd = ["fec", "-s", str(partition_size)] - output = common.RunAndCheckOutput(cmd, verbose=False) - return int(output) - - -def GetVerityTreeSize(partition_size): - cmd = ["build_verity_tree", "-s", str(partition_size)] - output = common.RunAndCheckOutput(cmd, verbose=False) - return int(output) - - -def GetVerityMetadataSize(partition_size): - cmd = ["build_verity_metadata.py", "size", str(partition_size)] - output = common.RunAndCheckOutput(cmd, verbose=False) - return int(output) - - -def GetVeritySize(partition_size, fec_supported): - verity_tree_size = GetVerityTreeSize(partition_size) - verity_metadata_size = GetVerityMetadataSize(partition_size) - verity_size = verity_tree_size + verity_metadata_size - if fec_supported: - fec_size = GetVerityFECSize(partition_size + verity_size) - return verity_size + fec_size - return verity_size - - def GetDiskUsage(path): """Returns the number of bytes that "path" occupies on host. @@ -102,258 +70,6 @@ def GetDiskUsage(path): return int(output.split()[0]) * 512 -def GetSimgSize(image_file): - simg = sparse_img.SparseImage(image_file, build_map=False) - return simg.blocksize * simg.total_blocks - - -def ZeroPadSimg(image_file, pad_size): - blocks = pad_size // BLOCK_SIZE - print("Padding %d blocks (%d bytes)" % (blocks, pad_size)) - simg = sparse_img.SparseImage(image_file, mode="r+b", build_map=False) - simg.AppendFillChunk(0, blocks) - - -def AVBCalcMaxImageSize(avbtool, footer_type, partition_size, additional_args): - """Calculates max image size for a given partition size. - - Args: - avbtool: String with path to avbtool. - footer_type: 'hash' or 'hashtree' for generating footer. - partition_size: The size of the partition in question. - additional_args: Additional arguments to pass to "avbtool add_hash_footer" - or "avbtool add_hashtree_footer". - - Returns: - The maximum image size. - - Raises: - BuildImageError: On invalid image size. - """ - cmd = [avbtool, "add_%s_footer" % footer_type, - "--partition_size", str(partition_size), "--calc_max_image_size"] - cmd.extend(shlex.split(additional_args)) - - output = common.RunAndCheckOutput(cmd) - image_size = int(output) - if image_size <= 0: - raise BuildImageError( - "Invalid max image size: {}".format(output)) - return image_size - - -def AVBCalcMinPartitionSize(image_size, size_calculator): - """Calculates min partition size for a given image size. - - Args: - image_size: The size of the image in question. - size_calculator: The function to calculate max image size - for a given partition size. - - Returns: - The minimum partition size required to accommodate the image size. - """ - # Use image size as partition size to approximate final partition size. - image_ratio = size_calculator(image_size) / float(image_size) - - # Prepare a binary search for the optimal partition size. - lo = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE - BLOCK_SIZE - - # Ensure lo is small enough: max_image_size should <= image_size. - delta = BLOCK_SIZE - max_image_size = size_calculator(lo) - while max_image_size > image_size: - image_ratio = max_image_size / float(lo) - lo = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE - delta - delta *= 2 - max_image_size = size_calculator(lo) - - hi = lo + BLOCK_SIZE - - # Ensure hi is large enough: max_image_size should >= image_size. - delta = BLOCK_SIZE - max_image_size = size_calculator(hi) - while max_image_size < image_size: - image_ratio = max_image_size / float(hi) - hi = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE + delta - delta *= 2 - max_image_size = size_calculator(hi) - - partition_size = hi - - # Start to binary search. - while lo < hi: - mid = ((lo + hi) // (2 * BLOCK_SIZE)) * BLOCK_SIZE - max_image_size = size_calculator(mid) - if max_image_size >= image_size: # if mid can accommodate image_size - if mid < partition_size: # if a smaller partition size is found - partition_size = mid - hi = mid - else: - lo = mid + BLOCK_SIZE - - if OPTIONS.verbose: - print("AVBCalcMinPartitionSize({}): partition_size: {}.".format( - image_size, partition_size)) - - return partition_size - - -def AVBAddFooter(image_path, avbtool, footer_type, partition_size, - partition_name, key_path, algorithm, salt, - additional_args): - """Adds dm-verity hashtree and AVB metadata to an image. - - Args: - image_path: Path to image to modify. - avbtool: String with path to avbtool. - footer_type: 'hash' or 'hashtree' for generating footer. - partition_size: The size of the partition in question. - partition_name: The name of the partition - will be embedded in metadata. - key_path: Path to key to use or None. - algorithm: Name of algorithm to use or None. - salt: The salt to use (a hexadecimal string) or None. - additional_args: Additional arguments to pass to "avbtool add_hash_footer" - or "avbtool add_hashtree_footer". - """ - cmd = [avbtool, "add_%s_footer" % footer_type, - "--partition_size", partition_size, - "--partition_name", partition_name, - "--image", image_path] - - if key_path and algorithm: - cmd.extend(["--key", key_path, "--algorithm", algorithm]) - if salt: - cmd.extend(["--salt", salt]) - - cmd.extend(shlex.split(additional_args)) - - common.RunAndCheckOutput(cmd) - - -def AdjustPartitionSizeForVerity(partition_size, fec_supported): - """Modifies the provided partition size to account for the verity metadata. - - This information is used to size the created image appropriately. - - Args: - partition_size: the size of the partition to be verified. - - Returns: - A tuple of the size of the partition adjusted for verity metadata, and - the size of verity metadata. - """ - key = "%d %d" % (partition_size, fec_supported) - if key in AdjustPartitionSizeForVerity.results: - return AdjustPartitionSizeForVerity.results[key] - - hi = partition_size - if hi % BLOCK_SIZE != 0: - hi = (hi // BLOCK_SIZE) * BLOCK_SIZE - - # verity tree and fec sizes depend on the partition size, which - # means this estimate is always going to be unnecessarily small - verity_size = GetVeritySize(hi, fec_supported) - lo = partition_size - verity_size - result = lo - - # do a binary search for the optimal size - while lo < hi: - i = ((lo + hi) // (2 * BLOCK_SIZE)) * BLOCK_SIZE - v = GetVeritySize(i, fec_supported) - if i + v <= partition_size: - if result < i: - result = i - verity_size = v - lo = i + BLOCK_SIZE - else: - hi = i - - if OPTIONS.verbose: - print("Adjusted partition size for verity, partition_size: {}," - " verity_size: {}".format(result, verity_size)) - AdjustPartitionSizeForVerity.results[key] = (result, verity_size) - return (result, verity_size) - - -AdjustPartitionSizeForVerity.results = {} - - -def BuildVerityFEC(sparse_image_path, verity_path, verity_fec_path, - padding_size): - cmd = ["fec", "-e", "-p", str(padding_size), sparse_image_path, - verity_path, verity_fec_path] - common.RunAndCheckOutput(cmd) - - -def BuildVerityTree(sparse_image_path, verity_image_path): - cmd = ["build_verity_tree", "-A", FIXED_SALT, sparse_image_path, - verity_image_path] - output = common.RunAndCheckOutput(cmd) - root, salt = output.split() - return root, salt - - -def BuildVerityMetadata(image_size, verity_metadata_path, root_hash, salt, - block_device, signer_path, key, signer_args, - verity_disable): - cmd = ["build_verity_metadata.py", "build", str(image_size), - verity_metadata_path, root_hash, salt, block_device, signer_path, key] - if signer_args: - cmd.append("--signer_args=\"%s\"" % (' '.join(signer_args),)) - if verity_disable: - cmd.append("--verity_disable") - common.RunAndCheckOutput(cmd) - - -def Append2Simg(sparse_image_path, unsparse_image_path, error_message): - """Appends the unsparse image to the given sparse image. - - Args: - sparse_image_path: the path to the (sparse) image - unsparse_image_path: the path to the (unsparse) image - - Raises: - BuildImageError: On error. - """ - cmd = ["append2simg", sparse_image_path, unsparse_image_path] - try: - common.RunAndCheckOutput(cmd) - except: - raise BuildImageError(error_message) - - -def Append(target, file_to_append, error_message): - """Appends file_to_append to target. - - Raises: - BuildImageError: On error. - """ - try: - with open(target, "a") as out_file, open(file_to_append, "r") as input_file: - for line in input_file: - out_file.write(line) - except IOError: - raise BuildImageError(error_message) - - -def BuildVerifiedImage(data_image_path, verity_image_path, - verity_metadata_path, verity_fec_path, - padding_size, fec_supported): - Append( - verity_image_path, verity_metadata_path, - "Could not append verity metadata!") - - if fec_supported: - # Build FEC for the entire partition, including metadata. - BuildVerityFEC( - data_image_path, verity_image_path, verity_fec_path, padding_size) - Append(verity_image_path, verity_fec_path, "Could not append FEC!") - - Append2Simg( - data_image_path, verity_image_path, "Could not append verity data!") - - def UnsparseImage(sparse_image_path, replace=True): img_dir = os.path.dirname(sparse_image_path) unsparse_image_path = "unsparse_" + os.path.basename(sparse_image_path) @@ -372,56 +88,6 @@ def UnsparseImage(sparse_image_path, replace=True): return unsparse_image_path -def MakeVerityEnabledImage(out_file, fec_supported, prop_dict): - """Creates an image that is verifiable using dm-verity. - - Args: - out_file: the location to write the verifiable image at - prop_dict: a dictionary of properties required for image creation and - verification - - Raises: - AssertionError: On invalid partition sizes. - BuildImageError: On other errors. - """ - # get properties - image_size = int(prop_dict["image_size"]) - block_dev = prop_dict["verity_block_device"] - signer_key = prop_dict["verity_key"] + ".pk8" - if OPTIONS.verity_signer_path is not None: - signer_path = OPTIONS.verity_signer_path - else: - signer_path = prop_dict["verity_signer_cmd"] - signer_args = OPTIONS.verity_signer_args - - tempdir_name = common.MakeTempDir(suffix="_verity_images") - - # Get partial image paths. - verity_image_path = os.path.join(tempdir_name, "verity.img") - verity_metadata_path = os.path.join(tempdir_name, "verity_metadata.img") - verity_fec_path = os.path.join(tempdir_name, "verity_fec.img") - - # Build the verity tree and get the root hash and salt. - root_hash, salt = BuildVerityTree(out_file, verity_image_path) - - # Build the metadata blocks. - verity_disable = "verity_disable" in prop_dict - BuildVerityMetadata( - image_size, verity_metadata_path, root_hash, salt, block_dev, signer_path, - signer_key, signer_args, verity_disable) - - # Build the full verified image. - partition_size = int(prop_dict["partition_size"]) - verity_size = int(prop_dict["verity_size"]) - - padding_size = partition_size - image_size - verity_size - assert padding_size >= 0 - - BuildVerifiedImage( - out_file, verity_image_path, verity_metadata_path, verity_fec_path, - padding_size, fec_supported) - - def ConvertBlockMapToBaseFs(block_map_file): base_fs_file = common.MakeTempFile(prefix="script_gen_", suffix=".base_fs") convert_command = ["blk_alloc_to_base_fs", block_map_file, base_fs_file] @@ -570,9 +236,9 @@ def BuildImage(in_dir, prop_dict, out_file, target_out=None): # Adjust partition_size to add more space for AVB footer, to prevent # it from consuming partition_reserved_size. if avb_footer_type: - size = AVBCalcMinPartitionSize( + size = verity_utils.AVBCalcMinPartitionSize( size, - lambda x: AVBCalcMaxImageSize( + lambda x: verity_utils.AVBCalcMaxImageSize( avbtool, avb_footer_type, x, avb_signing_args)) prop_dict["partition_size"] = str(size) if OPTIONS.verbose: @@ -583,7 +249,7 @@ def BuildImage(in_dir, prop_dict, out_file, target_out=None): # Adjust the image size to make room for the hashes if this is to be verified. if verity_supported and is_verity_partition: partition_size = int(prop_dict.get("partition_size")) - image_size, verity_size = AdjustPartitionSizeForVerity( + image_size, verity_size = verity_utils.AdjustPartitionSizeForVerity( partition_size, verity_fec_supported) prop_dict["image_size"] = str(image_size) prop_dict["verity_size"] = str(verity_size) @@ -592,7 +258,7 @@ def BuildImage(in_dir, prop_dict, out_file, target_out=None): if avb_footer_type: partition_size = prop_dict["partition_size"] # avb_add_hash_footer_args or avb_add_hashtree_footer_args. - max_image_size = AVBCalcMaxImageSize( + max_image_size = verity_utils.AVBCalcMaxImageSize( avbtool, avb_footer_type, partition_size, avb_signing_args) prop_dict["image_size"] = str(max_image_size) @@ -709,17 +375,18 @@ def BuildImage(in_dir, prop_dict, out_file, target_out=None): if not fs_spans_partition: mount_point = prop_dict.get("mount_point") image_size = int(prop_dict["image_size"]) - sparse_image_size = GetSimgSize(out_file) + sparse_image_size = verity_utils.GetSimgSize(out_file) if sparse_image_size > image_size: raise BuildImageError( "Error: {} image size of {} is larger than partition size of " "{}".format(mount_point, sparse_image_size, image_size)) if verity_supported and is_verity_partition: - ZeroPadSimg(out_file, image_size - sparse_image_size) + verity_utils.ZeroPadSimg(out_file, image_size - sparse_image_size) # Create the verified image if this is to be verified. if verity_supported and is_verity_partition: - MakeVerityEnabledImage(out_file, verity_fec_supported, prop_dict) + verity_utils.MakeVerityEnabledImage( + out_file, verity_fec_supported, prop_dict) # Add AVB HASH or HASHTREE footer (metadata). if avb_footer_type: @@ -729,7 +396,7 @@ def BuildImage(in_dir, prop_dict, out_file, target_out=None): key_path = prop_dict.get("avb_key_path") algorithm = prop_dict.get("avb_algorithm") salt = prop_dict.get("avb_salt") - AVBAddFooter( + verity_utils.AVBAddFooter( out_file, avbtool, avb_footer_type, partition_size, partition_name, key_path, algorithm, salt, avb_signing_args) diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py index d1bfc8f2f3..7cca7663f5 100644 --- a/tools/releasetools/common.py +++ b/tools/releasetools/common.py @@ -73,6 +73,9 @@ class Options(object): OPTIONS = Options() +# The block size that's used across the releasetools scripts. +BLOCK_SIZE = 4096 + # Values for "certificate" in apkcerts that mean special things. SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL") diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py index 7ea53f8067..daf959f80c 100755 --- a/tools/releasetools/ota_from_target_files.py +++ b/tools/releasetools/ota_from_target_files.py @@ -169,7 +169,6 @@ import os.path import shlex import shutil import struct -import subprocess import sys import tempfile import zipfile @@ -393,11 +392,7 @@ class PayloadSigner(object): cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"]) signing_key = common.MakeTempFile(prefix="key-", suffix=".key") cmd.extend(["-out", signing_key]) - - get_signing_key = common.Run(cmd, verbose=False) - stdoutdata, _ = get_signing_key.communicate() - assert get_signing_key.returncode == 0, \ - "Failed to get signing key: {}".format(stdoutdata) + common.RunAndCheckOutput(cmd, verbose=False) self.signer = "openssl" self.signer_args = ["pkeyutl", "-sign", "-inkey", signing_key, @@ -410,10 +405,7 @@ class PayloadSigner(object): """Signs the given input file. Returns the output filename.""" out_file = common.MakeTempFile(prefix="signed-", suffix=".bin") cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file] - signing = common.Run(cmd) - stdoutdata, _ = signing.communicate() - assert signing.returncode == 0, \ - "Failed to sign the input file: {}".format(stdoutdata) + common.RunAndCheckOutput(cmd) return out_file @@ -431,8 +423,6 @@ class Payload(object): Args: secondary: Whether it's generating a secondary payload (default: False). """ - # The place where the output from the subprocess should go. - self._log_file = sys.stdout if OPTIONS.verbose else subprocess.PIPE self.payload_file = None self.payload_properties = None self.secondary = secondary @@ -457,10 +447,7 @@ class Payload(object): if source_file is not None: cmd.extend(["--source_image", source_file]) cmd.extend(additional_args) - p = common.Run(cmd, stdout=self._log_file, stderr=subprocess.STDOUT) - stdoutdata, _ = p.communicate() - assert p.returncode == 0, \ - "brillo_update_payload generate failed: {}".format(stdoutdata) + common.RunAndCheckOutput(cmd) self.payload_file = payload_file self.payload_properties = None @@ -484,9 +471,7 @@ class Payload(object): "--signature_size", "256", "--metadata_hash_file", metadata_sig_file, "--payload_hash_file", payload_sig_file] - p1 = common.Run(cmd, stdout=self._log_file, stderr=subprocess.STDOUT) - p1.communicate() - assert p1.returncode == 0, "brillo_update_payload hash failed" + common.RunAndCheckOutput(cmd) # 2. Sign the hashes. signed_payload_sig_file = payload_signer.Sign(payload_sig_file) @@ -501,9 +486,7 @@ class Payload(object): "--signature_size", "256", "--metadata_signature_file", signed_metadata_sig_file, "--payload_signature_file", signed_payload_sig_file] - p1 = common.Run(cmd, stdout=self._log_file, stderr=subprocess.STDOUT) - p1.communicate() - assert p1.returncode == 0, "brillo_update_payload sign failed" + common.RunAndCheckOutput(cmd) # 4. Dump the signed payload properties. properties_file = common.MakeTempFile(prefix="payload-properties-", @@ -511,9 +494,7 @@ class Payload(object): cmd = ["brillo_update_payload", "properties", "--payload", signed_payload_file, "--properties_file", properties_file] - p1 = common.Run(cmd, stdout=self._log_file, stderr=subprocess.STDOUT) - p1.communicate() - assert p1.returncode == 0, "brillo_update_payload properties failed" + common.RunAndCheckOutput(cmd) if self.secondary: with open(properties_file, "a") as f: diff --git a/tools/releasetools/test_add_img_to_target_files.py b/tools/releasetools/test_add_img_to_target_files.py index cc7b887db5..ad22b7252f 100644 --- a/tools/releasetools/test_add_img_to_target_files.py +++ b/tools/releasetools/test_add_img_to_target_files.py @@ -16,7 +16,6 @@ import os import os.path -import unittest import zipfile import common @@ -30,14 +29,11 @@ from rangelib import RangeSet OPTIONS = common.OPTIONS -class AddImagesToTargetFilesTest(unittest.TestCase): +class AddImagesToTargetFilesTest(test_utils.ReleaseToolsTestCase): def setUp(self): OPTIONS.input_tmp = common.MakeTempDir() - def tearDown(self): - common.Cleanup() - def _verifyCareMap(self, expected, file_name): """Parses the care_map.pb; and checks the content in plain text.""" text_file = common.MakeTempFile(prefix="caremap-", suffix=".txt") diff --git a/tools/releasetools/test_blockimgdiff.py b/tools/releasetools/test_blockimgdiff.py index 124b4d54e8..857026e34a 100644 --- a/tools/releasetools/test_blockimgdiff.py +++ b/tools/releasetools/test_blockimgdiff.py @@ -14,17 +14,14 @@ # limitations under the License. # -from __future__ import print_function - -import unittest - import common -from blockimgdiff import (BlockImageDiff, EmptyImage, HeapItem, ImgdiffStats, - Transfer) +from blockimgdiff import ( + BlockImageDiff, EmptyImage, HeapItem, ImgdiffStats, Transfer) from rangelib import RangeSet +from test_utils import ReleaseToolsTestCase -class HealpItemTest(unittest.TestCase): +class HealpItemTest(ReleaseToolsTestCase): class Item(object): def __init__(self, score): @@ -54,7 +51,7 @@ class HealpItemTest(unittest.TestCase): self.assertFalse(item) -class BlockImageDiffTest(unittest.TestCase): +class BlockImageDiffTest(ReleaseToolsTestCase): def test_GenerateDigraphOrder(self): """Make sure GenerateDigraph preserves the order. @@ -245,7 +242,7 @@ class BlockImageDiffTest(unittest.TestCase): block_image_diff.imgdiff_stats.stats) -class ImgdiffStatsTest(unittest.TestCase): +class ImgdiffStatsTest(ReleaseToolsTestCase): def test_Log(self): imgdiff_stats = ImgdiffStats() diff --git a/tools/releasetools/test_build_image.py b/tools/releasetools/test_build_image.py index a2df27886a..634c6b1913 100644 --- a/tools/releasetools/test_build_image.py +++ b/tools/releasetools/test_build_image.py @@ -15,33 +15,20 @@ # import filecmp -import math import os.path -import random -import unittest import common from build_image import ( - AVBCalcMinPartitionSize, BLOCK_SIZE, BuildImageError, CheckHeadroom, - SetUpInDirAndFsConfig) + BuildImageError, CheckHeadroom, SetUpInDirAndFsConfig) +from test_utils import ReleaseToolsTestCase -class BuildImageTest(unittest.TestCase): +class BuildImageTest(ReleaseToolsTestCase): # Available: 1000 blocks. EXT4FS_OUTPUT = ( "Created filesystem with 2777/129024 inodes and 515099/516099 blocks") - def setUp(self): - # To test AVBCalcMinPartitionSize(), by using 200MB to 2GB image size. - # - 51200 = 200MB * 1024 * 1024 / 4096 - # - 524288 = 2GB * 1024 * 1024 * 1024 / 4096 - self._image_sizes = [BLOCK_SIZE * random.randint(51200, 524288) + offset - for offset in range(BLOCK_SIZE)] - - def tearDown(self): - common.Cleanup() - def test_CheckHeadroom_SizeUnderLimit(self): # Required headroom: 1000 blocks. prop_dict = { @@ -189,51 +176,3 @@ class BuildImageTest(unittest.TestCase): self.assertIn('fs-config-system\n', fs_config_data) self.assertIn('fs-config-root\n', fs_config_data) self.assertEqual('/', prop_dict['mount_point']) - - def test_AVBCalcMinPartitionSize_LinearFooterSize(self): - """Tests with footer size which is linear to partition size.""" - for image_size in self._image_sizes: - for ratio in 0.95, 0.56, 0.22: - expected_size = common.RoundUpTo4K(int(math.ceil(image_size / ratio))) - self.assertEqual( - expected_size, - AVBCalcMinPartitionSize(image_size, lambda x: int(x * ratio))) - - def test_AVBCalcMinPartitionSize_SlowerGrowthFooterSize(self): - """Tests with footer size which grows slower than partition size.""" - - def _SizeCalculator(partition_size): - """Footer size is the power of 0.95 of partition size.""" - # Minus footer size to return max image size. - return partition_size - int(math.pow(partition_size, 0.95)) - - for image_size in self._image_sizes: - min_partition_size = AVBCalcMinPartitionSize(image_size, _SizeCalculator) - # Checks min_partition_size can accommodate image_size. - self.assertGreaterEqual( - _SizeCalculator(min_partition_size), - image_size) - # Checks min_partition_size (round to BLOCK_SIZE) is the minimum. - self.assertLess( - _SizeCalculator(min_partition_size - BLOCK_SIZE), - image_size) - - def test_AVBCalcMinPartitionSize_FasterGrowthFooterSize(self): - """Tests with footer size which grows faster than partition size.""" - - def _SizeCalculator(partition_size): - """Max image size is the power of 0.95 of partition size.""" - # Max image size grows less than partition size, which means - # footer size grows faster than partition size. - return int(math.pow(partition_size, 0.95)) - - for image_size in self._image_sizes: - min_partition_size = AVBCalcMinPartitionSize(image_size, _SizeCalculator) - # Checks min_partition_size can accommodate image_size. - self.assertGreaterEqual( - _SizeCalculator(min_partition_size), - image_size) - # Checks min_partition_size (round to BLOCK_SIZE) is the minimum. - self.assertLess( - _SizeCalculator(min_partition_size - BLOCK_SIZE), - image_size) diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py index ec86eb2675..c99049adef 100644 --- a/tools/releasetools/test_common.py +++ b/tools/releasetools/test_common.py @@ -19,7 +19,6 @@ import os import subprocess import tempfile import time -import unittest import zipfile from hashlib import sha1 @@ -44,7 +43,8 @@ def get_2gb_string(): yield '\0' * (step_size - block_size) -class CommonZipTest(unittest.TestCase): +class CommonZipTest(test_utils.ReleaseToolsTestCase): + def _verify(self, zip_file, zip_file_name, arcname, expected_hash, test_file_name=None, expected_stat=None, expected_mode=0o644, expected_compress_type=zipfile.ZIP_STORED): @@ -359,7 +359,7 @@ class CommonZipTest(unittest.TestCase): os.remove(zip_file.name) -class CommonApkUtilsTest(unittest.TestCase): +class CommonApkUtilsTest(test_utils.ReleaseToolsTestCase): """Tests the APK utils related functions.""" APKCERTS_TXT1 = ( @@ -407,9 +407,6 @@ class CommonApkUtilsTest(unittest.TestCase): def setUp(self): self.testdata_dir = test_utils.get_testdata_dir() - def tearDown(self): - common.Cleanup() - @staticmethod def _write_apkcerts_txt(apkcerts_txt, additional=None): if additional is None: @@ -523,14 +520,11 @@ class CommonApkUtilsTest(unittest.TestCase): {}) -class CommonUtilsTest(unittest.TestCase): +class CommonUtilsTest(test_utils.ReleaseToolsTestCase): def setUp(self): self.testdata_dir = test_utils.get_testdata_dir() - def tearDown(self): - common.Cleanup() - def test_GetSparseImage_emptyBlockMapFile(self): target_files = common.MakeTempFile(prefix='target_files-', suffix='.zip') with zipfile.ZipFile(target_files, 'w') as target_files_zip: @@ -935,7 +929,7 @@ class CommonUtilsTest(unittest.TestCase): AssertionError, common.LoadInfoDict, target_files_zip, True) -class InstallRecoveryScriptFormatTest(unittest.TestCase): +class InstallRecoveryScriptFormatTest(test_utils.ReleaseToolsTestCase): """Checks the format of install-recovery.sh. Its format should match between common.py and validate_target_files.py. @@ -994,6 +988,3 @@ class InstallRecoveryScriptFormatTest(unittest.TestCase): recovery_image, boot_image, self._info) validate_target_files.ValidateInstallRecoveryScript(self._tempdir, self._info) - - def tearDown(self): - common.Cleanup() diff --git a/tools/releasetools/test_ota_from_target_files.py b/tools/releasetools/test_ota_from_target_files.py index 29e0d83dfc..44703db535 100644 --- a/tools/releasetools/test_ota_from_target_files.py +++ b/tools/releasetools/test_ota_from_target_files.py @@ -17,7 +17,6 @@ import copy import os import os.path -import unittest import zipfile import common @@ -104,7 +103,7 @@ class MockScriptWriter(object): self.script.append(('AssertSomeThumbprint',) + args) -class BuildInfoTest(unittest.TestCase): +class BuildInfoTest(test_utils.ReleaseToolsTestCase): TEST_INFO_DICT = { 'build.prop' : { @@ -352,10 +351,7 @@ class BuildInfoTest(unittest.TestCase): script_writer.script) -class LoadOemDictsTest(unittest.TestCase): - - def tearDown(self): - common.Cleanup() +class LoadOemDictsTest(test_utils.ReleaseToolsTestCase): def test_NoneDict(self): self.assertIsNone(_LoadOemDicts(None)) @@ -388,7 +384,7 @@ class LoadOemDictsTest(unittest.TestCase): self.assertEqual('{}'.format(i), oem_dict['ro.build.index']) -class OtaFromTargetFilesTest(unittest.TestCase): +class OtaFromTargetFilesTest(test_utils.ReleaseToolsTestCase): TEST_TARGET_INFO_DICT = { 'build.prop' : { @@ -430,9 +426,6 @@ class OtaFromTargetFilesTest(unittest.TestCase): common.OPTIONS.search_path = test_utils.get_search_path() self.assertIsNotNone(common.OPTIONS.search_path) - def tearDown(self): - common.Cleanup() - def test_GetPackageMetadata_abOta_full(self): target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT) target_info_dict['ab_update'] = 'true' @@ -720,14 +713,11 @@ class TestPropertyFiles(PropertyFiles): ) -class PropertyFilesTest(unittest.TestCase): +class PropertyFilesTest(test_utils.ReleaseToolsTestCase): def setUp(self): common.OPTIONS.no_signing = False - def tearDown(self): - common.Cleanup() - @staticmethod def construct_zip_package(entries): zip_file = common.MakeTempFile(suffix='.zip') @@ -1151,7 +1141,7 @@ class NonAbOtaPropertyFilesTest(PropertyFilesTest): property_files.Verify(zip_fp, raw_metadata) -class PayloadSignerTest(unittest.TestCase): +class PayloadSignerTest(test_utils.ReleaseToolsTestCase): SIGFILE = 'sigfile.bin' SIGNED_SIGFILE = 'signed-sigfile.bin' @@ -1167,9 +1157,6 @@ class PayloadSignerTest(unittest.TestCase): common.OPTIONS.package_key : None, } - def tearDown(self): - common.Cleanup() - def _assertFilesEqual(self, file1, file2): with open(file1, 'rb') as fp1, open(file2, 'rb') as fp2: self.assertEqual(fp1.read(), fp2.read()) @@ -1230,7 +1217,7 @@ class PayloadSignerTest(unittest.TestCase): self._assertFilesEqual(verify_file, signed_file) -class PayloadTest(unittest.TestCase): +class PayloadTest(test_utils.ReleaseToolsTestCase): def setUp(self): self.testdata_dir = test_utils.get_testdata_dir() @@ -1244,9 +1231,6 @@ class PayloadTest(unittest.TestCase): common.OPTIONS.package_key : None, } - def tearDown(self): - common.Cleanup() - @staticmethod def _create_payload_full(secondary=False): target_file = construct_target_files(secondary) @@ -1284,7 +1268,7 @@ class PayloadTest(unittest.TestCase): target_file = construct_target_files() common.ZipDelete(target_file, 'IMAGES/vendor.img') payload = Payload() - self.assertRaises(AssertionError, payload.Generate, target_file) + self.assertRaises(common.ExternalError, payload.Generate, target_file) def test_Sign_full(self): payload = self._create_payload_full() @@ -1332,7 +1316,7 @@ class PayloadTest(unittest.TestCase): payload = self._create_payload_full() payload_signer = PayloadSigner() payload_signer.signer_args.append('bad-option') - self.assertRaises(AssertionError, payload.Sign, payload_signer) + self.assertRaises(common.ExternalError, payload.Sign, payload_signer) def test_WriteToZip(self): payload = self._create_payload_full() diff --git a/tools/releasetools/test_rangelib.py b/tools/releasetools/test_rangelib.py index e1811870fe..1251e11f0d 100644 --- a/tools/releasetools/test_rangelib.py +++ b/tools/releasetools/test_rangelib.py @@ -14,11 +14,11 @@ # limitations under the License. # -import unittest - from rangelib import RangeSet +from test_utils import ReleaseToolsTestCase + -class RangeSetTest(unittest.TestCase): +class RangeSetTest(ReleaseToolsTestCase): def test_union(self): self.assertEqual(RangeSet("10-19 30-34").union(RangeSet("18-29")), @@ -129,8 +129,8 @@ class RangeSetTest(unittest.TestCase): self.assertEqual( RangeSet.parse_raw(RangeSet("0-9").to_string_raw()), RangeSet("0-9")) - self.assertEqual(RangeSet.parse_raw( - RangeSet("2-10 12").to_string_raw()), + self.assertEqual( + RangeSet.parse_raw(RangeSet("2-10 12").to_string_raw()), RangeSet("2-10 12")) self.assertEqual( RangeSet.parse_raw(RangeSet("11 2-10 12 1 0").to_string_raw()), diff --git a/tools/releasetools/test_sign_target_files_apks.py b/tools/releasetools/test_sign_target_files_apks.py index ac1b567ca9..18762eebad 100644 --- a/tools/releasetools/test_sign_target_files_apks.py +++ b/tools/releasetools/test_sign_target_files_apks.py @@ -14,11 +14,8 @@ # limitations under the License. # -from __future__ import print_function - import base64 import os.path -import unittest import zipfile import common @@ -28,7 +25,7 @@ from sign_target_files_apks import ( ReplaceVerityKeyId, RewriteProps) -class SignTargetFilesApksTest(unittest.TestCase): +class SignTargetFilesApksTest(test_utils.ReleaseToolsTestCase): MAC_PERMISSIONS_XML = """<?xml version="1.0" encoding="iso-8859-1"?> <policy> @@ -39,9 +36,6 @@ class SignTargetFilesApksTest(unittest.TestCase): def setUp(self): self.testdata_dir = test_utils.get_testdata_dir() - def tearDown(self): - common.Cleanup() - def test_EditTags(self): self.assertEqual(EditTags('dev-keys'), ('release-keys')) self.assertEqual(EditTags('test-keys'), ('release-keys')) diff --git a/tools/releasetools/test_utils.py b/tools/releasetools/test_utils.py index a15ff5b2a5..b9c8dc7261 100644 --- a/tools/releasetools/test_utils.py +++ b/tools/releasetools/test_utils.py @@ -21,6 +21,7 @@ Utils for running unittests. import os import os.path import struct +import unittest import common @@ -110,3 +111,10 @@ def construct_sparse_image(chunks): fp.write(os.urandom(data_size)) return sparse_image + + +class ReleaseToolsTestCase(unittest.TestCase): + """A common base class for all the releasetools unittests.""" + + def tearDown(self): + common.Cleanup() diff --git a/tools/releasetools/test_validate_target_files.py b/tools/releasetools/test_validate_target_files.py index ecb7fde22d..d778d117f1 100644 --- a/tools/releasetools/test_validate_target_files.py +++ b/tools/releasetools/test_validate_target_files.py @@ -16,27 +16,21 @@ """Unittests for validate_target_files.py.""" -from __future__ import print_function - import os import os.path import shutil -import unittest -import build_image import common import test_utils +import verity_utils from validate_target_files import ValidateVerifiedBootImages -class ValidateTargetFilesTest(unittest.TestCase): +class ValidateTargetFilesTest(test_utils.ReleaseToolsTestCase): def setUp(self): self.testdata_dir = test_utils.get_testdata_dir() - def tearDown(self): - common.Cleanup() - def _generate_boot_image(self, output_file): kernel = common.MakeTempFile(prefix='kernel-') with open(kernel, 'wb') as kernel_fp: @@ -115,7 +109,7 @@ class ValidateTargetFilesTest(unittest.TestCase): def _generate_system_image(self, output_file): verity_fec = True partition_size = 1024 * 1024 - image_size, verity_size = build_image.AdjustPartitionSizeForVerity( + image_size, verity_size = verity_utils.AdjustPartitionSizeForVerity( partition_size, verity_fec) # Use an empty root directory. @@ -138,7 +132,7 @@ class ValidateTargetFilesTest(unittest.TestCase): 'verity_signer_cmd' : 'verity_signer', 'verity_size' : str(verity_size), } - build_image.MakeVerityEnabledImage(output_file, verity_fec, prop_dict) + verity_utils.MakeVerityEnabledImage(output_file, verity_fec, prop_dict) def test_ValidateVerifiedBootImages_systemImage(self): input_tmp = common.MakeTempDir() diff --git a/tools/releasetools/test_verity_utils.py b/tools/releasetools/test_verity_utils.py index f318b02b99..0988d8e704 100644 --- a/tools/releasetools/test_verity_utils.py +++ b/tools/releasetools/test_verity_utils.py @@ -16,25 +16,24 @@ """Unittests for verity_utils.py.""" -from __future__ import print_function - +import math import os.path -import unittest +import random -import build_image import common import sparse_img -import test_utils from rangelib import RangeSet +from test_utils import get_testdata_dir, ReleaseToolsTestCase from verity_utils import ( - CreateHashtreeInfoGenerator, HashtreeInfo, + AdjustPartitionSizeForVerity, AVBCalcMinPartitionSize, BLOCK_SIZE, + CreateHashtreeInfoGenerator, HashtreeInfo, MakeVerityEnabledImage, VerifiedBootVersion1HashtreeInfoGenerator) -class VerifiedBootVersion1HashtreeInfoGeneratorTest(unittest.TestCase): +class VerifiedBootVersion1HashtreeInfoGeneratorTest(ReleaseToolsTestCase): def setUp(self): - self.testdata_dir = test_utils.get_testdata_dir() + self.testdata_dir = get_testdata_dir() self.partition_size = 1024 * 1024 self.prop_dict = { @@ -50,9 +49,6 @@ class VerifiedBootVersion1HashtreeInfoGeneratorTest(unittest.TestCase): self.expected_root_hash = \ "0b7c4565e87b1026e11fbab91c0bc29e185c847a5b44d40e6e86e461e8adf80d" - def tearDown(self): - common.Cleanup() - def _create_simg(self, raw_data): output_file = common.MakeTempFile() raw_image = common.MakeTempFile() @@ -68,7 +64,7 @@ class VerifiedBootVersion1HashtreeInfoGeneratorTest(unittest.TestCase): def _generate_image(self): partition_size = 1024 * 1024 - adjusted_size, verity_size = build_image.AdjustPartitionSizeForVerity( + adjusted_size, verity_size = AdjustPartitionSizeForVerity( partition_size, True) raw_image = "" @@ -86,7 +82,7 @@ class VerifiedBootVersion1HashtreeInfoGeneratorTest(unittest.TestCase): 'verity_signer_cmd': 'verity_signer', 'verity_size': str(verity_size), } - build_image.MakeVerityEnabledImage(output_file, True, prop_dict) + MakeVerityEnabledImage(output_file, True, prop_dict) return output_file @@ -165,3 +161,62 @@ class VerifiedBootVersion1HashtreeInfoGeneratorTest(unittest.TestCase): self.assertEqual(self.hash_algorithm, info.hash_algorithm) self.assertEqual(self.fixed_salt, info.salt) self.assertEqual(self.expected_root_hash, info.root_hash) + + +class VerityUtilsTest(ReleaseToolsTestCase): + + def setUp(self): + # To test AVBCalcMinPartitionSize(), by using 200MB to 2GB image size. + # - 51200 = 200MB * 1024 * 1024 / 4096 + # - 524288 = 2GB * 1024 * 1024 * 1024 / 4096 + self._image_sizes = [BLOCK_SIZE * random.randint(51200, 524288) + offset + for offset in range(BLOCK_SIZE)] + + def test_AVBCalcMinPartitionSize_LinearFooterSize(self): + """Tests with footer size which is linear to partition size.""" + for image_size in self._image_sizes: + for ratio in 0.95, 0.56, 0.22: + expected_size = common.RoundUpTo4K(int(math.ceil(image_size / ratio))) + self.assertEqual( + expected_size, + AVBCalcMinPartitionSize( + image_size, lambda x, ratio=ratio: int(x * ratio))) + + def test_AVBCalcMinPartitionSize_SlowerGrowthFooterSize(self): + """Tests with footer size which grows slower than partition size.""" + + def _SizeCalculator(partition_size): + """Footer size is the power of 0.95 of partition size.""" + # Minus footer size to return max image size. + return partition_size - int(math.pow(partition_size, 0.95)) + + for image_size in self._image_sizes: + min_partition_size = AVBCalcMinPartitionSize(image_size, _SizeCalculator) + # Checks min_partition_size can accommodate image_size. + self.assertGreaterEqual( + _SizeCalculator(min_partition_size), + image_size) + # Checks min_partition_size (round to BLOCK_SIZE) is the minimum. + self.assertLess( + _SizeCalculator(min_partition_size - BLOCK_SIZE), + image_size) + + def test_AVBCalcMinPartitionSize_FasterGrowthFooterSize(self): + """Tests with footer size which grows faster than partition size.""" + + def _SizeCalculator(partition_size): + """Max image size is the power of 0.95 of partition size.""" + # Max image size grows less than partition size, which means + # footer size grows faster than partition size. + return int(math.pow(partition_size, 0.95)) + + for image_size in self._image_sizes: + min_partition_size = AVBCalcMinPartitionSize(image_size, _SizeCalculator) + # Checks min_partition_size can accommodate image_size. + self.assertGreaterEqual( + _SizeCalculator(min_partition_size), + image_size) + # Checks min_partition_size (round to BLOCK_SIZE) is the minimum. + self.assertLess( + _SizeCalculator(min_partition_size - BLOCK_SIZE), + image_size) diff --git a/tools/releasetools/verity_utils.py b/tools/releasetools/verity_utils.py index c512ef3bb3..626a1ddd73 100644 --- a/tools/releasetools/verity_utils.py +++ b/tools/releasetools/verity_utils.py @@ -16,13 +16,354 @@ from __future__ import print_function +import os.path +import shlex import struct import common -from build_image import (AdjustPartitionSizeForVerity, GetVerityTreeSize, - GetVerityMetadataSize, BuildVerityTree) +import sparse_img from rangelib import RangeSet +OPTIONS = common.OPTIONS +BLOCK_SIZE = common.BLOCK_SIZE +FIXED_SALT = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7" + + +class BuildVerityImageError(Exception): + """An Exception raised during verity image building.""" + + def __init__(self, message): + Exception.__init__(self, message) + + +def GetVerityFECSize(partition_size): + cmd = ["fec", "-s", str(partition_size)] + output = common.RunAndCheckOutput(cmd, verbose=False) + return int(output) + + +def GetVerityTreeSize(partition_size): + cmd = ["build_verity_tree", "-s", str(partition_size)] + output = common.RunAndCheckOutput(cmd, verbose=False) + return int(output) + + +def GetVerityMetadataSize(partition_size): + cmd = ["build_verity_metadata.py", "size", str(partition_size)] + output = common.RunAndCheckOutput(cmd, verbose=False) + return int(output) + + +def GetVeritySize(partition_size, fec_supported): + verity_tree_size = GetVerityTreeSize(partition_size) + verity_metadata_size = GetVerityMetadataSize(partition_size) + verity_size = verity_tree_size + verity_metadata_size + if fec_supported: + fec_size = GetVerityFECSize(partition_size + verity_size) + return verity_size + fec_size + return verity_size + + +def GetSimgSize(image_file): + simg = sparse_img.SparseImage(image_file, build_map=False) + return simg.blocksize * simg.total_blocks + + +def ZeroPadSimg(image_file, pad_size): + blocks = pad_size // BLOCK_SIZE + print("Padding %d blocks (%d bytes)" % (blocks, pad_size)) + simg = sparse_img.SparseImage(image_file, mode="r+b", build_map=False) + simg.AppendFillChunk(0, blocks) + + +def AdjustPartitionSizeForVerity(partition_size, fec_supported): + """Modifies the provided partition size to account for the verity metadata. + + This information is used to size the created image appropriately. + + Args: + partition_size: the size of the partition to be verified. + + Returns: + A tuple of the size of the partition adjusted for verity metadata, and + the size of verity metadata. + """ + key = "%d %d" % (partition_size, fec_supported) + if key in AdjustPartitionSizeForVerity.results: + return AdjustPartitionSizeForVerity.results[key] + + hi = partition_size + if hi % BLOCK_SIZE != 0: + hi = (hi // BLOCK_SIZE) * BLOCK_SIZE + + # verity tree and fec sizes depend on the partition size, which + # means this estimate is always going to be unnecessarily small + verity_size = GetVeritySize(hi, fec_supported) + lo = partition_size - verity_size + result = lo + + # do a binary search for the optimal size + while lo < hi: + i = ((lo + hi) // (2 * BLOCK_SIZE)) * BLOCK_SIZE + v = GetVeritySize(i, fec_supported) + if i + v <= partition_size: + if result < i: + result = i + verity_size = v + lo = i + BLOCK_SIZE + else: + hi = i + + if OPTIONS.verbose: + print("Adjusted partition size for verity, partition_size: {}," + " verity_size: {}".format(result, verity_size)) + AdjustPartitionSizeForVerity.results[key] = (result, verity_size) + return (result, verity_size) + + +AdjustPartitionSizeForVerity.results = {} + + +def BuildVerityFEC(sparse_image_path, verity_path, verity_fec_path, + padding_size): + cmd = ["fec", "-e", "-p", str(padding_size), sparse_image_path, + verity_path, verity_fec_path] + common.RunAndCheckOutput(cmd) + + +def BuildVerityTree(sparse_image_path, verity_image_path): + cmd = ["build_verity_tree", "-A", FIXED_SALT, sparse_image_path, + verity_image_path] + output = common.RunAndCheckOutput(cmd) + root, salt = output.split() + return root, salt + + +def BuildVerityMetadata(image_size, verity_metadata_path, root_hash, salt, + block_device, signer_path, key, signer_args, + verity_disable): + cmd = ["build_verity_metadata.py", "build", str(image_size), + verity_metadata_path, root_hash, salt, block_device, signer_path, key] + if signer_args: + cmd.append("--signer_args=\"%s\"" % (' '.join(signer_args),)) + if verity_disable: + cmd.append("--verity_disable") + common.RunAndCheckOutput(cmd) + + +def Append2Simg(sparse_image_path, unsparse_image_path, error_message): + """Appends the unsparse image to the given sparse image. + + Args: + sparse_image_path: the path to the (sparse) image + unsparse_image_path: the path to the (unsparse) image + + Raises: + BuildVerityImageError: On error. + """ + cmd = ["append2simg", sparse_image_path, unsparse_image_path] + try: + common.RunAndCheckOutput(cmd) + except: + raise BuildVerityImageError(error_message) + + +def Append(target, file_to_append, error_message): + """Appends file_to_append to target. + + Raises: + BuildVerityImageError: On error. + """ + try: + with open(target, "a") as out_file, open(file_to_append, "r") as input_file: + for line in input_file: + out_file.write(line) + except IOError: + raise BuildVerityImageError(error_message) + + +def BuildVerifiedImage(data_image_path, verity_image_path, + verity_metadata_path, verity_fec_path, + padding_size, fec_supported): + Append( + verity_image_path, verity_metadata_path, + "Could not append verity metadata!") + + if fec_supported: + # Build FEC for the entire partition, including metadata. + BuildVerityFEC( + data_image_path, verity_image_path, verity_fec_path, padding_size) + Append(verity_image_path, verity_fec_path, "Could not append FEC!") + + Append2Simg( + data_image_path, verity_image_path, "Could not append verity data!") + + +def MakeVerityEnabledImage(out_file, fec_supported, prop_dict): + """Creates an image that is verifiable using dm-verity. + + Args: + out_file: the location to write the verifiable image at + prop_dict: a dictionary of properties required for image creation and + verification + + Raises: + AssertionError: On invalid partition sizes. + """ + # get properties + image_size = int(prop_dict["image_size"]) + block_dev = prop_dict["verity_block_device"] + signer_key = prop_dict["verity_key"] + ".pk8" + if OPTIONS.verity_signer_path is not None: + signer_path = OPTIONS.verity_signer_path + else: + signer_path = prop_dict["verity_signer_cmd"] + signer_args = OPTIONS.verity_signer_args + + tempdir_name = common.MakeTempDir(suffix="_verity_images") + + # Get partial image paths. + verity_image_path = os.path.join(tempdir_name, "verity.img") + verity_metadata_path = os.path.join(tempdir_name, "verity_metadata.img") + verity_fec_path = os.path.join(tempdir_name, "verity_fec.img") + + # Build the verity tree and get the root hash and salt. + root_hash, salt = BuildVerityTree(out_file, verity_image_path) + + # Build the metadata blocks. + verity_disable = "verity_disable" in prop_dict + BuildVerityMetadata( + image_size, verity_metadata_path, root_hash, salt, block_dev, signer_path, + signer_key, signer_args, verity_disable) + + # Build the full verified image. + partition_size = int(prop_dict["partition_size"]) + verity_size = int(prop_dict["verity_size"]) + + padding_size = partition_size - image_size - verity_size + assert padding_size >= 0 + + BuildVerifiedImage( + out_file, verity_image_path, verity_metadata_path, verity_fec_path, + padding_size, fec_supported) + + +def AVBCalcMaxImageSize(avbtool, footer_type, partition_size, additional_args): + """Calculates max image size for a given partition size. + + Args: + avbtool: String with path to avbtool. + footer_type: 'hash' or 'hashtree' for generating footer. + partition_size: The size of the partition in question. + additional_args: Additional arguments to pass to "avbtool add_hash_footer" + or "avbtool add_hashtree_footer". + + Returns: + The maximum image size. + + Raises: + BuildVerityImageError: On invalid image size. + """ + cmd = [avbtool, "add_%s_footer" % footer_type, + "--partition_size", str(partition_size), "--calc_max_image_size"] + cmd.extend(shlex.split(additional_args)) + + output = common.RunAndCheckOutput(cmd) + image_size = int(output) + if image_size <= 0: + raise BuildVerityImageError( + "Invalid max image size: {}".format(output)) + return image_size + + +def AVBCalcMinPartitionSize(image_size, size_calculator): + """Calculates min partition size for a given image size. + + Args: + image_size: The size of the image in question. + size_calculator: The function to calculate max image size + for a given partition size. + + Returns: + The minimum partition size required to accommodate the image size. + """ + # Use image size as partition size to approximate final partition size. + image_ratio = size_calculator(image_size) / float(image_size) + + # Prepare a binary search for the optimal partition size. + lo = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE - BLOCK_SIZE + + # Ensure lo is small enough: max_image_size should <= image_size. + delta = BLOCK_SIZE + max_image_size = size_calculator(lo) + while max_image_size > image_size: + image_ratio = max_image_size / float(lo) + lo = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE - delta + delta *= 2 + max_image_size = size_calculator(lo) + + hi = lo + BLOCK_SIZE + + # Ensure hi is large enough: max_image_size should >= image_size. + delta = BLOCK_SIZE + max_image_size = size_calculator(hi) + while max_image_size < image_size: + image_ratio = max_image_size / float(hi) + hi = int(image_size / image_ratio) // BLOCK_SIZE * BLOCK_SIZE + delta + delta *= 2 + max_image_size = size_calculator(hi) + + partition_size = hi + + # Start to binary search. + while lo < hi: + mid = ((lo + hi) // (2 * BLOCK_SIZE)) * BLOCK_SIZE + max_image_size = size_calculator(mid) + if max_image_size >= image_size: # if mid can accommodate image_size + if mid < partition_size: # if a smaller partition size is found + partition_size = mid + hi = mid + else: + lo = mid + BLOCK_SIZE + + if OPTIONS.verbose: + print("AVBCalcMinPartitionSize({}): partition_size: {}.".format( + image_size, partition_size)) + + return partition_size + + +def AVBAddFooter(image_path, avbtool, footer_type, partition_size, + partition_name, key_path, algorithm, salt, + additional_args): + """Adds dm-verity hashtree and AVB metadata to an image. + + Args: + image_path: Path to image to modify. + avbtool: String with path to avbtool. + footer_type: 'hash' or 'hashtree' for generating footer. + partition_size: The size of the partition in question. + partition_name: The name of the partition - will be embedded in metadata. + key_path: Path to key to use or None. + algorithm: Name of algorithm to use or None. + salt: The salt to use (a hexadecimal string) or None. + additional_args: Additional arguments to pass to "avbtool add_hash_footer" + or "avbtool add_hashtree_footer". + """ + cmd = [avbtool, "add_%s_footer" % footer_type, + "--partition_size", partition_size, + "--partition_name", partition_name, + "--image", image_path] + + if key_path and algorithm: + cmd.extend(["--key", key_path, "--algorithm", algorithm]) + if salt: + cmd.extend(["--salt", salt]) + + cmd.extend(shlex.split(additional_args)) + + common.RunAndCheckOutput(cmd) + class HashtreeInfoGenerationError(Exception): """An Exception raised during hashtree info generation.""" |