Merge "Verify the contents in install-recovery.sh" am: 924c1c0598
am: 01c463e406
Change-Id: Ie8177614c855e3f554184d570b2f98b29aefc8c6
This commit is contained in:
commit
e0bda97ea1
2 changed files with 158 additions and 10 deletions
|
@ -14,12 +14,14 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
import common
|
import common
|
||||||
|
import validate_target_files
|
||||||
|
|
||||||
|
|
||||||
def random_string_with_holes(size, block_size, step_size):
|
def random_string_with_holes(size, block_size, step_size):
|
||||||
|
@ -295,3 +297,55 @@ class CommonZipTest(unittest.TestCase):
|
||||||
expected_mode=0o400)
|
expected_mode=0o400)
|
||||||
finally:
|
finally:
|
||||||
os.remove(zip_file_name)
|
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)
|
||||||
|
|
|
@ -26,6 +26,7 @@ It performs checks to ensure the integrity of the input zip.
|
||||||
import common
|
import common
|
||||||
import logging
|
import logging
|
||||||
import os.path
|
import os.path
|
||||||
|
import re
|
||||||
import sparse_img
|
import sparse_img
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -43,13 +44,38 @@ def _GetImage(which, tmpdir):
|
||||||
return sparse_img.SparseImage(path, mappath, clobbered_blocks)
|
return sparse_img.SparseImage(path, mappath, clobbered_blocks)
|
||||||
|
|
||||||
|
|
||||||
def ValidateFileConsistency(input_zip, input_tmp):
|
def _CalculateFileSha1(file_name, unpacked_name, round_up=False):
|
||||||
"""Compare the files from image files and unpacked folders."""
|
"""Calculate the SHA-1 for a given file. Round up its size to 4K if needed."""
|
||||||
|
|
||||||
def RoundUpTo4K(value):
|
def RoundUpTo4K(value):
|
||||||
rounded_up = value + 4095
|
rounded_up = value + 4095
|
||||||
return rounded_up - (rounded_up % 4096)
|
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):
|
def CheckAllFiles(which):
|
||||||
logging.info('Checking %s image.', which)
|
logging.info('Checking %s image.', which)
|
||||||
image = _GetImage(which, input_tmp)
|
image = _GetImage(which, input_tmp)
|
||||||
|
@ -66,12 +92,7 @@ def ValidateFileConsistency(input_zip, input_tmp):
|
||||||
# The filename under unpacked directory, such as SYSTEM/bin/sh.
|
# The filename under unpacked directory, such as SYSTEM/bin/sh.
|
||||||
unpacked_name = os.path.join(
|
unpacked_name = os.path.join(
|
||||||
input_tmp, which.upper(), entry[(len(prefix) + 1):])
|
input_tmp, which.upper(), entry[(len(prefix) + 1):])
|
||||||
with open(unpacked_name) as f:
|
file_sha1 = _CalculateFileSha1(entry, unpacked_name, True)
|
||||||
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
|
|
||||||
|
|
||||||
assert blocks_sha1 == file_sha1, \
|
assert blocks_sha1 == file_sha1, \
|
||||||
'file: %s, range: %s, blocks_sha1: %s, file_sha1: %s' % (
|
'file: %s, range: %s, blocks_sha1: %s, file_sha1: %s' % (
|
||||||
|
@ -89,6 +110,78 @@ def ValidateFileConsistency(input_zip, input_tmp):
|
||||||
# Not checking IMAGES/system_other.img since it doesn't have the map file.
|
# 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 main(argv):
|
||||||
def option_handler():
|
def option_handler():
|
||||||
return True
|
return True
|
||||||
|
@ -112,11 +205,12 @@ def main(argv):
|
||||||
|
|
||||||
ValidateFileConsistency(input_zip, input_tmp)
|
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,
|
# TODO: Check if the OTA keys have been properly updated (the ones on /system,
|
||||||
# in recovery image).
|
# in recovery image).
|
||||||
|
|
||||||
# TODO(b/35411009): Verify the contents in /system/bin/install-recovery.sh.
|
|
||||||
|
|
||||||
logging.info("Done.")
|
logging.info("Done.")
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue