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 'verity' : 'true', 160 } 161 options = { 162 'verity_key' : os.path.join(self.testdata_dir, 'testkey.x509.pem'), 163 'verity_key_mincrypt' : verity_key_mincrypt, 164 } 165 ValidateVerifiedBootImages(input_tmp, info_dict, options) 166 167 @test_utils.SkipIfExternalToolsUnavailable() 168 def test_ValidateVerifiedBootImages_nonSystemRootImage(self): 169 input_tmp = common.MakeTempDir() 170 os.mkdir(os.path.join(input_tmp, 'IMAGES')) 171 system_image = os.path.join(input_tmp, 'IMAGES', 'system.img') 172 self._generate_system_image(system_image) 173 174 # Pack the verity key into the root dir in system.img. 175 verity_key_mincrypt = os.path.join(input_tmp, 'ROOT', 'verity_key') 176 os.makedirs(os.path.dirname(verity_key_mincrypt)) 177 shutil.copyfile( 178 os.path.join(self.testdata_dir, 'testkey_mincrypt'), 179 verity_key_mincrypt) 180 181 # And a copy in ramdisk. 182 verity_key_ramdisk = os.path.join( 183 input_tmp, 'BOOT', 'RAMDISK', 'verity_key') 184 os.makedirs(os.path.dirname(verity_key_ramdisk)) 185 shutil.copyfile( 186 os.path.join(self.testdata_dir, 'testkey_mincrypt'), 187 verity_key_ramdisk) 188 189 info_dict = { 190 'verity' : 'true', 191 } 192 options = { 193 'verity_key' : os.path.join(self.testdata_dir, 'testkey.x509.pem'), 194 'verity_key_mincrypt' : verity_key_mincrypt, 195 } 196 ValidateVerifiedBootImages(input_tmp, info_dict, options) 197 198 @test_utils.SkipIfExternalToolsUnavailable() 199 def test_ValidateVerifiedBootImages_nonSystemRootImage_mismatchingKeys(self): 200 input_tmp = common.MakeTempDir() 201 os.mkdir(os.path.join(input_tmp, 'IMAGES')) 202 system_image = os.path.join(input_tmp, 'IMAGES', 'system.img') 203 self._generate_system_image(system_image) 204 205 # Pack the verity key into the root dir in system.img. 206 verity_key_mincrypt = os.path.join(input_tmp, 'ROOT', 'verity_key') 207 os.makedirs(os.path.dirname(verity_key_mincrypt)) 208 shutil.copyfile( 209 os.path.join(self.testdata_dir, 'testkey_mincrypt'), 210 verity_key_mincrypt) 211 212 # And an invalid copy in ramdisk. 213 verity_key_ramdisk = os.path.join( 214 input_tmp, 'BOOT', 'RAMDISK', 'verity_key') 215 os.makedirs(os.path.dirname(verity_key_ramdisk)) 216 shutil.copyfile( 217 os.path.join(self.testdata_dir, 'verity_mincrypt'), 218 verity_key_ramdisk) 219 220 info_dict = { 221 'verity' : 'true', 222 } 223 options = { 224 'verity_key' : os.path.join(self.testdata_dir, 'testkey.x509.pem'), 225 'verity_key_mincrypt' : verity_key_mincrypt, 226 } 227 self.assertRaises( 228 AssertionError, ValidateVerifiedBootImages, input_tmp, info_dict, 229 options) 230 231 @test_utils.SkipIfExternalToolsUnavailable() 232 def test_ValidateFileConsistency_incompleteRange(self): 233 input_tmp = common.MakeTempDir() 234 os.mkdir(os.path.join(input_tmp, 'IMAGES')) 235 system_image = os.path.join(input_tmp, 'IMAGES', 'system.img') 236 system_root = os.path.join(input_tmp, "SYSTEM") 237 os.mkdir(system_root) 238 239 # Write test files that contain multiple blocks of zeros, and these zero 240 # blocks will be omitted by kernel. Each test file will occupy one block in 241 # the final system image. 242 with open(os.path.join(system_root, 'a'), 'w') as f: 243 f.write('aaa') 244 f.write('\0' * 4096 * 3) 245 with open(os.path.join(system_root, 'b'), 'w') as f: 246 f.write('bbb') 247 f.write('\0' * 4096 * 3) 248 249 raw_file_map = os.path.join(input_tmp, 'IMAGES', 'raw_system.map') 250 self._generate_system_image(system_image, system_root, raw_file_map) 251 252 # Parse the generated file map and update the block ranges for each file. 253 file_map_list = {} 254 image_ranges = RangeSet() 255 with open(raw_file_map) as f: 256 for line in f.readlines(): 257 info = line.split() 258 self.assertEqual(2, len(info)) 259 image_ranges = image_ranges.union(RangeSet(info[1])) 260 file_map_list[info[0]] = RangeSet(info[1]) 261 262 # Add one unoccupied block as the shared block for all test files. 263 mock_shared_block = RangeSet("10-20").subtract(image_ranges).first(1) 264 with open(os.path.join(input_tmp, 'IMAGES', 'system.map'), 'w') as f: 265 for key in sorted(file_map_list.keys()): 266 line = '{} {}\n'.format( 267 key, file_map_list[key].union(mock_shared_block)) 268 f.write(line) 269 270 # Prepare for the target zip file 271 input_file = common.MakeTempFile() 272 all_entries = ['SYSTEM/', 'SYSTEM/b', 'SYSTEM/a', 'IMAGES/', 273 'IMAGES/system.map', 'IMAGES/system.img'] 274 with zipfile.ZipFile(input_file, 'w', allowZip64=True) as input_zip: 275 for name in all_entries: 276 input_zip.write(os.path.join(input_tmp, name), arcname=name) 277 278 # Expect the validation to pass and both files are skipped due to 279 # 'incomplete' block range. 280 with zipfile.ZipFile(input_file) as input_zip: 281 info_dict = {'extfs_sparse_flag': '-s'} 282 ValidateFileConsistency(input_zip, input_tmp, info_dict) 283 284 @test_utils.SkipIfExternalToolsUnavailable() 285 def test_ValidateFileConsistency_nonMonotonicRanges(self): 286 input_tmp = common.MakeTempDir() 287 os.mkdir(os.path.join(input_tmp, 'IMAGES')) 288 system_image = os.path.join(input_tmp, 'IMAGES', 'system.img') 289 system_root = os.path.join(input_tmp, "SYSTEM") 290 os.mkdir(system_root) 291 292 # Write the test file that contain three blocks of 'a', 'b', 'c'. 293 with open(os.path.join(system_root, 'abc'), 'w') as f: 294 f.write('a' * 4096 + 'b' * 4096 + 'c' * 4096) 295 raw_file_map = os.path.join(input_tmp, 'IMAGES', 'raw_system.map') 296 self._generate_system_image(system_image, system_root, raw_file_map) 297 298 # Parse the generated file map and manipulate the block ranges of 'abc' to 299 # be 'cba'. 300 file_map_list = {} 301 with open(raw_file_map) as f: 302 for line in f.readlines(): 303 info = line.split() 304 self.assertEqual(2, len(info)) 305 ranges = RangeSet(info[1]) 306 self.assertTrue(ranges.monotonic) 307 blocks = reversed(list(ranges.next_item())) 308 file_map_list[info[0]] = ' '.join([str(block) for block in blocks]) 309 310 # Update the contents of 'abc' to be 'cba'. 311 with open(os.path.join(system_root, 'abc'), 'w') as f: 312 f.write('c' * 4096 + 'b' * 4096 + 'a' * 4096) 313 314 # Update the system.map. 315 with open(os.path.join(input_tmp, 'IMAGES', 'system.map'), 'w') as f: 316 for key in sorted(file_map_list.keys()): 317 f.write('{} {}\n'.format(key, file_map_list[key])) 318 319 # Get the target zip file. 320 input_file = common.MakeTempFile() 321 all_entries = ['SYSTEM/', 'SYSTEM/abc', 'IMAGES/', 322 'IMAGES/system.map', 'IMAGES/system.img'] 323 with zipfile.ZipFile(input_file, 'w', allowZip64=True) as input_zip: 324 for name in all_entries: 325 input_zip.write(os.path.join(input_tmp, name), arcname=name) 326 327 with zipfile.ZipFile(input_file) as input_zip: 328 info_dict = {'extfs_sparse_flag': '-s'} 329 ValidateFileConsistency(input_zip, input_tmp, info_dict) 330 331 @staticmethod 332 def make_build_prop(build_prop): 333 input_tmp = common.MakeTempDir() 334 system_dir = os.path.join(input_tmp, 'SYSTEM') 335 os.makedirs(system_dir) 336 prop_file = os.path.join(system_dir, 'build.prop') 337 with open(prop_file, 'w') as output_file: 338 output_file.write("\n".join(build_prop)) 339 return input_tmp 340 341 def test_checkDuplicateProps_noDuplicate(self): 342 build_prop = [ 343 'ro.odm.build.date.utc=1578430045', 344 'ro.odm.build.fingerprint=' 345 'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys', 346 'ro.product.odm.device=coral', 347 ] 348 input_tmp = ValidateTargetFilesTest.make_build_prop(build_prop) 349 CheckBuildPropDuplicity(input_tmp) 350 351 def test_checkDuplicateProps_withDuplicate(self): 352 build_prop = [ 353 'ro.odm.build.date.utc=1578430045', 354 'ro.odm.build.date.utc=1578430049', 355 'ro.odm.build.fingerprint=' 356 'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys', 357 'ro.product.odm.device=coral', 358 ] 359 input_tmp = ValidateTargetFilesTest.make_build_prop(build_prop) 360 361 self.assertRaises(ValueError, CheckBuildPropDuplicity, input_tmp) 362