#!/bin/sh -ue # Copyright 2011 The ChromiumOS Authors # 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 data_key_ver="$1" local ver="$2" printf '0x%04x%04x' "${data_key_ver}" "${ver}" } 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 } report_firmware_mismatch() { # Check for mismatched OS/firmware and send UMA metrics if ! type "chromeos-firmwareupdate" >/dev/null 2>&1 ; then debug "Skip checking firmware mismatch: missing 'chromeos-firmwareupdate'." return 1 fi local cros_fwid="$(crossystem fwid 2>/dev/null)" local model="$(cros_config / name || echo unknown)" local manifest="$(chromeos-firmwareupdate --manifest 2>/dev/null)" local expect_fwid=$(echo "${manifest}" | jq -c -r ".${model}.host.versions.rw" 2>/dev/null) if [ -z "${expect_fwid}" ] || [ "${expect_fwid}" = "null" ]; then debug "Failed to get the expected fwid for model '${model}'." elif [ "${cros_fwid}" = "${expect_fwid}" ]; then info "Report UMA metrics: System firmware matched OS bundled firmware." metrics_client -e "Platform.Firmware.Mismatch" 0 2 else info "Report UMA metrics: System firmware mismatched OS bundled firmware." metrics_client -e "Platform.Firmware.Mismatch" 1 2 fi } ############################################################################## # 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 futility flash --wp-status tpm_fwver=$(crossystem tpm_fwver) || tpm_fwver="UNKNOWN" tpm_kernver=$(crossystem tpm_kernver) || tpm_kernver="UNKNOWN" set -e info "Extracting BIOS components..." BIOS_IMAGE="${OPT_BIOS}" if [ -z "${BIOS_IMAGE}" ]; then info "Reading BIOS image from flash..." BIOS_IMAGE="bios.rom" if ! log futility read "${BIOS_IMAGE}" ; then logdie "Fail to read BIOS." fi fi # Extract all FMAP sections. log futility dump_fmap -x "${BIOS_IMAGE}" fix_old_names 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 firmware verification fails. set +e log futility verify -P "${BIOS_IMAGE}" # Rerun to get version numbers. futility verify -P "${BIOS_IMAGE}" > tmp.txt for fw in A B; do infon "Verify firmware ${fw} with root key: " grep -q "^bios::VBLOCK_${fw}::verified" tmp.txt ; result if [ "${LAST_RESULT}" = "0" ]; then data_key_ver="$(sed -nE "s/^bios::VBLOCK_${fw}::keyblock::data_key::version::(.*)$/\1/p" tmp.txt)" fw_ver="$(sed -nE "s/^bios::VBLOCK_${fw}::preamble::firmware_version::(.*)$/\1/p" tmp.txt)" ver="$(format_as_tpm_version "${data_key_ver}" "${fw_ver}")" 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 VBLOCK_A VBLOCK_B recoverykey.vbpubk; do infon " Verify ${kname} with $key: " log futility verify -P --publickey "${key}" "${kfile}" ; result if [ "${LAST_RESULT}" = "0" ]; then # rerun to get version numbers futility verify -P --publickey "${key}" "${kfile}" > tmp.txt data_key_ver="$(sed -nE "s/^kernel::keyblock::data_key::version::(.*)$/\1/p" tmp.txt)" kernel_ver="$(sed -nE "s/^kernel::preamble::kernel_version::(.*)$/\1/p" tmp.txt)" ver="$(format_as_tpm_version "${data_key_ver}" "${kernel_ver}")" info " TPM=${tpm_kernver} this=${ver}" fi done fi kc=$(expr $kc + 1) done report_firmware_mismatch || true exit 0