1#!/bin/sh -u 2# Copyright 2010 The ChromiumOS Authors 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# Most of the diagnostics examine the TPM state and try to fix it. This may 10# require clearing TPM ownership. 11 12tpmc=${USR_BIN:=/usr/bin}/tpmc 13crossystem=${USR_BIN}/crossystem 14dot_recovery=${DOT_RECOVERY:=/mnt/stateful_partition/.recovery} 15awk=/usr/bin/awk 16initctl=/sbin/initctl 17daemon_was_running= 18err=0 19secdata_firmware=0x1007 20secdata_kernel=0x1008 21 22tpm2_target() { 23 # This is not an ideal way to tell if we are running on a tpm2 target, but 24 # it will have to do for now. 25 if [ -f "/etc/init/trunksd.conf" ]; then 26 return 0 27 else 28 return 1 29 fi 30} 31 32use_v0_secdata_kernel() { 33 local fwid="$(crossystem ro_fwid)" 34 local major="$(printf "$fwid" | cut -d. -f2)" 35 local minor="$(printf "$fwid" | cut -d. -f3)" 36 37 # TPM1 firmware never supports the v1 kernel space format. 38 if ! tpm2_target; then 39 return 0 40 fi 41 42 # First some validity checks: X -eq X checks that X is a number. cut may 43 # return the whole string if no delimiter found, so major != minor checks that 44 # the version was at least somewhat correctly formatted. 45 if [ $major -eq $major ] && [ $minor -eq $minor ] && [ $major -ne $minor ]; then 46 # Now what we really care about: is this firmware older than CL:2041695? 47 if [ $major -lt 12953 ]; then 48 return 0 49 else 50 return 1 51 fi 52 else 53 log "Cannot parse FWID. Assuming local build that supports v1 kernel space." 54 return 1 55 fi 56} 57 58log() { 59 echo "$*" 60} 61 62quit() { 63 log "ERROR: $*" 64 restart_daemon_if_needed 65 log "exiting" 66 67 exit 1 68} 69 70log_tryfix() { 71 log "$*: attempting to fix" 72} 73 74log_error() { 75 err=$((err + 1)) 76 log "ERROR: $*" 77} 78 79 80log_warn() { 81 log "WARNING: $*" 82} 83 84tpm_clear_and_reenable () { 85 $tpmc clear 86 87 # The below commands are are no-op on tpm2, but let's keep them here for 88 # both TPM versions in case they are implemented in the future for 89 # version 2. 90 $tpmc enable 91 $tpmc activate 92} 93 94write_space () { 95 # do not quote "$2", as we mean to expand it here 96 if ! $tpmc write $1 $2; then 97 log_error "writing to $1 failed" 98 else 99 log "$1 written successfully" 100 fi 101} 102 103reset_ro_space () { 104 local index=$1 105 local bytes="$2" 106 local size=$(printf "$bytes" | wc -w) 107 local permissions=0x8001 108 109 if tpm2_target; then 110 log "Cannot redefine RO space for TPM2 (b/140958855). Let's just hope it looks good..." 111 else 112 if ! $tpmc definespace $index $size $permissions; then 113 log_error "could not redefine RO space $index" 114 # try writing it anyway, just in case it works... 115 fi 116 fi 117 118 write_space $index "$bytes" 119} 120 121reset_rw_space () { 122 local index=$1 123 local bytes="$2" 124 local size=$(printf "$bytes" | wc -w) 125 local permissions=0x1 126 127 if tpm2_target; then 128 permissions=0x40050001 129 fi 130 131 if ! $tpmc definespace $index $size $permissions; then 132 log_error "could not redefine RW space $index" 133 # try writing it anyway, just in case it works... 134 fi 135 136 write_space $index "$bytes" 137} 138 139restart_daemon_if_needed() { 140 if [ "$daemon_was_running" = 1 ]; then 141 log "Restarting ${DAEMON}..." 142 $initctl start "${DAEMON}" >/dev/null 143 fi 144} 145 146# ------------ 147# MAIN PROGRAM 148# ------------ 149 150# validity check: are we executing in a recovery image? 151 152if [ -e $dot_recovery ]; then 153 quit "This is a developer utility, it should never run on a (production) recovery image" 154fi 155 156# Did the firmware keep the TPM unlocked? 157 158if ! $($crossystem mainfw_type?recovery); then 159 quit "You must put a test image on a USB stick and boot it in recovery mode (this means Esc+Refresh+Power, *not* Ctrl-U!) to run this" 160fi 161 162if tpm2_target; then 163 DAEMON="trunksd" 164else 165 DAEMON="tcsd" 166fi 167 168# TPM daemon may or may not be running 169 170log "Stopping ${DAEMON}..." 171if $initctl stop "${DAEMON}" >/dev/null 2>/dev/null; then 172 daemon_was_running=1 173 log "done" 174else 175 daemon_was_running=0 176 log "(was not running)" 177fi 178 179# Is the state of the PP enable flags correct? 180 181if ! tpm2_target; then 182 if ! ($tpmc getpf | grep -q "physicalPresenceLifetimeLock 1" && 183 $tpmc getpf | grep -q "physicalPresenceHWEnable 0" && 184 $tpmc getpf | grep -q "physicalPresenceCMDEnable 1"); then 185 log_tryfix "bad state of physical presence enable flags" 186 if $tpmc ppfin; then 187 log "physical presence enable flags are now correctly set" 188 else 189 quit "could not set physical presence enable flags" 190 fi 191 fi 192 193 # Is physical presence turned on? 194 195 if $tpmc getvf | grep -q "physicalPresence 0"; then 196 log_tryfix "physical presence is OFF, expected ON" 197 # attempt to turn on physical presence 198 if $tpmc ppon; then 199 log "physical presence is now on" 200 else 201 quit "could not turn physical presence on" 202 fi 203 fi 204else 205 if ! $tpmc getvf | grep -q 'phEnable 1'; then 206 quit "Platform Hierarchy is disabled, TPM can't be recovered" 207 fi 208fi 209 210# I never learned what this does, but it's probably good just in case... 211tpm_clear_and_reenable 212 213# Reset firmware and kernel spaces to default (rollback version 1/1) 214reset_ro_space $secdata_firmware "02 0 1 0 1 0 0 0 0 4f" 215 216if use_v0_secdata_kernel; then 217 reset_rw_space $secdata_kernel "02 4c 57 52 47 1 0 1 0 0 0 0 55" 218else 219 reset_rw_space $secdata_kernel "10 28 0c 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0" 220fi 221 222restart_daemon_if_needed 223 224if [ "$err" -eq 0 ]; then 225 log "TPM has successfully been reset to factory defaults" 226else 227 log_error "TPM was not fully recovered." 228 exit 1 229fi 230