Merge "Verify the contents in install-recovery.sh" am: 924c1c0598

am: 01c463e406

Change-Id: Ie8177614c855e3f554184d570b2f98b29aefc8c6
This commit is contained in:
Tianjie Xu 2017-06-22 18:27:39 +00:00 committed by android-build-merger
commit e0bda97ea1
2 changed files with 158 additions and 10 deletions

View file

@ -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)

View file

@ -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.")