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 17 PUBLOGFILE="/var/log/debug_vboot_noisy.log" 18 19 OPT_CLEANUP= 20 OPT_BIOS= 21 OPT_FORCE= 22 OPT_IMAGE= 23 OPT_KERNEL= 24 OPT_VERBOSE= 25 26 FLAG_SAVE_LOG_FILE=yes 27 28 LOGFILE=/dev/stdout 29 TMPDIR= 30 31 ############################################################################## 32 33 usage() { 34 local prog 35 36 prog=${0##*/} 37 cat <<EOF 38 39 Usage: $prog [options] [DIRECTORY] 40 41 This logs as much as it can about the verified boot process. With no arguments 42 it will attempt to read the current BIOS, extract the firmware keys, and use 43 those keys to validate all the ChromeOS kernel partitions it can find. A 44 summary output is printed on stdout, and the detailed log is copied to 45 $PUBLOGFILE afterwards. 46 47 If a directory is given, it will attempt to use the components from that 48 directory and will leave the detailed log in that directory. 49 50 Options: 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 61 EOF 62 exit 0 63 } 64 65 cleanup() { 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 77 die() { 78 echo "$*" 1>&2 79 exit 1 80 } 81 82 info() { 83 echo "$@" 84 echo "#" "$@" >> "$LOGFILE" 85 } 86 87 infon() { 88 echo -n "$@" 89 echo "#" "$@" >> "$LOGFILE" 90 } 91 92 debug() { 93 echo "#" "$@" >> "$LOGFILE" 94 } 95 96 log() { 97 echo "+" "$@" >> "$LOGFILE" 98 "$@" >> "$LOGFILE" 2>&1 99 } 100 101 loghead() { 102 echo "+" "$@" "| head" >> "$LOGFILE" 103 "$@" | head >> "$LOGFILE" 2>&1 104 } 105 106 logdie() { 107 echo "+ERROR:" "$@" >> "$LOGFILE" 108 die "$@" 109 } 110 111 result() { 112 LAST_RESULT=$? 113 if [ "${LAST_RESULT}" = "0" ]; then 114 info "OK" 115 else 116 info "FAILED" 117 fi 118 } 119 120 require_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 134 extract_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 149 format_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 164 fix_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 177 umask 022 178 179 # defaults 180 DEV_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. 186 TEMP=$(getopt -o hvb:i:k:cf --long help,bios:,image:,kernel:,cleanup,force \ 187 -n $0 -- "$@") 188 eval set -- "$TEMP" 189 190 # Now look at them. 191 while 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 233 done 234 235 if [ -z "${1:-}" ]; then 236 TMPDIR=$(mktemp -d /tmp/debug_vboot_XXXXXXXXX) 237 else 238 TMPDIR="$1" 239 [ -d ${TMPDIR} ] || die "$TMPDIR doesn't exist" 240 FLAG_SAVE_LOG_FILE= 241 fi 242 [ -z "${OPT_VERBOSE}" ] && LOGFILE="${TMPDIR}/noisy.log" 243 244 [ -d ${TMPDIR} ] || mkdir -p ${TMPDIR} || exit 1 245 cd ${TMPDIR} || exit 1 246 echo "Running $0 $*" > "$LOGFILE" 247 log date 248 debug "DEV_DEBUG_FORCE=($DEV_DEBUG_FORCE)" 249 debug "OPT_CLEANUP=($OPT_CLEANUP)" 250 debug "OPT_BIOS=($OPT_BIOS)" 251 debug "OPT_FORCE=($OPT_FORCE)" 252 debug "OPT_IMAGE=($OPT_IMAGE)" 253 debug "OPT_KERNEL=($OPT_KERNEL)" 254 debug "FLAG_SAVE_LOG_FILE=($FLAG_SAVE_LOG_FILE)" 255 echo "Saving verbose log as $LOGFILE" 256 trap cleanup EXIT 257 258 if [ -n "${DEV_DEBUG_FORCE}" ] && [ -z "${OPT_FORCE}" ]; then 259 info "Not gonna do anything without the --force option." 260 exit 0 261 fi 262 263 264 # Make sure we have the programs we need 265 need="futility" 266 [ -z "${OPT_BIOS}" ] && need="$need flashrom" 267 [ -z "${OPT_KERNEL}" ] && need="$need cgpt" 268 require_utils $need 269 270 271 # Assuming we're on a ChromeOS device, see what we know. 272 set +e 273 log crossystem --all 274 log rootdev -s 275 log ls -aCF /root 276 log ls -aCF /mnt/stateful_partition 277 devs=$(awk '/(mmcblk[0-9])$|(sd[a-z])$/ {print "/dev/"$4}' /proc/partitions) 278 for d in $devs; do 279 log cgpt show $d 280 done 281 log flashrom -V -p host --wp-status 282 tpm_fwver=$(crossystem tpm_fwver) || tpm_fwver="UNKNOWN" 283 tpm_kernver=$(crossystem tpm_kernver) || tpm_kernver="UNKNOWN" 284 set -e 285 286 287 info "Extracting BIOS components..." 288 if [ -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 292 else 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 311 fi 312 313 info "Pulling root and recovery keys from GBB..." 314 log futility gbb_utility -g --rootkey rootkey.vbpubk \ 315 --recoverykey recoverykey.vbpubk \ 316 "GBB" || logdie "Unable to extract keys from GBB" 317 log futility vbutil_key --unpack rootkey.vbpubk 318 log futility vbutil_key --unpack recoverykey.vbpubk 319 futility vbutil_key --unpack rootkey.vbpubk | 320 grep -q b11d74edd286c144e1135b49e7f0bc20cf041f10 && 321 info " Looks like dev-keys" 322 # Okay if one of the firmware verifications fails 323 set +e 324 for 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 337 done 338 set -e 339 340 info "Examining kernels..." 341 if [ -n "${OPT_KERNEL}" ]; then 342 kernparts="${OPT_KERNEL}" 343 elif [ -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 349 else 350 kernparts=$(cgpt find -t kernel) 351 fi 352 [ -n "${kernparts}" ] || logdie "No kernels found" 353 354 # Okay if any of the kernel verifications fails 355 set +e 356 kc=0 357 for 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) 385 done 386 387 exit 0 388