Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e0bda97e authored by Tianjie Xu's avatar Tianjie Xu Committed by android-build-merger
Browse files

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

am: 01c463e4

Change-Id: Ie8177614c855e3f554184d570b2f98b29aefc8c6
parents 3487a612 01c463e4
Loading
Loading
Loading
Loading
+54 −0
Original line number Diff line number Diff line
@@ -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):
@@ -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)
+104 −10
Original line number Diff line number Diff line
@@ -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

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