Home | History | Annotate | Download | only in releasetools
      1 #
      2 # Copyright (C) 2018 The Android Open Source Project
      3 #
      4 # Licensed under the Apache License, Version 2.0 (the "License");
      5 # you may not use this file except in compliance with the License.
      6 # You may obtain a copy of the License at
      7 #
      8 #      http://www.apache.org/licenses/LICENSE-2.0
      9 #
     10 # Unless required by applicable law or agreed to in writing, software
     11 # distributed under the License is distributed on an "AS IS" BASIS,
     12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 # See the License for the specific language governing permissions and
     14 # limitations under the License.
     15 #
     16 
     17 """Unittests for validate_target_files.py."""
     18 
     19 import os
     20 import os.path
     21 import shutil
     22 import zipfile
     23 
     24 import common
     25 import test_utils
     26 from rangelib import RangeSet
     27 from validate_target_files import (ValidateVerifiedBootImages,
     28                                    ValidateFileConsistency)
     29 from verity_utils import CreateVerityImageBuilder
     30 
     31 
     32 class ValidateTargetFilesTest(test_utils.ReleaseToolsTestCase):
     33 
     34   def setUp(self):
     35     self.testdata_dir = test_utils.get_testdata_dir()
     36 
     37   def _generate_boot_image(self, output_file):
     38     kernel = common.MakeTempFile(prefix='kernel-')
     39     with open(kernel, 'wb') as kernel_fp:
     40       kernel_fp.write(os.urandom(10))
     41 
     42     cmd = ['mkbootimg', '--kernel', kernel, '-o', output_file]
     43     proc = common.Run(cmd)
     44     stdoutdata, _ = proc.communicate()
     45     self.assertEqual(
     46         0, proc.returncode,
     47         "Failed to run mkbootimg: {}".format(stdoutdata))
     48 
     49     cmd = ['boot_signer', '/boot', output_file,
     50            os.path.join(self.testdata_dir, 'testkey.pk8'),
     51            os.path.join(self.testdata_dir, 'testkey.x509.pem'), output_file]
     52     proc = common.Run(cmd)
     53     stdoutdata, _ = proc.communicate()
     54     self.assertEqual(
     55         0, proc.returncode,
     56         "Failed to sign boot image with boot_signer: {}".format(stdoutdata))
     57 
     58   def test_ValidateVerifiedBootImages_bootImage(self):
     59     input_tmp = common.MakeTempDir()
     60     os.mkdir(os.path.join(input_tmp, 'IMAGES'))
     61     boot_image = os.path.join(input_tmp, 'IMAGES', 'boot.img')
     62     self._generate_boot_image(boot_image)
     63 
     64     info_dict = {
     65         'boot_signer' : 'true',
     66     }
     67     options = {
     68         'verity_key' : os.path.join(self.testdata_dir, 'testkey.x509.pem'),
     69     }
     70     ValidateVerifiedBootImages(input_tmp, info_dict, options)
     71 
     72   def test_ValidateVerifiedBootImages_bootImage_wrongKey(self):
     73     input_tmp = common.MakeTempDir()
     74     os.mkdir(os.path.join(input_tmp, 'IMAGES'))
     75     boot_image = os.path.join(input_tmp, 'IMAGES', 'boot.img')
     76     self._generate_boot_image(boot_image)
     77 
     78     info_dict = {
     79         'boot_signer' : 'true',
     80     }
     81     options = {
     82         'verity_key' : os.path.join(self.testdata_dir, 'verity.x509.pem'),
     83     }
     84     self.assertRaises(
     85         AssertionError, ValidateVerifiedBootImages, input_tmp, info_dict,
     86         options)
     87 
     88   def test_ValidateVerifiedBootImages_bootImage_corrupted(self):
     89     input_tmp = common.MakeTempDir()
     90     os.mkdir(os.path.join(input_tmp, 'IMAGES'))
     91     boot_image = os.path.join(input_tmp, 'IMAGES', 'boot.img')
     92     self._generate_boot_image(boot_image)
     93 
     94     # Corrupt the late byte of the image.
     95     with open(boot_image, 'r+b') as boot_fp:
     96       boot_fp.seek(-1, os.SEEK_END)
     97       last_byte = boot_fp.read(1)
     98       last_byte = chr(255 - ord(last_byte))
     99       boot_fp.seek(-1, os.SEEK_END)
    100       boot_fp.write(last_byte)
    101 
    102     info_dict = {
    103         'boot_signer' : 'true',
    104     }
    105     options = {
    106         'verity_key' : os.path.join(self.testdata_dir, 'testkey.x509.pem'),
    107     }
    108     self.assertRaises(
    109         AssertionError, ValidateVerifiedBootImages, input_tmp, info_dict,
    110         options)
    111 
    112   def _generate_system_image(self, output_file, system_root=None,
    113                              file_map=None):
    114     prop_dict = {
    115         'partition_size': str(1024 * 1024),
    116         'verity': 'true',
    117         'verity_block_device': '/dev/block/system',
    118         'verity_key' : os.path.join(self.testdata_dir, 'testkey'),
    119         'verity_fec': "true",
    120         'verity_signer_cmd': 'verity_signer',
    121     }
    122     verity_image_builder = CreateVerityImageBuilder(prop_dict)
    123     image_size = verity_image_builder.CalculateMaxImageSize()
    124 
    125     # Use an empty root directory.
    126     if not system_root:
    127       system_root = common.MakeTempDir()
    128     cmd = ['mkuserimg_mke2fs', '-s', system_root, output_file, 'ext4',
    129            '/system', str(image_size), '-j', '0']
    130     if file_map:
    131       cmd.extend(['-B', file_map])
    132     proc = common.Run(cmd)
    133     stdoutdata, _ = proc.communicate()
    134     self.assertEqual(
    135         0, proc.returncode,
    136         "Failed to create system image with mkuserimg_mke2fs: {}".format(
    137             stdoutdata))
    138 
    139     # Append the verity metadata.
    140     verity_image_builder.Build(output_file)
    141 
    142   def test_ValidateVerifiedBootImages_systemImage(self):
    143     input_tmp = common.MakeTempDir()
    144     os.mkdir(os.path.join(input_tmp, 'IMAGES'))
    145     system_image = os.path.join(input_tmp, 'IMAGES', 'system.img')
    146     self._generate_system_image(system_image)
    147 
    148     # Pack the verity key.
    149     verity_key_mincrypt = os.path.join(
    150         input_tmp, 'BOOT', 'RAMDISK', 'verity_key')
    151     os.makedirs(os.path.dirname(verity_key_mincrypt))
    152     shutil.copyfile(
    153         os.path.join(self.testdata_dir, 'testkey_mincrypt'),
    154         verity_key_mincrypt)
    155 
    156     info_dict = {
    157         'verity' : 'true',
    158     }
    159     options = {
    160         'verity_key' : os.path.join(self.testdata_dir, 'testkey.x509.pem'),
    161         'verity_key_mincrypt' : verity_key_mincrypt,
    162     }
    163     ValidateVerifiedBootImages(input_tmp, info_dict, options)
    164 
    165   def test_ValidateFileConsistency_incompleteRange(self):
    166     input_tmp = common.MakeTempDir()
    167     os.mkdir(os.path.join(input_tmp, 'IMAGES'))
    168     system_image = os.path.join(input_tmp, 'IMAGES', 'system.img')
    169     system_root = os.path.join(input_tmp, "SYSTEM")
    170     os.mkdir(system_root)
    171 
    172     # Write the test file that contain multiple blocks of zeros, and these
    173     # zero blocks will be omitted by kernel. And the test files will occupy one
    174     # block range each in the final system image.
    175     with open(os.path.join(system_root, 'a'), 'w') as f:
    176       f.write("aaa")
    177       f.write('\0' * 4096 * 3)
    178     with open(os.path.join(system_root, 'b'), 'w') as f:
    179       f.write("bbb")
    180       f.write('\0' * 4096 * 3)
    181 
    182     raw_file_map = os.path.join(input_tmp, 'IMAGES', 'raw_system.map')
    183     self._generate_system_image(system_image, system_root, raw_file_map)
    184 
    185     # Parse the generated file map and update the block ranges for each file.
    186     file_map_list = {}
    187     image_ranges = RangeSet()
    188     with open(raw_file_map, 'r') as f:
    189       for line in f.readlines():
    190         info = line.split()
    191         self.assertEqual(2, len(info))
    192         image_ranges = image_ranges.union(RangeSet(info[1]))
    193         file_map_list[info[0]] = RangeSet(info[1])
    194 
    195     # Add one unoccupied block as the shared block for all test files.
    196     mock_shared_block = RangeSet("10-20").subtract(image_ranges).first(1)
    197     with open(os.path.join(input_tmp, 'IMAGES', 'system.map'), 'w') as f:
    198       for key in sorted(file_map_list.keys()):
    199         line = "{} {}\n".format(
    200             key, file_map_list[key].union(mock_shared_block))
    201         f.write(line)
    202 
    203     # Prepare for the target zip file
    204     input_file = common.MakeTempFile()
    205     all_entries = ['SYSTEM/', 'SYSTEM/b', 'SYSTEM/a', 'IMAGES/',
    206                    'IMAGES/system.map', 'IMAGES/system.img']
    207     with zipfile.ZipFile(input_file, 'w') as input_zip:
    208       for name in all_entries:
    209         input_zip.write(os.path.join(input_tmp, name), arcname=name)
    210 
    211     input_zip = zipfile.ZipFile(input_file, 'r')
    212     info_dict = {'extfs_sparse_flag': '-s'}
    213 
    214     # Expect the validation to pass and both files are skipped due to
    215     # 'incomplete' block range.
    216     ValidateFileConsistency(input_zip, input_tmp, info_dict)
    217