| #!/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" |