#!/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 </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])$|(nvme[0-9]+n[0-9]+)$/ {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 -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