• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2017 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import dbus
6import os
7import shutil
8import subprocess
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    LOAD_COMPONENT = 'LoadComponent'
28    LOAD_COMPONENT_AT_PATH = 'LoadComponentAtPath'
29    BAD_RESULT = ''
30    USER = 'chronos'
31    COMPONENT_NAME = 'TestFlashComponent'
32    CORRUPT_COMPONENT_NAME = 'CorruptTestFlashComponent'
33    CORRUPT_COMPONENT_PATH = '/tmp/CorruptTestFlashComponent'
34    OLD_VERSION = '23.0.0.207'
35    NEW_VERSION = '24.0.0.186'
36
37    def _get_component_version(self, name):
38        args = [dbus.String(name)]
39        return dbus_send.dbus_send(
40            self.BUS_NAME,
41            self.BUS_INTERFACE,
42            self.BUS_PATH,
43            self.GET_COMPONENT_VERSION,
44            user=self.USER,
45            args=args).response
46
47    def _register_component(self, name, version, path):
48        args = [dbus.String(name), dbus.String(version), dbus.String(path)]
49        return dbus_send.dbus_send(
50            self.BUS_NAME,
51            self.BUS_INTERFACE,
52            self.BUS_PATH,
53            self.REGISTER_COMPONENT,
54            timeout_seconds=20,
55            user=self.USER,
56            args=args).response
57
58    def _load_component(self, name):
59        args = [dbus.String(name)]
60        return dbus_send.dbus_send(
61            self.BUS_NAME,
62            self.BUS_INTERFACE,
63            self.BUS_PATH,
64            self.LOAD_COMPONENT,
65            timeout_seconds=20,
66            user=self.USER,
67            args=args).response
68
69    def _load_component_at_path(self, name, path):
70        args = [dbus.String(name), dbus.String(path)]
71        return dbus_send.dbus_send(
72            self.BUS_NAME,
73            self.BUS_INTERFACE,
74            self.BUS_PATH,
75            self.LOAD_COMPONENT_AT_PATH,
76            timeout_seconds=20,
77            user=self.USER,
78            args=args).response
79
80    def _corrupt_and_load_component(self, component, iteration, target, offset):
81        """Registers a valid component and then corrupts it by writing
82        a random byte to the target file at the given offset.
83
84        It then attemps to load the component and returns whether or
85        not that succeeded.
86        @component The path to the component to register.
87        @iteration A prefix to append to the name of the component, so that
88                   multiple registrations do not clash.
89        @target    The name of the file in the component to corrupt.
90        @offset    The offset in the file to corrupt.
91        """
92
93        versioned_name = self.CORRUPT_COMPONENT_NAME + iteration
94        if not self._register_component(versioned_name, self.OLD_VERSION,
95                                        component):
96            raise error.TestError('Failed to register a valid component')
97
98        self._components_to_delete.append(versioned_name)
99
100        corrupt_path = os.path.join('/var/lib/imageloader', versioned_name,
101                                    self.OLD_VERSION)
102        os.system('printf \'\\xa1\' | dd conv=notrunc of=%s bs=1 seek=%s' %
103                  (corrupt_path + '/' + target, offset))
104
105        return self._load_component(versioned_name)
106
107    def initialize(self):
108        self._paths_to_unmount = []
109        self._components_to_delete = []
110
111    def run_once(self, component1=None, component2=None):
112
113        if component1 == None or component2 == None:
114            raise error.TestError('Must supply two versions of '
115                                  'a production signed component.')
116
117        # Make sure there is no version returned at first.
118        if self._get_component_version(self.COMPONENT_NAME) != self.BAD_RESULT:
119            raise error.TestError('There should be no currently '
120                                  'registered component version')
121
122        # Register a component and fetch the version.
123        if not self._register_component(self.COMPONENT_NAME, self.OLD_VERSION,
124                                        component1):
125            raise error.TestError('The component failed to register')
126
127        self._components_to_delete.append(self.COMPONENT_NAME)
128
129        if self._get_component_version(self.COMPONENT_NAME) != '23.0.0.207':
130            raise error.TestError('The component version is incorrect')
131
132        # Make sure the same version cannot be re-registered.
133        if self._register_component(self.COMPONENT_NAME, self.OLD_VERSION,
134                                    component1):
135            raise error.TestError('ImageLoader allowed registration '
136                                  'of duplicate component version')
137
138        # Make sure that ImageLoader matches the reported version to the
139        # manifest.
140        if self._register_component(self.COMPONENT_NAME, self.NEW_VERSION,
141                                    component1):
142            raise error.TestError('ImageLoader allowed registration of a '
143                                  'mismatched component version')
144
145        # Register a newer component and fetch the version.
146        if not self._register_component(self.COMPONENT_NAME, self.NEW_VERSION,
147                                        component2):
148            raise error.TestError('Failed to register updated version')
149
150        if self._get_component_version(self.COMPONENT_NAME) != '24.0.0.186':
151            raise error.TestError('The component version is incorrect')
152
153        # Simulate a rollback.
154        if self._register_component(self.COMPONENT_NAME, self.OLD_VERSION,
155                                    component1):
156            raise error.TestError('ImageLoader allowed a rollback')
157
158        # Test loading a component that lives at an arbitrary path.
159        mnt_path = self._load_component_at_path('FlashLoadedAtPath', component1)
160        if mnt_path == self.BAD_RESULT:
161            raise error.TestError('LoadComponentAtPath failed')
162        self._paths_to_unmount.append(mnt_path)
163
164        known_mount_path = '/run/imageloader/TestFlashComponent_testing'
165        # Now test loading the component on the command line.
166        if subprocess.call([
167                '/usr/sbin/imageloader', '--mount',
168                '--mount_component=TestFlashComponent', '--mount_point=%s' %
169            (known_mount_path)
170        ]) != 0:
171            raise error.TestError('Failed to mount component')
172
173        # If the component is already mounted, it should return the path again.
174        if subprocess.call([
175                '/usr/sbin/imageloader', '--mount',
176                '--mount_component=TestFlashComponent', '--mount_point=%s' %
177            (known_mount_path)
178        ]) != 0:
179            raise error.TestError('Failed to remount component')
180
181        self._paths_to_unmount.append(known_mount_path)
182        if not os.path.exists(
183                os.path.join(known_mount_path, 'libpepflashplayer.so')):
184            raise error.TestError('Flash player file does not exist')
185
186        mount_path = self._load_component(self.COMPONENT_NAME)
187        if mount_path == self.BAD_RESULT:
188            raise error.TestError('Failed to mount component as dbus service.')
189        self._paths_to_unmount.append(mount_path)
190
191        if not os.path.exists(os.path.join(mount_path, 'libpepflashplayer.so')):
192            raise error.TestError('Flash player file does not exist ' +
193                                  'after loading as dbus service.')
194
195        # Now test some corrupt components.
196        shutil.copytree(component1, self.CORRUPT_COMPONENT_PATH)
197        # Corrupt the disk image file in the corrupt component.
198        os.system('printf \'\\xa1\' | dd conv=notrunc of=%s bs=1 seek=1000000' %
199                  (self.CORRUPT_COMPONENT_PATH + '/image.squash'))
200        # Make sure registration fails.
201        if self._register_component(self.CORRUPT_COMPONENT_NAME,
202                                    self.OLD_VERSION,
203                                    self.CORRUPT_COMPONENT_PATH):
204            raise error.TestError('Registered a corrupt component')
205
206        # Now register a valid component, and then corrupt it.
207        mount_path = self._corrupt_and_load_component(component1, '1',
208                                                      'image.squash', '1000000')
209        if mount_path == self.BAD_RESULT:
210            raise error.TestError('Failed to load component with corrupt image')
211        self._paths_to_unmount.append(mount_path)
212
213        corrupt_file = os.path.join(mount_path, 'libpepflashplayer.so')
214        if not os.path.exists(corrupt_file):
215            raise error.TestError('Flash player file does not exist ' +
216                                  'for corrupt image')
217
218        # Reading the files should fail.
219        # This is a critical test. We assume dm-verity works, but by default it
220        # panics the machine and forces a powerwash. For component updates,
221        # ImageLoader should configure the dm-verity table to just return an I/O
222        # error. If this test does not throw an exception at all, ImageLoader
223        # may not be attaching the dm-verity tree correctly.
224        try:
225            with open(corrupt_file, 'rb') as f:
226                byte = f.read(1)
227                while byte != '':
228                    byte = f.read(1)
229        except IOError:
230            pass
231            # Catching an IOError once we read the corrupt block is the expected
232            # behavior.
233        else:
234            raise error.TestError(
235                'Did not receive an I/O error while reading the corrupt image')
236
237        # Modify the signature and make sure the component does not load.
238        if self._corrupt_and_load_component(
239                component1, '2', 'imageloader.sig.1', '50') != self.BAD_RESULT:
240            raise error.TestError('Mounted component with corrupt signature')
241
242        # Modify the manifest and make sure the component does not load.
243        if self._corrupt_and_load_component(component1, '3', 'imageloader.json',
244                                            '1') != self.BAD_RESULT:
245            raise error.TestError('Mounted component with corrupt manifest')
246
247        # Modify the table and make sure the component does not load.
248        if self._corrupt_and_load_component(component1, '4', 'table',
249                                            '1') != self.BAD_RESULT:
250            raise error.TestError('Mounted component with corrupt table')
251
252    def cleanup(self):
253        shutil.rmtree(self.CORRUPT_COMPONENT_PATH, ignore_errors=True)
254        for name in self._components_to_delete:
255            shutil.rmtree(os.path.join(self.STORAGE, name), ignore_errors=True)
256        for path in self._paths_to_unmount:
257            utils.system('umount %s' % (path), ignore_status=True)
258            shutil.rmtree(path, ignore_errors=True)
259