• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2010 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, re
6from autotest_lib.client.bin import test, utils
7from autotest_lib.client.common_lib import error
8from autotest_lib.client.cros import service_stopper
9
10
11# Expected results of 'tpmc getX' commands.
12TPMC_EXPECTED_1_2 = {
13    'getvf': # volatile (ST_CLEAR) flags
14     set([('deactivated', '0'), ('physicalPresence', '0'),
15          ('physicalPresenceLock', '1'), ('bGlobalLock', '1')]),
16    'getpf': # permanent flags
17     set([('disable', '0'), ('ownership', '1'), ('deactivated', '0'),
18          ('physicalPresenceHWEnable', '0'), ('physicalPresenceCMDEnable', '1'),
19          ('physicalPresenceLifetimeLock', '1'), ('nvLocked', '1')])}
20
21TPMC_EXPECTED_2_0 = {
22    'getvf': # volatile (ST_CLEAR) flags
23     set([('phEnable', '0'), ('shEnable', '1'),
24          ('ehEnable', '1'), ('phEnableNV', '1')]),
25    'getpf': # permanent flags
26     set([('inLockout', '0')])}
27
28# Expected permissions for NV indexes.
29PERM_EXPECTED_1_2 = {'0x1007': '0x8001', '0x1008': '0x1'}
30PERM_EXPECTED_2_0 = {'0x1007': '0x60054c01', '0x1008': '0x60050001'}
31
32def missing_firmware_version():
33    """Check for empty fwid.
34
35    @return True if no fwid else False.
36    """
37    cmd = 'crossystem fwid'
38    return not utils.system_output(cmd, ignore_status=True).strip()
39
40
41def __run_tpmc_cmd(subcommand):
42    """Make this test more readable by simplifying commonly used tpmc command.
43
44    @param subcommand: String of the tpmc subcommand (getvf, getpf, getp, ...)
45    @return String output (which may be empty).
46    """
47    cmd = 'tpmc %s' % subcommand
48    return utils.system_output(cmd, ignore_status=True).strip()
49
50
51def check_tpmc(subcommand, expected):
52    """Runs tpmc command and checks the output against an expected result.
53
54    The expected results take 2 different forms:
55    1. A regular expression that is matched.
56    2. A set of tuples that are matched.
57
58    @param subcommand: String of the tpmc subcommand (getvf, getpf, getp, ...)
59    @param expected: Either a String re or the set of expected tuples.
60    @raises error.TestError() for invalidly matching expected.
61    """
62    error_msg = 'invalid response to tpmc %s' % subcommand
63    if isinstance(expected, str):
64        out = __run_tpmc_cmd(subcommand)
65        if (not re.match(expected, out)):
66            raise error.TestError('%s: %s' % (error_msg, out))
67    else:
68        result_set = utils.set_from_keyval_output(__run_tpmc_cmd(subcommand))
69        if set(expected) <= result_set:
70            return
71        raise error.TestError('%s: expected=%s.' %
72                              (error_msg, sorted(set(expected) - result_set)))
73
74
75def check_perm(index, perm):
76    return check_tpmc('getp %s' % index, '.*%s$' % perm)
77
78
79def is_tpm2():
80    """Check TPM version.
81
82    @return True if the system has TPM2.0 else False.
83    """
84    trunks_init_file = '/etc/init/trunksd.conf'
85    cmd = 'ls %s' % trunks_init_file
86    output = utils.system_output(cmd, ignore_status=True).strip()
87    return output == trunks_init_file
88
89
90class hardware_TPMCheck(test.test):
91    """Check that the state of the TPM is as expected."""
92    version = 1
93
94
95    def initialize(self):
96        # Must stop the TCSD process to be able to collect TPM status,
97        # then restart TCSD process to leave system in a known good state.
98        # Must also stop services which depend on tcsd.
99        # Note: for TPM2 the order of re-starting services (they are started
100        # in the reversed listed order) is important: e.g. tpm_managerd must
101        # start after trunksd, and cryptohomed after attestationd.
102        self._services = service_stopper.ServiceStopper(['cryptohomed',
103                                                         'chapsd',
104                                                         'attestationd',
105                                                         'tpm_managerd',
106                                                         'tcsd', 'trunksd'])
107        self._services.stop_services()
108
109
110    def run_once(self):
111        """Run a few TPM state checks."""
112        if missing_firmware_version():
113            logging.warning('no firmware version, skipping test')
114            return
115
116        if is_tpm2():
117            logging.info('Running on TPM 2.0')
118            tpmc_expected = TPMC_EXPECTED_2_0
119            perm_expected = PERM_EXPECTED_2_0
120        else:
121            logging.info('Running on TPM 1.2')
122            tpmc_expected = TPMC_EXPECTED_1_2
123            perm_expected = PERM_EXPECTED_1_2
124
125        # Check volatile and permanent flags
126        for subcommand in ['getvf', 'getpf']:
127            check_tpmc(subcommand, tpmc_expected[subcommand])
128
129        # Check space permissions
130        for index in ['0x1007', '0x1008']:
131            check_perm(index, perm_expected[index])
132
133        # Check kernel space UID
134        # First check the TPM data version. If it is ver 1.0, then skip
135        # the kernel UID because it is not available.
136        try:
137            # Read the first two bytes of TPM kernel data, and check if it is
138            # in the format version 1.0.
139            #   byte[0]: version. bit[7:4]=Major, bit[3:0]=Minor.
140            #   byte[1]: byte size. 0x28, 40(0x28) bytes
141            check_tpmc('read 0x1008 0x2', '^10 28')
142        except error.TestError as e:
143            # If TPM ver 1.0 format pattern matching fails, then it should be in
144            # the old format. Let's check kernel space UID.
145            check_tpmc('read 0x1008 0x5', '^.* 4c 57 52 47$')
146
147
148    def cleanup(self):
149        self._services.restore_services()
150