318 lines
13 KiB
Python
318 lines
13 KiB
Python
# 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')
|