388 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			388 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
| #!/bin/sh -ue
 | |
| # Copyright (c) 2011 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.
 | |
| #
 | |
| # Usage:  dev_debug_vboot [ --cleanup | DIRECTORY ]
 | |
| #
 | |
| # This extracts some useful debugging information about verified boot. A short
 | |
| # summary is printed on stdout, more detailed information and working files are
 | |
| # left in a log directory.
 | |
| #
 | |
| ##############################################################################
 | |
| 
 | |
| # Clean up PATH for root use. Note that we're assuming [ is always built-in.
 | |
| [ "${EUID:-0}" = 0 ] && PATH=/bin:/sbin:/usr/bin:/usr/sbin
 | |
| 
 | |
| PUBLOGFILE="/var/log/debug_vboot_noisy.log"
 | |
| 
 | |
| OPT_CLEANUP=
 | |
| OPT_BIOS=
 | |
| OPT_FORCE=
 | |
| OPT_IMAGE=
 | |
| OPT_KERNEL=
 | |
| OPT_VERBOSE=
 | |
| 
 | |
| FLAG_SAVE_LOG_FILE=yes
 | |
| 
 | |
| LOGFILE=/dev/stdout
 | |
| TMPDIR=
 | |
| 
 | |
| ##############################################################################
 | |
| 
 | |
