1# Copyright 2018 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 os 6import re 7import logging 8 9from autotest_lib.server.cros.faft.fingerprint_test import FingerprintTest 10from autotest_lib.client.common_lib import error 11 12 13class firmware_Fingerprint(FingerprintTest): 14 """ 15 Common class for running fingerprint firmware tests. Initializes the 16 firmware to a known state and then runs the test executable with 17 specified arguments on the DUT. 18 """ 19 version = 1 20 21 def run_once(self, test_exe, test_exe_args=None, 22 use_dev_signed_fw=False, 23 enable_hardware_write_protect=True, 24 enable_software_write_protect=True, 25 force_firmware_flashing=False, 26 init_entropy=True): 27 """Run the test.""" 28 test_dir = os.path.join(self.bindir, 'tests/') 29 logging.info('test_dir: %s', test_dir) 30 31 # Initialize DUT state and set up tmp working directory on device. 32 self.setup_test( 33 test_dir, use_dev_signed_fw, enable_hardware_write_protect, 34 enable_software_write_protect, force_firmware_flashing, 35 init_entropy) 36 37 # Check if FPMCU firmware needs to be re-flashed during cleanup 38 self._need_fw_restore = True 39 self._test_exe = test_exe 40 41 # Convert the arguments (test image names) to the actual filenames of 42 # the test images. 43 image_args = [] 44 if test_exe_args: 45 for arg in test_exe_args: 46 image_args.append(getattr(self, arg)) 47 self._test_exe_args = image_args 48 49 if self.is_uart_device(): 50 # TODO(b/170770251): Move the rdp1 and rdp0 tests to separate files 51 # 52 # On devices with UART, RDP1 and RDP0 tests requires an AP reboot. 53 if self._test_exe == 'rdp1.sh': 54 self.test_rdp1() 55 elif self._test_exe == 'rdp0.sh': 56 self.test_rdp0() 57 else: 58 logging.info('Running test: %s', self._test_exe) 59 self.run_test(self._test_exe, *self._test_exe_args) 60 61 def test_rdp1(self): 62 """ 63 Validate initial state for the RDP1 test. The test tries to read from 64 flash while maintaining RDP level 1. Then it tries to read from flash 65 while changing RDP level to 0. 66 """ 67 if self.get_fp_board() == 'bloonchipper': 68 _HW_WP_OFF_AND_SW_WP_ON = ( 69 'Flash protect flags: 0x00000407 ro_at_boot ro_now rollback_now all_now\n' 70 'Valid flags: 0x0000083f wp_gpio_asserted ro_at_boot ro_now all_now STUCK INCONSISTENT UNKNOWN_ERROR\n' 71 'Writable flags: 0x00000000\n') 72 else: 73 _HW_WP_OFF_AND_SW_WP_ON = ( 74 'Flash protect flags: 0x00000003 ro_at_boot ro_now\n' 75 'Valid flags: 0x0000083f wp_gpio_asserted ro_at_boot ro_now all_now STUCK INCONSISTENT UNKNOWN_ERROR\n' 76 'Writable flags: 0x00000000\n') 77 78 logging.info('Running test to validate RDP level 1') 79 original_fw_file = self._test_exe_args[0] 80 self.check_file_exists(original_fw_file) 81 82 logging.info('Making sure hardware write protect is DISABLED and ' 83 'software write protect is ENABLED') 84 flashprotect_result = self._run_ectool_cmd('flashprotect') 85 if flashprotect_result.stdout != _HW_WP_OFF_AND_SW_WP_ON: 86 raise error.TestFail('Incorrect flashprotect state') 87 88 logging.info('Validating initial state') 89 # TODO(yichengli): Check that we are running MP-signed RO and RW by 90 # checking the key id. 91 if self.get_running_firmware_type() != self._FIRMWARE_TYPE_RW: 92 raise error.TestFail('Not running RW copy of firmware') 93 if not self.is_rollback_set_to_initial_val(): 94 raise error.TestFail('Rollback is not set to initial value') 95 96 self.test_rdp1_without_modifying_rdp_level() 97 self.test_rdp1_while_setting_rdp_level_0() 98 99 def test_rdp0(self): 100 """ 101 Validate initial state for the RDP0 test. The test tries to read from 102 flash while maintaining RDP level 0. Then it tries to read from flash 103 while setting RDP level to 0. 104 """ 105 _HW_AND_SW_WP_OFF = ( 106 'Flash protect flags: 0x00000000\n' 107 'Valid flags: 0x0000083f wp_gpio_asserted ro_at_boot ro_now all_now STUCK INCONSISTENT UNKNOWN_ERROR\n' 108 'Writable flags: 0x00000001 ro_at_boot\n') 109 110 logging.info('Running test to validate RDP level 0') 111 original_fw_file = self._test_exe_args[0] 112 self.check_file_exists(original_fw_file) 113 114 logging.info('Making sure all write protect is disabled') 115 flashprotect_result = self._run_ectool_cmd('flashprotect') 116 if flashprotect_result.stdout != _HW_AND_SW_WP_OFF: 117 raise error.TestFail('Incorrect flashprotect state') 118 119 logging.info('Validating initial state') 120 # TODO(yichengli): Check that we are running MP-signed RO and RW by 121 # checking the key id. 122 if self.get_running_firmware_type() != self._FIRMWARE_TYPE_RW: 123 raise error.TestFail('Not running RW copy of firmware') 124 if not self.is_rollback_unset(): 125 raise error.TestFail('Rollback should be unset.') 126 127 self.check_firmware_is_functional() 128 129 self.test_rdp0_without_modifying_rdp_level() 130 self.test_rdp0_while_setting_rdp_level_0() 131 132 def test_rdp1_without_modifying_rdp_level(self): 133 """ 134 Given: 135 * Hardware write protect is disabled 136 (so we can use bootloader to read and change RDP level) 137 * Software write protect is enabled 138 * RDP is at level 1 139 140 Then: 141 * Reading from flash without changing the RDP level should fail 142 (and we should not have read any bytes from flash). 143 * The firmware should still be functional because mass erase is NOT 144 triggered since we are NOT changing the RDP level. 145 """ 146 logging.info('Reading firmware without modifying RDP level') 147 148 # This should fail and the file should be empty 149 file_read_from_flash = os.path.join(self._dut_working_dir, 150 'test_keep_rdp.bin') 151 cmd = 'flash_fp_mcu --noservices --read' + \ 152 ' --noremove_flash_read_protect %s' % file_read_from_flash 153 result = self.run_cmd(cmd) 154 if result.exit_status == 0: 155 raise error.TestFail('Should not be able to read from flash') 156 157 logging.info('Checking file_read_from_flash is empty') 158 if self.get_file_size(file_read_from_flash) != 0: 159 raise error.TestFail('File read from flash is not empty') 160 161 # On devices with UART, an AP reboot is needed after using flash_fp_mcu. 162 if self.is_uart_device(): 163 self.host.reboot() 164 165 self.check_firmware_is_functional() 166 167 def test_rdp1_while_setting_rdp_level_0(self): 168 """ 169 Given: 170 * Hardware write protect is disabled 171 (so we can use bootloader to read and change RDP level) 172 * Software write protect is enabled 173 * RDP is at level 1 174 175 Then: 176 * Setting the RDP level to 0 (after being at level 1) should trigger 177 a mass erase. 178 * A mass erase sets all flash bytes to 0xFF, so all bytes read from flash 179 should have that value. 180 * Since the flash was mass erased, the firmware should no longer function. 181 """ 182 logging.info('Reading firmware after setting RDP to level 0') 183 184 # This command partially fails (and returns an error) because it causes the 185 # flash to be mass erased, but we should still have a file with the contents 186 # that we can compare against. 187 188 file_read_from_flash = os.path.join(self._dut_working_dir, 189 'test_change_rdp.bin') 190 cmd = 'flash_fp_mcu --noservices --read %s' % file_read_from_flash 191 self.run_cmd(cmd) 192 193 logging.info( 194 'Checking that value read is made up entirely of OxFF bytes') 195 original_fw_file = self._test_exe_args[0] 196 if self.get_file_size(original_fw_file) != self.get_file_size( 197 file_read_from_flash): 198 raise error.TestFail( 199 'Flash read output size doesn\'t match original fw size') 200 self.check_file_contains_all_0xFF_bytes(file_read_from_flash) 201 202 # On devices with UART, an AP reboot is needed after using flash_fp_mcu. 203 if self.is_uart_device(): 204 self.host.reboot() 205 206 logging.info('Checking that firmware is non-functional') 207 result = self._run_ectool_cmd('version') 208 if result.exit_status == 0: 209 raise error.TestFail( 210 'Firmware should not be responding to commands') 211 212 def test_rdp0_without_modifying_rdp_level(self): 213 """ 214 Given: 215 * Hardware write protect is disabled 216 * Software write protect is disabled 217 * RDP is at level 0 218 219 Then: 220 * Reading from flash without changing the RDP level should succeed 221 (we're already at level 0). Thus we should be able to read the 222 entire firmware out of flash and it should exactly match the 223 firmware that we flashed for testing. 224 """ 225 logging.info('Reading firmware without modifying RDP level') 226 227 file_read_from_flash = os.path.join(self._dut_working_dir, 228 'test_keep_rdp.bin') 229 cmd = 'flash_fp_mcu --noservices --read' + \ 230 ' --noremove_flash_read_protect %s' % file_read_from_flash 231 result = self.run_cmd(cmd) 232 if result.exit_status != 0: 233 raise error.TestFail('Failed to read from flash') 234 235 logging.info('Checking that value read matches the flashed version') 236 original_fw_file = self._test_exe_args[0] 237 if not self.files_match(file_read_from_flash, original_fw_file): 238 raise error.TestFail( 239 'File read from flash does not match original fw file') 240 241 # On devices with UART, an AP reboot is needed after using flash_fp_mcu. 242 if self.is_uart_device(): 243 self.host.reboot() 244 245 self.check_firmware_is_functional() 246 247 def test_rdp0_while_setting_rdp_level_0(self): 248 """ 249 Given: 250 * Hardware write protect is disabled 251 * Software write protect is disabled 252 * RDP is at level 0 253 254 Then: 255 * Changing the RDP level to 0 should have no effect 256 (we're already at level 0). Thus we should be able to read the 257 entire firmware out of flash and it should exactly match the 258 firmware that we flashed for testing. 259 """ 260 logging.info('Reading firmware while setting RDP to level 0') 261 262 file_read_from_flash = os.path.join(self._dut_working_dir, 263 'test_change_rdp.bin') 264 cmd = 'flash_fp_mcu --noservices --read %s' % file_read_from_flash 265 result = self.run_cmd(cmd) 266 if result.exit_status != 0: 267 raise error.TestFail('Failed to read from flash') 268 269 logging.info('Checking that value read matches the flashed version') 270 original_fw_file = self._test_exe_args[0] 271 if not self.files_match(file_read_from_flash, original_fw_file): 272 raise error.TestFail( 273 'File read from flash does not match original fw file') 274 275 # On devices with UART, an AP reboot is needed after using flash_fp_mcu. 276 if self.is_uart_device(): 277 self.host.reboot() 278 279 self.check_firmware_is_functional() 280 281 def check_file_exists(self, filename): 282 """Checks that |filename| exists on DUT. Fails the test otherwise.""" 283 if not self.host.is_file_exists(filename): 284 raise error.TestFail('Cannot find file: %s' % filename) 285 286 def get_file_size(self, filename): 287 """Returns the size of |filename| on DUT. Fails the test on error.""" 288 cmd = 'stat --printf %%s %s' % filename 289 result = self.run_cmd(cmd) 290 if result.exit_status != 0 or not result.stdout.isdigit(): 291 raise error.TestFail('Cannot get the size of file: %s' % filename) 292 return int(result.stdout) 293 294 def files_match(self, filename1, filename2): 295 """Returns True if two files are identical, False otherwise.""" 296 cmd = 'cmp %s %s' % (filename1, filename2) 297 return self.run_cmd(cmd).exit_status == 0 298 299 def check_file_contains_all_0xFF_bytes(self, file_to_check): 300 """ 301 Checks that |file_to_check| is made of only 0xFF bytes. 302 Fails the test otherwise. 303 """ 304 regex = '0000000 ffff ffff ffff ffff ffff ffff ffff ffff\n\*\n[0-9]+\n$' 305 cmd = 'hexdump %s' % file_to_check 306 result = self.run_cmd(cmd) 307 if not re.match(regex, result.stdout): 308 raise error.TestFail('%s does not contain all 0xFF bytes' % 309 file_to_check) 310 311 def check_firmware_is_functional(self): 312 """ 313 Returns true if AP can talk to FPMCU firmware. Fails the test otherwise 314 """ 315 logging.info('Checking that firmware is functional') 316 # Catch exception to show better error message. 317 try: 318 self.get_running_firmware_type() 319 except error.TestFail: 320 raise error.TestFail('Firmware is not functional') 321