| #!/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 |