1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
|
#!/bin/sh -u
# Copyright 2010 The ChromiumOS Authors
# 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
|