xref: /aosp_15_r20/external/vboot_reference/utility/chromeos-tpm-recovery (revision 8617a60d3594060b7ecbd21bc622a7c14f3cf2bc)
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