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