diff options
Diffstat (limited to 'tools/releasetools/build_image.py')
-rwxr-xr-x | tools/releasetools/build_image.py | 234 |
1 files changed, 223 insertions, 11 deletions
diff --git a/tools/releasetools/build_image.py b/tools/releasetools/build_image.py index f8f2ada..712e0cd 100755 --- a/tools/releasetools/build_image.py +++ b/tools/releasetools/build_image.py @@ -24,6 +24,13 @@ import os import os.path import subprocess import sys +import commands +import shutil +import tempfile + +import simg_map + +FIXED_SALT = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7" def RunCommand(cmd): """ Echo and run the given command @@ -38,13 +45,182 @@ def RunCommand(cmd): p.communicate() return p.returncode -def BuildImage(in_dir, prop_dict, out_file): +def GetVerityTreeSize(partition_size): + cmd = "build_verity_tree -s %d" + cmd %= partition_size + status, output = commands.getstatusoutput(cmd) + if status: + print output + return False, 0 + return True, int(output) + +def GetVerityMetadataSize(partition_size): + cmd = "system/extras/verity/build_verity_metadata.py -s %d" + cmd %= partition_size + status, output = commands.getstatusoutput(cmd) + if status: + print output + return False, 0 + return True, int(output) + +def AdjustPartitionSizeForVerity(partition_size): + """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: + The size of the partition adjusted for verity metadata. + """ + success, verity_tree_size = GetVerityTreeSize(partition_size) + if not success: + return 0; + success, verity_metadata_size = GetVerityMetadataSize(partition_size) + if not success: + return 0 + return partition_size - verity_tree_size - verity_metadata_size + +def BuildVerityTree(sparse_image_path, verity_image_path, prop_dict): + cmd = ("build_verity_tree -A %s %s %s" % (FIXED_SALT, sparse_image_path, verity_image_path)) + print cmd + status, output = commands.getstatusoutput(cmd) + if status: + print "Could not build verity tree! Error: %s" % output + return False + root, salt = output.split() + prop_dict["verity_root_hash"] = root + prop_dict["verity_salt"] = salt + return True + +def BuildVerityMetadata(image_size, verity_metadata_path, root_hash, salt, + block_device, signer_path, key): + cmd = ("system/extras/verity/build_verity_metadata.py %s %s %s %s %s %s %s" % + (image_size, + verity_metadata_path, + root_hash, + salt, + block_device, + signer_path, + key)) + print cmd + status, output = commands.getstatusoutput(cmd) + if status: + print "Could not build verity metadata! Error: %s" % output + return False + return True + +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 + Returns: + True on success, False on failure. + """ + cmd = "append2simg %s %s" + cmd %= (sparse_image_path, unsparse_image_path) + print cmd + status, output = commands.getstatusoutput(cmd) + if status: + print "%s: %s" % (error_message, output) + return False + return True + +def BuildVerifiedImage(data_image_path, verity_image_path, verity_metadata_path): + if not Append2Simg(data_image_path, verity_metadata_path, "Could not append verity metadata!"): + return False + if not Append2Simg(data_image_path, verity_image_path, "Could not append verity tree!"): + return False + return True + +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) + unsparse_image_path = os.path.join(img_dir, unsparse_image_path) + if os.path.exists(unsparse_image_path): + if replace: + os.unlink(unsparse_image_path) + else: + return True, unsparse_image_path + inflate_command = ["simg2img", sparse_image_path, unsparse_image_path] + exit_code = RunCommand(inflate_command) + if exit_code != 0: + os.remove(unsparse_image_path) + return False, None + return True, unsparse_image_path + +def MappedUnsparseImage(sparse_image_path, unsparse_image_path, + map_path, mapped_unsparse_image_path): + if simg_map.ComputeMap(sparse_image_path, unsparse_image_path, + map_path, mapped_unsparse_image_path): + return False + return True + +def MakeVerityEnabledImage(out_file, 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 + Returns: + True on success, False otherwise. + """ + # get properties + image_size = prop_dict["partition_size"] + block_dev = prop_dict["verity_block_device"] + signer_key = prop_dict["verity_key"] + signer_path = prop_dict["verity_signer_cmd"] + + # make a tempdir + tempdir_name = tempfile.mkdtemp(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") + + # build the verity tree and get the root hash and salt + if not BuildVerityTree(out_file, verity_image_path, prop_dict): + shutil.rmtree(tempdir_name, ignore_errors=True) + return False + + # build the metadata blocks + root_hash = prop_dict["verity_root_hash"] + salt = prop_dict["verity_salt"] + if not BuildVerityMetadata(image_size, + verity_metadata_path, + root_hash, + salt, + block_dev, + signer_path, + signer_key): + shutil.rmtree(tempdir_name, ignore_errors=True) + return False + + # build the full verified image + if not BuildVerifiedImage(out_file, + verity_image_path, + verity_metadata_path): + shutil.rmtree(tempdir_name, ignore_errors=True) + return False + + shutil.rmtree(tempdir_name, ignore_errors=True) + return True + +def BuildImage(in_dir, prop_dict, out_file, + fs_config=None, + fc_config=None): """Build an image to out_file from in_dir with property prop_dict. Args: in_dir: path of input directory. prop_dict: property dictionary. out_file: path of the output image file. + fs_config: path to the fs_config file (typically + META/filesystem_config.txt). If None then the configuration in + the local client will be used. + fc_config: path to the SELinux file_contexts file. If None then + the value from prop_dict['selinux_fc'] will be used. Returns: True iff the image is built successfully. @@ -52,6 +228,18 @@ def BuildImage(in_dir, prop_dict, out_file): build_command = [] fs_type = prop_dict.get("fs_type", "") run_fsck = False + + is_verity_partition = "verity_block_device" in prop_dict + verity_supported = prop_dict.get("verity") == "true" + # adjust the partition 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")) + adjusted_size = AdjustPartitionSizeForVerity(partition_size) + if not adjusted_size: + return False + prop_dict["partition_size"] = str(adjusted_size) + prop_dict["original_partition_size"] = str(partition_size) + if fs_type.startswith("ext"): build_command = ["mkuserimg.sh"] if "extfs_sparse_flag" in prop_dict: @@ -59,10 +247,18 @@ def BuildImage(in_dir, prop_dict, out_file): run_fsck = True build_command.extend([in_dir, out_file, fs_type, prop_dict["mount_point"]]) - if "partition_size" in prop_dict: - build_command.append(prop_dict["partition_size"]) - if "selinux_fc" in prop_dict: + build_command.append(prop_dict["partition_size"]) + if "timestamp" in prop_dict: + build_command.extend(["-T", str(prop_dict["timestamp"])]) + if fs_config is not None: + build_command.extend(["-C", fs_config]) + if fc_config is not None: + build_command.append(fc_config) + elif "selinux_fc" in prop_dict: build_command.append(prop_dict["selinux_fc"]) + elif fs_type.startswith("f2fs"): + build_command = ["mkf2fsuserimg.sh"] + build_command.extend([out_file, prop_dict["partition_size"]]) else: build_command = ["mkyaffs2image", "-f"] if prop_dict.get("mkyaffs2_extra_flags", None): @@ -77,14 +273,14 @@ def BuildImage(in_dir, prop_dict, out_file): if exit_code != 0: return False + # create the verified image if this is to be verified + if verity_supported and is_verity_partition: + if not MakeVerityEnabledImage(out_file, prop_dict): + return False + if run_fsck and prop_dict.get("skip_fsck") != "true": - # Inflate the sparse image - unsparse_image = os.path.join( - os.path.dirname(out_file), "unsparse_" + os.path.basename(out_file)) - inflate_command = ["simg2img", out_file, unsparse_image] - exit_code = RunCommand(inflate_command) - if exit_code != 0: - os.remove(unsparse_image) + success, unsparse_image = UnsparseImage(out_file, replace=False) + if not success: return False # Run e2fsck on the inflated image file @@ -104,6 +300,10 @@ def ImagePropFromGlobalDict(glob_dict, mount_point): mount_point: such as "system", "data" etc. """ d = {} + if "build.prop" in glob_dict: + bp = glob_dict["build.prop"] + if "ro.build.date.utc" in bp: + d["timestamp"] = bp["ro.build.date.utc"] def copy_prop(src_p, dest_p): if src_p in glob_dict: @@ -114,6 +314,9 @@ def ImagePropFromGlobalDict(glob_dict, mount_point): "mkyaffs2_extra_flags", "selinux_fc", "skip_fsck", + "verity", + "verity_key", + "verity_signer_cmd" ) for p in common_props: copy_prop(p, p) @@ -122,8 +325,11 @@ def ImagePropFromGlobalDict(glob_dict, mount_point): if mount_point == "system": copy_prop("fs_type", "fs_type") copy_prop("system_size", "partition_size") + copy_prop("system_verity_block_device", "verity_block_device") elif mount_point == "data": + # Copy the generic fs type first, override with specific one if available. copy_prop("fs_type", "fs_type") + copy_prop("userdata_fs_type", "fs_type") copy_prop("userdata_size", "partition_size") elif mount_point == "cache": copy_prop("cache_fs_type", "fs_type") @@ -131,6 +337,10 @@ def ImagePropFromGlobalDict(glob_dict, mount_point): elif mount_point == "vendor": copy_prop("vendor_fs_type", "fs_type") copy_prop("vendor_size", "partition_size") + copy_prop("vendor_verity_block_device", "verity_block_device") + elif mount_point == "oem": + copy_prop("fs_type", "fs_type") + copy_prop("oem_size", "partition_size") return d @@ -169,6 +379,8 @@ def main(argv): mount_point = "cache" elif image_filename == "vendor.img": mount_point = "vendor" + elif image_filename == "oem.img": + mount_point = "oem" else: print >> sys.stderr, "error: unknown image file name ", image_filename exit(1) |