| #!/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])$|(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 |