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