xref: /aosp_15_r20/external/autotest/server/site_tests/firmware_Fingerprint/firmware_Fingerprint.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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