Home | History | Annotate | Download | only in utility
      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 
     17 PUBLOGFILE="/var/log/debug_vboot_noisy.log"
     18 
     19 OPT_CLEANUP=
     20 OPT_BIOS=
     21 OPT_FORCE=
     22 OPT_IMAGE=
     23 OPT_KERNEL=
     24 OPT_VERBOSE=
     25 
     26 FLAG_SAVE_LOG_FILE=yes
     27 
     28 LOGFILE=/dev/stdout
     29 TMPDIR=
     30 
     31 ##############################################################################
     32 
     33 usage() {
     34   local prog
     35 
     36   prog=${0##*/}
     37   cat <<EOF
     38 
     39 Usage: $prog [options] [DIRECTORY]
     40 
     41 This logs as much as it can about the verified boot process. With no arguments
     42 it will attempt to read the current BIOS, extract the firmware keys, and use
     43 those keys to validate all the ChromeOS kernel partitions it can find. A
     44 summary output is printed on stdout, and the detailed log is copied to
     45 $PUBLOGFILE afterwards.
     46 
     47 If a directory is given, it will attempt to use the components from that
     48 directory and will leave the detailed log in that directory.
     49 
     50 Options:
     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 
     61 EOF
     62 exit 0
     63 }
     64 
     65 cleanup() {
     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 
     77 die() {
     78   echo "$*" 1>&2
     79   exit 1
     80 }
     81 
     82 info() {
     83   echo "$@"
     84   echo "#" "$@" >> "$LOGFILE"
     85 }
     86 
     87 infon() {
     88   echo -n "$@"
     89   echo "#" "$@" >> "$LOGFILE"
     90 }
     91 
     92 debug() {
     93   echo "#" "$@" >> "$LOGFILE"
     94 }
     95 
     96 log() {
     97   echo "+" "$@" >> "$LOGFILE"
     98   "$@" >> "$LOGFILE" 2>&1
     99 }
    100 
    101 loghead() {
    102   echo "+" "$@" "| head" >> "$LOGFILE"
    103   "$@" | head >> "$LOGFILE" 2>&1
    104 }
    105 
    106 logdie() {
    107   echo "+ERROR:" "$@" >> "$LOGFILE"
    108   die "$@"
    109 }
    110 
    111 result() {
    112   LAST_RESULT=$?
    113   if [ "${LAST_RESULT}" = "0" ]; then
    114     info "OK"
    115   else
    116     info "FAILED"
    117   fi
    118 }
    119 
    120 require_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 
    134 extract_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 
    149 format_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 
    164 fix_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 
    177 umask 022
    178 
    179 # defaults
    180 DEV_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.
    186 TEMP=$(getopt -o hvb:i:k:cf --long help,bios:,image:,kernel:,cleanup,force \
    187        -n $0 -- "$@")
    188 eval set -- "$TEMP"
    189 
    190 # Now look at them.
    191 while 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
    233 done
    234 
    235 if [ -z "${1:-}" ]; then
    236   TMPDIR=$(mktemp -d /tmp/debug_vboot_XXXXXXXXX)
    237 else
    238   TMPDIR="$1"
    239   [ -d ${TMPDIR} ] || die "$TMPDIR doesn't exist"
    240   FLAG_SAVE_LOG_FILE=
    241 fi
    242 [ -z "${OPT_VERBOSE}" ] && LOGFILE="${TMPDIR}/noisy.log"
    243 
    244 [ -d ${TMPDIR} ] || mkdir -p ${TMPDIR} || exit 1
    245 cd ${TMPDIR} || exit 1
    246 echo "Running $0 $*" > "$LOGFILE"
    247 log date
    248 debug "DEV_DEBUG_FORCE=($DEV_DEBUG_FORCE)"
    249 debug "OPT_CLEANUP=($OPT_CLEANUP)"
    250 debug "OPT_BIOS=($OPT_BIOS)"
    251 debug "OPT_FORCE=($OPT_FORCE)"
    252 debug "OPT_IMAGE=($OPT_IMAGE)"
    253 debug "OPT_KERNEL=($OPT_KERNEL)"
    254 debug "FLAG_SAVE_LOG_FILE=($FLAG_SAVE_LOG_FILE)"
    255 echo "Saving verbose log as $LOGFILE"
    256 trap cleanup EXIT
    257 
    258 if [ -n "${DEV_DEBUG_FORCE}" ] && [ -z "${OPT_FORCE}" ]; then
    259   info "Not gonna do anything without the --force option."
    260   exit 0
    261 fi
    262 
    263 
    264 # Make sure we have the programs we need
    265 need="futility"
    266 [ -z "${OPT_BIOS}" ] && need="$need flashrom"
    267 [ -z "${OPT_KERNEL}" ] && need="$need cgpt"
    268 require_utils $need
    269 
    270 
    271 # Assuming we're on a ChromeOS device, see what we know.
    272 set +e
    273 log crossystem --all
    274 log rootdev -s
    275 log ls -aCF /root
    276 log ls -aCF /mnt/stateful_partition
    277 devs=$(awk '/(mmcblk[0-9])$|(sd[a-z])$/ {print "/dev/"$4}' /proc/partitions)
    278 for d in $devs; do
    279   log cgpt show $d
    280 done
    281 log flashrom -V -p host --wp-status
    282 tpm_fwver=$(crossystem tpm_fwver) || tpm_fwver="UNKNOWN"
    283 tpm_kernver=$(crossystem tpm_kernver) || tpm_kernver="UNKNOWN"
    284 set -e
    285 
    286 
    287 info "Extracting BIOS components..."
    288 if [ -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
    292 else
    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
    311 fi
    312 
    313 info "Pulling root and recovery keys from GBB..."
    314 log futility gbb_utility -g --rootkey rootkey.vbpubk \
    315   --recoverykey recoverykey.vbpubk \
    316   "GBB" || logdie "Unable to extract keys from GBB"
    317 log futility vbutil_key --unpack rootkey.vbpubk
    318 log futility vbutil_key --unpack recoverykey.vbpubk
    319 futility vbutil_key --unpack rootkey.vbpubk |
    320   grep -q b11d74edd286c144e1135b49e7f0bc20cf041f10 &&
    321   info "  Looks like dev-keys"
    322 # Okay if one of the firmware verifications fails
    323 set +e
    324 for 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
    337 done
    338 set -e
    339 
    340 info "Examining kernels..."
    341 if [ -n "${OPT_KERNEL}" ]; then
    342   kernparts="${OPT_KERNEL}"
    343 elif [ -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
    349 else
    350   kernparts=$(cgpt find -t kernel)
    351 fi
    352 [ -n "${kernparts}" ] || logdie "No kernels found"
    353 
    354 # Okay if any of the kernel verifications fails
    355 set +e
    356 kc=0
    357 for 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)
    385 done
    386 
    387 exit 0
    388