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