summaryrefslogtreecommitdiff
path: root/utility/chromeos-tpm-recovery
blob: 5bb2ec6aa86da579f18ece91292fdaf5f4345e9d (plain)
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"