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