150 lines
5.5 KiB
Python
150 lines
5.5 KiB
Python
# Copyright (c) 2010 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 logging, re
|
|
from autotest_lib.client.bin import test, utils
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.client.cros import service_stopper
|
|
|
|
|
|
# Expected results of 'tpmc getX' commands.
|
|
TPMC_EXPECTED_1_2 = {
|
|
'getvf': # volatile (ST_CLEAR) flags
|
|
set([('deactivated', '0'), ('physicalPresence', '0'),
|
|
('physicalPresenceLock', '1'), ('bGlobalLock', '1')]),
|
|
'getpf': # permanent flags
|
|
set([('disable', '0'), ('ownership', '1'), ('deactivated', '0'),
|
|
('physicalPresenceHWEnable', '0'), ('physicalPresenceCMDEnable', '1'),
|
|
('physicalPresenceLifetimeLock', '1'), ('nvLocked', '1')])}
|
|
|
|
TPMC_EXPECTED_2_0 = {
|
|
'getvf': # volatile (ST_CLEAR) flags
|
|
set([('phEnable', '0'), ('shEnable', '1'),
|
|
('ehEnable', '1'), ('phEnableNV', '1')]),
|
|
'getpf': # permanent flags
|
|
set([('inLockout', '0')])}
|
|
|
|
# Expected permissions for NV indexes.
|
|
PERM_EXPECTED_1_2 = {'0x1007': '0x8001', '0x1008': '0x1'}
|
|
PERM_EXPECTED_2_0 = {'0x1007': '0x60054c01', '0x1008': '0x60050001'}
|
|
|
|
def missing_firmware_version():
|
|
"""Check for empty fwid.
|
|
|
|
@return True if no fwid else False.
|
|
"""
|
|
cmd = 'crossystem fwid'
|
|
return not utils.system_output(cmd, ignore_status=True).strip()
|
|
|
|
|
|
def __run_tpmc_cmd(subcommand):
|
|
"""Make this test more readable by simplifying commonly used tpmc command.
|
|
|
|
@param subcommand: String of the tpmc subcommand (getvf, getpf, getp, ...)
|
|
@return String output (which may be empty).
|
|
"""
|
|
cmd = 'tpmc %s' % subcommand
|
|
return utils.system_output(cmd, ignore_status=True).strip()
|
|
|
|
|
|
def check_tpmc(subcommand, expected):
|
|
"""Runs tpmc command and checks the output against an expected result.
|
|
|
|
The expected results take 2 different forms:
|
|
1. A regular expression that is matched.
|
|
2. A set of tuples that are matched.
|
|
|
|
@param subcommand: String of the tpmc subcommand (getvf, getpf, getp, ...)
|
|
@param expected: Either a String re or the set of expected tuples.
|
|
@raises error.TestError() for invalidly matching expected.
|
|
"""
|
|
error_msg = 'invalid response to tpmc %s' % subcommand
|
|
if isinstance(expected, str):
|
|
out = __run_tpmc_cmd(subcommand)
|
|
if (not re.match(expected, out)):
|
|
raise error.TestError('%s: %s' % (error_msg, out))
|
|
else:
|
|
result_set = utils.set_from_keyval_output(__run_tpmc_cmd(subcommand))
|
|
if set(expected) <= result_set:
|
|
return
|
|
raise error.TestError('%s: expected=%s.' %
|
|
(error_msg, sorted(set(expected) - result_set)))
|
|
|
|
|
|
def check_perm(index, perm):
|
|
return check_tpmc('getp %s' % index, '.*%s$' % perm)
|
|
|
|
|
|
def is_tpm2():
|
|
"""Check TPM version.
|
|
|
|
@return True if the system has TPM2.0 else False.
|
|
"""
|
|
trunks_init_file = '/etc/init/trunksd.conf'
|
|
cmd = 'ls %s' % trunks_init_file
|
|
output = utils.system_output(cmd, ignore_status=True).strip()
|
|
return output == trunks_init_file
|
|
|
|
|
|
class hardware_TPMCheck(test.test):
|
|
"""Check that the state of the TPM is as expected."""
|
|
version = 1
|
|
|
|
|
|
def initialize(self):
|
|
# Must stop the TCSD process to be able to collect TPM status,
|
|
# then restart TCSD process to leave system in a known good state.
|
|
# Must also stop services which depend on tcsd.
|
|
# Note: for TPM2 the order of re-starting services (they are started
|
|
# in the reversed listed order) is important: e.g. tpm_managerd must
|
|
# start after trunksd, and cryptohomed after attestationd.
|
|
self._services = service_stopper.ServiceStopper(['cryptohomed',
|
|
'chapsd',
|
|
'attestationd',
|
|
'tpm_managerd',
|
|
'tcsd', 'trunksd'])
|
|
self._services.stop_services()
|
|
|
|
|
|
def run_once(self):
|
|
"""Run a few TPM state checks."""
|
|
if missing_firmware_version():
|
|
logging.warning('no firmware version, skipping test')
|
|
return
|
|
|
|
if is_tpm2():
|
|
logging.info('Running on TPM 2.0')
|
|
tpmc_expected = TPMC_EXPECTED_2_0
|
|
perm_expected = PERM_EXPECTED_2_0
|
|
else:
|
|
logging.info('Running on TPM 1.2')
|
|
tpmc_expected = TPMC_EXPECTED_1_2
|
|
perm_expected = PERM_EXPECTED_1_2
|
|
|
|
# Check volatile and permanent flags
|
|
for subcommand in ['getvf', 'getpf']:
|
|
check_tpmc(subcommand, tpmc_expected[subcommand])
|
|
|
|
# Check space permissions
|
|
for index in ['0x1007', '0x1008']:
|
|
check_perm(index, perm_expected[index])
|
|
|
|
# Check kernel space UID
|
|
# First check the TPM data version. If it is ver 1.0, then skip
|
|
# the kernel UID because it is not available.
|
|
try:
|
|
# Read the first two bytes of TPM kernel data, and check if it is
|
|
# in the format version 1.0.
|
|
# byte[0]: version. bit[7:4]=Major, bit[3:0]=Minor.
|
|
# byte[1]: byte size. 0x28, 40(0x28) bytes
|
|
check_tpmc('read 0x1008 0x2', '^10 28')
|
|
except error.TestError as e:
|
|
# If TPM ver 1.0 format pattern matching fails, then it should be in
|
|
# the old format. Let's check kernel space UID.
|
|
check_tpmc('read 0x1008 0x5', '^.* 4c 57 52 47$')
|
|
|
|
|
|
def cleanup(self):
|
|
self._services.restore_services()
|