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 14tpmc=${USR_BIN:=/usr/bin}/tpmc 15nvtool=${USR_LOCAL_BIN:=/usr/local/bin}/tpm-nvtool 16tpm_takeownership=${USR_LOCAL_SBIN:=/usr/local/sbin}/tpm_takeownership 17tcsd=${USR_SBIN:=/usr/sbin}/tcsd 18dot_recovery=${DOT_RECOVERY:=/mnt/stateful_partition/.recovery} 19acpi=${ACPI_DIR:=/sys/devices/platform/chromeos_acpi} 20awk=/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 27tpm_owned_with_well_known_password=0 28tpm_unowned=0 29tcsd_pid=0 30 31log() { 32 echo "$(date): $*" >> $RECOVERY_LOG 33} 34 35quit() { 36 log "ERROR: $*" 37 log "exiting" 38 exit 1 39} 40 41log_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 47bit () { 48 echo $(( ( $1 >> $2 ) & 1 )) 49} 50 51ensure_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 59ensure_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 70tpm_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 81ensure_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 91ensure_tpm_is_unowned () { 92 if [ $tpm_unowned = 0 ]; then 93 tpm_clear_and_reenable 94 fi 95} 96 97remove_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 109make_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 122EOF 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 149define_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 181fix_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 240if [ $# = 1 ]; then 241 RECOVERY_LOG="$1" 242 /usr/bin/logger "$0 started, output in $RECOVERY_LOG" 243 log "starting $0" 244else 245 /usr/bin/logger "$0 usage error" 246 echo "usage: $0 <log file>" 247 exit 1 248fi 249 250# Sanity check: are we executing in a recovery image? 251 252if [ ! -e $dot_recovery ]; then 253 quit "not a recovery image" 254fi 255 256# Mnemonic: "B, I, N, F, O, and BINFO was his name-o." 257# Except it's a zero (0), not an O. 258BINF0=$acpi/BINF.0 259CHSW=$acpi/CHSW 260 261# There is no point running unless this a ChromeOS device. 262 263if [ ! -e $BINF0 ]; then 264 log "not a chromeos device, exiting" 265 exit 0 266fi 267 268BOOT_REASON=$(cat $BINF0) 269log "boot reason is $BOOT_REASON" 270 271# Sanity check: did we boot in recovery mode? 272 273if ! echo $BOOT_REASON | grep -q "^[345678]$"; then 274 quit "unexpected boot reason $BOOT_REASON" 275fi 276 277# Do we even have these tools in the image? 278 279if [ ! -e $tpmc -o ! -e $nvtool -o ! -e $tpm_takeownership ]; then 280 quit "tpmc or nvtool or tpm_takeownership are missing" 281fi 282 283# Is the state of the PP enable flags correct? 284 285if ! ($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 294fi 295 296# Is physical presence turned on? 297 298if $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 306fi 307 308DEV_MODE_NOW=$(bit $(cat $CHSW) 4) 309DEV_MODE_AT_BOOT=$(bit $(cat $CHSW) 5) 310 311# Check that bGlobalLock is unset 312 313if [ $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" 316fi 317 318BGLOBALLOCK=$($tpmc getvf | $awk '/bGlobalLock/ {print $2;}') 319 320if [ 0 -ne $BGLOBALLOCK ]; then 321 # this indicates either TPM malfunction or firmware malfunction. 322 log "bGlobalLock is $BGLOBALLOCK (dev mode is $DEV_MODE_NOW)." 323fi 324 325# Check firmware and kernel spaces 326fix_space 0x1007 0x8001 0xa "01 00 00 00 00 00 00 00 00 00" || \ 327 log "could not fix firmware space" 328fix_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. 332if [ $tpm_owned_with_well_known_password -eq 1 ]; then 333 tpm_clear_and_reenable 334fi 335 336ensure_tcsd_is_not_running 337log "tpm recovery has completed" 338