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