• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Lint as: python2, python3
2# Copyright 2017 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import dbus
7import os
8import shutil
9import utils
10
11from autotest_lib.client.common_lib import error
12from autotest_lib.client.bin import test
13from autotest_lib.client.common_lib.cros import dbus_send
14
15
16class platform_ImageLoader(test.test):
17    """Tests the ImageLoader dbus service.
18    """
19
20    version = 1
21    STORAGE = '/var/lib/imageloader'
22    BUS_NAME = 'org.chromium.ImageLoader'
23    BUS_PATH = '/org/chromium/ImageLoader'
24    BUS_INTERFACE = 'org.chromium.ImageLoaderInterface'
25    GET_COMPONENT_VERSION = 'GetComponentVersion'
26    REGISTER_COMPONENT = 'RegisterComponent'
27    REMOVE_COMPONENT = 'RemoveComponent'
28    LOAD_COMPONENT = 'LoadComponent'
29    UNMOUNT_COMPONENT = 'UnmountComponent'
30    LOAD_COMPONENT_AT_PATH = 'LoadComponentAtPath'
31    BAD_RESULT = ''
32    USER = 'chronos'
33    COMPONENT_NAME = 'TestFlashComponent'
34    CORRUPT_COMPONENT_NAME = 'CorruptTestFlashComponent'
35    CORRUPT_COMPONENT_PATH = '/tmp/CorruptTestFlashComponent'
36    OLD_VERSION = '23.0.0.207'
37    NEW_VERSION = '24.0.0.186'
38
39    def _get_component_version(self, name):
40        args = [dbus.String(name)]
41        return dbus_send.dbus_send(
42            self.BUS_NAME,
43            self.BUS_INTERFACE,
44            self.BUS_PATH,
45            self.GET_COMPONENT_VERSION,
46            user=self.USER,
47            args=args).response
48
49    def _register_component(self, name, version, path):
50        args = [dbus.String(name), dbus.String(version), dbus.String(path)]
51        return dbus_send.dbus_send(
52            self.BUS_NAME,
53            self.BUS_INTERFACE,
54            self.BUS_PATH,
55            self.REGISTER_COMPONENT,
56            timeout_seconds=20,
57            user=self.USER,
58            args=args).response
59
60    def _remove_component(self, name):
61        args = [dbus.String(name)]
62        return dbus_send.dbus_send(
63            self.BUS_NAME,
64            self.BUS_INTERFACE,
65            self.BUS_PATH,
66            self.REMOVE_COMPONENT,
67            timeout_seconds=20,
68            user=self.USER,
69            args=args).response
70
71    def _load_component(self, name):
72        args = [dbus.String(name)]
73        return dbus_send.dbus_send(
74            self.BUS_NAME,
75            self.BUS_INTERFACE,
76            self.BUS_PATH,
77            self.LOAD_COMPONENT,
78            timeout_seconds=20,
79            user=self.USER,
80            args=args).response
81
82    def _load_component_at_path(self, name, path):
83        args = [dbus.String(name), dbus.String(path)]
84        return dbus_send.dbus_send(
85            self.BUS_NAME,
86            self.BUS_INTERFACE,
87            self.BUS_PATH,
88            self.LOAD_COMPONENT_AT_PATH,
89            timeout_seconds=20,
90            user=self.USER,
91            args=args).response
92
93    def _unmount_component(self, name):
94        args = [dbus.String(name)]
95        return dbus_send.dbus_send(
96            self.BUS_NAME,
97            self.BUS_INTERFACE,
98            self.BUS_PATH,
99            self.UNMOUNT_COMPONENT,
100            timeout_seconds=20,
101            user=self.USER,
102            args=args).response
103
104    def _corrupt_and_load_component(self, component, iteration, target, offset):
105        """Registers a valid component and then corrupts it by writing
106        a random byte to the target file at the given offset.
107
108        It then attemps to load the component and returns whether or
109        not that succeeded.
110        @component The path to the component to register.
111        @iteration A prefix to append to the name of the component, so that
112                   multiple registrations do not clash.
113        @target    The name of the file in the component to corrupt.
114        @offset    The offset in the file to corrupt.
115        """
116
117        versioned_name = self.CORRUPT_COMPONENT_NAME + iteration
118        if not self._register_component(versioned_name, self.OLD_VERSION,
119                                        component):
120            raise error.TestError('Failed to register a valid component')
121
122        self._components_to_delete.append(versioned_name)
123
124        corrupt_path = os.path.join('/var/lib/imageloader', versioned_name,
125                                    self.OLD_VERSION)
126        os.system('printf \'\\xa1\' | dd conv=notrunc of=%s bs=1 seek=%s' %
127                  (corrupt_path + '/' + target, offset))
128
129        return self._load_component(versioned_name)
130
131    def _get_unmount_all_paths(self):
132        """Returns a set representing all the paths that would be unmounted by
133        imageloader.
134        """
135        return utils.system_output(
136                '/usr/sbin/imageloader --dry_run --unmount_all').splitlines()
137
138    def _test_remove_unmount_component(self, component):
139        component_name = "cros-termina"
140        if not self._register_component(component_name, "10209.0.0",
141                                    component):
142            raise error.TestError('Failed to register a valid component')
143
144        mount_path = self._load_component(component_name)
145        if mount_path == self.BAD_RESULT:
146            raise error.TestError('Failed to mount component as dbus service.')
147
148        if not self._unmount_component(component_name):
149            raise error.TestError(
150                'Failed to unmount component as dbus service.')
151
152        if not self._remove_component(component_name):
153            self._components_to_delete.append(component_name)
154            raise error.TestError('Failed to remove a removable component')
155
156    def initialize(self):
157        """Initialize the test variables."""
158        self._paths_to_unmount = []
159        self._components_to_delete = []
160
161    def run_once(self, component1=None, component2=None, component3=None):
162        """Executes the test cases."""
163
164        if component3 != None:
165            self._test_remove_unmount_component(component3)
166
167        if component1 == None or component2 == None:
168            raise error.TestError('Must supply two versions of '
169                                  'a production signed component.')
170
171        paths_before = self._get_unmount_all_paths()
172
173        # Make sure there is no version returned at first.
174        if self._get_component_version(self.COMPONENT_NAME) != self.BAD_RESULT:
175            raise error.TestError('There should be no currently '
176                                  'registered component version')
177
178        # Register a component and fetch the version.
179        if not self._register_component(self.COMPONENT_NAME, self.OLD_VERSION,
180                                        component1):
181            raise error.TestError('The component failed to register')
182
183        self._components_to_delete.append(self.COMPONENT_NAME)
184
185        if self._get_component_version(self.COMPONENT_NAME) != '23.0.0.207':
186            raise error.TestError('The component version is incorrect')
187
188        # Make sure the same version cannot be re-registered.
189        if self._register_component(self.COMPONENT_NAME, self.OLD_VERSION,
190                                    component1):
191            raise error.TestError('ImageLoader allowed registration '
192                                  'of duplicate component version')
193
194        # Make sure that ImageLoader matches the reported version to the
195        # manifest.
196        if self._register_component(self.COMPONENT_NAME, self.NEW_VERSION,
197                                    component1):
198            raise error.TestError('ImageLoader allowed registration of a '
199                                  'mismatched component version')
200
201        # Register a newer component and fetch the version.
202        if not self._register_component(self.COMPONENT_NAME, self.NEW_VERSION,
203                                        component2):
204            raise error.TestError('Failed to register updated version')
205
206        if self._get_component_version(self.COMPONENT_NAME) != '24.0.0.186':
207            raise error.TestError('The component version is incorrect')
208
209        # Simulate a rollback.
210        if self._register_component(self.COMPONENT_NAME, self.OLD_VERSION,
211                                    component1):
212            raise error.TestError('ImageLoader allowed a rollback')
213
214        # Test loading a component that lives at an arbitrary path.
215        mnt_path = self._load_component_at_path('FlashLoadedAtPath', component1)
216        if mnt_path == self.BAD_RESULT:
217            raise error.TestError('LoadComponentAtPath failed')
218        self._paths_to_unmount.append(mnt_path)
219
220        known_mount_path = '/run/imageloader/TestFlashComponent_testing'
221        # Now test loading the component on the command line.
222        if utils.system('/usr/sbin/imageloader --mount ' +
223                        '--mount_component=TestFlashComponent ' +
224                        '--mount_point=%s' % (known_mount_path)) != 0:
225            raise error.TestError('Failed to mount component')
226
227        # If the component is already mounted, it should return the path again.
228        if utils.system('/usr/sbin/imageloader --mount ' +
229                        '--mount_component=TestFlashComponent ' +
230                        '--mount_point=%s' % (known_mount_path)) != 0:
231            raise error.TestError('Failed to remount component')
232
233        self._paths_to_unmount.append(known_mount_path)
234        if not os.path.exists(
235                os.path.join(known_mount_path, 'libpepflashplayer.so')):
236            raise error.TestError('Flash player file does not exist')
237
238        mount_path = self._load_component(self.COMPONENT_NAME)
239        if mount_path == self.BAD_RESULT:
240            raise error.TestError('Failed to mount component as dbus service.')
241        self._paths_to_unmount.append(mount_path)
242
243        if not os.path.exists(os.path.join(mount_path, 'libpepflashplayer.so')):
244            raise error.TestError('Flash player file does not exist ' +
245                                  'after loading as dbus service.')
246
247        # Now test some corrupt components.
248        shutil.copytree(component1, self.CORRUPT_COMPONENT_PATH)
249        # Corrupt the disk image file in the corrupt component.
250        os.system('printf \'\\xa1\' | dd conv=notrunc of=%s bs=1 seek=1000000' %
251                  (self.CORRUPT_COMPONENT_PATH + '/image.squash'))
252        # Make sure registration fails.
253        if self._register_component(self.CORRUPT_COMPONENT_NAME,
254                                    self.OLD_VERSION,
255                                    self.CORRUPT_COMPONENT_PATH):
256            raise error.TestError('Registered a corrupt component')
257
258        # Now register a valid component, and then corrupt it.
259        mount_path = self._corrupt_and_load_component(component1, '1',
260                                                      'image.squash', '1000000')
261        if mount_path == self.BAD_RESULT:
262            raise error.TestError('Failed to load component with corrupt image')
263        self._paths_to_unmount.append(mount_path)
264
265        corrupt_file = os.path.join(mount_path, 'libpepflashplayer.so')
266        if not os.path.exists(corrupt_file):
267            raise error.TestError('Flash player file does not exist ' +
268                                  'for corrupt image')
269
270        # Reading the files should fail.
271        # This is a critical test. We assume dm-verity works, but by default it
272        # panics the machine and forces a powerwash. For component updates,
273        # ImageLoader should configure the dm-verity table to just return an I/O
274        # error. If this test does not throw an exception at all, ImageLoader
275        # may not be attaching the dm-verity tree correctly.
276        try:
277            with open(corrupt_file, 'rb') as f:
278                byte = f.read(1)
279                while byte != '':
280                    byte = f.read(1)
281        except IOError:
282            pass
283            # Catching an IOError once we read the corrupt block is the expected
284            # behavior.
285        else:
286            raise error.TestError(
287                'Did not receive an I/O error while reading the corrupt image')
288
289        # Modify the signature and make sure the component does not load.
290        if self._corrupt_and_load_component(
291                component1, '2', 'imageloader.sig.1', '50') != self.BAD_RESULT:
292            raise error.TestError('Mounted component with corrupt signature')
293
294        # Modify the manifest and make sure the component does not load.
295        if self._corrupt_and_load_component(component1, '3', 'imageloader.json',
296                                            '1') != self.BAD_RESULT:
297            raise error.TestError('Mounted component with corrupt manifest')
298
299        # Modify the table and make sure the component does not load.
300        if self._corrupt_and_load_component(component1, '4', 'table',
301                                            '1') != self.BAD_RESULT:
302            raise error.TestError('Mounted component with corrupt table')
303
304        # Determine which mount points were added during the test and verify
305        # that they match the expected mount points.
306        paths_after = self._get_unmount_all_paths()
307        measured_unmount = set(paths_after) - set(paths_before);
308        expected_unmount = set(self._paths_to_unmount);
309        if measured_unmount != expected_unmount:
310            raise error.TestError('Discrepency between --unmount_all and '
311                                  'paths_to_unmount "%s" and "%s"' %
312                                  (measured_unmount, expected_unmount))
313
314        # Clean up each mount point created by the test to verify the
315        # functionality of the --unmount flag.
316        for path in expected_unmount:
317            if utils.system('/usr/sbin/imageloader --unmount --mount_point=%s'
318                            % (path,)) != 0:
319                raise error.TestError('Failed to unmount component')
320
321        # Verify that the mount points were indeed cleaned up.
322        paths_cleanup = self._get_unmount_all_paths()
323        if paths_cleanup != paths_before:
324            raise error.TestError('--unmount failed.')
325
326    def cleanup(self):
327        """Cleans up the files and mounts created by the test."""
328        shutil.rmtree(self.CORRUPT_COMPONENT_PATH, ignore_errors=True)
329        for name in self._components_to_delete:
330            shutil.rmtree(os.path.join(self.STORAGE, name), ignore_errors=True)
331        for path in self._paths_to_unmount:
332            utils.system('umount %s' % (path), ignore_status=True)
333            shutil.rmtree(path, ignore_errors=True)
334