summaryrefslogtreecommitdiff
path: root/utility/chromeos-tpm-recovery
blob: 12616b41c9d3258ebdb2c94ba23b5ca38512bf19 (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
#!/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