Loading tools/releasetools/test_common.py +54 −0 Original line number Diff line number Diff line Loading @@ -14,12 +14,14 @@ # limitations under the License. # import os import shutil import tempfile import time import unittest import zipfile import common import validate_target_files def random_string_with_holes(size, block_size, step_size): Loading Loading @@ -295,3 +297,55 @@ class CommonZipTest(unittest.TestCase): expected_mode=0o400) finally: os.remove(zip_file_name) class InstallRecoveryScriptFormatTest(unittest.TestCase): """Check the format of install-recovery.sh Its format should match between common.py and validate_target_files.py.""" def setUp(self): self._tempdir = tempfile.mkdtemp() # Create a dummy dict that contains the fstab info for boot&recovery. self._info = {"fstab" : {}} dummy_fstab = \ ["/dev/soc.0/by-name/boot /boot emmc defaults defaults", "/dev/soc.0/by-name/recovery /recovery emmc defaults defaults"] self._info["fstab"] = common.LoadRecoveryFSTab(lambda x : "\n".join(x), 2, dummy_fstab) def _out_tmp_sink(self, name, data, prefix="SYSTEM"): loc = os.path.join(self._tempdir, prefix, name) if not os.path.exists(os.path.dirname(loc)): os.makedirs(os.path.dirname(loc)) with open(loc, "w+") as f: f.write(data) def test_full_recovery(self): recovery_image = common.File("recovery.img", "recovery"); boot_image = common.File("boot.img", "boot"); self._info["full_recovery_image"] = "true" common.MakeRecoveryPatch(self._tempdir, self._out_tmp_sink, recovery_image, boot_image, self._info) validate_target_files.ValidateInstallRecoveryScript(self._tempdir, self._info) def test_recovery_from_boot(self): recovery_image = common.File("recovery.img", "recovery"); self._out_tmp_sink("recovery.img", recovery_image.data, "IMAGES") boot_image = common.File("boot.img", "boot"); self._out_tmp_sink("boot.img", boot_image.data, "IMAGES") common.MakeRecoveryPatch(self._tempdir, self._out_tmp_sink, recovery_image, boot_image, self._info) validate_target_files.ValidateInstallRecoveryScript(self._tempdir, self._info) # Validate 'recovery-from-boot' with bonus argument. self._out_tmp_sink("etc/recovery-resource.dat", "bonus", "SYSTEM") common.MakeRecoveryPatch(self._tempdir, self._out_tmp_sink, recovery_image, boot_image, self._info) validate_target_files.ValidateInstallRecoveryScript(self._tempdir, self._info) def tearDown(self): shutil.rmtree(self._tempdir) tools/releasetools/validate_target_files.py +104 −10 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ It performs checks to ensure the integrity of the input zip. import common import logging import os.path import re import sparse_img import sys Loading @@ -43,13 +44,38 @@ def _GetImage(which, tmpdir): return sparse_img.SparseImage(path, mappath, clobbered_blocks) def ValidateFileConsistency(input_zip, input_tmp): """Compare the files from image files and unpacked folders.""" def _CalculateFileSha1(file_name, unpacked_name, round_up=False): """Calculate the SHA-1 for a given file. Round up its size to 4K if needed.""" def RoundUpTo4K(value): rounded_up = value + 4095 return rounded_up - (rounded_up % 4096) assert os.path.exists(unpacked_name) with open(unpacked_name, 'r') as f: file_data = f.read() file_size = len(file_data) if round_up: file_size_rounded_up = RoundUpTo4K(file_size) file_data += '\0' * (file_size_rounded_up - file_size) return common.File(file_name, file_data).sha1 def ValidateFileAgainstSha1(input_tmp, file_name, file_path, expected_sha1): """Check if the file has the expected SHA-1.""" logging.info('Validating the SHA-1 of {}'.format(file_name)) unpacked_name = os.path.join(input_tmp, file_path) assert os.path.exists(unpacked_name) actual_sha1 = _CalculateFileSha1(file_name, unpacked_name, False) assert actual_sha1 == expected_sha1, \ 'SHA-1 mismatches for {}. actual {}, expected {}'.format( file_name, actual_sha1, expected_sha1) def ValidateFileConsistency(input_zip, input_tmp): """Compare the files from image files and unpacked folders.""" def CheckAllFiles(which): logging.info('Checking %s image.', which) image = _GetImage(which, input_tmp) Loading @@ -66,12 +92,7 @@ def ValidateFileConsistency(input_zip, input_tmp): # The filename under unpacked directory, such as SYSTEM/bin/sh. unpacked_name = os.path.join( input_tmp, which.upper(), entry[(len(prefix) + 1):]) with open(unpacked_name) as f: file_data = f.read() file_size = len(file_data) file_size_rounded_up = RoundUpTo4K(file_size) file_data += '\0' * (file_size_rounded_up - file_size) file_sha1 = common.File(entry, file_data).sha1 file_sha1 = _CalculateFileSha1(entry, unpacked_name, True) assert blocks_sha1 == file_sha1, \ 'file: %s, range: %s, blocks_sha1: %s, file_sha1: %s' % ( Loading @@ -89,6 +110,78 @@ def ValidateFileConsistency(input_zip, input_tmp): # Not checking IMAGES/system_other.img since it doesn't have the map file. def ValidateInstallRecoveryScript(input_tmp, info_dict): """Validate the SHA-1 embedded in install-recovery.sh. install-recovery.sh is written in common.py and has the following format: 1. full recovery: ... if ! applypatch -c type:device:size:SHA-1; then applypatch /system/etc/recovery.img type:device sha1 size && ... ... 2. recovery from boot: ... applypatch [-b bonus_args] boot_info recovery_info recovery_sha1 \ recovery_size patch_info && ... ... For full recovery, we want to calculate the SHA-1 of /system/etc/recovery.img and compare it against the one embedded in the script. While for recovery from boot, we want to check the SHA-1 for both recovery.img and boot.img under IMAGES/. """ script_path = 'SYSTEM/bin/install-recovery.sh' if not os.path.exists(os.path.join(input_tmp, script_path)): logging.info('{} does not exist in input_tmp'.format(script_path)) return logging.info('Checking {}'.format(script_path)) with open(os.path.join(input_tmp, script_path), 'r') as script: lines = script.read().strip().split('\n') assert len(lines) >= 6 check_cmd = re.search(r'if ! applypatch -c \w+:.+:\w+:(\w+);', lines[1].strip()) expected_recovery_check_sha1 = check_cmd.group(1) patch_cmd = re.search(r'(applypatch.+)&&', lines[2].strip()) applypatch_argv = patch_cmd.group(1).strip().split() full_recovery_image = info_dict.get("full_recovery_image") == "true" if full_recovery_image: assert len(applypatch_argv) == 5 # Check we have the same expected SHA-1 of recovery.img in both check mode # and patch mode. expected_recovery_sha1 = applypatch_argv[3].strip() assert expected_recovery_check_sha1 == expected_recovery_sha1 ValidateFileAgainstSha1(input_tmp, 'recovery.img', 'SYSTEM/etc/recovery.img', expected_recovery_sha1) else: # We're patching boot.img to get recovery.img where bonus_args is optional if applypatch_argv[1] == "-b": assert len(applypatch_argv) == 8 boot_info_index = 3 else: assert len(applypatch_argv) == 6 boot_info_index = 1 # boot_info: boot_type:boot_device:boot_size:boot_sha1 boot_info = applypatch_argv[boot_info_index].strip().split(':') assert len(boot_info) == 4 ValidateFileAgainstSha1(input_tmp, file_name='boot.img', file_path='IMAGES/boot.img', expected_sha1=boot_info[3]) recovery_sha1_index = boot_info_index + 2 expected_recovery_sha1 = applypatch_argv[recovery_sha1_index] assert expected_recovery_check_sha1 == expected_recovery_sha1 ValidateFileAgainstSha1(input_tmp, file_name='recovery.img', file_path='IMAGES/recovery.img', expected_sha1=expected_recovery_sha1) logging.info('Done checking {}'.format(script_path)) def main(argv): def option_handler(): return True Loading @@ -112,11 +205,12 @@ def main(argv): ValidateFileConsistency(input_zip, input_tmp) info_dict = common.LoadInfoDict(input_tmp) ValidateInstallRecoveryScript(input_tmp, info_dict) # TODO: Check if the OTA keys have been properly updated (the ones on /system, # in recovery image). # TODO(b/35411009): Verify the contents in /system/bin/install-recovery.sh. logging.info("Done.") Loading Loading
tools/releasetools/test_common.py +54 −0 Original line number Diff line number Diff line Loading @@ -14,12 +14,14 @@ # limitations under the License. # import os import shutil import tempfile import time import unittest import zipfile import common import validate_target_files def random_string_with_holes(size, block_size, step_size): Loading Loading @@ -295,3 +297,55 @@ class CommonZipTest(unittest.TestCase): expected_mode=0o400) finally: os.remove(zip_file_name) class InstallRecoveryScriptFormatTest(unittest.TestCase): """Check the format of install-recovery.sh Its format should match between common.py and validate_target_files.py.""" def setUp(self): self._tempdir = tempfile.mkdtemp() # Create a dummy dict that contains the fstab info for boot&recovery. self._info = {"fstab" : {}} dummy_fstab = \ ["/dev/soc.0/by-name/boot /boot emmc defaults defaults", "/dev/soc.0/by-name/recovery /recovery emmc defaults defaults"] self._info["fstab"] = common.LoadRecoveryFSTab(lambda x : "\n".join(x), 2, dummy_fstab) def _out_tmp_sink(self, name, data, prefix="SYSTEM"): loc = os.path.join(self._tempdir, prefix, name) if not os.path.exists(os.path.dirname(loc)): os.makedirs(os.path.dirname(loc)) with open(loc, "w+") as f: f.write(data) def test_full_recovery(self): recovery_image = common.File("recovery.img", "recovery"); boot_image = common.File("boot.img", "boot"); self._info["full_recovery_image"] = "true" common.MakeRecoveryPatch(self._tempdir, self._out_tmp_sink, recovery_image, boot_image, self._info) validate_target_files.ValidateInstallRecoveryScript(self._tempdir, self._info) def test_recovery_from_boot(self): recovery_image = common.File("recovery.img", "recovery"); self._out_tmp_sink("recovery.img", recovery_image.data, "IMAGES") boot_image = common.File("boot.img", "boot"); self._out_tmp_sink("boot.img", boot_image.data, "IMAGES") common.MakeRecoveryPatch(self._tempdir, self._out_tmp_sink, recovery_image, boot_image, self._info) validate_target_files.ValidateInstallRecoveryScript(self._tempdir, self._info) # Validate 'recovery-from-boot' with bonus argument. self._out_tmp_sink("etc/recovery-resource.dat", "bonus", "SYSTEM") common.MakeRecoveryPatch(self._tempdir, self._out_tmp_sink, recovery_image, boot_image, self._info) validate_target_files.ValidateInstallRecoveryScript(self._tempdir, self._info) def tearDown(self): shutil.rmtree(self._tempdir)
tools/releasetools/validate_target_files.py +104 −10 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ It performs checks to ensure the integrity of the input zip. import common import logging import os.path import re import sparse_img import sys Loading @@ -43,13 +44,38 @@ def _GetImage(which, tmpdir): return sparse_img.SparseImage(path, mappath, clobbered_blocks) def ValidateFileConsistency(input_zip, input_tmp): """Compare the files from image files and unpacked folders.""" def _CalculateFileSha1(file_name, unpacked_name, round_up=False): """Calculate the SHA-1 for a given file. Round up its size to 4K if needed.""" def RoundUpTo4K(value): rounded_up = value + 4095 return rounded_up - (rounded_up % 4096) assert os.path.exists(unpacked_name) with open(unpacked_name, 'r') as f: file_data = f.read() file_size = len(file_data) if round_up: file_size_rounded_up = RoundUpTo4K(file_size) file_data += '\0' * (file_size_rounded_up - file_size) return common.File(file_name, file_data).sha1 def ValidateFileAgainstSha1(input_tmp, file_name, file_path, expected_sha1): """Check if the file has the expected SHA-1.""" logging.info('Validating the SHA-1 of {}'.format(file_name)) unpacked_name = os.path.join(input_tmp, file_path) assert os.path.exists(unpacked_name) actual_sha1 = _CalculateFileSha1(file_name, unpacked_name, False) assert actual_sha1 == expected_sha1, \ 'SHA-1 mismatches for {}. actual {}, expected {}'.format( file_name, actual_sha1, expected_sha1) def ValidateFileConsistency(input_zip, input_tmp): """Compare the files from image files and unpacked folders.""" def CheckAllFiles(which): logging.info('Checking %s image.', which) image = _GetImage(which, input_tmp) Loading @@ -66,12 +92,7 @@ def ValidateFileConsistency(input_zip, input_tmp): # The filename under unpacked directory, such as SYSTEM/bin/sh. unpacked_name = os.path.join( input_tmp, which.upper(), entry[(len(prefix) + 1):]) with open(unpacked_name) as f: file_data = f.read() file_size = len(file_data) file_size_rounded_up = RoundUpTo4K(file_size) file_data += '\0' * (file_size_rounded_up - file_size) file_sha1 = common.File(entry, file_data).sha1 file_sha1 = _CalculateFileSha1(entry, unpacked_name, True) assert blocks_sha1 == file_sha1, \ 'file: %s, range: %s, blocks_sha1: %s, file_sha1: %s' % ( Loading @@ -89,6 +110,78 @@ def ValidateFileConsistency(input_zip, input_tmp): # Not checking IMAGES/system_other.img since it doesn't have the map file. def ValidateInstallRecoveryScript(input_tmp, info_dict): """Validate the SHA-1 embedded in install-recovery.sh. install-recovery.sh is written in common.py and has the following format: 1. full recovery: ... if ! applypatch -c type:device:size:SHA-1; then applypatch /system/etc/recovery.img type:device sha1 size && ... ... 2. recovery from boot: ... applypatch [-b bonus_args] boot_info recovery_info recovery_sha1 \ recovery_size patch_info && ... ... For full recovery, we want to calculate the SHA-1 of /system/etc/recovery.img and compare it against the one embedded in the script. While for recovery from boot, we want to check the SHA-1 for both recovery.img and boot.img under IMAGES/. """ script_path = 'SYSTEM/bin/install-recovery.sh' if not os.path.exists(os.path.join(input_tmp, script_path)): logging.info('{} does not exist in input_tmp'.format(script_path)) return logging.info('Checking {}'.format(script_path)) with open(os.path.join(input_tmp, script_path), 'r') as script: lines = script.read().strip().split('\n') assert len(lines) >= 6 check_cmd = re.search(r'if ! applypatch -c \w+:.+:\w+:(\w+);', lines[1].strip()) expected_recovery_check_sha1 = check_cmd.group(1) patch_cmd = re.search(r'(applypatch.+)&&', lines[2].strip()) applypatch_argv = patch_cmd.group(1).strip().split() full_recovery_image = info_dict.get("full_recovery_image") == "true" if full_recovery_image: assert len(applypatch_argv) == 5 # Check we have the same expected SHA-1 of recovery.img in both check mode # and patch mode. expected_recovery_sha1 = applypatch_argv[3].strip() assert expected_recovery_check_sha1 == expected_recovery_sha1 ValidateFileAgainstSha1(input_tmp, 'recovery.img', 'SYSTEM/etc/recovery.img', expected_recovery_sha1) else: # We're patching boot.img to get recovery.img where bonus_args is optional if applypatch_argv[1] == "-b": assert len(applypatch_argv) == 8 boot_info_index = 3 else: assert len(applypatch_argv) == 6 boot_info_index = 1 # boot_info: boot_type:boot_device:boot_size:boot_sha1 boot_info = applypatch_argv[boot_info_index].strip().split(':') assert len(boot_info) == 4 ValidateFileAgainstSha1(input_tmp, file_name='boot.img', file_path='IMAGES/boot.img', expected_sha1=boot_info[3]) recovery_sha1_index = boot_info_index + 2 expected_recovery_sha1 = applypatch_argv[recovery_sha1_index] assert expected_recovery_check_sha1 == expected_recovery_sha1 ValidateFileAgainstSha1(input_tmp, file_name='recovery.img', file_path='IMAGES/recovery.img', expected_sha1=expected_recovery_sha1) logging.info('Done checking {}'.format(script_path)) def main(argv): def option_handler(): return True Loading @@ -112,11 +205,12 @@ def main(argv): ValidateFileConsistency(input_zip, input_tmp) info_dict = common.LoadInfoDict(input_tmp) ValidateInstallRecoveryScript(input_tmp, info_dict) # TODO: Check if the OTA keys have been properly updated (the ones on /system, # in recovery image). # TODO(b/35411009): Verify the contents in /system/bin/install-recovery.sh. logging.info("Done.") Loading