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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
|
#!/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.
#
# Usage: chromeos-tpm-recovery <log file>
#
# 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
nvtool=${USR_LOCAL_BIN:=/usr/local/bin}/tpm-nvtool
tpm_takeownership=${USR_LOCAL_SBIN:=/usr/local/sbin}/tpm_takeownership
tcsd=${USR_SBIN:=/usr/sbin}/tcsd
dot_recovery=${DOT_RECOVERY:=/mnt/stateful_partition/.recovery}
acpi=${ACPI_DIR:=/sys/devices/platform/chromeos_acpi}
awk=/usr/bin/awk
# At the time this script starts, we assume the following holds:
#
# - TPM may be owned, but not with the well-known password
# - tcsd has not been started
tpm_owned_with_well_known_password=0
tpm_unowned=0
tcsd_pid=0
log() {
echo "$(date): $*" >> $RECOVERY_LOG
}
quit() {
log "ERROR: $*"
log "exiting"
exit 1
}
log_tryfix() {
log "$*: attempting to fix"
}
# bit <n> <i> outputs bit i of number n, with bit 0 being the lsb.
bit () {
echo $(( ( $1 >> $2 ) & 1 ))
}
ensure_tcsd_is_running () {
if [ $tcsd_pid = 0 ]; then
$tcsd -f &
tcsd_pid=$!
sleep 2 # give tcsd time to initialize
fi
}
ensure_tcsd_is_not_running () {
if [ $tcsd_pid != 0 ]; then
kill $tcsd_pid
sleep 0.5
kill $tcsd_pid > /dev/null 2>&1
sleep 0.5
wait $tcsd_pid > /dev/null 2>&1 # we trust that tcsd will agree to die
tcsd_pid=0
fi
}
tpm_clear_and_reenable () {
ensure_tcsd_is_not_running
$tpmc clear
$tpmc enable
$tpmc activate
tpm_owned_with_well_known_password=0
tpm_unowned=1
}
# We want the TPM owned with the well-known password.
ensure_tpm_is_owned () {
if [ $tpm_owned_with_well_known_password = 0 ]; then
tpm_clear_and_reenable
ensure_tcsd_is_running
$tpm_takeownership -y -z || log "takeownership failed with status $?"
tpm_owned_with_well_known_password=1
tpm_unowned=0
fi
}
ensure_tpm_is_unowned () {
if [ $tpm_unowned = 0 ]; then
tpm_clear_and_reenable
fi
}
remove_space () {
index=$1
log "removing space $index"
ensure_tpm_is_owned
ensure_tcsd_is_running
$nvtool --release --index "$index" --owner_password "" >> $RECOVERY_LOG 2>&1
log "nvtool --release: status $?"
}
# Makes some room by removing a TPM space it doesn't recognize. It would be
# nice to let the user choose which space, but we may not have a UI.
make_room () {
# Check NVRAM spaces.
AWK_PROGRAM=/tmp/tpm_recovery_$$.awk
cat > $AWK_PROGRAM <<"EOF"
/# NV Index 0xffffffff/ { next } # NV_INDEX_LOCK
/# NV Index 0x00000000/ { next } # NV_INDEX0
/# NV Index 0x00000001/ { next } # NV_INDEX_DIR
/# NV Index 0x0000f.../ { next } # reserved for TPM use
/# NV Index 0x0001..../ { next } # reserved for TCG WGs
/# NV Index 0x00001007/ { next } # firmware space index
/# NV Index 0x00001008/ { next } # kernel space index
/# NV Index / { print $4 } #unexpected space
EOF
local index
log "trying to make room by freeing one space"
ensure_tcsd_is_running
ensure_tpm_is_owned
unexpected_spaces=$($nvtool --list | $awk -f $AWK_PROGRAM)
status=1
if [ "$unexpected_spaces" != "" ]; then
log_tryfix "unexpected spaces: $unexpected_spaces"
for index in $unexpected_spaces; do
log "trying to remove space $index"
if remove_space $(printf "0x%x" $(( $index )) ); then
status=0
break;
fi
done
fi
return $status
}
# define_space <index> <size> <permissions>
define_space () {
local index=$1
local size=$2
local permissions=$3
# 0xf004 is for testing if there is enough room without side effects.
local test_space=0xf004
local perm_ppwrite=0x1
local enough_room
ensure_tpm_is_unowned
while true; do
log "checking for NVRAM room for space with size $size"
if $tpmc definespace $test_space $size $perm_ppwrite; then
log "there is enough room"
enough_room=1
break
else
log "definespace $test_space $size failed with status $?"
if ! make_room; then
enough_room=0
break
fi
fi
done
if [ $enough_room -eq 0 ]; then
log "not enough room to define space $index"
return 1
fi
$tpmc definespace $index $size $permissions
}
fix_space () {
local index=$1
local permissions=$2
local size=$3
local bytes="$4"
local space_exists=1
ensure_tcsd_is_not_running
observed_permissions=$($tpmc getp $index | $awk '{print $5;}')
if [ $? -ne 0 ]; then
space_exists=0
fi
# Check kernel space ID.
if [ $space_exists -eq 1 -a $index = 0x1008 ]; then
if ! $tpmc read 0x1008 0x5 | grep -q " 4c 57 52 47[ ]*$"; then
log "bad kernel space id"
remove_space $index
space_exists=0
fi
fi
# Check that space is large enough (we don't care if it's larger)
if [ $space_exists -eq 1 ]; then
if ! $tpmc read $index $size > /dev/null; then
log "space $index read of size $size failed"
remove_space $index
space_exists=0
fi
fi
# If space exists but permissions are bad, delete the space.
if [ $space_exists -eq 1 -a $observed_permissions != $permissions ]; then
log "space $index has unexpected permissions $permissions"
remove_space $index
space_exists=0
fi
# If space does not exist, reconstruct it.
if [ $space_exists -eq 0 ]; then
log_tryfix "space $index is gone"
if ! define_space $index $size $permissions; then
log "could not redefine space $index"
return 1
fi
# do not quote "$bytes", as we mean to expand it here
$tpmc write $index $bytes || log "writing to $index failed with code $?"
log "space $index was recreated successfully"
fi
}
# ------------
# MAIN PROGRAM
# ------------
# Set up logging and announce ourselves.
if [ $# = 1 ]; then
RECOVERY_LOG="$1"
/usr/bin/logger "$0 started, output in $RECOVERY_LOG"
log "starting $0"
else
/usr/bin/logger "$0 usage error"
echo "usage: $0 <log file>"
exit 1
fi
# Sanity check: are we executing in a recovery image?
if [ ! -e $dot_recovery ]; then
quit "not a recovery image"
fi
# Mnemonic: "B, I, N, F, O, and BINFO was his name-o."
# Except it's a zero (0), not an O.
BINF0=$acpi/BINF.0
CHSW=$acpi/CHSW
# There is no point running unless this a ChromeOS device.
if [ ! -e $BINF0 ]; then
log "not a chromeos device, exiting"
exit 0
fi
BOOT_REASON=$(cat $BINF0)
log "boot reason is $BOOT_REASON"
# Sanity check: did we boot in recovery mode?
if ! echo $BOOT_REASON | grep -q "^[345678]$"; then
quit "unexpected boot reason $BOOT_REASON"
fi
# Do we even have these tools in the image?
if [ ! -e $tpmc -o ! -e $nvtool -o ! -e $tpm_takeownership ]; then
quit "tpmc or nvtool or tpm_takeownership are missing"
fi
# Is the state of the PP enable flags correct?
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
DEV_MODE_NOW=$(bit $(cat $CHSW) 4)
DEV_MODE_AT_BOOT=$(bit $(cat $CHSW) 5)
# Check that bGlobalLock is unset
if [ $DEV_MODE_NOW != $DEV_MODE_AT_BOOT ]; then
# this is either too weird or malicious, so we give up
quit "dev mode is $DEV_MODE_NOW, but was $DEV_MODE_AT_BOOT at boot"
fi
BGLOBALLOCK=$($tpmc getvf | $awk '/bGlobalLock/ {print $2;}')
if [ 0 -ne $BGLOBALLOCK ]; then
# this indicates either TPM malfunction or firmware malfunction.
log "bGlobalLock is $BGLOBALLOCK (dev mode is $DEV_MODE_NOW)."
fi
# Check firmware and kernel spaces
fix_space 0x1007 0x8001 0xa "01 00 00 00 00 00 00 00 00 00" || \
log "could not fix firmware space"
fix_space 0x1008 0x1 0xd "01 4c 57 52 47 00 00 00 00 00 00 00 00" || \
log "could not fix kernel space"
# Cleanup: don't leave the tpm owned with the well-known password.
if [ $tpm_owned_with_well_known_password -eq 1 ]; then
tpm_clear_and_reenable
fi
ensure_tcsd_is_not_running
log "tpm recovery has completed"
|