1# Copyright (c) 2016 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 logging
6
7from autotest_lib.client.bin import utils
8from autotest_lib.client.common_lib import error
9from autotest_lib.client.common_lib.utils import crc8
10from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
11
12
13class firmware_TPMNotCorruptedDevMode(FirmwareTest):
14    """
15    Checks the kernel anti-rollback info stored in the TPM NVRAM, and then boots
16    to USB and checks the firmware version and kernel version via crossystem for
17    corruption.
18
19    This test requires a USB disk plugged-in, which contains a ChromeOS test
20    image (built by "build_image test").
21    """
22    version = 1
23    NEEDS_SERVO_USB = True
24
25    # The "what" of KERNEL_ANTIROLLBACK_SPACE_BYTES, KERNEL_NV_INDEX, and
26    # TPM_NVRAM_EXPECTED_VALUES can be understood from
27    # platform/vboot_reference/firmware/lib/include/rollback_index.h.
28    KERNEL_ANTIROLLBACK_SPACE_BYTES = 0xd
29    KERNEL_ANTIROLLBACK_SPACE_BYTES_v10 = 0x28
30    KERNEL_NV_INDEX = 0x1008
31    # TODO(kmshelton): This test intends to check whether the kernel version
32    # stored in the TPM's NVRAM is corrupted during a transition from normal
33    # mode to dev mode (according to it's objective in an internal tool named
34    # test tracker).  Figure out why a specific kernel version is checked for,
35    # and take a look at not requiring a specific kernel version. Examine
36    # transitioning to crossystem for the normal mode read of the kernel
37    # version (e.g. look at things like whether a successful exit code from
38    # a crossystem reading of the kernel version can establish whether the
39    # kernel version starts in a valid state).
40    TPM_NVRAM_EXPECTED_VALUES = set([
41            '1 4c 57 52 47 1 0 1 0 0 0 0 0', '2 4c 57 52 47 1 0 1 0 0 0 0 55',
42            '2 4c 57 52 47 0 0 0 0 0 0 0 e8'
43    ])
44
45    # TPM kernel data format version 1.0
46    #   byte[0]: version. bit[7:4]=Major, bit[3:0]=Minor.
47    #   byte[1]: byte size. 0x28, 40(0x28) bytes
48    #   byte[2]: crc8
49    #   byte[3]: flag
50    #   byte[7-4]: kernel version. 0x00010001
51    #   byte[39:8]: ECFW hash in sha256.
52    TPM_NVRAM_V10_HEADER = '10 28 '
53    TPM_NVRAM_V10_CRC_OFFSET = 2
54
55    def initialize(self, host, cmdline_args, ec_wp=None):
56        """Initialize the test"""
57        dict_args = utils.args_to_dict(cmdline_args)
58        super(firmware_TPMNotCorruptedDevMode, self).initialize(
59                host, cmdline_args, ec_wp=ec_wp)
60
61        self.switcher.setup_mode('dev')
62        # Use the USB key for Ctrl-U dev boot, not recovery.
63        self.setup_usbkey(usbkey=True, host=False, used_for_recovery=False)
64
65        self.original_dev_boot_usb = self.faft_client.system.get_dev_boot_usb()
66        logging.info('Original dev_boot_usb value: %s',
67                     str(self.original_dev_boot_usb))
68
69    def cleanup(self):
70        """Cleanup the test"""
71        try:
72            self.ensure_dev_internal_boot(self.original_dev_boot_usb)
73        except Exception as e:
74            logging.error("Caught exception: %s", str(e))
75        super(firmware_TPMNotCorruptedDevMode, self).cleanup()
76
77    def ensure_usb_device_boot(self):
78        """Ensure USB device boot and if not reboot into USB."""
79        if not self.faft_client.system.is_removable_device_boot():
80            logging.info('Reboot into USB...')
81            self.faft_client.system.set_dev_boot_usb(1)
82            self.switcher.simple_reboot()
83            self.switcher.bypass_dev_boot_usb()
84            self.switcher.wait_for_client()
85
86        self.check_state((self.checkers.dev_boot_usb_checker, (True, True),
87                          'Device not booted from USB image properly.'))
88
89    def read_tmpc(self):
90        """First checks if internal device boot and if not reboots into it.
91        Then stops the TPM daemon, reads the anti-rollback kernel version data
92        and compares it to expected values.
93        """
94        self.ensure_dev_internal_boot(self.original_dev_boot_usb)
95        logging.info('Reading kernel anti-rollback data from the TPM.')
96        self.faft_client.tpm.stop_daemon()
97        kernel_rollback_space = (
98                self.faft_client.system.run_shell_command_get_output(
99                        'tpmc read %s %s' %
100                        (hex(self.KERNEL_NV_INDEX),
101                         hex(self.KERNEL_ANTIROLLBACK_SPACE_BYTES))))
102        self.faft_client.tpm.restart_daemon()
103
104        logging.info('===== TPMC OUTPUT: %s =====', kernel_rollback_space)
105        if not self.check_tpmc(kernel_rollback_space):
106            raise error.TestFail(
107                    'Kernel anti-rollback info looks unexpected. Actual: %s '
108                    'Expected one of: %s or r%r' %
109                    (kernel_rollback_space, self.TPM_NVRAM_EXPECTED_VALUES,
110                     self.TPM_NVRAM_V10_REGEXP))
111
112    def read_kern_fw_ver(self):
113        """First ensures that we are booted on a USB device. Then checks the
114        firmware and kernel version reported by crossystem.
115        """
116        self.ensure_usb_device_boot()
117        logging.info('Checking kernel and fw version via crossystem.')
118
119        if self.checkers.crossystem_checker({
120                'tpm_fwver': '0xffffffff',
121                'tpm_kernver': '0xffffffff',
122        }):
123            raise error.TestFail(
124                    'Invalid kernel and firmware versions stored in the TPM.')
125
126    def check_tpmc_v10(self):
127        self.faft_client.tpm.stop_daemon()
128        try:
129            kernel_data = (
130                    self.faft_client.system.run_shell_command_get_output(
131                            'tpmc read %s %s' %
132                            (hex(self.KERNEL_NV_INDEX),
133                             hex(self.KERNEL_ANTIROLLBACK_SPACE_BYTES_v10)))
134                    )[0].split()
135        finally:
136            self.faft_client.tpm.restart_daemon()
137
138        # Convert string list to int list
139        kernel_data_i = [ int(i, 16) for i in kernel_data ]
140
141        # For all the bytes from offset 3, calculate CRC8.
142        crc8_calc = crc8(kernel_data_i[(self.TPM_NVRAM_V10_CRC_OFFSET + 1):])
143
144        if kernel_data_i[self.TPM_NVRAM_V10_CRC_OFFSET] != crc8_calc:
145            logging.error('Kernel Data CRC(%s) is not correct: should be %s.',
146                          hex(kernel_data_i[self.TPM_NVRAM_V10_CRC_OFFSET]),
147                          hex(crc8_calc))
148            return False
149
150        logging.info('Kernel data v1.0 CRC is checked.')
151        return True
152
153    def check_tpmc(self, tpmc_output):
154        """Checks that the kernel anti-rollback data from the tpmc read command
155        is one of the expected values.
156        """
157        if len(tpmc_output) != 1:
158            return False
159
160        # Check if TPM kernel data is in the format of version 1.0.
161        if tpmc_output[0].startswith(self.TPM_NVRAM_V10_HEADER):
162            return self.check_tpmc_v10()
163
164        return (tpmc_output[0] in self.TPM_NVRAM_EXPECTED_VALUES)
165
166    def run_once(self):
167        """Runs a single iteration of the test."""
168        self.read_tmpc()
169        self.read_kern_fw_ver()
170