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