1#!/bin/sh -ue 2# Copyright (c) 2011 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5# 6# Usage: dev_debug_vboot [ --cleanup | DIRECTORY ] 7# 8# This extracts some useful debugging information about verified boot. A short 9# summary is printed on stdout, more detailed information and working files are 10# left in a log directory. 11# 12############################################################################## 13 14# Clean up PATH for root use. Note that we're assuming [ is always built-in. 15[ "${EUID:-0}" = 0 ] && PATH=/bin:/sbin:/usr/bin:/usr/sbin 16 17PUBLOGFILE="/var/log/debug_vboot_noisy.log" 18 19OPT_CLEANUP= 20OPT_BIOS= 21OPT_FORCE= 22OPT_IMAGE= 23OPT_KERNEL= 24OPT_VERBOSE= 25 26FLAG_SAVE_LOG_FILE=yes 27 28LOGFILE=/dev/stdout 29TMPDIR= 30 31############################################################################## 32 33usage() { 34 local prog 35 36 prog=${0##*/} 37 cat <<EOF 38 39Usage: $prog [options] [DIRECTORY] 40 41This logs as much as it can about the verified boot process. With no arguments 42it will attempt to read the current BIOS, extract the firmware keys, and use 43those keys to validate all the ChromeOS kernel partitions it can find. A 44summary output is printed on stdout, and the detailed log is copied to 45$PUBLOGFILE afterwards. 46 47If a directory is given, it will attempt to use the components from that 48directory and will leave the detailed log in that directory. 49 50Options: 51 52 -b FILE, --bios FILE Specify the BIOS image to use 53 -i FILE, --image FILE Specify the disk image to use 54 -k FILE, --kernel FILE Specify the kernel partition image to use 55 -v Spew the detailed log to stdout 56 57 -c, --cleanup Delete the DIRECTORY when done 58 59 -h, --help Print this help message and exit 60 61EOF 62exit 0 63} 64 65cleanup() { 66 if [ -n "${FLAG_SAVE_LOG_FILE}" ]; then 67 if cp -f "${LOGFILE}" "${PUBLOGFILE}" 2>/dev/null; then 68 info "Exporting log file as ${PUBLOGFILE}" 69 fi 70 fi 71 if [ -n "${OPT_CLEANUP}" ] && [ -d "${TMPDIR}" ] ; then 72 cd / 73 rm -rf "${TMPDIR}" 74 fi 75} 76 77die() { 78 echo "$*" 1>&2 79 exit 1 80} 81 82info() { 83 echo "$@" 84 echo "#" "$@" >> "$LOGFILE" 85} 86 87infon() { 88 echo -n "$@" 89 echo "#" "$@" >> "$LOGFILE" 90} 91 92debug() { 93 echo "#" "$@" >> "$LOGFILE" 94} 95 96log() { 97 echo "+" "$@" >> "$LOGFILE" 98 "$@" >> "$LOGFILE" 2>&1 99} 100 101loghead() { 102 echo "+" "$@" "| head" >> "$LOGFILE" 103 "$@" | head >> "$LOGFILE" 2>&1 104} 105 106logdie() { 107 echo "+ERROR:" "$@" >> "$LOGFILE" 108 die "$@" 109} 110 111result() { 112 LAST_RESULT=$? 113 if [ "${LAST_RESULT}" = "0" ]; then 114 info "OK" 115 else 116 info "FAILED" 117 fi 118} 119 120require_utils() { 121 local missing 122 123 missing= 124 for tool in $* ; do 125 if ! type "$tool" >/dev/null 2>&1 ; then 126 missing="$missing $tool" 127 fi 128 done 129 if [ -n "$missing" ]; then 130 logdie "can't find these programs: $missing" 131 fi 132} 133 134extract_kerns_from_file() { 135 local start 136 local size 137 local part 138 local rest 139 140 debug "Extracting kernel partitions from $1 ..." 141 cgpt find -v -t kernel "$1" | grep 'Label:' | 142 while read start size part rest; do 143 name="part_${part}" 144 log dd if="$1" bs=512 skip=${start} count=${size} of="${name}" && 145 echo "${name}" 146 done 147} 148 149format_as_tpm_version() { 150 local a 151 local b 152 local what 153 local num 154 local rest 155 156 a='/(Data|Kernel) key version/ {print $1,$4}' 157 b='/Kernel version/ {print $1, $3}' 158 awk "$a $b" "$1" | while read what num rest; do 159 [ "${what}" = "Data" ] && block="${num}" 160 [ "${what}" = "Kernel" ] && printf '0x%04x%04x' "${block}" "${num}" 161 done 162} 163 164fix_old_names() { 165 # Convert any old-style names to new-style 166 [ -f GBB_Area ] && log mv -f GBB_Area GBB 167 [ -f Firmware_A_Key ] && log mv -f Firmware_A_Key VBLOCK_A 168 [ -f Firmware_B_Key ] && log mv -f Firmware_B_Key VBLOCK_B 169 [ -f Firmware_A_Data ] && log mv -f Firmware_A_Data FW_MAIN_A 170 [ -f Firmware_B_Data ] && log mv -f Firmware_B_Data FW_MAIN_B 171 true 172} 173 174############################################################################## 175# Here we go... 176 177umask 022 178 179# defaults 180DEV_DEBUG_FORCE= 181 182# override them? 183[ -f /etc/default/vboot_reference ] && . /etc/default/vboot_reference 184 185# Pre-parse args to replace actual args with a sanitized version. 186TEMP=$(getopt -o hvb:i:k:cf --long help,bios:,image:,kernel:,cleanup,force \ 187 -n $0 -- "$@") 188eval set -- "$TEMP" 189 190# Now look at them. 191while true ; do 192 case "${1:-}" in 193 -b|--bios) 194 OPT_BIOS=$(readlink -f "$2") 195 shift 2 196 FLAG_SAVE_LOG_FILE= 197 ;; 198 -i|--image=*) 199 OPT_IMAGE=$(readlink -f "$2") 200 shift 2 201 FLAG_SAVE_LOG_FILE= 202 ;; 203 -k|--kernel) 204 OPT_KERNEL=$(readlink -f "$2") 205 shift 2 206 FLAG_SAVE_LOG_FILE= 207 ;; 208 -c|--cleanup) 209 OPT_CLEANUP=yes 210 shift 211 ;; 212 -f|--force) 213 OPT_FORCE=yes 214 shift 215 ;; 216 -v) 217 OPT_VERBOSE=yes 218 shift 219 FLAG_SAVE_LOG_FILE= 220 ;; 221 -h|--help) 222 usage 223 break 224 ;; 225 --) 226 shift 227 break 228 ;; 229 *) 230 die "Internal error in option parsing" 231 ;; 232 esac 233done 234 235if [ -z "${1:-}" ]; then 236 TMPDIR=$(mktemp -d /tmp/debug_vboot_XXXXXXXXX) 237else 238 TMPDIR="$1" 239 [ -d ${TMPDIR} ] || die "$TMPDIR doesn't exist" 240 FLAG_SAVE_LOG_FILE= 241fi 242[ -z "${OPT_VERBOSE}" ] && LOGFILE="${TMPDIR}/noisy.log" 243 244[ -d ${TMPDIR} ] || mkdir -p ${TMPDIR} || exit 1 245cd ${TMPDIR} || exit 1 246echo "Running $0 $*" > "$LOGFILE" 247log date 248debug "DEV_DEBUG_FORCE=($DEV_DEBUG_FORCE)" 249debug "OPT_CLEANUP=($OPT_CLEANUP)" 250debug "OPT_BIOS=($OPT_BIOS)" 251debug "OPT_FORCE=($OPT_FORCE)" 252debug "OPT_IMAGE=($OPT_IMAGE)" 253debug "OPT_KERNEL=($OPT_KERNEL)" 254debug "FLAG_SAVE_LOG_FILE=($FLAG_SAVE_LOG_FILE)" 255echo "Saving verbose log as $LOGFILE" 256trap cleanup EXIT 257 258if [ -n "${DEV_DEBUG_FORCE}" ] && [ -z "${OPT_FORCE}" ]; then 259 info "Not gonna do anything without the --force option." 260 exit 0 261fi 262 263 264# Make sure we have the programs we need 265need="futility" 266[ -z "${OPT_BIOS}" ] && need="$need flashrom" 267[ -z "${OPT_KERNEL}" ] && need="$need cgpt" 268require_utils $need 269 270 271# Assuming we're on a ChromeOS device, see what we know. 272set +e 273log crossystem --all 274log rootdev -s 275log ls -aCF /root 276log ls -aCF /mnt/stateful_partition 277devs=$(awk '/(mmcblk[0-9])$|(sd[a-z])$/ {print "/dev/"$4}' /proc/partitions) 278for d in $devs; do 279 log cgpt show $d 280done 281log flashrom -V -p host --wp-status 282tpm_fwver=$(crossystem tpm_fwver) || tpm_fwver="UNKNOWN" 283tpm_kernver=$(crossystem tpm_kernver) || tpm_kernver="UNKNOWN" 284set -e 285 286 287info "Extracting BIOS components..." 288if [ -n "${OPT_BIOS}" ]; then 289 # If we've already got a file, just extract everything. 290 log futility dump_fmap -x "${OPT_BIOS}" 291 fix_old_names 292else 293 # First try pulling just the components we want (using new-style names) 294 if log flashrom -p host -r /dev/null \ 295 -i"GBB":GBB \ 296 -i"FMAP":FMAP \ 297 -i"VBLOCK_A":VBLOCK_A \ 298 -i"VBLOCK_B":VBLOCK_B \ 299 -i"FW_MAIN_A":FW_MAIN_A \ 300 -i"FW_MAIN_B":FW_MAIN_B ; then 301 log futility dump_fmap FMAP 302 else 303 info "Couldn't read individual components. Read the whole thing..." 304 if log flashrom -p host -r bios.rom ; then 305 log futility dump_fmap -x bios.rom 306 fix_old_names 307 else 308 logdie "Can't read BIOS at all. Giving up." 309 fi 310 fi 311fi 312 313info "Pulling root and recovery keys from GBB..." 314log futility gbb_utility -g --rootkey rootkey.vbpubk \ 315 --recoverykey recoverykey.vbpubk \ 316 "GBB" || logdie "Unable to extract keys from GBB" 317log futility vbutil_key --unpack rootkey.vbpubk 318log futility vbutil_key --unpack recoverykey.vbpubk 319futility vbutil_key --unpack rootkey.vbpubk | 320 grep -q b11d74edd286c144e1135b49e7f0bc20cf041f10 && 321 info " Looks like dev-keys" 322# Okay if one of the firmware verifications fails 323set +e 324for fw in A B; do 325 infon "Verify firmware ${fw} with root key: " 326 log futility vbutil_firmware --verify "VBLOCK_${fw}" \ 327 --signpubkey rootkey.vbpubk \ 328 --fv "FW_MAIN_${fw}" --kernelkey "kern_subkey_${fw}.vbpubk" ; result 329 if [ "${LAST_RESULT}" = "0" ]; then 330 # rerun to get version numbers 331 futility vbutil_firmware --verify "VBLOCK_${fw}" \ 332 --signpubkey rootkey.vbpubk \ 333 --fv "FW_MAIN_${fw}" > tmp.txt 334 ver=$(format_as_tpm_version tmp.txt) 335 info " TPM=${tpm_fwver}, this=${ver}" 336 fi 337done 338set -e 339 340info "Examining kernels..." 341if [ -n "${OPT_KERNEL}" ]; then 342 kernparts="${OPT_KERNEL}" 343elif [ -n "${OPT_IMAGE}" ]; then 344 if [ -f "${OPT_IMAGE}" ]; then 345 kernparts=$(extract_kerns_from_file "${OPT_IMAGE}") 346 else 347 kernparts=$(cgpt find -t kernel "${OPT_IMAGE}") 348 fi 349else 350 kernparts=$(cgpt find -t kernel) 351fi 352[ -n "${kernparts}" ] || logdie "No kernels found" 353 354# Okay if any of the kernel verifications fails 355set +e 356kc=0 357for kname in ${kernparts}; do 358 if [ -f "${kname}" ]; then 359 kfile="${kname}" 360 else 361 kfile="kern_${kc}" 362 debug "copying ${kname} to ${kfile}..." 363 log dd if="${kname}" of="${kfile}" 364 fi 365 366 infon "Kernel ${kname}: " 367 log futility vbutil_keyblock --unpack "${kfile}" ; result 368 if [ "${LAST_RESULT}" != "0" ]; then 369 loghead od -Ax -tx1 "${kfile}" 370 else 371 # Test each kernel with each key 372 for key in kern_subkey_A.vbpubk kern_subkey_B.vbpubk recoverykey.vbpubk; do 373 infon " Verify ${kname} with $key: " 374 log futility vbutil_kernel --verify "${kfile}" --signpubkey "$key" ; result 375 if [ "${LAST_RESULT}" = "0" ]; then 376 # rerun to get version numbers 377 futility vbutil_kernel --verify "${kfile}" --signpubkey "$key" > tmp.txt 378 ver=$(format_as_tpm_version tmp.txt) 379 info " TPM=${tpm_kernver} this=${ver}" 380 fi 381 done 382 fi 383 384 kc=$(expr $kc + 1) 385done 386 387exit 0 388