1#!/bin/sh -ue
2# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5#
6# Usage:  dev_debug_vboot [ --cleanup | DIRECTORY ]
7#
8# This extracts some useful debugging information about verified boot. A short
9# summary is printed on stdout, more detailed information and working files are
10# left in a log directory.
11#
12##############################################################################
13
14# Clean up PATH for root use. Note that we're assuming [ is always built-in.
15[ "${EUID:-0}" = 0 ] && PATH=/bin:/sbin:/usr/bin:/usr/sbin
16
17PUBLOGFILE="/var/log/debug_vboot_noisy.log"
18
19OPT_CLEANUP=
20OPT_BIOS=
21OPT_FORCE=
22OPT_IMAGE=
23OPT_KERNEL=
24OPT_VERBOSE=
25
26FLAG_SAVE_LOG_FILE=yes
27
28LOGFILE=/dev/stdout
29TMPDIR=
30
31##############################################################################
32
33usage() {
34  local prog
35
36  prog=${0##*/}
37  cat <<EOF
38
39Usage: $prog [options] [DIRECTORY]
40
41This logs as much as it can about the verified boot process. With no arguments
42it will attempt to read the current BIOS, extract the firmware keys, and use
43those keys to validate all the ChromeOS kernel partitions it can find. A
44summary output is printed on stdout, and the detailed log is copied to
45$PUBLOGFILE afterwards.
46
47If a directory is given, it will attempt to use the components from that
48directory and will leave the detailed log in that directory.
49
50Options:
51
52   -b FILE, --bios FILE        Specify the BIOS image to use
53   -i FILE, --image FILE       Specify the disk image to use
54   -k FILE, --kernel FILE      Specify the kernel partition image to use
55   -v                          Spew the detailed log to stdout
56
57   -c, --cleanup               Delete the DIRECTORY when done
58
59   -h, --help                  Print this help message and exit
60
61EOF
62exit 0
63}
64
65cleanup() {
66  if [ -n "${FLAG_SAVE_LOG_FILE}" ]; then
67    if cp -f "${LOGFILE}" "${PUBLOGFILE}" 2>/dev/null; then
68      info "Exporting log file as ${PUBLOGFILE}"
69    fi
70  fi
71  if [ -n "${OPT_CLEANUP}" ] && [ -d "${TMPDIR}" ] ; then
72    cd /
73    rm -rf "${TMPDIR}"
74  fi
75}
76
77die() {
78  echo "$*" 1>&2
79  exit 1
80}
81
82info() {
83  echo "$@"
84  echo "#" "$@" >> "$LOGFILE"
85}
86
87infon() {
88  echo -n "$@"
89  echo "#" "$@" >> "$LOGFILE"
90}
91
92debug() {
93  echo "#" "$@" >> "$LOGFILE"
94}
95
96log() {
97  echo "+" "$@" >> "$LOGFILE"
98  "$@" >> "$LOGFILE" 2>&1
99}
100
101loghead() {
102  echo "+" "$@" "| head" >> "$LOGFILE"
103  "$@" | head >> "$LOGFILE" 2>&1
104}
105
106logdie() {
107  echo "+ERROR:" "$@" >> "$LOGFILE"
108  die "$@"
109}
110
111result() {
112  LAST_RESULT=$?
113  if [ "${LAST_RESULT}" = "0" ]; then
114    info "OK"
115  else
116    info "FAILED"
117  fi
118}
119
120require_utils() {
121  local missing
122
123  missing=
124  for tool in $* ; do
125    if ! type "$tool" >/dev/null 2>&1 ; then
126      missing="$missing $tool"
127    fi
128  done
129  if [ -n "$missing" ]; then
130    logdie "can't find these programs: $missing"
131  fi
132}
133
134extract_kerns_from_file() {
135  local start
136  local size
137  local part
138  local rest
139
140  debug "Extracting kernel partitions from $1 ..."
141  cgpt find -v -t kernel "$1" | grep 'Label:' |
142    while read start size part rest; do
143      name="part_${part}"
144      log dd if="$1" bs=512 skip=${start} count=${size} of="${name}" &&
145        echo "${name}"
146    done
147}
148
149format_as_tpm_version() {
150  local a
151  local b
152  local what
153  local num
154  local rest
155
156  a='/(Data|Kernel) key version/ {print $1,$4}'
157  b='/Kernel version/ {print $1, $3}'
158  awk "$a $b" "$1" | while read what num rest; do
159    [ "${what}" = "Data" ] && block="${num}"
160    [ "${what}" = "Kernel" ] && printf '0x%04x%04x' "${block}" "${num}"
161  done
162}
163
164fix_old_names() {
165  # Convert any old-style names to new-style
166  [ -f GBB_Area ]        && log mv -f GBB_Area GBB
167  [ -f Firmware_A_Key ]  && log mv -f Firmware_A_Key VBLOCK_A
168  [ -f Firmware_B_Key ]  && log mv -f Firmware_B_Key VBLOCK_B
169  [ -f Firmware_A_Data ] && log mv -f Firmware_A_Data FW_MAIN_A
170  [ -f Firmware_B_Data ] && log mv -f Firmware_B_Data FW_MAIN_B
171  true
172}
173
174##############################################################################
175# Here we go...
176
177umask 022
178
179# defaults
180DEV_DEBUG_FORCE=
181
182# override them?
183[ -f /etc/default/vboot_reference ] && . /etc/default/vboot_reference
184
185# Pre-parse args to replace actual args with a sanitized version.
186TEMP=$(getopt -o hvb:i:k:cf --long help,bios:,image:,kernel:,cleanup,force \
187       -n $0 -- "$@")
188eval set -- "$TEMP"
189
190# Now look at them.
191while true ; do
192  case "${1:-}" in
193    -b|--bios)
194      OPT_BIOS=$(readlink -f "$2")
195      shift 2
196      FLAG_SAVE_LOG_FILE=
197      ;;
198    -i|--image=*)
199      OPT_IMAGE=$(readlink -f "$2")
200      shift 2
201      FLAG_SAVE_LOG_FILE=
202      ;;
203    -k|--kernel)
204      OPT_KERNEL=$(readlink -f "$2")
205      shift 2
206      FLAG_SAVE_LOG_FILE=
207      ;;
208    -c|--cleanup)
209      OPT_CLEANUP=yes
210      shift
211      ;;
212    -f|--force)
213      OPT_FORCE=yes
214      shift
215      ;;
216    -v)
217      OPT_VERBOSE=yes
218      shift
219      FLAG_SAVE_LOG_FILE=
220      ;;
221    -h|--help)
222      usage
223      break
224      ;;
225    --)
226      shift
227      break
228      ;;
229    *)
230      die "Internal error in option parsing"
231      ;;
232  esac
233done
234
235if [ -z "${1:-}" ]; then
236  TMPDIR=$(mktemp -d /tmp/debug_vboot_XXXXXXXXX)
237else
238  TMPDIR="$1"
239  [ -d ${TMPDIR} ] || die "$TMPDIR doesn't exist"
240  FLAG_SAVE_LOG_FILE=
241fi
242[ -z "${OPT_VERBOSE}" ] && LOGFILE="${TMPDIR}/noisy.log"
243
244[ -d ${TMPDIR} ] || mkdir -p ${TMPDIR} || exit 1
245cd ${TMPDIR} || exit 1
246echo "Running $0 $*" > "$LOGFILE"
247log date
248debug "DEV_DEBUG_FORCE=($DEV_DEBUG_FORCE)"
249debug "OPT_CLEANUP=($OPT_CLEANUP)"
250debug "OPT_BIOS=($OPT_BIOS)"
251debug "OPT_FORCE=($OPT_FORCE)"
252debug "OPT_IMAGE=($OPT_IMAGE)"
253debug "OPT_KERNEL=($OPT_KERNEL)"
254debug "FLAG_SAVE_LOG_FILE=($FLAG_SAVE_LOG_FILE)"
255echo "Saving verbose log as $LOGFILE"
256trap cleanup EXIT
257
258if [ -n "${DEV_DEBUG_FORCE}" ] && [ -z "${OPT_FORCE}" ]; then
259  info "Not gonna do anything without the --force option."
260  exit 0
261fi
262
263
264# Make sure we have the programs we need
265need="futility"
266[ -z "${OPT_BIOS}" ] && need="$need flashrom"
267[ -z "${OPT_KERNEL}" ] && need="$need cgpt"
268require_utils $need
269
270
271# Assuming we're on a ChromeOS device, see what we know.
272set +e
273log crossystem --all
274log rootdev -s
275log ls -aCF /root
276log ls -aCF /mnt/stateful_partition
277devs=$(awk '/(mmcblk[0-9])$|(sd[a-z])$/ {print "/dev/"$4}' /proc/partitions)
278for d in $devs; do
279  log cgpt show $d
280done
281log flashrom -V -p host --wp-status
282tpm_fwver=$(crossystem tpm_fwver) || tpm_fwver="UNKNOWN"
283tpm_kernver=$(crossystem tpm_kernver) || tpm_kernver="UNKNOWN"
284set -e
285
286
287info "Extracting BIOS components..."
288if [ -n "${OPT_BIOS}" ]; then
289  # If we've already got a file, just extract everything.
290  log futility dump_fmap -x "${OPT_BIOS}"
291  fix_old_names
292else
293  # First try pulling just the components we want (using new-style names)
294  if log flashrom -p host -r /dev/null \
295    -i"GBB":GBB \
296    -i"FMAP":FMAP \
297    -i"VBLOCK_A":VBLOCK_A \
298    -i"VBLOCK_B":VBLOCK_B \
299    -i"FW_MAIN_A":FW_MAIN_A \
300    -i"FW_MAIN_B":FW_MAIN_B ; then
301      log futility dump_fmap FMAP
302    else
303      info "Couldn't read individual components. Read the whole thing..."
304      if log flashrom -p host -r bios.rom ; then
305        log futility dump_fmap -x bios.rom
306        fix_old_names
307      else
308        logdie "Can't read BIOS at all. Giving up."
309      fi
310  fi
311fi
312
313info "Pulling root and recovery keys from GBB..."
314log futility gbb_utility -g --rootkey rootkey.vbpubk \
315  --recoverykey recoverykey.vbpubk \
316  "GBB" || logdie "Unable to extract keys from GBB"
317log futility vbutil_key --unpack rootkey.vbpubk
318log futility vbutil_key --unpack recoverykey.vbpubk
319futility vbutil_key --unpack rootkey.vbpubk |
320  grep -q b11d74edd286c144e1135b49e7f0bc20cf041f10 &&
321  info "  Looks like dev-keys"
322# Okay if one of the firmware verifications fails
323set +e
324for fw in A B; do
325  infon "Verify firmware ${fw} with root key: "
326  log futility vbutil_firmware --verify "VBLOCK_${fw}" \
327    --signpubkey rootkey.vbpubk \
328    --fv "FW_MAIN_${fw}" --kernelkey "kern_subkey_${fw}.vbpubk" ; result
329  if [ "${LAST_RESULT}" = "0" ]; then
330    # rerun to get version numbers
331    futility vbutil_firmware --verify "VBLOCK_${fw}" \
332      --signpubkey rootkey.vbpubk \
333      --fv "FW_MAIN_${fw}" > tmp.txt
334    ver=$(format_as_tpm_version tmp.txt)
335    info "  TPM=${tpm_fwver}, this=${ver}"
336  fi
337done
338set -e
339
340info "Examining kernels..."
341if [ -n "${OPT_KERNEL}" ]; then
342  kernparts="${OPT_KERNEL}"
343elif [ -n "${OPT_IMAGE}" ]; then
344  if [ -f "${OPT_IMAGE}" ]; then
345    kernparts=$(extract_kerns_from_file "${OPT_IMAGE}")
346  else
347    kernparts=$(cgpt find -t kernel "${OPT_IMAGE}")
348  fi
349else
350  kernparts=$(cgpt find -t kernel)
351fi
352[ -n "${kernparts}" ] || logdie "No kernels found"
353
354# Okay if any of the kernel verifications fails
355set +e
356kc=0
357for kname in ${kernparts}; do
358  if [ -f "${kname}" ]; then
359    kfile="${kname}"
360  else
361    kfile="kern_${kc}"
362    debug "copying ${kname} to ${kfile}..."
363    log dd if="${kname}" of="${kfile}"
364  fi
365
366  infon "Kernel ${kname}: "
367  log futility vbutil_keyblock --unpack "${kfile}" ; result
368  if [ "${LAST_RESULT}" != "0" ]; then
369    loghead od -Ax -tx1 "${kfile}"
370  else
371    # Test each kernel with each key
372    for key in kern_subkey_A.vbpubk kern_subkey_B.vbpubk recoverykey.vbpubk; do
373      infon "  Verify ${kname} with $key: "
374      log futility vbutil_kernel --verify "${kfile}" --signpubkey "$key" ; result
375      if [ "${LAST_RESULT}" = "0" ]; then
376        # rerun to get version numbers
377        futility vbutil_kernel --verify "${kfile}" --signpubkey "$key" > tmp.txt
378        ver=$(format_as_tpm_version tmp.txt)
379        info "    TPM=${tpm_kernver} this=${ver}"
380      fi
381    done
382  fi
383
384  kc=$(expr $kc + 1)
385done
386
387exit 0
388