• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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