| usage() {
 | |
|   local prog
 | |
| 
 | |
|   prog=${0##*/}
 | |
|   cat <<EOF
 | |
| 
 | |
| Usage: $prog [options] [DIRECTORY]
 | |
| 
 | |
| This logs as much as it can about the verified boot process. With no arguments
 | |
| it will attempt to read the current BIOS, extract the firmware keys, and use
 | |
| those keys to validate all the ChromeOS kernel partitions it can find. A
 | |
| summary output is printed on stdout, and the detailed log is copied to
 | |
| $PUBLOGFILE afterwards.
 | |
| 
 | |
| If a directory is given, it will attempt to use the components from that
 | |
| directory and will leave the detailed log in that directory.
 | |
| 
 | |
| Options:
 | |
| 
 | |
|    -b FILE, --bios FILE        Specify the BIOS image to use
 | |
|    -i FILE, --image FILE       Specify the disk image to use
 | |
|    -k FILE, --kernel FILE      Specify the kernel partition image to use
 | |
|    -v                          Spew the detailed log to stdout
 | |
| 
 | |
|    -c, --cleanup               Delete the DIRECTORY when done
 | |
| 
 | |
|    -h, --help                  Print this help message and exit
 | |
| 
 | |
| EOF
 | |
| exit 0
 | |
| }
 | |
| 
 | |
| cleanup() {
 | |
|   if [ -n "${FLAG_SAVE_LOG_FILE}" ]; then
 | |
|     if cp -f "${LOGFILE}" "${PUBLOGFILE}" 2>/dev/null; then
 | |
|       info "Exporting log file as ${PUBLOGFILE}"
 | |
|     fi
 | |
|   fi
 | |
|   if [ -n "${OPT_CLEANUP}" ] && [ -d "${TMPDIR}" ] ; then
 | |
|     cd /
 | |
|     rm -rf "${TMPDIR}"
 | |
|   fi
 | |
| }
 | |
| 
 | |
| die() {
 | |
|   echo "$*" 1>&2
 | |
|   exit 1
 | |
| }
 | |
| 
 | |
| info() {
 | |
|   echo "$@"
 | |
|   echo "#" "$@" >> "$LOGFILE"
 | |
| }
 | |
| 
 | |
| infon() {
 | |
|   echo -n "$@"
 | |
|   echo "#" "$@" >> "$LOGFILE"
 | |
| }
 | |
| 
 | |
| debug() {
 | |
|   echo "#" "$@" >> "$LOGFILE"
 | |
| }
 | |
| 
 | |
| log() {
 | |
|   echo "+" "$@" >> "$LOGFILE"
 | |
|   "$@" >> "$LOGFILE" 2>&1
 | |
| }
 | |
| 
 | |
| loghead() {
 | |
|   echo "+" "$@" "| head" >> "$LOGFILE"
 | |
|   "$@" | head >> "$LOGFILE" 2>&1
 | |
| }
 | |
| 
 | |
| logdie() {
 | |
|   echo "+ERROR:" "$@" >> "$LOGFILE"
 | |
|   die "$@"
 | |
| }
 | |
| 
 | |
| result() {
 | |
|   LAST_RESULT=$?
 | |
|   if [ "${LAST_RESULT}" = "0" ]; then
 | |
|     info "OK"
 | |
|   else
 | |
|     info "FAILED"
 | |
|   fi
 | |
| }
 | |
| 
 | |
| require_utils() {
 | |
|   local missing
 | |
| 
 | |
|   missing=
 | |
|   for tool in $* ; do
 | |
|     if ! type "$tool" >/dev/null 2>&1 ; then
 | |
|       missing="$missing $tool"
 | |
|     fi
 | |
|   done
 | |
|   if [ -n "$missing" ]; then
 | |
|     logdie "can't find these programs: $missing"
 | |
|   fi
 | |
| }
 | |
| 
 | |
| extract_kerns_from_file() {
 | |
|   local start
 | |
|   local size
 | |
|   local part
 | |
|   local rest
 | |
| 
 | |
|   debug "Extracting kernel partitions from $1 ..."
 | |
|   cgpt find -v -t kernel "$1" | grep 'Label:' |
 | |
|     while read start size part rest; do
 | |
|       name="part_${part}"
 | |
|       log dd if="$1" bs=512 skip=${start} count=${size} of="${name}" &&
 | |
|         echo "${name}"
 | |
|     done
 | |
| }
 | |
| 
 | |
| format_as_tpm_version() {
 | |
|   local a
 | |
|   local b
 | |
|   local what
 | |
|   local num
 | |
|   local rest
 | |
| 
 | |
|   a='/(Data|Kernel) key version/ {print $1,$4}'
 | |
|   b='/Kernel version/ {print $1, $3}'
 | |
|   awk "$a $b" "$1" | while read what num rest; do
 | |
|     [ "${what}" = "Data" ] && block="${num}"
 | |
|     [ "${what}" = "Kernel" ] && printf '0x%04x%04x' "${block}" "${num}"
 | |
|   done
 | |
| }
 | |
| 
 | |
| fix_old_names() {
 | |
|   # Convert any old-style names to new-style
 | |
|   [ -f GBB_Area ]        && log mv -f GBB_Area GBB
 | |
|   [ -f Firmware_A_Key ]  && log mv -f Firmware_A_Key VBLOCK_A
 | |
|   [ -f Firmware_B_Key ]  && log mv -f Firmware_B_Key VBLOCK_B
 | |
|   [ -f Firmware_A_Data ] && log mv -f Firmware_A_Data FW_MAIN_A
 | |
|   [ -f Firmware_B_Data ] && log mv -f Firmware_B_Data FW_MAIN_B
 | |
|   true
 | |
| }
 | |
| 
 | |
| ##############################################################################
 | |
| # Here we go...
 | |
| 
 | |
| umask 022
 | |
| 
 | |
| # defaults
 | |
| DEV_DEBUG_FORCE=
 | |
| 
 | |
| # override them?
 | |
| [ -f /etc/default/vboot_reference ] && . /etc/default/vboot_reference
 | |
| 
 | |
| # Pre-parse args to replace actual args with a sanitized version.
 | |
| TEMP=$(getopt -o hvb:i:k:cf --long help,bios:,image:,kernel:,cleanup,force \
 | |
|        -n $0 -- "$@")
 | |
| eval set -- "$TEMP"
 | |
| 
 | |
| # Now look at them.
 | |
| while true ; do
 | |
|   case "${1:-}" in
 | |
|     -b|--bios)
 | |
|       OPT_BIOS=$(readlink -f "$2")
 | |
|       shift 2
 | |
|       FLAG_SAVE_LOG_FILE=
 | |
|       ;;
 | |
|     -i|--image=*)
 | |
|       OPT_IMAGE=$(readlink -f "$2")
 | |
|       shift 2
 | |
|       FLAG_SAVE_LOG_FILE=
 | |
|       ;;
 | |
|     -k|--kernel)
 | |
|       OPT_KERNEL=$(readlink -f "$2")
 | |
|       shift 2
 | |
|       FLAG_SAVE_LOG_FILE=
 | |
|       ;;
 | |
|     -c|--cleanup)
 | |
|       OPT_CLEANUP=yes
 | |
|       shift
 | |
|       ;;
 | |
|     -f|--force)
 | |
|       OPT_FORCE=yes
 | |
|       shift
 | |
|       ;;
 | |
|     -v)
 | |
