diff options
Diffstat (limited to 'utility/chromeos-tpm-recovery')
-rwxr-xr-x | utility/chromeos-tpm-recovery | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/utility/chromeos-tpm-recovery b/utility/chromeos-tpm-recovery new file mode 100755 index 00000000..5bb2ec6a --- /dev/null +++ b/utility/chromeos-tpm-recovery @@ -0,0 +1,337 @@ +#!/bin/sh -u +# 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. +# +# Run TPM diagnostics in recovery mode, and attempt to fix problems. This is +# specific to devices with chromeos firmware. +# +# Usage: chromeos-tpm-recovery <log file> +# +# Most of the diagnostics examine the TPM state and try to fix it. This may +# require clearing TPM ownership. + +tpmc=${USR_BIN:=/usr/bin}/tpmc +nvtool=${USR_LOCAL_BIN:=/usr/local/bin}/tpm-nvtool +tpm_takeownership=${USR_LOCAL_SBIN:=/usr/local/sbin}/tpm_takeownership +tcsd=${USR_SBIN:=/usr/sbin}/tcsd +dot_recovery=${DOT_RECOVERY:=/mnt/stateful_partition/.recovery} +acpi=${ACPI_DIR:=/sys/devices/platform/chromeos_acpi} +awk=/usr/bin/awk + +# At the time this script starts, we assume the following holds: +# +# - TPM may be owned, but not with the well-known password +# - tcsd has not been started + +tpm_owned_with_well_known_password=0 +tpm_unowned=0 +tcsd_pid=0 + +log() { + echo "$(date): $*" >> $RECOVERY_LOG +} + +quit() { + log "ERROR: $*" + log "exiting" + exit 1 +} + +log_tryfix() { + log "$*: attempting to fix" +} + +# bit <n> <i> outputs bit i of number n, with bit 0 being the lsb. + +bit () { + echo $(( ( $1 >> $2 ) & 1 )) +} + +ensure_tcsd_is_running () { + if [ $tcsd_pid = 0 ]; then + $tcsd -f & + tcsd_pid=$! + sleep 2 # give tcsd time to initialize + fi +} + +ensure_tcsd_is_not_running () { + if [ $tcsd_pid != 0 ]; then + kill $tcsd_pid + sleep 0.5 + kill $tcsd_pid > /dev/null 2>&1 + sleep 0.5 + wait $tcsd_pid > /dev/null 2>&1 # we trust that tcsd will agree to die + tcsd_pid=0 + fi +} + +tpm_clear_and_reenable () { + ensure_tcsd_is_not_running + $tpmc clear + $tpmc enable + $tpmc activate + tpm_owned_with_well_known_password=0 + tpm_unowned=1 +} + +# We want the TPM owned with the well-known password. + +ensure_tpm_is_owned () { + if [ $tpm_owned_with_well_known_password = 0 ]; then + tpm_clear_and_reenable + ensure_tcsd_is_running + $tpm_takeownership -y -z || log "takeownership failed with status $?" + tpm_owned_with_well_known_password=1 + tpm_unowned=0 + fi +} + +ensure_tpm_is_unowned () { + if [ $tpm_unowned = 0 ]; then + tpm_clear_and_reenable + fi +} + +remove_space () { + index=$1 + log "removing space $index" + ensure_tpm_is_owned + ensure_tcsd_is_running + $nvtool --release --index "$index" --owner_password "" >> $RECOVERY_LOG 2>&1 + log "nvtool --release: status $?" +} + +# Makes some room by removing a TPM space it doesn't recognize. It would be +# nice to let the user choose which space, but we may not have a UI. + +make_room () { + + # Check NVRAM spaces. + AWK_PROGRAM=/tmp/tpm_recovery_$$.awk + cat > $AWK_PROGRAM <<"EOF" +/# NV Index 0xffffffff/ { next } # NV_INDEX_LOCK +/# NV Index 0x00000000/ { next } # NV_INDEX0 +/# NV Index 0x00000001/ { next } # NV_INDEX_DIR +/# NV Index 0x0000f.../ { next } # reserved for TPM use +/# NV Index 0x0001..../ { next } # reserved for TCG WGs +/# NV Index 0x00001007/ { next } # firmware space index +/# NV Index 0x00001008/ { next } # kernel space index +/# NV Index / { print $4 } #unexpected space +EOF + + local index + + log "trying to make room by freeing one space" + ensure_tcsd_is_running + ensure_tpm_is_owned + unexpected_spaces=$($nvtool --list | $awk -f $AWK_PROGRAM) + + status=1 + + if [ "$unexpected_spaces" != "" ]; then + log_tryfix "unexpected spaces: $unexpected_spaces" + for index in $unexpected_spaces; do + log "trying to remove space $index" + if remove_space $(printf "0x%x" $(( $index )) ); then + status=0 + break; + fi + done + fi + + return $status +} + +# define_space <index> <size> <permissions> + +define_space () { + local index=$1 + local size=$2 + local permissions=$3 + # 0xf004 is for testing if there is enough room without side effects. + local test_space=0xf004 + local perm_ppwrite=0x1 + local enough_room + + ensure_tpm_is_unowned + while true; do + log "checking for NVRAM room for space with size $size" + if $tpmc definespace $test_space $size $perm_ppwrite; then + log "there is enough room" + enough_room=1 + break + else + log "definespace $test_space $size failed with status $?" + if ! make_room; then + enough_room=0 + break + fi + fi + done + + if [ $enough_room -eq 0 ]; then + log "not enough room to define space $index" + return 1 + fi + $tpmc definespace $index $size $permissions +} + +fix_space () { + local index=$1 + local permissions=$2 + local size=$3 + local bytes="$4" + + local space_exists=1 + + ensure_tcsd_is_not_running + observed_permissions=$($tpmc getp $index | $awk '{print $5;}') + if [ $? -ne 0 ]; then + space_exists=0 + fi + + # Check kernel space ID. + if [ $space_exists -eq 1 -a $index = 0x1008 ]; then + if ! $tpmc read 0x1008 0x5 | grep -q " 4c 57 52 47[ ]*$"; then + log "bad kernel space id" + remove_space $index + space_exists=0 + fi + fi + + # Check that space is large enough (we don't care if it's larger) + if [ $space_exists -eq 1 ]; then + if ! $tpmc read $index $size > /dev/null; then + log "space $index read of size $size failed" + remove_space $index + space_exists=0 + fi + fi + + # If space exists but permissions are bad, delete the space. + if [ $space_exists -eq 1 -a $observed_permissions != $permissions ]; then + log "space $index has unexpected permissions $permissions" + remove_space $index + space_exists=0 + fi + + # If space does not exist, reconstruct it. + if [ $space_exists -eq 0 ]; then + log_tryfix "space $index is gone" + if ! define_space $index $size $permissions; then + log "could not redefine space $index" + return 1 + fi + # do not quote "$bytes", as we mean to expand it here + $tpmc write $index $bytes || log "writing to $index failed with code $?" + log "space $index was recreated successfully" + fi +} + + +# ------------ +# MAIN PROGRAM +# ------------ + +# Set up logging and announce ourselves. + +if [ $# = 1 ]; then + RECOVERY_LOG="$1" + /usr/bin/logger "$0 started, output in $RECOVERY_LOG" + log "starting $0" +else + /usr/bin/logger "$0 usage error" + echo "usage: $0 <log file>" + exit 1 +fi + +# Sanity check: are we executing in a recovery image? + +if [ ! -e $dot_recovery ]; then + quit "not a recovery image" +fi + +# Mnemonic: "B, I, N, F, O, and BINFO was his name-o." +# Except it's a zero (0), not an O. +BINF0=$acpi/BINF.0 +CHSW=$acpi/CHSW + +# There is no point running unless this a ChromeOS device. + +if [ ! -e $BINF0 ]; then + log "not a chromeos device, exiting" + exit 0 +fi + +BOOT_REASON=$(cat $BINF0) +log "boot reason is $BOOT_REASON" + +# Sanity check: did we boot in recovery mode? + +if ! echo $BOOT_REASON | grep -q "^[345678]$"; then + quit "unexpected boot reason $BOOT_REASON" +fi + +# Do we even have these tools in the image? + +if [ ! -e $tpmc -o ! -e $nvtool -o ! -e $tpm_takeownership ]; then + quit "tpmc or nvtool or tpm_takeownership are missing" +fi + +# Is the state of the PP enable flags correct? + +if ! ($tpmc getpf | grep -q "physicalPresenceLifetimeLock 1" && + $tpmc getpf | grep -q "physicalPresenceHWEnable 0" && + $tpmc getpf | grep -q "physicalPresenceCMDEnable 1"); then + log_tryfix "bad state of physical presence enable flags" + if $tpmc ppfin; then + log "physical presence enable flags are now correctly set" + else + quit "could not set physical presence enable flags" + fi +fi + +# Is physical presence turned on? + +if $tpmc getvf | grep -q "physicalPresence 0"; then + log_tryfix "physical presence is OFF, expected ON" + # attempt to turn on physical presence + if $tpmc ppon; then + log "physical presence is now on" + else + quit "could not turn physical presence on" + fi +fi + +DEV_MODE_NOW=$(bit $(cat $CHSW) 4) +DEV_MODE_AT_BOOT=$(bit $(cat $CHSW) 5) + +# Check that bGlobalLock is unset + +if [ $DEV_MODE_NOW != $DEV_MODE_AT_BOOT ]; then + # this is either too weird or malicious, so we give up + quit "dev mode is $DEV_MODE_NOW, but was $DEV_MODE_AT_BOOT at boot" +fi + +BGLOBALLOCK=$($tpmc getvf | $awk '/bGlobalLock/ {print $2;}') + +if [ 0 -ne $BGLOBALLOCK ]; then + # this indicates either TPM malfunction or firmware malfunction. + log "bGlobalLock is $BGLOBALLOCK (dev mode is $DEV_MODE_NOW)." +fi + +# Check firmware and kernel spaces +fix_space 0x1007 0x8001 0xa "01 00 00 00 00 00 00 00 00 00" || \ + log "could not fix firmware space" +fix_space 0x1008 0x1 0xd "01 4c 57 52 47 00 00 00 00 00 00 00 00" || \ + log "could not fix kernel space" + +# Cleanup: don't leave the tpm owned with the well-known password. +if [ $tpm_owned_with_well_known_password -eq 1 ]; then + tpm_clear_and_reenable +fi + +ensure_tcsd_is_not_running +log "tpm recovery has completed" |