diff options
author | Bill Richardson <wfrichar@chromium.org> | 2011-11-08 10:06:06 -0800 |
---|---|---|
committer | Bill Richardson <wfrichar@chromium.org> | 2011-11-09 16:38:15 -0800 |
commit | 9b717be86ba6155a7542bf1649dd3ab2dbc2dc3b (patch) | |
tree | ae12e6a45c6444e0a5de566f9c4c05fa03902c11 | |
parent | f1282d321f40ab52e696795b28ba77a2083edb5f (diff) | |
download | vboot-9b717be86ba6155a7542bf1649dd3ab2dbc2dc3b.tar.gz |
New and improved dev_debug_vboot
This new version adds a bunch more output, displays the TPM rollback version
values (if it can; Cr-48 doesn't export this info through crossystem), looks
for and validates all kernels on all devices, etc.
It also add some command-line arguments to use to examine files containing
BIOS, kernel, and disk images.
BUG=chromium-os:6676
TEST=manual
Boot, wait a minute or so, then log in and go to chrome://system
Click the Expand button for "verified boot". You should see a bunch of
useful text describing the firmware and kernel partitions.
I tried this on Cr-48, Stumpy, and Kaen.
Change-Id: I2d9aa0fcb0c12cf2b951ce9e2316b89532901125
Reviewed-on: https://gerrit.chromium.org/gerrit/11327
Reviewed-by: Bill Richardson <wfrichar@chromium.org>
Tested-by: Bill Richardson <wfrichar@chromium.org>
-rwxr-xr-x | utility/dev_debug_vboot | 401 |
1 files changed, 270 insertions, 131 deletions
diff --git a/utility/dev_debug_vboot b/utility/dev_debug_vboot index 2f0b643b..760d6f86 100755 --- a/utility/dev_debug_vboot +++ b/utility/dev_debug_vboot @@ -1,4 +1,4 @@ -#!/bin/sh -u +#!/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. @@ -9,30 +9,65 @@ # summary is printed on stdout, more detailed information and working files are # left in a log directory. # +############################################################################## -PATH=/bin:/sbin:/usr/bin:/usr/sbin +# 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 -TMPDIR=$(mktemp -d /tmp/debug_vboot_XXXXXXXXX) -LOGFILE="${TMPDIR}/noisy.log" PUBLOGFILE="/var/log/debug_vboot_noisy.log" -ARCH="$(crossystem arch)" -ROOT_BASE=$(rootdev -s -d) +OPT_CLEANUP= +OPT_BIOS= +OPT_IMAGE= +OPT_KERNEL= +OPT_VERBOSE= -if [ "${ARCH}" = "arm" ]; then - HD='/dev/mmcblk0' - PARTITION_PREFIX='p' -else - HD='/dev/sda' - PARTITION_PREFIX='' -fi +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 [ -z "${USE_EXISTING:-}" ]; then - cp -f "${LOGFILE}" "${PUBLOGFILE}" - info "exporting log file as ${PUBLOGFILE}" + 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 "${CLEANUP:-}" ]; then + if [ -n "${OPT_CLEANUP}" ] && [ -d "${TMPDIR}" ] ; then cd / rm -rf "${TMPDIR}" fi @@ -53,6 +88,10 @@ infon() { echo "#" "$@" >> "$LOGFILE" } +debug() { + echo "#" "$@" >> "$LOGFILE" +} + log() { echo "+" "$@" >> "$LOGFILE" "$@" >> "$LOGFILE" 2>&1 @@ -64,161 +103,261 @@ loghead() { } logdie() { - echo "+" "$@" >> "$LOGFILE" + echo "+ERROR:" "$@" >> "$LOGFILE" die "$@" } result() { - if [ "$?" = "0" ]; then + LAST_RESULT=$? + if [ "${LAST_RESULT}" = "0" ]; then info "OK" else info "FAILED" fi } -require_chromeos_bios() { - log cgpt show "${HD}" - log rootdev -s - log crossystem --all - log ls -aCF /root - log ls -aCF /mnt/stateful_partition -} +require_utils() { + local missing -# Search for files from the FMAP, in the order listed. Return the first one -# found or die if none are there. -find_name() { - for fn in "$@"; do - if [ -e "$fn" ]; then - echo "$fn" - return + missing= + for tool in $* ; do + if ! type "$tool" >/dev/null 2>&1 ; then + missing="$missing $tool" fi done - echo "+ no files named $@" >> "$LOGFILE" - exit 1 + if [ -n "$missing" ]; then + logdie "can't find these programs: $missing" + fi } -# Iterate through block devices, as soon as the first removable USB device -# which is not mounted as root file system is found - consider it the USB -# flash stick and return its device name. -find_usb_device() { - dm_dev=$(rootdev | cut -f3 -d/) - real_dev=$(echo ${ROOT_BASE} | cut -f3 -d/) - block_dir='/sys/block' - for d in $(ls ${block_dir}); do - if [ "$d" = "${dm_dev}" -o "$d" = "${real_dev}" ]; then - continue # Skip root file system. - fi - if [ "$(readlink -f ${block_dir}/$d | grep usb)" = "" ]; then - continue # Not a usb device. - fi - r=${block_dir}/$d/removable - if [ -f $r -a "$(cat $r)" = "1" ]; then - echo /dev/$d - return - 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 -trap cleanup EXIT -# Parse args -if [ -n "${1:-}" ]; then - if [ "$1" = "--cleanup" ]; then - CLEANUP=1 - else - TMPDIR="$1" - [ -d ${TMPDIR} ] || die "${TMPDIR} doesn't exist" - USE_EXISTING=yes - fi +# Pre-parse args to replace actual args with a sanitized version. +TEMP=$(getopt -o hvb:i:k:c --long help,bios:,image:,kernel:,cleanup \ + -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 + ;; + -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 "$0 $*" > "$LOGFILE" +echo "Running $0 $*" > "$LOGFILE" log date +debug "OPT_CLEANUP=($OPT_CLEANUP)" +debug "OPT_BIOS=($OPT_BIOS)" +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 -BIOS=bios.rom -# Find BIOS and kernel images -if [ -n "${USE_EXISTING:-}" ]; then - info "Using images in $(pwd)/" -else - require_chromeos_bios - info "Extracting BIOS image from flash..." - log flashrom -p internal:bus=spi --wp-status - log flashrom -p internal:bus=spi -r ${BIOS} +# Make sure we have the programs we need +need="vbutil_key vbutil_keyblock vbutil_kernel vbutil_firmware" +[ -z "${OPT_BIOS}" ] && need="$need flashrom" +[ -z "${OPT_KERNEL}" ] && need="$need cgpt" +require_utils $need - HD_KERN_A="${HD}${PARTITION_PREFIX}2" - HD_KERN_B="${HD}${PARTITION_PREFIX}4" - if [ "${ROOT_BASE}" != "${HD}" ]; then - # Not running off HD, must be ruining off USB. - USB_KERN_A="${ROOT_BASE}2" - else - USB_DEV="$(find_usb_device)" - if [ -n "${USB_DEV}" ]; then - USB_KERN_A="${USB_DEV}2" - fi - fi - info "Extracting kernel images from drives..." - log dd if=${HD_KERN_A} of=hd_kern_a.blob - log dd if=${HD_KERN_B} of=hd_kern_b.blob - if [ -n "${USB_KERN_A:-}" ]; then - log dd if=${USB_KERN_A} of=usb_kern_a.blob - fi -fi +# 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 internal:bus=spi --wp-status +tpm_fwver=$(crossystem tpm_fwver) || tpm_fwver="UNKNOWN" +tpm_kernver=$(crossystem tpm_kernver) || tpm_kernver="UNKNOWN" +set -e -# Make sure we have something to work on -[ -f "$BIOS" ] || logdie "no BIOS image found" -ls *kern*.blob >/dev/null 2>&1 || logdie "no kernel images found" info "Extracting BIOS components..." -log dump_fmap -x ${BIOS} || logdie "Unable to extract BIOS components" - -# Find the FMAP regions we're interested in. Look first for the new names, then -# the old names. -area_gbb=$(find_name GBB GBB_Area) || \ - logdie "no area_gbb" -area_vblock_a=$(find_name VBLOCK_A Firmware_A_Key) || \ - logdie "no area_vblock_a" -area_vblock_b=$(find_name VBLOCK_B Firmware_B_Key) || \ - logdie "no area_vblock_b" -area_fw_main_a=$(find_name FW_MAIN_A Firmware_A_Data) || \ - logdie "no area_fw_main_a" -area_fw_main_b=$(find_name FW_MAIN_B Firmware_B_Data) || \ - logdie "no area_fw_main_a" +if [ -n "${OPT_BIOS}" ]; then + # If we've already got a file, just extract everything. + log dump_fmap -x "${OPT_BIOS}" + fix_old_names +else + # Read it from the flash + if log flashrom -p internal:bus=spi -r bios.rom ; then + # If we can read the whole BIOS at once, great. + log dump_fmap -x bios.rom + fix_old_names + else + # Otherwise pull just the components we want (implying new-style names) + info " ...individually..." + log flashrom -p internal:bus=spi -r /dev/null \ + -i"GBB":GBB \ + -i"VBLOCK_A":VBLOCK_A \ + -i"VBLOCK_B":VBLOCK_B \ + -i"FW_MAIN_A":FW_MAIN_A \ + -i"FW_MAIN_B":FW_MAIN_B + fi +fi info "Pulling root and recovery keys from GBB..." log gbb_utility -g --rootkey rootkey.vbpubk --recoverykey recoverykey.vbpubk \ - "$area_gbb" || logdie "Unable to extract keys from GBB" + "GBB" || logdie "Unable to extract keys from GBB" log vbutil_key --unpack rootkey.vbpubk log vbutil_key --unpack recoverykey.vbpubk +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 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 + 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" -infon "Verify firmware A with root key... " -log vbutil_firmware --verify "$area_vblock_a" --signpubkey rootkey.vbpubk \ - --fv "$area_fw_main_a" --kernelkey kernel_subkey_a.vbpubk ; result -infon "Verify firmware B with root key... " -log vbutil_firmware --verify "$area_vblock_b" --signpubkey rootkey.vbpubk \ - --fv "$area_fw_main_b" --kernelkey kernel_subkey_b.vbpubk ; result +# 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 -for key in kernel_subkey_a.vbpubk kernel_subkey_b.vbpubk; do - infon "Test $key... " - log vbutil_key --unpack $key ; result -done + infon "Kernel ${kname}: " + log 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 vbutil_kernel --verify "${kfile}" --signpubkey "$key" ; result + if [ "${LAST_RESULT}" = "0" ]; then + # rerun to get version numbers + vbutil_kernel --verify "${kfile}" --signpubkey "$key" > tmp.txt + ver=$(format_as_tpm_version tmp.txt) + info " TPM=${tpm_kernver} this=${ver}" + fi + done + fi -for keyblock in *kern*.blob; do - infon "Test $keyblock... " - log vbutil_keyblock --unpack $keyblock ; result - loghead od -Ax -tx1 $keyblock + kc=$(expr $kc + 1) done -# Test each kernel with each key -for key in kernel_subkey_a.vbpubk kernel_subkey_b.vbpubk recoverykey.vbpubk; do - for kern in *kern*.blob; do - infon "Verify $kern with $key... " - log vbutil_kernel --verify $kern --signpubkey $key ; result - done -done +exit 0 |