#!/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