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 19import os 20import os.path 21import shutil 22import zipfile 23 24import common 25import test_utils 26from rangelib import RangeSet 27from validate_target_files import (ValidateVerifiedBootImages, 28 ValidateFileConsistency, CheckBuildPropDuplicity) 29from verity_utils import CreateVerityImageBuilder 30 31class ValidateTargetFilesTest(test_utils.ReleaseToolsTestCase): 32 33 def setUp(self): 34 self.testdata_dir = test_utils.get_testdata_dir() 35 36 def _generate_boot_image(self, output_file): 37 kernel = common.MakeTempFile(prefix='kernel-') 38 with open(kernel, 'wb') as kernel_fp: 39 kernel_fp.write(os.urandom(10)) 40 41 cmd = ['mkbootimg', '--kernel', kernel, '-o', output_file] 42 proc = common.Run(cmd) 43 stdoutdata, _ = proc.communicate() 44 self.assertEqual( 45 0, proc.returncode, 46 "Failed to run mkbootimg: {}".format(stdoutdata)) 47 48 cmd = ['boot_signer', '/boot', output_file, 49 os.path.join(self.testdata_dir, 'testkey.pk8'), 50 os.path.join(self.testdata_dir, 'testkey.x509.pem'), output_file] 51 proc = common.Run(cmd) 52 stdoutdata, _ = proc.communicate() 53 self.assertEqual( 54 0, proc.returncode, 55 "Failed to sign boot image with boot_signer: {}".format(stdoutdata)) 56 57 @test_utils.SkipIfExternalToolsUnavailable() 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 @test_utils.SkipIfExternalToolsUnavailable() 73 def test_ValidateVerifiedBootImages_bootImage_wrongKey(self): 74 input_tmp = common.MakeTempDir() 75 os.mkdir(os.path.join(input_tmp, 'IMAGES')) 76 boot_image = os.path.join(input_tmp, 'IMAGES', 'boot.img') 77 self._generate_boot_image(boot_image) 78 79 info_dict = { 80 'boot_signer' : 'true', 81 } 82 options = { 83 'verity_key' : os.path.join(self.testdata_dir, 'verity.x509.pem'), 84 } 85 self.assertRaises( 86 AssertionError, ValidateVerifiedBootImages, input_tmp, info_dict, 87 options) 88 89 @test_utils.SkipIfExternalToolsUnavailable() 90 def test_ValidateVerifiedBootImages_bootImage_corrupted(self): 91 input_tmp = common.MakeTempDir() 92 os.mkdir(os.path.join(input_tmp, 'IMAGES')) 93 boot_image = os.path.join(input_tmp, 'IMAGES', 'boot.img') 94 self._generate_boot_image(boot_image) 95 96 # Corrupt the late byte of the image. 97 with open(boot_image, 'r+b') as boot_fp: 98 boot_fp.seek(-1, os.SEEK_END) 99 last_byte = boot_fp.read(1) 100 last_byte = bytes([255 - ord(last_byte)]) 101 boot_fp.seek(-1, os.SEEK_END) 102 boot_fp.write(last_byte) 103 104 info_dict = { 105 'boot_signer' : 'true', 106 } 107 options = { 108 'verity_key' : os.path.join(self.testdata_dir, 'testkey.x509.pem'), 109 } 110 self.assertRaises( 111 AssertionError, ValidateVerifiedBootImages, input_tmp, info_dict, 112 options) 113 114 def _generate_system_image(self, output_file, system_root=None, 115 file_map=None): 116 prop_dict = { 117 'partition_size': str(1024 * 1024), 118 'verity': 'true', 119 'verity_block_device': '/dev/block/system', 120 'verity_key' : os.path.join(self.testdata_dir, 'testkey'), 121 'verity_fec': "true", 122 'verity_signer_cmd': 'verity_signer', 123 } 124 verity_image_builder = CreateVerityImageBuilder(prop_dict) 125 image_size = verity_image_builder.CalculateMaxImageSize() 126 127 # Use an empty root directory. 128 if not system_root: 129 system_root = common.MakeTempDir() 130 cmd = ['mkuserimg_mke2fs', '-s', system_root, output_file, 'ext4', 131 '/system', str(image_size), '-j', '0'] 132 if file_map: 133 cmd.extend(['-B', file_map]) 134 proc = common.Run(cmd) 135 stdoutdata, _ = proc.communicate() 136 self.assertEqual( 137 0, proc.returncode, 138 "Failed to create system image with mkuserimg_mke2fs: {}".format( 139 stdoutdata)) 140 141 # Append the verity metadata. 142 verity_image_builder.Build(output_file) 143 144 @test_utils.SkipIfExternalToolsUnavailable() 145 def test_ValidateVerifiedBootImages_systemRootImage(self): 146 input_tmp = common.MakeTempDir() 147 os.mkdir(os.path.join(input_tmp, 'IMAGES')) 148 system_image = os.path.join(input_tmp, 'IMAGES', 'system.img') 149 self._generate_system_image(system_image) 150 151 # Pack the verity key. 152 verity_key_mincrypt = os.path.join(input_tmp, 'ROOT', 'verity_key') 153 os.makedirs(os.path.dirname(verity_key_mincrypt)) 154 shutil.copyfile( 155 os.path.join(self.testdata_dir, 'testkey_mincrypt'), 156 verity_key_mincrypt) 157 158 info_dict = { 159 'system_root_image' : 'true', 160 'verity' : 'true', 161 } 162 options = { 163 'verity_key' : os.path.join(self.testdata_dir, 'testkey.x509.pem'), 164 'verity_key_mincrypt' : verity_key_mincrypt, 165 } 166 ValidateVerifiedBootImages(input_tmp, info_dict, options) 167 168 @test_utils.SkipIfExternalToolsUnavailable() 169 def test_ValidateVerifiedBootImages_nonSystemRootImage(self): 170 input_tmp = common.MakeTempDir() 171 os.mkdir(os.path.join(input_tmp, 'IMAGES')) 172 system_image = os.path.join(input_tmp, 'IMAGES', 'system.img') 173 self._generate_system_image(system_image) 174 175 # Pack the verity key into the root dir in system.img. 176 verity_key_mincrypt = os.path.join(input_tmp, 'ROOT', 'verity_key') 177 os.makedirs(os.path.dirname(verity_key_mincrypt)) 178 shutil.copyfile( 179 os.path.join(self.testdata_dir, 'testkey_mincrypt'), 180 verity_key_mincrypt) 181 182 # And a copy in ramdisk. 183 verity_key_ramdisk = os.path.join( 184 input_tmp, 'BOOT', 'RAMDISK', 'verity_key') 185 os.makedirs(os.path.dirname(verity_key_ramdisk)) 186 shutil.copyfile( 187 os.path.join(self.testdata_dir, 'testkey_mincrypt'), 188 verity_key_ramdisk) 189 190 info_dict = { 191 'verity' : 'true', 192 } 193 options = { 194 'verity_key' : os.path.join(self.testdata_dir, 'testkey.x509.pem'), 195 'verity_key_mincrypt' : verity_key_mincrypt, 196 } 197 ValidateVerifiedBootImages(input_tmp, info_dict, options) 198 199 @test_utils.SkipIfExternalToolsUnavailable() 200 def test_ValidateVerifiedBootImages_nonSystemRootImage_mismatchingKeys(self): 201 input_tmp = common.MakeTempDir() 202 os.mkdir(os.path.join(input_tmp, 'IMAGES')) 203 system_image = os.path.join(input_tmp, 'IMAGES', 'system.img') 204 self._generate_system_image(system_image) 205 206 # Pack the verity key into the root dir in system.img. 207 verity_key_mincrypt = os.path.join(input_tmp, 'ROOT', 'verity_key') 208 os.makedirs(os.path.dirname(verity_key_mincrypt)) 209 shutil.copyfile( 210 os.path.join(self.testdata_dir, 'testkey_mincrypt'), 211 verity_key_mincrypt) 212 213 # And an invalid copy in ramdisk. 214 verity_key_ramdisk = os.path.join( 215 input_tmp, 'BOOT', 'RAMDISK', 'verity_key') 216 os.makedirs(os.path.dirname(verity_key_ramdisk)) 217 shutil.copyfile( 218 os.path.join(self.testdata_dir, 'verity_mincrypt'), 219 verity_key_ramdisk) 220 221 info_dict = { 222 'verity' : 'true', 223 } 224 options = { 225 'verity_key' : os.path.join(self.testdata_dir, 'testkey.x509.pem'), 226 'verity_key_mincrypt' : verity_key_mincrypt, 227 } 228 self.assertRaises( 229 AssertionError, ValidateVerifiedBootImages, input_tmp, info_dict, 230 options) 231 232 @test_utils.SkipIfExternalToolsUnavailable() 233 def test_ValidateFileConsistency_incompleteRange(self): 234 input_tmp = common.MakeTempDir() 235 os.mkdir(os.path.join(input_tmp, 'IMAGES')) 236 system_image = os.path.join(input_tmp, 'IMAGES', 'system.img') 237 system_root = os.path.join(input_tmp, "SYSTEM") 238 os.mkdir(system_root) 239 240 # Write test files that contain multiple blocks of zeros, and these zero 241 # blocks will be omitted by kernel. Each test file will occupy one block in 242 # the final system image. 243 with open(os.path.join(system_root, 'a'), 'w') as f: 244 f.write('aaa') 245 f.write('\0' * 4096 * 3) 246 with open(os.path.join(system_root, 'b'), 'w') as f: 247 f.write('bbb') 248 f.write('\0' * 4096 * 3) 249 250 raw_file_map = os.path.join(input_tmp, 'IMAGES', 'raw_system.map') 251 self._generate_system_image(system_image, system_root, raw_file_map) 252 253 # Parse the generated file map and update the block ranges for each file. 254 file_map_list = {} 255 image_ranges = RangeSet() 256 with open(raw_file_map) as f: 257 for line in f.readlines(): 258 info = line.split() 259 self.assertEqual(2, len(info)) 260 image_ranges = image_ranges.union(RangeSet(info[1])) 261 file_map_list[info[0]] = RangeSet(info[1]) 262 263 # Add one unoccupied block as the shared block for all test files. 264 mock_shared_block = RangeSet("10-20").subtract(image_ranges).first(1) 265 with open(os.path.join(input_tmp, 'IMAGES', 'system.map'), 'w') as f: 266 for key in sorted(file_map_list.keys()): 267 line = '{} {}\n'.format( 268 key, file_map_list[key].union(mock_shared_block)) 269 f.write(line) 270 271 # Prepare for the target zip file 272 input_file = common.MakeTempFile() 273 all_entries = ['SYSTEM/', 'SYSTEM/b', 'SYSTEM/a', 'IMAGES/', 274 'IMAGES/system.map', 'IMAGES/system.img'] 275 with zipfile.ZipFile(input_file, 'w') as input_zip: 276 for name in all_entries: 277 input_zip.write(os.path.join(input_tmp, name), arcname=name) 278 279 # Expect the validation to pass and both files are skipped due to 280 # 'incomplete' block range. 281 with zipfile.ZipFile(input_file) as input_zip: 282 info_dict = {'extfs_sparse_flag': '-s'} 283 ValidateFileConsistency(input_zip, input_tmp, info_dict) 284 285 @test_utils.SkipIfExternalToolsUnavailable() 286 def test_ValidateFileConsistency_nonMonotonicRanges(self): 287 input_tmp = common.MakeTempDir() 288 os.mkdir(os.path.join(input_tmp, 'IMAGES')) 289 system_image = os.path.join(input_tmp, 'IMAGES', 'system.img') 290 system_root = os.path.join(input_tmp, "SYSTEM") 291 os.mkdir(system_root) 292 293 # Write the test file that contain three blocks of 'a', 'b', 'c'. 294 with open(os.path.join(system_root, 'abc'), 'w') as f: 295 f.write('a' * 4096 + 'b' * 4096 + 'c' * 4096) 296 raw_file_map = os.path.join(input_tmp, 'IMAGES', 'raw_system.map') 297 self._generate_system_image(system_image, system_root, raw_file_map) 298 299 # Parse the generated file map and manipulate the block ranges of 'abc' to 300 # be 'cba'. 301 file_map_list = {} 302 with open(raw_file_map) as f: 303 for line in f.readlines(): 304 info = line.split() 305 self.assertEqual(2, len(info)) 306 ranges = RangeSet(info[1]) 307 self.assertTrue(ranges.monotonic) 308 blocks = reversed(list(ranges.next_item())) 309 file_map_list[info[0]] = ' '.join([str(block) for block in blocks]) 310 311 # Update the contents of 'abc' to be 'cba'. 312 with open(os.path.join(system_root, 'abc'), 'w') as f: 313 f.write('c' * 4096 + 'b' * 4096 + 'a' * 4096) 314 315 # Update the system.map. 316 with open(os.path.join(input_tmp, 'IMAGES', 'system.map'), 'w') as f: 317 for key in sorted(file_map_list.keys()): 318 f.write('{} {}\n'.format(key, file_map_list[key])) 319 320 # Get the target zip file. 321 input_file = common.MakeTempFile() 322 all_entries = ['SYSTEM/', 'SYSTEM/abc', 'IMAGES/', 323 'IMAGES/system.map', 'IMAGES/system.img'] 324 with zipfile.ZipFile(input_file, 'w') as input_zip: 325 for name in all_entries: 326 input_zip.write(os.path.join(input_tmp, name), arcname=name) 327 328 with zipfile.ZipFile(input_file) as input_zip: 329 info_dict = {'extfs_sparse_flag': '-s'} 330 ValidateFileConsistency(input_zip, input_tmp, info_dict) 331 332 @staticmethod 333 def make_build_prop(build_prop): 334 input_tmp = common.MakeTempDir() 335 system_dir = os.path.join(input_tmp, 'SYSTEM') 336 os.makedirs(system_dir) 337 prop_file = os.path.join(system_dir, 'build.prop') 338 with open(prop_file, 'w') as output_file: 339 output_file.write("\n".join(build_prop)) 340 return input_tmp 341 342 def test_checkDuplicateProps_noDuplicate(self): 343 build_prop = [ 344 'ro.odm.build.date.utc=1578430045', 345 'ro.odm.build.fingerprint=' 346 'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys', 347 'ro.product.odm.device=coral', 348 ] 349 input_tmp = ValidateTargetFilesTest.make_build_prop(build_prop) 350 CheckBuildPropDuplicity(input_tmp) 351 352 def test_checkDuplicateProps_withDuplicate(self): 353 build_prop = [ 354 'ro.odm.build.date.utc=1578430045', 355 'ro.odm.build.date.utc=1578430049', 356 'ro.odm.build.fingerprint=' 357 'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys', 358 'ro.product.odm.device=coral', 359 ] 360 input_tmp = ValidateTargetFilesTest.make_build_prop({ 361 'ODM/etc/build.prop': '\n'.join(build_prop), 362 }) 363 364 self.assertRaises(ValueError, CheckBuildPropDuplicity, 365 input_tmp) 366