blob: 48b563d7b009475db03191987349c6bfe1c71316 [file] [log] [blame]
Tao Baoba557702018-03-10 20:41:16 -08001#
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
Tao Baob4ec6d72018-03-15 23:21:28 -070017"""Unittests for validate_target_files.py."""
Tao Baoba557702018-03-10 20:41:16 -080018
Tao Baoba557702018-03-10 20:41:16 -080019import os
20import os.path
21import shutil
xunchangc0f77ee2019-02-20 15:03:43 -080022import zipfile
Tao Baoba557702018-03-10 20:41:16 -080023
Tao Baoba557702018-03-10 20:41:16 -080024import common
25import test_utils
xunchangc0f77ee2019-02-20 15:03:43 -080026from rangelib import RangeSet
27from validate_target_files import (ValidateVerifiedBootImages,
Kelvin Zhangf2e846f2020-06-29 16:04:51 -040028 ValidateFileConsistency, CheckBuildPropDuplicity)
Tao Bao7549e5e2018-10-03 14:23:59 -070029from verity_utils import CreateVerityImageBuilder
Tao Baoba557702018-03-10 20:41:16 -080030
Tao Bao65b94e92018-10-11 21:57:26 -070031class ValidateTargetFilesTest(test_utils.ReleaseToolsTestCase):
Tao Baoba557702018-03-10 20:41:16 -080032
33 def setUp(self):
34 self.testdata_dir = test_utils.get_testdata_dir()
35
Tao Baoba557702018-03-10 20:41:16 -080036 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]
Tao Bao73dd4f42018-10-04 16:25:33 -070042 proc = common.Run(cmd)
Tao Baoba557702018-03-10 20:41:16 -080043 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]
Tao Bao73dd4f42018-10-04 16:25:33 -070051 proc = common.Run(cmd)
Tao Baoba557702018-03-10 20:41:16 -080052 stdoutdata, _ = proc.communicate()
53 self.assertEqual(
54 0, proc.returncode,
55 "Failed to sign boot image with boot_signer: {}".format(stdoutdata))
56
Tao Bao82490d32019-04-09 00:12:30 -070057 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baoba557702018-03-10 20:41:16 -080058 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
Tao Bao82490d32019-04-09 00:12:30 -070072 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baoba557702018-03-10 20:41:16 -080073 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
Tao Bao82490d32019-04-09 00:12:30 -070089 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baoba557702018-03-10 20:41:16 -080090 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)
Tao Baoa3705452019-06-24 15:33:41 -0700100 last_byte = bytes([255 - ord(last_byte)])
Tao Baoba557702018-03-10 20:41:16 -0800101 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
xunchangc0f77ee2019-02-20 15:03:43 -0800114 def _generate_system_image(self, output_file, system_root=None,
115 file_map=None):
Tao Bao7549e5e2018-10-03 14:23:59 -0700116 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()
Tao Baoba557702018-03-10 20:41:16 -0800126
127 # Use an empty root directory.
xunchangc0f77ee2019-02-20 15:03:43 -0800128 if not system_root:
129 system_root = common.MakeTempDir()
Tianjie Xu57332222018-08-15 16:16:21 -0700130 cmd = ['mkuserimg_mke2fs', '-s', system_root, output_file, 'ext4',
Tao Bao35f4ebc2018-09-27 15:31:11 -0700131 '/system', str(image_size), '-j', '0']
xunchangc0f77ee2019-02-20 15:03:43 -0800132 if file_map:
133 cmd.extend(['-B', file_map])
Tao Bao73dd4f42018-10-04 16:25:33 -0700134 proc = common.Run(cmd)
Tao Baoba557702018-03-10 20:41:16 -0800135 stdoutdata, _ = proc.communicate()
136 self.assertEqual(
137 0, proc.returncode,
Tianjie Xu57332222018-08-15 16:16:21 -0700138 "Failed to create system image with mkuserimg_mke2fs: {}".format(
Tao Baoba557702018-03-10 20:41:16 -0800139 stdoutdata))
140
141 # Append the verity metadata.
Tao Bao7549e5e2018-10-03 14:23:59 -0700142 verity_image_builder.Build(output_file)
Tao Baoba557702018-03-10 20:41:16 -0800143
Tao Bao82490d32019-04-09 00:12:30 -0700144 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baoc9981932019-09-16 12:10:43 -0700145 def test_ValidateVerifiedBootImages_systemRootImage(self):
Tao Baoba557702018-03-10 20:41:16 -0800146 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.
Tao Baoc9981932019-09-16 12:10:43 -0700152 verity_key_mincrypt = os.path.join(input_tmp, 'ROOT', 'verity_key')
Tao Baoba557702018-03-10 20:41:16 -0800153 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 = {
Tao Baoc9981932019-09-16 12:10:43 -0700159 '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 = {
Tao Baoba557702018-03-10 20:41:16 -0800191 '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)
xunchangc0f77ee2019-02-20 15:03:43 -0800198
Tao Bao82490d32019-04-09 00:12:30 -0700199 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baoc9981932019-09-16 12:10:43 -0700200 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()
xunchangc0f77ee2019-02-20 15:03:43 -0800233 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
Tao Bao22632cc2019-10-03 23:12:55 -0700240 # 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.
xunchangc0f77ee2019-02-20 15:03:43 -0800243 with open(os.path.join(system_root, 'a'), 'w') as f:
Tao Bao22632cc2019-10-03 23:12:55 -0700244 f.write('aaa')
xunchangc0f77ee2019-02-20 15:03:43 -0800245 f.write('\0' * 4096 * 3)
246 with open(os.path.join(system_root, 'b'), 'w') as f:
Tao Bao22632cc2019-10-03 23:12:55 -0700247 f.write('bbb')
xunchangc0f77ee2019-02-20 15:03:43 -0800248 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()
Tao Bao22632cc2019-10-03 23:12:55 -0700256 with open(raw_file_map) as f:
xunchangc0f77ee2019-02-20 15:03:43 -0800257 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()):
Tao Bao22632cc2019-10-03 23:12:55 -0700267 line = '{} {}\n'.format(
xunchangc0f77ee2019-02-20 15:03:43 -0800268 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']
Kelvin Zhang928c2342020-09-22 16:15:57 -0400275 with zipfile.ZipFile(input_file, 'w', allowZip64=True) as input_zip:
xunchangc0f77ee2019-02-20 15:03:43 -0800276 for name in all_entries:
277 input_zip.write(os.path.join(input_tmp, name), arcname=name)
278
xunchangc0f77ee2019-02-20 15:03:43 -0800279 # Expect the validation to pass and both files are skipped due to
280 # 'incomplete' block range.
Tao Bao22632cc2019-10-03 23:12:55 -0700281 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']
Kelvin Zhang928c2342020-09-22 16:15:57 -0400324 with zipfile.ZipFile(input_file, 'w', allowZip64=True) as input_zip:
Tao Bao22632cc2019-10-03 23:12:55 -0700325 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)
Kelvin Zhangf2e846f2020-06-29 16:04:51 -0400331
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 ]
Tianjie2e0b8352021-01-12 14:04:58 -0800360 input_tmp = ValidateTargetFilesTest.make_build_prop(build_prop)
Kelvin Zhangf2e846f2020-06-29 16:04:51 -0400361
Tianjie2e0b8352021-01-12 14:04:58 -0800362 self.assertRaises(ValueError, CheckBuildPropDuplicity, input_tmp)