From 571e182e9cd517128d5fb0bae44b071296a70d6f Mon Sep 17 00:00:00 2001 From: Daniel Norman Date: Fri, 25 Jun 2021 17:18:25 -0700 Subject: [PATCH] Regenerate odm or vendor using combined sepolicy if --rebuild-sepolicy. This allows merged devices to boot using a precompiled_sepolicy built from merged sources, rather than recompiling this sepolicy at boot time every boot. Bug: 178727214 Test: Merge an R+S build using --rebuild-sepolicy and --vendor-otatools. Observe odm.img is rebuilt by the vendor otatools.zip when merging. Observe device boots using ODM's precompiled_sepolicy file. Test: Same as above, for S+S. Test: Merge an S+S build using --rebuild-sepolicy and *not* --vendor-otatools. Observe odm.img is rebuilt without using a separate otatools.zip. Observe device boots using ODM's precompiled_sepolicy file. Change-Id: I9595b8a3296d6deec21db8f0c9bc5b7ec4debd57 --- tools/releasetools/merge_target_files.py | 132 ++++++++++++++++-- tools/releasetools/test_merge_target_files.py | 4 +- 2 files changed, 122 insertions(+), 14 deletions(-) diff --git a/tools/releasetools/merge_target_files.py b/tools/releasetools/merge_target_files.py index 5e6c42d84e..c1fa9e7d32 100755 --- a/tools/releasetools/merge_target_files.py +++ b/tools/releasetools/merge_target_files.py @@ -78,6 +78,14 @@ Usage: merge_target_files [args] If provided, duplicate APK/APEX keys are ignored and the value from the framework is used. + --rebuild-sepolicy + If provided, rebuilds odm.img or vendor.img to include merged sepolicy + files. If odm is present then odm is preferred. + + --vendor-otatools otatools.zip + If provided, use this otatools.zip when recompiling the odm or vendor + image to include sepolicy. + --keep-tmp Keep tempoary files for debugging purposes. """ @@ -129,6 +137,8 @@ OPTIONS.output_super_empty = None OPTIONS.rebuild_recovery = False # TODO(b/150582573): Remove this option. OPTIONS.allow_duplicate_apkapex_keys = False +OPTIONS.vendor_otatools = None +OPTIONS.rebuild_sepolicy = False OPTIONS.keep_tmp = False # In an item list (framework or vendor), we may see entries that select whole @@ -666,7 +676,7 @@ def copy_file_contexts(framework_target_files_dir, vendor_target_files_dir, os.path.join(output_target_files_dir, 'META', 'vendor_file_contexts.bin')) -def compile_split_sepolicy(product_out, partition_map, output_policy): +def compile_split_sepolicy(product_out, partition_map): """Uses secilc to compile a split sepolicy file. Depends on various */etc/selinux/* and */etc/vintf/* files within partitions. @@ -674,7 +684,6 @@ def compile_split_sepolicy(product_out, partition_map, output_policy): Args: product_out: PRODUCT_OUT directory, containing partition directories. partition_map: A map of partition name -> relative path within product_out. - output_policy: The name of the output policy created by secilc. Returns: A command list that can be executed to create the compiled sepolicy. @@ -709,7 +718,7 @@ def compile_split_sepolicy(product_out, partition_map, output_policy): # Use the same flags and arguments as selinux.cpp OpenSplitPolicy(). cmd = ['secilc', '-m', '-M', 'true', '-G', '-N'] cmd.extend(['-c', kernel_sepolicy_version]) - cmd.extend(['-o', output_policy]) + cmd.extend(['-o', os.path.join(product_out, 'META/combined_sepolicy')]) cmd.extend(['-f', '/dev/null']) required_policy_files = ( @@ -747,7 +756,8 @@ def validate_merged_apex_info(output_target_files_dir, partitions): Depends on the /apex/* APEX files within partitions. Args: - output_target_files_dir: Output directory containing merged partition directories. + output_target_files_dir: Output directory containing merged partition + directories. partitions: A list of all the partitions in the output directory. Raises: @@ -965,6 +975,92 @@ def generate_images(target_files_dir, rebuild_recovery): add_img_to_target_files.main(add_img_args) +def rebuild_image_with_sepolicy(target_files_dir, + vendor_otatools=None, + vendor_target_files=None): + """Rebuilds odm.img or vendor.img to include merged sepolicy files. + + If odm is present then odm is preferred -- otherwise vendor is used. + + Args: + target_files_dir: Path to the extracted merged target-files package. + vendor_otatools: If not None, path to an otatools.zip from the vendor build + that is used when recompiling the image. + vendor_target_files: Expected if vendor_otatools is not None. Path to the + vendor target-files zip. + """ + partition = 'vendor' + if os.path.exists(os.path.join(target_files_dir, 'ODM')) or os.path.exists( + os.path.join(target_files_dir, 'IMAGES/odm.img')): + partition = 'odm' + partition_img = '{}.img'.format(partition) + + logger.info('Recompiling %s using the merged sepolicy files.', partition_img) + + # Copy the combined SEPolicy file and framework hashes to the image that is + # being rebuilt. + def copy_selinux_file(input_path, output_filename): + shutil.copy( + os.path.join(target_files_dir, input_path), + os.path.join(target_files_dir, partition.upper(), 'etc/selinux', + output_filename)) + + copy_selinux_file('META/combined_sepolicy', 'precompiled_sepolicy') + copy_selinux_file('SYSTEM/etc/selinux/plat_sepolicy_and_mapping.sha256', + 'precompiled_sepolicy.plat_sepolicy_and_mapping.sha256') + copy_selinux_file( + 'SYSTEM_EXT/etc/selinux/system_ext_sepolicy_and_mapping.sha256', + 'precompiled_sepolicy.system_ext_sepolicy_and_mapping.sha256') + copy_selinux_file('PRODUCT/etc/selinux/product_sepolicy_and_mapping.sha256', + 'precompiled_sepolicy.product_sepolicy_and_mapping.sha256') + + if not vendor_otatools: + # Remove the partition from the merged target-files archive. It will be + # rebuilt later automatically by generate_images(). + os.remove(os.path.join(target_files_dir, 'IMAGES', partition_img)) + else: + # TODO(b/192253131): Remove the need for vendor_otatools by fixing + # backwards-compatibility issues when compiling images on R from S+. + if not vendor_target_files: + raise ValueError( + 'Expected vendor_target_files if vendor_otatools is not None.') + logger.info( + '%s recompilation will be performed using the vendor otatools.zip', + partition_img) + + # Unzip the vendor build's otatools.zip and target-files archive. + vendor_otatools_dir = common.MakeTempDir( + prefix='merge_target_files_vendor_otatools_') + vendor_target_files_dir = common.MakeTempDir( + prefix='merge_target_files_vendor_target_files_') + common.UnzipToDir(vendor_otatools, vendor_otatools_dir) + common.UnzipToDir(vendor_target_files, vendor_target_files_dir) + + # Copy the partition contents from the merged target-files archive to the + # vendor target-files archive. + shutil.rmtree(os.path.join(vendor_target_files_dir, partition.upper())) + shutil.copytree( + os.path.join(target_files_dir, partition.upper()), + os.path.join(vendor_target_files_dir, partition.upper())) + + # Delete then rebuild the partition. + os.remove(os.path.join(vendor_target_files_dir, 'IMAGES', partition_img)) + rebuild_partition_command = [ + os.path.join(vendor_otatools_dir, 'bin', 'add_img_to_target_files'), + '--verbose', + '--add_missing', + vendor_target_files_dir, + ] + logger.info('Recompiling %s: %s', partition_img, + ' '.join(rebuild_partition_command)) + common.RunAndCheckOutput(rebuild_partition_command, verbose=True) + + # Move the newly-created image to the merged target files dir. + shutil.move( + os.path.join(vendor_target_files_dir, 'IMAGES', partition_img), + os.path.join(target_files_dir, 'IMAGES', partition_img)) + + def generate_super_empty_image(target_dir, output_super_empty): """Generates super_empty image from target package. @@ -1049,7 +1145,8 @@ def merge_target_files(temp_dir, framework_target_files, framework_item_list, framework_misc_info_keys, vendor_target_files, vendor_item_list, output_target_files, output_dir, output_item_list, output_ota, output_img, - output_super_empty, rebuild_recovery): + output_super_empty, rebuild_recovery, vendor_otatools, + rebuild_sepolicy): """Merges two target files packages together. This function takes framework and vendor target files packages as input, @@ -1085,6 +1182,9 @@ def merge_target_files(temp_dir, framework_target_files, framework_item_list, merged target files package and saves it at this path. rebuild_recovery: If true, rebuild the recovery patch used by non-A/B devices and write it to the system image. + vendor_otatools: Path to an otatools zip used for recompiling vendor images. + rebuild_sepolicy: If true, rebuild odm.img (if target uses ODM) or + vendor.img using a merged precompiled_sepolicy file. """ logger.info('starting: merge framework %s and vendor %s into output %s', @@ -1137,14 +1237,14 @@ def merge_target_files(temp_dir, framework_target_files, framework_item_list, partition_map=filtered_partitions) # Check that the split sepolicy from the multiple builds can compile. - split_sepolicy_cmd = compile_split_sepolicy( - product_out=output_target_files_temp_dir, - partition_map=filtered_partitions, - output_policy=os.path.join(output_target_files_temp_dir, - 'META/combined.policy')) + split_sepolicy_cmd = compile_split_sepolicy(output_target_files_temp_dir, + filtered_partitions) logger.info('Compiling split sepolicy: %s', ' '.join(split_sepolicy_cmd)) common.RunAndCheckOutput(split_sepolicy_cmd) - # TODO(b/178864050): Run tests on the combined.policy file. + # Include the compiled policy in an image if requested. + if rebuild_sepolicy: + rebuild_image_with_sepolicy(output_target_files_temp_dir, vendor_otatools, + vendor_target_files) # Run validation checks on the pre-installed APEX files. validate_merged_apex_info(output_target_files_temp_dir, partition_map.keys()) @@ -1261,6 +1361,10 @@ def main(): OPTIONS.rebuild_recovery = True elif o == '--allow-duplicate-apkapex-keys': OPTIONS.allow_duplicate_apkapex_keys = True + elif o == '--vendor-otatools': + OPTIONS.vendor_otatools = a + elif o == '--rebuild-sepolicy': + OPTIONS.rebuild_sepolicy = True elif o == '--keep-tmp': OPTIONS.keep_tmp = True else: @@ -1289,6 +1393,8 @@ def main(): 'output-super-empty=', 'rebuild_recovery', 'allow-duplicate-apkapex-keys', + 'vendor-otatools=', + 'rebuild-sepolicy', 'keep-tmp', ], extra_option_handler=option_handler) @@ -1342,7 +1448,9 @@ def main(): output_ota=OPTIONS.output_ota, output_img=OPTIONS.output_img, output_super_empty=OPTIONS.output_super_empty, - rebuild_recovery=OPTIONS.rebuild_recovery), OPTIONS.keep_tmp) + rebuild_recovery=OPTIONS.rebuild_recovery, + vendor_otatools=OPTIONS.vendor_otatools, + rebuild_sepolicy=OPTIONS.rebuild_sepolicy), OPTIONS.keep_tmp) if __name__ == '__main__': diff --git a/tools/releasetools/test_merge_target_files.py b/tools/releasetools/test_merge_target_files.py index 4f61472571..835edab713 100644 --- a/tools/releasetools/test_merge_target_files.py +++ b/tools/releasetools/test_merge_target_files.py @@ -265,10 +265,10 @@ class MergeTargetFilesTest(test_utils.ReleaseToolsTestCase): 'system': 'system', 'product': 'product', 'vendor': 'vendor', - }, os.path.join(product_out_dir, 'policy')) + }) self.assertEqual(' '.join(cmd), ('secilc -m -M true -G -N -c 30 ' - '-o {OTP}/policy -f /dev/null ' + '-o {OTP}/META/combined_sepolicy -f /dev/null ' '{OTP}/system/etc/selinux/plat_sepolicy.cil ' '{OTP}/system/etc/selinux/mapping/30.0.cil ' '{OTP}/vendor/etc/selinux/vendor_sepolicy.cil '