#!/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. # # 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 crossystem=${USR_BIN}/crossystem dot_recovery=${DOT_RECOVERY:=/mnt/stateful_partition/.recovery} awk=/usr/bin/awk initctl=/sbin/initctl daemon_was_running= err=0 secdata_firmware=0x1007 secdata_kernel=0x1008 tpm2_target() { # This is not an ideal way to tell if we are running on a tpm2 target, but # it will have to do for now. if [ -f "/etc/init/trunksd.conf" ]; then return 0 else return 1 fi } use_v0_secdata_kernel() { local fwid=$(crossystem ro_fwid) local major=$(printf "$fwid" | cut -d. -f2) local minor=$(printf "$fwid" | cut -d. -f3) # TPM1 firmware never supports the v1 kernel space format. if ! tpm2_target; then return 0 fi # First some validity checks: X -eq X checks that X is a number. cut may # return the whole string if no delimiter found, so major != minor checks that # the version was at least somewhat correctly formatted. if [ $major -eq $major ] && [ $minor -eq $minor ] && [ $major -ne $minor ]; then # Now what we really care about: is this firmware older than CL:2041695? if [ $major -lt 12953 ]; then return 0 else return 1 fi else log "Cannot parse FWID. Assuming local build that supports v1 kernel space." return 1 fi } log() { echo "$*" } quit() { log "ERROR: $*" restart_daemon_if_needed log "exiting" exit 1 } log_tryfix() { log "$*: attempting to fix" } log_error() { err=$((err + 1)) log "ERROR: $*" } log_warn() { log "WARNING: $*" } tpm_clear_and_reenable () { $tpmc clear # The below commands are are no-op on tpm2, but let's keep them here for # both TPM versions in case they are implemented in the future for # version 2. $tpmc enable $tpmc activate } write_space () { # do not quote "$2", as we mean to expand it here if ! $tpmc write $1 $2; then log_error "writing to $1 failed" else log "$1 written successfully" fi } reset_ro_space () { local index=$1 local bytes="$2" local size=$(printf "$bytes" | wc -w) local permissions=0x8001 if tpm2_target; then log "Cannot redefine RO space for TPM2 (b/140958855). Let's just hope it looks good..." else if ! $tpmc definespace $index $size $permissions; then log_error "could not redefine RO space $index" # try writing it anyway, just in case it works... fi fi write_space $index "$bytes" } reset_rw_space () { local index=$1 local bytes="$2" local size=$(printf "$bytes" | wc -w) local permissions=0x1 if tpm2_target; then permissions=0x40050001 fi if ! $tpmc definespace $index $size $permissions; then log_error "could not redefine RW space $index" # try writing it anyway, just in case it works... fi write_space $index "$bytes" } restart_daemon_if_needed() { if [ "$daemon_was_running" = 1 ]; then log "Restarting ${DAEMON}..." $initctl start "${DAEMON}" >/dev/null fi } # ------------ # MAIN PROGRAM # ------------ # validity check: are we executing in a recovery image? if [ -e $dot_recovery ]; then quit "This is a developer utility, it should never run on a (production) recovery image" fi # Did the firmware keep the TPM unlocked? if ! $($crossystem mainfw_type?recovery); then 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" fi if tpm2_target; then DAEMON="trunksd" else DAEMON="tcsd" fi # TPM daemon may or may not be running log "Stopping ${DAEMON}..." if $initctl stop "${DAEMON}" >/dev/null 2>/dev/null; then daemon_was_running=1 log "done" else daemon_was_running=0 log "(was not running)" fi # Is the state of the PP enable flags correct? if ! tpm2_target; then 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 else if ! $tpmc getvf | grep -q 'phEnable 1'; then quit "Platform Hierarchy is disabled, TPM can't be recovered" fi fi # I never learned what this does, but it's probably good just in case... tpm_clear_and_reenable # Reset firmware and kernel spaces to default (rollback version 1/1) reset_ro_space $secdata_firmware "02 0 1 0 1 0 0 0 0 4f" if use_v0_secdata_kernel; then reset_rw_space $secdata_kernel "02 4c 57 52 47 1 0 1 0 0 0 0 55" else 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" fi restart_daemon_if_needed if [ "$err" -eq 0 ]; then log "TPM has successfully been reset to factory defaults" else log_error "TPM was not fully recovered." exit 1 fi