|       OPT_VERBOSE=yes
 | |
|       shift
 | |
|       FLAG_SAVE_LOG_FILE=
 | |
|       ;;
 | |
|     -h|--help)
 | |
|       usage
 | |
|       break
 | |
|       ;;
 | |
|     --)
 | |
|       shift
 | |
|       break
 | |
|       ;;
 | |
|     *)
 | |
|       die "Internal error in option parsing"
 | |
|       ;;
 | |
|   esac
 | |
| done
 | |
| 
 | |
| if [ -z "${1:-}" ]; then
 | |
|   TMPDIR=$(mktemp -d /tmp/debug_vboot_XXXXXXXXX)
 | |
| else
 | |
|   TMPDIR="$1"
 | |
|   [ -d ${TMPDIR} ] || die "$TMPDIR doesn't exist"
 | |
|   FLAG_SAVE_LOG_FILE=
 | |
| fi
 | |
| [ -z "${OPT_VERBOSE}" ] && LOGFILE="${TMPDIR}/noisy.log"
 | |
| 
 | |
| [ -d ${TMPDIR} ] || mkdir -p ${TMPDIR} || exit 1
 | |
| cd ${TMPDIR} || exit 1
 | |
| echo "Running $0 $*" > "$LOGFILE"
 | |
| log date
 | |
| debug "DEV_DEBUG_FORCE=($DEV_DEBUG_FORCE)"
 | |
| debug "OPT_CLEANUP=($OPT_CLEANUP)"
 | |
| debug "OPT_BIOS=($OPT_BIOS)"
 | |
| debug "OPT_FORCE=($OPT_FORCE)"
 | |
| debug "OPT_IMAGE=($OPT_IMAGE)"
 | |
| debug "OPT_KERNEL=($OPT_KERNEL)"
 | |
| debug "FLAG_SAVE_LOG_FILE=($FLAG_SAVE_LOG_FILE)"
 | |
| echo "Saving verbose log as $LOGFILE"
 | |
| trap cleanup EXIT
 | |
| 
 | |
| if [ -n "${DEV_DEBUG_FORCE}" ] && [ -z "${OPT_FORCE}" ]; then
 | |
|   info "Not gonna do anything without the --force option."
 | |
|   exit 0
 | |
| fi
 | |
| 
 | |
| 
 | |
| # Make sure we have the programs we need
 | |
| need="futility"
 | |
| [ -z "${OPT_BIOS}" ] && need="$need flashrom"
 | |
| [ -z "${OPT_KERNEL}" ] && need="$need cgpt"
 | |
| require_utils $need
 | |
| 
 | |
| 
 | |
| # Assuming we're on a ChromeOS device, see what we know.
 | |
| set +e
 | |
| log crossystem --all
 | |
| log rootdev -s
 | |
| log ls -aCF /root
 | |
| log ls -aCF /mnt/stateful_partition
 | |
| devs=$(awk '/(mmcblk[0-9])$|(sd[a-z])$/ {print "/dev/"$4}' /proc/partitions)
 | |
| for d in $devs; do
 | |
|   log cgpt show $d
 | |
| done
 | |
| log flashrom -V -p host --wp-status
 | |
| tpm_fwver=$(crossystem tpm_fwver) || tpm_fwver="UNKNOWN"
 | |
| tpm_kernver=$(crossystem tpm_kernver) || tpm_kernver="UNKNOWN"
 | |
| set -e
 | |
| 
 | |
| 
 | |
| info "Extracting BIOS components..."
 | |
| if [ -n "${OPT_BIOS}" ]; then
 | |
|   # If we've already got a file, just extract everything.
 | |
|   log futility dump_fmap -x "${OPT_BIOS}"
 | |
|   fix_old_names
 | |
| else
 | |
|   # First try pulling just the components we want (using new-style names)
 | |
|   if log flashrom -p host -r /dev/null \
 | |
|     -i"GBB":GBB \
 | |
|     -i"FMAP":FMAP \
 | |
|     -i"VBLOCK_A":VBLOCK_A \
 | |
|     -i"VBLOCK_B":VBLOCK_B \
 | |
|     -i"FW_MAIN_A":FW_MAIN_A \
 | |
|     -i"FW_MAIN_B":FW_MAIN_B ; then
 | |
|       log futility dump_fmap FMAP
 | |
|     else
 | |
|       info "Couldn't read individual components. Read the whole thing..."
 | |
