| # Copyright 2018 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import os |
| import re |
| import logging |
| |
| from autotest_lib.server.cros.faft.fingerprint_test import FingerprintTest |
| from autotest_lib.client.common_lib import error |
| |
| |
| class firmware_Fingerprint(FingerprintTest): |
| """ |
| Common class for running fingerprint firmware tests. Initializes the |
| firmware to a known state and then runs the test executable with |
| specified arguments on the DUT. |
| """ |
| version = 1 |
| |
| def run_once(self, test_exe, test_exe_args=None, |
| use_dev_signed_fw=False, |
| enable_hardware_write_protect=True, |
| enable_software_write_protect=True, |
| force_firmware_flashing=False, |
| init_entropy=True): |
| """Run the test.""" |
| test_dir = os.path.join(self.bindir, 'tests/') |
| logging.info('test_dir: %s', test_dir) |
| |
| # Initialize DUT state and set up tmp working directory on device. |
| self.setup_test( |
| test_dir, use_dev_signed_fw, enable_hardware_write_protect, |
| enable_software_write_protect, force_firmware_flashing, |
| init_entropy) |
| |
| self._test_exe = test_exe |
| |
| # Convert the arguments (test image names) to the actual filenames of |
| # the test images. |
| image_args = [] |
| if test_exe_args: |
| for arg in test_exe_args: |
| image_args.append(getattr(self, arg)) |
| self._test_exe_args = image_args |
| |
| if self.get_host_board() == 'zork': |
| # TODO(b/170770251): Move the rdp1 and rdp0 tests to separate files |
| # |
| # Zork's RDP1 and RDP0 tests requires an AP reboot, so do it in |
| # this class |
| if self._test_exe == 'rdp1.sh': |
| self.test_rdp1() |
| elif self._test_exe == 'rdp0.sh': |
| self.test_rdp0() |
| else: |
| logging.info('Running test: %s', self._test_exe) |
| self.run_test(self._test_exe, *self._test_exe_args) |
| |
| def test_rdp1(self): |
| """ |
| Validate initial state for the RDP1 test. The test tries to read from |
| flash while maintaining RDP level 1. Then it tries to read from flash |
| while changing RDP level to 0. |
| """ |
| if self.get_fp_board() == 'bloonchipper': |
| _HW_WP_OFF_AND_SW_WP_ON = ( |
| 'Flash protect flags: 0x00000407 ro_at_boot ro_now rollback_now all_now\n' |
| 'Valid flags: 0x0000003f wp_gpio_asserted ro_at_boot ro_now all_now STUCK INCONSISTENT\n' |
| 'Writable flags: 0x00000000\n') |
| else: |
| _HW_WP_OFF_AND_SW_WP_ON = ( |
| 'Flash protect flags: 0x00000003 ro_at_boot ro_now\n' |
| 'Valid flags: 0x0000003f wp_gpio_asserted ro_at_boot ro_now all_now STUCK INCONSISTENT\n' |
| 'Writable flags: 0x00000000\n') |
| |
| logging.info('Running test to validate RDP level 1') |
| original_fw_file = self._test_exe_args[0] |
| self.check_file_exists(original_fw_file) |
| |
| logging.info('Making sure hardware write protect is DISABLED and ' |
| 'software write protect is ENABLED') |
| flashprotect_result = self._run_ectool_cmd('flashprotect') |
| if flashprotect_result.stdout != _HW_WP_OFF_AND_SW_WP_ON: |
| raise error.TestFail('Incorrect flashprotect state') |
| |
| logging.info('Validating initial state') |
| # TODO(yichengli): Check that we are running MP-signed RO and RW by |
| # checking the key id. |
| if self.get_running_firmware_type() != self._FIRMWARE_TYPE_RW: |
| raise error.TestFail('Not running RW copy of firmware') |
| if not self.is_rollback_set_to_initial_val(): |
| raise error.TestFail('Rollback is not set to initial value') |
| |
| self.test_rdp1_without_modifying_rdp_level() |
| self.test_rdp1_while_setting_rdp_level_0() |
| |
| def test_rdp0(self): |
| """ |
| Validate initial state for the RDP0 test. The test tries to read from |
| flash while maintaining RDP level 0. Then it tries to read from flash |
| while setting RDP level to 0. |
| """ |
| _HW_AND_SW_WP_OFF = ( |
| 'Flash protect flags: 0x00000000\n' |
| 'Valid flags: 0x0000003f wp_gpio_asserted ro_at_boot ro_now all_now STUCK INCONSISTENT\n' |
| 'Writable flags: 0x00000001 ro_at_boot\n') |
| |
| logging.info('Running test to validate RDP level 0') |
| original_fw_file = self._test_exe_args[0] |
| self.check_file_exists(original_fw_file) |
| |
| logging.info('Making sure all write protect is disabled') |
| flashprotect_result = self._run_ectool_cmd('flashprotect') |
| if flashprotect_result.stdout != _HW_AND_SW_WP_OFF: |
| raise error.TestFail('Incorrect flashprotect state') |
| |
| logging.info('Validating initial state') |
| # TODO(yichengli): Check that we are running MP-signed RO and RW by |
| # checking the key id. |
| if self.get_running_firmware_type() != self._FIRMWARE_TYPE_RW: |
| raise error.TestFail('Not running RW copy of firmware') |
| if not self.is_rollback_unset(): |
| raise error.TestFail('Rollback should be unset.') |
| |
| self.check_firmware_is_functional() |
| |
| self.test_rdp0_without_modifying_rdp_level() |
| self.test_rdp0_while_setting_rdp_level_0() |
| |
| def test_rdp1_without_modifying_rdp_level(self): |
| """ |
| Given: |
| * Hardware write protect is disabled |
| (so we can use bootloader to read and change RDP level) |
| * Software write protect is enabled |
| * RDP is at level 1 |
| |
| Then: |
| * Reading from flash without changing the RDP level should fail |
| (and we should not have read any bytes from flash). |
| * The firmware should still be functional because mass erase is NOT |
| triggered since we are NOT changing the RDP level. |
| """ |
| logging.info('Reading firmware without modifying RDP level') |
| |
| # This should fail and the file should be empty |
| file_read_from_flash = os.path.join(self._dut_working_dir, |
| 'test_keep_rdp.bin') |
| cmd = 'flash_fp_mcu --read --noremove_flash_read_protect %s' % file_read_from_flash |
| result = self.run_cmd(cmd) |
| if result.exit_status == 0: |
| raise error.TestFail('Should not be able to read from flash') |
| |
| logging.info('Checking file_read_from_flash is empty') |
| if self.get_file_size(file_read_from_flash) != 0: |
| raise error.TestFail('File read from flash is not empty') |
| |
| # On zork, an AP reboot is needed after using flash_fp_mcu. |
| if self.get_host_board() == 'zork': |
| self.host.reboot() |
| |
| self.check_firmware_is_functional() |
| |
| def test_rdp1_while_setting_rdp_level_0(self): |
| """ |
| Given: |
| * Hardware write protect is disabled |
| (so we can use bootloader to read and change RDP level) |
| * Software write protect is enabled |
| * RDP is at level 1 |
| |
| Then: |
| * Setting the RDP level to 0 (after being at level 1) should trigger |
| a mass erase. |
| * A mass erase sets all flash bytes to 0xFF, so all bytes read from flash |
| should have that value. |
| * Since the flash was mass erased, the firmware should no longer function. |
| """ |
| logging.info('Reading firmware after setting RDP to level 0') |
| |
| # This command partially fails (and returns an error) because it causes the |
| # flash to be mass erased, but we should still have a file with the contents |
| # that we can compare against. |
| |
| file_read_from_flash = os.path.join(self._dut_working_dir, |
| 'test_change_rdp.bin') |
| cmd = 'flash_fp_mcu --read %s' % file_read_from_flash |
| self.run_cmd(cmd) |
| |
| logging.info( |
| 'Checking that value read is made up entirely of OxFF bytes') |
| original_fw_file = self._test_exe_args[0] |
| if self.get_file_size(original_fw_file) != self.get_file_size( |
| file_read_from_flash): |
| raise error.TestFail( |
| 'Flash read output size doesn\'t match original fw size') |
| self.check_file_contains_all_0xFF_bytes(file_read_from_flash) |
| |
| # On zork, an AP reboot is needed after using flash_fp_mcu. |
| if self.get_host_board() == 'zork': |
| self.host.reboot() |
| |
| logging.info('Checking that firmware is non-functional') |
| result = self._run_ectool_cmd('version') |
| if result.exit_status == 0: |
| raise error.TestFail( |
| 'Firmware should not be responding to commands') |
| |
| def test_rdp0_without_modifying_rdp_level(self): |
| """ |
| Given: |
| * Hardware write protect is disabled |
| * Software write protect is disabled |
| * RDP is at level 0 |
| |
| Then: |
| * Reading from flash without changing the RDP level should succeed |
| (we're already at level 0). Thus we should be able to read the |
| entire firmware out of flash and it should exactly match the |
| firmware that we flashed for testing. |
| """ |
| logging.info('Reading firmware without modifying RDP level') |
| |
| file_read_from_flash = os.path.join(self._dut_working_dir, |
| 'test_keep_rdp.bin') |
| cmd = 'flash_fp_mcu --read --noremove_flash_read_protect %s' % file_read_from_flash |
| result = self.run_cmd(cmd) |
| if result.exit_status != 0: |
| raise error.TestFail('Failed to read from flash') |
| |
| logging.info('Checking that value read matches the flashed version') |
| original_fw_file = self._test_exe_args[0] |
| if not self.files_match(file_read_from_flash, original_fw_file): |
| raise error.TestFail( |
| 'File read from flash does not match original fw file') |
| |
| # On zork, an AP reboot is needed after using flash_fp_mcu. |
| if self.get_host_board() == 'zork': |
| self.host.reboot() |
| |
| self.check_firmware_is_functional() |
| |
| def test_rdp0_while_setting_rdp_level_0(self): |
| """ |
| Given: |
| * Hardware write protect is disabled |
| * Software write protect is disabled |
| * RDP is at level 0 |
| |
| Then: |
| * Changing the RDP level to 0 should have no effect |
| (we're already at level 0). Thus we should be able to read the |
| entire firmware out of flash and it should exactly match the |
| firmware that we flashed for testing. |
| """ |
| logging.info('Reading firmware while setting RDP to level 0') |
| |
| file_read_from_flash = os.path.join(self._dut_working_dir, |
| 'test_change_rdp.bin') |
| cmd = 'flash_fp_mcu --read %s' % file_read_from_flash |
| result = self.run_cmd(cmd) |
| if result.exit_status != 0: |
| raise error.TestFail('Failed to read from flash') |
| |
| logging.info('Checking that value read matches the flashed version') |
| original_fw_file = self._test_exe_args[0] |
| if not self.files_match(file_read_from_flash, original_fw_file): |
| raise error.TestFail( |
| 'File read from flash does not match original fw file') |
| |
| # On zork, an AP reboot is needed after using flash_fp_mcu. |
| if self.get_host_board() == 'zork': |
| self.host.reboot() |
| |
| self.check_firmware_is_functional() |
| |
| def check_file_exists(self, filename): |
| """Checks that |filename| exists on DUT. Fails the test otherwise.""" |
| if not self.host.is_file_exists(filename): |
| raise error.TestFail('Cannot find file: %s' % filename) |
| |
| def get_file_size(self, filename): |
| """Returns the size of |filename| on DUT. Fails the test on error.""" |
| cmd = 'stat --printf %%s %s' % filename |
| result = self.run_cmd(cmd) |
| if result.exit_status != 0 or not result.stdout.isdigit(): |
| raise error.TestFail('Cannot get the size of file: %s' % filename) |
| return int(result.stdout) |
| |
| def files_match(self, filename1, filename2): |
| """Returns True if two files are identical, False otherwise.""" |
| cmd = 'cmp %s %s' % (filename1, filename2) |
| return self.run_cmd(cmd).exit_status == 0 |
| |
| def check_file_contains_all_0xFF_bytes(self, file_to_check): |
| """ |
| Checks that |file_to_check| is made of only 0xFF bytes. |
| Fails the test otherwise. |
| """ |
| regex = '0000000 ffff ffff ffff ffff ffff ffff ffff ffff\n\*\n[0-9]+\n$' |
| cmd = 'hexdump %s' % file_to_check |
| result = self.run_cmd(cmd) |
| if not re.match(regex, result.stdout): |
| raise error.TestFail('%s does not contain all 0xFF bytes' % |
| file_to_check) |
| |
| def check_firmware_is_functional(self): |
| """ |
| Returns true if AP can talk to FPMCU firmware. Fails the test otherwise |
| """ |
| logging.info('Checking that firmware is functional') |
| # Catch exception to show better error message. |
| try: |
| self.get_running_firmware_type() |
| except error.TestFail: |
| raise error.TestFail('Firmware is not functional') |