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