|       if log flashrom -p host -r bios.rom ; then
 | |
|         log futility dump_fmap -x bios.rom
 | |
|         fix_old_names
 | |
|       else
 | |
|         logdie "Can't read BIOS at all. Giving up."
 | |
|       fi
 | |
|   fi
 | |
| fi
 | |
| 
 | |
| info "Pulling root and recovery keys from GBB..."
 | |
| log futility gbb_utility -g --rootkey rootkey.vbpubk \
 | |
|   --recoverykey recoverykey.vbpubk \
 | |
|   "GBB" || logdie "Unable to extract keys from GBB"
 | |
| log futility vbutil_key --unpack rootkey.vbpubk
 | |
| log futility vbutil_key --unpack recoverykey.vbpubk
 | |
| futility vbutil_key --unpack rootkey.vbpubk |
 | |
|   grep -q b11d74edd286c144e1135b49e7f0bc20cf041f10 &&
 | |
|   info "  Looks like dev-keys"
 | |
| # Okay if one of the firmware verifications fails
 | |
| set +e
 | |
| for fw in A B; do
 | |
|   infon "Verify firmware ${fw} with root key: "
 | |
|   log futility vbutil_firmware --verify "VBLOCK_${fw}" \
 | |
|     --signpubkey rootkey.vbpubk \
 | |
|     --fv "FW_MAIN_${fw}" --kernelkey "kern_subkey_${fw}.vbpubk" ; result
 | |
|   if [ "${LAST_RESULT}" = "0" ]; then
 | |
|     # rerun to get version numbers
 | |
|     futility vbutil_firmware --verify "VBLOCK_${fw}" \
 | |
|       --signpubkey rootkey.vbpubk \
 | |
|       --fv "FW_MAIN_${fw}" > tmp.txt
 | |
|     ver=$(format_as_tpm_version tmp.txt)
 | |
|     info "  TPM=${tpm_fwver}, this=${ver}"
 | |
|   fi
 | |
| done
 | |
| set -e
 | |
| 
 | |
| info "Examining kernels..."
 | |
| if [ -n "${OPT_KERNEL}" ]; then
 | |
|   kernparts="${OPT_KERNEL}"
 | |
| elif [ -n "${OPT_IMAGE}" ]; then
 | |
|   if [ -f "${OPT_IMAGE}" ]; then
 | |
|     kernparts=$(extract_kerns_from_file "${OPT_IMAGE}")
 | |
|   else
 | |
|     kernparts=$(cgpt find -t kernel "${OPT_IMAGE}")
 | |
|   fi
 | |
| else
 | |
|   kernparts=$(cgpt find -t kernel)
 | |
| fi
 | |
| [ -n "${kernparts}" ] || logdie "No kernels found"
 | |
| 
 | |
| # Okay if any of the kernel verifications fails
 | |
| set +e
 | |
| kc=0
 | |
| for kname in ${kernparts}; do
 | |
|   if [ -f "${kname}" ]; then
 | |
|     kfile="${kname}"
 | |
|   else
 | |
|     kfile="kern_${kc}"
 | |
|     debug "copying ${kname} to ${kfile}..."
 | |
|     log dd if="${kname}" of="${kfile}"
 | |
|   fi
 | |
| 
 | |
|   infon "Kernel ${kname}: "
 | |
|   log futility vbutil_keyblock --unpack "${kfile}" ; result
 | |
|   if [ "${LAST_RESULT}" != "0" ]; then
 | |
|     loghead od -Ax -tx1 "${kfile}"
 | |
|   else
 | |
|     # Test each kernel with each key
 | |
|     for key in kern_subkey_A.vbpubk kern_subkey_B.vbpubk recoverykey.vbpubk; do
 | |
|       infon "  Verify ${kname} with $key: "
 | |
|       log futility vbutil_kernel --verify "${kfile}" --signpubkey "$key" ; result
 | |
|       if [ "${LAST_RESULT}" = "0" ]; then
 | |
|         # rerun to get version numbers
 | |
|         futility vbutil_kernel --verify "${kfile}" --signpubkey "$key" > tmp.txt
 | |
|         ver=$(format_as_tpm_version tmp.txt)
 | |
|         info "    TPM=${tpm_kernver} this=${ver}"
 | |
|       fi
 | |
|     done
 | |
|   fi
 | |
| 
 | |
|   kc=$(expr $kc + 1)
 | |
| done
 | |
| 
 | |
| exit 0
 |