Luigi Semenzato | 3e3704f | 2010-10-25 12:36:03 -0700 | [diff] [blame] | 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 | # |
Luigi Semenzato | 900ce4b | 2010-11-02 14:04:24 -0700 | [diff] [blame] | 9 | # Usage: chromeos-tpm-recovery <log file> |
Luigi Semenzato | 3e3704f | 2010-10-25 12:36:03 -0700 | [diff] [blame] | 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 |
Luigi Semenzato | 35a6cb7 | 2010-11-02 10:37:16 -0700 | [diff] [blame] | 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 |
Luigi Semenzato | 3e3704f | 2010-10-25 12:36:03 -0700 | [diff] [blame] | 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=$! |
Luigi Semenzato | 35a6cb7 | 2010-11-02 10:37:16 -0700 | [diff] [blame] | 55 | sleep 2 # give tcsd time to initialize |
Luigi Semenzato | 3e3704f | 2010-10-25 12:36:03 -0700 | [diff] [blame] | 56 | fi |
| 57 | } |
| 58 | |
| 59 | ensure_tcsd_is_not_running () { |
| 60 | if [ $tcsd_pid != 0 ]; then |
| 61 | kill $tcsd_pid |
Luigi Semenzato | 35a6cb7 | 2010-11-02 10:37:16 -0700 | [diff] [blame] | 62 | sleep 0.5 |
| 63 | kill $tcsd_pid > /dev/null 2>&1 |
| 64 | sleep 0.5 |
Luigi Semenzato | 3e3704f | 2010-10-25 12:36:03 -0700 | [diff] [blame] | 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 | |
Luigi Semenzato | ba04b8b | 2010-10-28 10:31:47 -0700 | [diff] [blame] | 79 | # We want the TPM owned with the well-known password. |
| 80 | |
Luigi Semenzato | 3e3704f | 2010-10-25 12:36:03 -0700 | [diff] [blame] | 81 | ensure_tpm_is_owned () { |
Luigi Semenzato | ba04b8b | 2010-10-28 10:31:47 -0700 | [diff] [blame] | 82 | if [ $tpm_owned_with_well_known_password = 0 ]; then |
Luigi Semenzato | 3e3704f | 2010-10-25 12:36:03 -0700 | [diff] [blame] | 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 | |
Luigi Semenzato | ba04b8b | 2010-10-28 10:31:47 -0700 | [diff] [blame] | 124 | local index |
| 125 | |
| 126 | log "trying to make room by freeing one space" |
Luigi Semenzato | 3e3704f | 2010-10-25 12:36:03 -0700 | [diff] [blame] | 127 | ensure_tcsd_is_running |
| 128 | ensure_tpm_is_owned |
| 129 | unexpected_spaces=$($nvtool --list | $awk -f $AWK_PROGRAM) |
| 130 | |
| 131 | status=1 |
| 132 | |
Luigi Semenzato | ba04b8b | 2010-10-28 10:31:47 -0700 | [diff] [blame] | 133 | if [ "$unexpected_spaces" != "" ]; then |
Luigi Semenzato | 3e3704f | 2010-10-25 12:36:03 -0700 | [diff] [blame] | 134 | log_tryfix "unexpected spaces: $unexpected_spaces" |
| 135 | for index in $unexpected_spaces; do |
Luigi Semenzato | ba04b8b | 2010-10-28 10:31:47 -0700 | [diff] [blame] | 136 | log "trying to remove space $index" |
| 137 | if remove_space $(printf "0x%x" $(( $index )) ); then |
Luigi Semenzato | 3e3704f | 2010-10-25 12:36:03 -0700 | [diff] [blame] | 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 |
Luigi Semenzato | ba04b8b | 2010-10-28 10:31:47 -0700 | [diff] [blame] | 160 | log "checking for NVRAM room for space with size $size" |
Luigi Semenzato | 3e3704f | 2010-10-25 12:36:03 -0700 | [diff] [blame] | 161 | if $tpmc definespace $test_space $size $perm_ppwrite; then |
Luigi Semenzato | ba04b8b | 2010-10-28 10:31:47 -0700 | [diff] [blame] | 162 | log "there is enough room" |
Luigi Semenzato | 3e3704f | 2010-10-25 12:36:03 -0700 | [diff] [blame] | 163 | enough_room=1 |
| 164 | break |
| 165 | else |
Luigi Semenzato | ba04b8b | 2010-10-28 10:31:47 -0700 | [diff] [blame] | 166 | log "definespace $test_space $size failed with status $?" |
Luigi Semenzato | 3e3704f | 2010-10-25 12:36:03 -0700 | [diff] [blame] | 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" |
Luigi Semenzato | ba04b8b | 2010-10-28 10:31:47 -0700 | [diff] [blame] | 243 | log "starting $0" |
Luigi Semenzato | 3e3704f | 2010-10-25 12:36:03 -0700 | [diff] [blame] | 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 |
Luigi Semenzato | 35a6cb7 | 2010-11-02 10:37:16 -0700 | [diff] [blame] | 259 | CHSW=$acpi/CHSW |
Luigi Semenzato | 3e3704f | 2010-10-25 12:36:03 -0700 | [diff] [blame] | 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 | |
Luigi Semenzato | 35a6cb7 | 2010-11-02 10:37:16 -0700 | [diff] [blame] | 308 | DEV_MODE_NOW=$(bit $(cat $CHSW) 4) |
| 309 | DEV_MODE_AT_BOOT=$(bit $(cat $CHSW) 5) |
Luigi Semenzato | 3e3704f | 2010-10-25 12:36:03 -0700 | [diff] [blame] | 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 |
Luigi Semenzato | 35a6cb7 | 2010-11-02 10:37:16 -0700 | [diff] [blame] | 337 | log "tpm recovery has completed" |