Home | History | Annotate | Download | only in utility
      1 #!/bin/sh -u
      2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 #
      6 # Run TPM diagnostics in recovery mode, and attempt to fix problems.  This is
      7 # specific to devices with chromeos firmware.
      8 #
      9 # Usage: chromeos-tpm-recovery <log file>
     10 #
     11 # Most of the diagnostics examine the TPM state and try to fix it.  This may
     12 # require clearing TPM ownership.
     13 
     14 tpmc=${USR_BIN:=/usr/bin}/tpmc
     15 nvtool=${USR_LOCAL_BIN:=/usr/local/bin}/tpm-nvtool
     16 tpm_takeownership=${USR_LOCAL_SBIN:=/usr/local/sbin}/tpm_takeownership
     17 tcsd=${USR_SBIN:=/usr/sbin}/tcsd
     18 dot_recovery=${DOT_RECOVERY:=/mnt/stateful_partition/.recovery}
     19 acpi=${ACPI_DIR:=/sys/devices/platform/chromeos_acpi}
     20 awk=/usr/bin/awk
     21 
     22 # At the time this script starts, we assume the following holds:
     23 #
     24 # - TPM may be owned, but not with the well-known password
     25 # - tcsd has not been started
     26 
     27 tpm_owned_with_well_known_password=0
     28 tpm_unowned=0
     29 tcsd_pid=0
     30 
     31 log() {
     32   echo "$(date): $*" >> $RECOVERY_LOG
     33 }
     34 
     35 quit() {
     36   log "ERROR: $*"
     37   log "exiting"
     38   exit 1
     39 }
     40 
     41 log_tryfix() {
     42   log "$*: attempting to fix"
     43 }
     44 
     45 # bit <n> <i> outputs bit i of number n, with bit 0 being the lsb.
     46 
     47 bit () {
     48   echo $(( ( $1 >> $2 ) & 1 ))
     49 }
     50 
     51 ensure_tcsd_is_running () {
     52   if [ $tcsd_pid = 0 ]; then
     53     $tcsd -f &
     54     tcsd_pid=$!
     55     sleep 2    # give tcsd time to initialize
     56   fi
     57 }
     58 
     59 ensure_tcsd_is_not_running () {
     60   if [ $tcsd_pid != 0 ]; then
     61     kill $tcsd_pid
     62     sleep 0.5
     63     kill $tcsd_pid > /dev/null 2>&1
     64     sleep 0.5
     65     wait $tcsd_pid > /dev/null 2>&1  # we trust that tcsd will agree to die
     66     tcsd_pid=0
     67   fi
     68 }
     69 
     70 tpm_clear_and_reenable () {
     71   ensure_tcsd_is_not_running
     72   $tpmc clear
     73   $tpmc enable
     74   $tpmc activate
     75   tpm_owned_with_well_known_password=0
     76   tpm_unowned=1
     77 }
     78 
     79 # We want the TPM owned with the well-known password.
     80 
     81 ensure_tpm_is_owned () {
     82   if [ $tpm_owned_with_well_known_password = 0 ]; then
     83     tpm_clear_and_reenable
     84     ensure_tcsd_is_running
     85     $tpm_takeownership -y -z || log "takeownership failed with status $?"
     86     tpm_owned_with_well_known_password=1
     87     tpm_unowned=0
     88   fi
     89 }
     90 
     91 ensure_tpm_is_unowned () {
     92   if [ $tpm_unowned = 0 ]; then
     93     tpm_clear_and_reenable
     94   fi
     95 }
     96 
     97 remove_space () {
     98   index=$1
     99   log "removing space $index"
    100   ensure_tpm_is_owned
    101   ensure_tcsd_is_running
    102   $nvtool --release --index "$index" --owner_password "" >> $RECOVERY_LOG 2>&1
    103   log "nvtool --release: status $?"
    104 }
    105 
    106 # Makes some room by removing a TPM space it doesn't recognize.  It would be
    107 # nice to let the user choose which space, but we may not have a UI.
    108 
    109 make_room () {
    110 
    111   # Check NVRAM spaces.
    112   AWK_PROGRAM=/tmp/tpm_recovery_$$.awk
    113   cat > $AWK_PROGRAM <<"EOF"
    114 /# NV Index 0xffffffff/ { next } # NV_INDEX_LOCK
    115 /# NV Index 0x00000000/ { next } # NV_INDEX0
    116 /# NV Index 0x00000001/ { next } # NV_INDEX_DIR
    117 /# NV Index 0x0000f.../ { next } # reserved for TPM use
    118 /# NV Index 0x0001..../ { next } # reserved for TCG WGs
    119 /# NV Index 0x00001007/ { next } # firmware space index
    120 /# NV Index 0x00001008/ { next } # kernel space index
    121 /# NV Index / { print $4 } #unexpected space
    122 EOF
    123 
    124   local index
    125 
    126   log "trying to make room by freeing one space"
    127   ensure_tcsd_is_running
    128   ensure_tpm_is_owned
    129   unexpected_spaces=$($nvtool --list | $awk -f $AWK_PROGRAM)
    130 
    131   status=1
    132 
    133   if [ "$unexpected_spaces" != "" ]; then
    134     log_tryfix "unexpected spaces: $unexpected_spaces"
    135     for index in $unexpected_spaces; do
    136       log "trying to remove space $index"
    137       if remove_space $(printf "0x%x" $(( $index )) ); then
    138         status=0
    139         break;
    140       fi
    141     done
    142   fi
    143 
    144   return $status
    145 }
    146 
    147 # define_space <index> <size> <permissions>
    148 
    149 define_space () {
    150   local index=$1
    151   local size=$2
    152   local permissions=$3
    153   # 0xf004 is for testing if there is enough room without side effects.
    154   local test_space=0xf004
    155   local perm_ppwrite=0x1
    156   local enough_room
    157 
    158   ensure_tpm_is_unowned
    159   while true; do
    160     log "checking for NVRAM room for space with size $size"
    161     if $tpmc definespace $test_space $size $perm_ppwrite; then
    162       log "there is enough room"
    163       enough_room=1
    164       break
    165     else
    166       log "definespace $test_space $size failed with status $?"
    167       if ! make_room; then
    168         enough_room=0
    169         break
    170       fi
    171     fi
    172   done
    173 
    174   if [ $enough_room -eq 0 ]; then
    175     log "not enough room to define space $index"
    176     return 1
    177   fi
    178   $tpmc definespace $index $size $permissions
    179 }
    180 
    181 fix_space () {
    182   local index=$1
    183   local permissions=$2
    184   local size=$3
    185   local bytes="$4"
    186 
    187   local space_exists=1
    188 
    189   ensure_tcsd_is_not_running
    190   observed_permissions=$($tpmc getp $index | $awk '{print $5;}')
    191   if [ $? -ne 0 ]; then
    192     space_exists=0
    193   fi
    194 
    195   # Check kernel space ID.
    196   if [ $space_exists -eq 1 -a $index = 0x1008 ]; then
    197     if ! $tpmc read 0x1008 0x5 | grep -q " 4c 57 52 47[ ]*$"; then
    198       log "bad kernel space id"
    199       remove_space $index
    200       space_exists=0
    201     fi
    202   fi
    203 
    204   # Check that space is large enough (we don't care if it's larger)
    205   if [ $space_exists -eq 1 ]; then
    206     if ! $tpmc read $index $size > /dev/null; then
    207       log "space $index read of size $size failed"
    208       remove_space $index
    209       space_exists=0
    210     fi
    211   fi
    212 
    213   # If space exists but permissions are bad, delete the space.
    214   if [ $space_exists -eq 1 -a $observed_permissions != $permissions ]; then
    215     log "space $index has unexpected permissions $permissions"
    216     remove_space $index
    217     space_exists=0
    218   fi
    219 
    220   # If space does not exist, reconstruct it.
    221   if [ $space_exists -eq 0 ]; then
    222     log_tryfix "space $index is gone"
    223     if ! define_space $index $size $permissions; then
    224       log "could not redefine space $index"
    225       return 1
    226     fi
    227     # do not quote "$bytes", as we mean to expand it here
    228     $tpmc write $index $bytes || log "writing to $index failed with code $?"
    229     log "space $index was recreated successfully"
    230   fi
    231 }
    232 
    233 
    234 # ------------
    235 # MAIN PROGRAM
    236 # ------------
    237 
    238 # Set up logging and announce ourselves.
    239 
    240 if [ $# = 1 ]; then
    241   RECOVERY_LOG="$1"
    242   /usr/bin/logger "$0 started, output in $RECOVERY_LOG"
    243   log "starting $0"
    244 else
    245   /usr/bin/logger "$0 usage error"
    246   echo "usage: $0 <log file>"
    247   exit 1
    248 fi
    249 
    250 # Sanity check: are we executing in a recovery image?
    251 
    252 if [ ! -e $dot_recovery ]; then
    253   quit "not a recovery image"
    254 fi
    255 
    256 # Mnemonic: "B, I, N, F, O, and BINFO was his name-o."
    257 # Except it's a zero (0), not an O.
    258 BINF0=$acpi/BINF.0
    259 CHSW=$acpi/CHSW
    260 
    261 # There is no point running unless this a ChromeOS device.
    262 
    263 if [ ! -e $BINF0 ]; then
    264   log "not a chromeos device, exiting"
    265   exit 0
    266 fi
    267 
    268 BOOT_REASON=$(cat $BINF0)
    269 log "boot reason is $BOOT_REASON"
    270 
    271 # Sanity check: did we boot in recovery mode?
    272 
    273 if ! echo $BOOT_REASON | grep -q "^[345678]$"; then
    274   quit "unexpected boot reason $BOOT_REASON"
    275 fi
    276 
    277 # Do we even have these tools in the image?
    278 
    279 if [ ! -e $tpmc -o ! -e $nvtool -o ! -e $tpm_takeownership ]; then
    280   quit "tpmc or nvtool or tpm_takeownership are missing"
    281 fi
    282 
    283 # Is the state of the PP enable flags correct?
    284 
    285 if ! ($tpmc getpf | grep -q "physicalPresenceLifetimeLock 1" &&
    286       $tpmc getpf | grep -q "physicalPresenceHWEnable 0" &&
    287       $tpmc getpf | grep -q "physicalPresenceCMDEnable 1"); then
    288   log_tryfix "bad state of physical presence enable flags"
    289   if $tpmc ppfin; then
    290     log "physical presence enable flags are now correctly set"
    291   else
    292     quit "could not set physical presence enable flags"
    293   fi
    294 fi
    295 
    296 # Is physical presence turned on?
    297 
    298 if $tpmc getvf | grep -q "physicalPresence 0"; then
    299   log_tryfix "physical presence is OFF, expected ON"
    300   # attempt to turn on physical presence
    301   if $tpmc ppon; then
    302     log "physical presence is now on"
    303   else
    304     quit "could not turn physical presence on"
    305   fi
    306 fi
    307 
    308 DEV_MODE_NOW=$(bit $(cat $CHSW) 4)
    309 DEV_MODE_AT_BOOT=$(bit $(cat $CHSW) 5)
    310 
    311 # Check that bGlobalLock is unset
    312 
    313 if [ $DEV_MODE_NOW != $DEV_MODE_AT_BOOT ]; then
    314   # this is either too weird or malicious, so we give up
    315   quit "dev mode is $DEV_MODE_NOW, but was $DEV_MODE_AT_BOOT at boot"
    316 fi
    317 
    318 BGLOBALLOCK=$($tpmc getvf | $awk '/bGlobalLock/ {print $2;}')
    319 
    320 if [ 0 -ne $BGLOBALLOCK ]; then
    321   # this indicates either TPM malfunction or firmware malfunction.
    322   log "bGlobalLock is $BGLOBALLOCK (dev mode is $DEV_MODE_NOW)."
    323 fi
    324 
    325 # Check firmware and kernel spaces
    326 fix_space 0x1007 0x8001 0xa "01 00    00 00 00 00    00 00 00 00" || \
    327   log "could not fix firmware space"
    328 fix_space 0x1008 0x1 0xd "01    4c 57 52 47    00 00 00 00    00 00 00 00" || \
    329   log "could not fix kernel space"
    330 
    331 # Cleanup: don't leave the tpm owned with the well-known password.
    332 if [ $tpm_owned_with_well_known_password -eq 1 ]; then
    333   tpm_clear_and_reenable
    334 fi
    335 
    336 ensure_tcsd_is_not_running
    337 log "tpm recovery has completed"
    338