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