1#!/bin/sh -ue 2# Copyright 2011 The ChromiumOS Authors 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 data_key_ver="$1" 151 local ver="$2" 152 printf '0x%04x%04x' "${data_key_ver}" "${ver}" 153} 154 155fix_old_names() { 156 # Convert any old-style names to new-style 157 [ -f GBB_Area ] && log mv -f GBB_Area GBB 158 [ -f Firmware_A_Key ] && log mv -f Firmware_A_Key VBLOCK_A 159 [ -f Firmware_B_Key ] && log mv -f Firmware_B_Key VBLOCK_B 160 [ -f Firmware_A_Data ] && log mv -f Firmware_A_Data FW_MAIN_A 161 [ -f Firmware_B_Data ] && log mv -f Firmware_B_Data FW_MAIN_B 162 true 163} 164 165report_firmware_mismatch() { 166 # Check for mismatched OS/firmware and send UMA metrics 167 if ! type "chromeos-firmwareupdate" >/dev/null 2>&1 ; then 168 debug "Skip checking firmware mismatch: missing 'chromeos-firmwareupdate'." 169 return 1 170 fi 171 172 local cros_fwid="$(crossystem fwid 2>/dev/null)" 173 174 local model="$(cros_config / name || echo unknown)" 175 local manifest="$(chromeos-firmwareupdate --manifest 2>/dev/null)" 176 local expect_fwid=$(echo "${manifest}" | 177 jq -c -r ".${model}.host.versions.rw" 2>/dev/null) 178 179 if [ -z "${expect_fwid}" ] || [ "${expect_fwid}" = "null" ]; then 180 debug "Failed to get the expected fwid for model '${model}'." 181 elif [ "${cros_fwid}" = "${expect_fwid}" ]; then 182 info "Report UMA metrics: System firmware matched OS bundled firmware." 183 metrics_client -e "Platform.Firmware.Mismatch" 0 2 184 else 185 info "Report UMA metrics: System firmware mismatched OS bundled firmware." 186 metrics_client -e "Platform.Firmware.Mismatch" 1 2 187 fi 188} 189 190############################################################################## 191# Here we go... 192 193umask 022 194 195# defaults 196DEV_DEBUG_FORCE= 197 198# override them? 199[ -f /etc/default/vboot_reference ] && . /etc/default/vboot_reference 200 201# Pre-parse args to replace actual args with a sanitized version. 202TEMP=$(getopt -o hvb:i:k:cf --long help,bios:,image:,kernel:,cleanup,force \ 203 -n $0 -- "$@") 204eval set -- "$TEMP" 205 206# Now look at them. 207while true ; do 208 case "${1:-}" in 209 -b|--bios) 210 OPT_BIOS=$(readlink -f "$2") 211 shift 2 212 FLAG_SAVE_LOG_FILE= 213 ;; 214 -i|--image=*) 215 OPT_IMAGE=$(readlink -f "$2") 216 shift 2 217 FLAG_SAVE_LOG_FILE= 218 ;; 219 -k|--kernel) 220 OPT_KERNEL=$(readlink -f "$2") 221 shift 2 222 FLAG_SAVE_LOG_FILE= 223 ;; 224 -c|--cleanup) 225 OPT_CLEANUP=yes 226 shift 227 ;; 228 -f|--force) 229 OPT_FORCE=yes 230 shift 231 ;; 232 -v) 233 OPT_VERBOSE=yes 234 shift 235 FLAG_SAVE_LOG_FILE= 236 ;; 237 -h|--help) 238 usage 239 break 240 ;; 241 --) 242 shift 243 break 244 ;; 245 *) 246 die "Internal error in option parsing" 247 ;; 248 esac 249done 250 251if [ -z "${1:-}" ]; then 252 TMPDIR=$(mktemp -d /tmp/debug_vboot_XXXXXXXXX) 253else 254 TMPDIR="$1" 255 [ -d ${TMPDIR} ] || die "$TMPDIR doesn't exist" 256 FLAG_SAVE_LOG_FILE= 257fi 258[ -z "${OPT_VERBOSE}" ] && LOGFILE="${TMPDIR}/noisy.log" 259 260[ -d ${TMPDIR} ] || mkdir -p ${TMPDIR} || exit 1 261cd ${TMPDIR} || exit 1 262echo "Running $0 $*" > "$LOGFILE" 263log date 264debug "DEV_DEBUG_FORCE=($DEV_DEBUG_FORCE)" 265debug "OPT_CLEANUP=($OPT_CLEANUP)" 266debug "OPT_BIOS=($OPT_BIOS)" 267debug "OPT_FORCE=($OPT_FORCE)" 268debug "OPT_IMAGE=($OPT_IMAGE)" 269debug "OPT_KERNEL=($OPT_KERNEL)" 270debug "FLAG_SAVE_LOG_FILE=($FLAG_SAVE_LOG_FILE)" 271echo "Saving verbose log as $LOGFILE" 272trap cleanup EXIT 273 274if [ -n "${DEV_DEBUG_FORCE}" ] && [ -z "${OPT_FORCE}" ]; then 275 info "Not gonna do anything without the --force option." 276 exit 0 277fi 278 279 280# Make sure we have the programs we need 281need="futility" 282[ -z "${OPT_BIOS}" ] && need="$need flashrom" 283[ -z "${OPT_KERNEL}" ] && need="$need cgpt" 284require_utils $need 285 286 287# Assuming we're on a ChromeOS device, see what we know. 288set +e 289log crossystem --all 290log rootdev -s 291log ls -aCF /root 292log ls -aCF /mnt/stateful_partition 293devs=$(awk '/(mmcblk[0-9])$|(sd[a-z])$|(nvme[0-9]+n[0-9]+)$/ {print "/dev/"$4}' /proc/partitions) 294for d in $devs; do 295 log cgpt show $d 296done 297log futility flash --wp-status 298tpm_fwver=$(crossystem tpm_fwver) || tpm_fwver="UNKNOWN" 299tpm_kernver=$(crossystem tpm_kernver) || tpm_kernver="UNKNOWN" 300set -e 301 302 303info "Extracting BIOS components..." 304BIOS_IMAGE="${OPT_BIOS}" 305if [ -z "${BIOS_IMAGE}" ]; then 306 info "Reading BIOS image from flash..." 307 BIOS_IMAGE="bios.rom" 308 if ! log futility read "${BIOS_IMAGE}" ; then 309 logdie "Fail to read BIOS." 310 fi 311fi 312 313# Extract all FMAP sections. 314log futility dump_fmap -x "${BIOS_IMAGE}" 315fix_old_names 316 317info "Pulling root and recovery keys from GBB..." 318log futility gbb -g --rootkey rootkey.vbpubk \ 319 --recoverykey recoverykey.vbpubk \ 320 "GBB" || logdie "Unable to extract keys from GBB" 321log futility vbutil_key --unpack rootkey.vbpubk 322log futility vbutil_key --unpack recoverykey.vbpubk 323futility vbutil_key --unpack rootkey.vbpubk | 324 grep -q b11d74edd286c144e1135b49e7f0bc20cf041f10 && 325 info " Looks like dev-keys" 326 327# Okay if firmware verification fails. 328set +e 329log futility verify -P "${BIOS_IMAGE}" 330# Rerun to get version numbers. 331futility verify -P "${BIOS_IMAGE}" > tmp.txt 332for fw in A B; do 333 infon "Verify firmware ${fw} with root key: " 334 grep -q "^bios::VBLOCK_${fw}::verified" tmp.txt ; result 335 if [ "${LAST_RESULT}" = "0" ]; then 336 data_key_ver="$(sed -nE "s/^bios::VBLOCK_${fw}::keyblock::data_key::version::(.*)$/\1/p" tmp.txt)" 337 fw_ver="$(sed -nE "s/^bios::VBLOCK_${fw}::preamble::firmware_version::(.*)$/\1/p" tmp.txt)" 338 ver="$(format_as_tpm_version "${data_key_ver}" "${fw_ver}")" 339 info " TPM=${tpm_fwver}, this=${ver}" 340 fi 341done 342set -e 343 344info "Examining kernels..." 345if [ -n "${OPT_KERNEL}" ]; then 346 kernparts="${OPT_KERNEL}" 347elif [ -n "${OPT_IMAGE}" ]; then 348 if [ -f "${OPT_IMAGE}" ]; then 349 kernparts=$(extract_kerns_from_file "${OPT_IMAGE}") 350 else 351 kernparts=$(cgpt find -t kernel "${OPT_IMAGE}") 352 fi 353else 354 kernparts=$(cgpt find -t kernel) 355fi 356[ -n "${kernparts}" ] || logdie "No kernels found" 357 358# Okay if any of the kernel verifications fails. 359set +e 360kc=0 361for kname in ${kernparts}; do 362 if [ -f "${kname}" ]; then 363 kfile="${kname}" 364 else 365 kfile="kern_${kc}" 366 debug "copying ${kname} to ${kfile}..." 367 log dd if="${kname}" of="${kfile}" 368 fi 369 370 infon "Kernel ${kname}: " 371 log futility vbutil_keyblock --unpack "${kfile}" ; result 372 if [ "${LAST_RESULT}" != "0" ]; then 373 loghead od -Ax -tx1 "${kfile}" 374 else 375 # Test each kernel with each key 376 for key in VBLOCK_A VBLOCK_B recoverykey.vbpubk; do 377 infon " Verify ${kname} with $key: " 378 log futility verify -P --publickey "${key}" "${kfile}" ; result 379 if [ "${LAST_RESULT}" = "0" ]; then 380 # rerun to get version numbers 381 futility verify -P --publickey "${key}" "${kfile}" > tmp.txt 382 data_key_ver="$(sed -nE "s/^kernel::keyblock::data_key::version::(.*)$/\1/p" tmp.txt)" 383 kernel_ver="$(sed -nE "s/^kernel::preamble::kernel_version::(.*)$/\1/p" tmp.txt)" 384 ver="$(format_as_tpm_version "${data_key_ver}" "${kernel_ver}")" 385 info " TPM=${tpm_kernver} this=${ver}" 386 fi 387 done 388 fi 389 390 kc=$(expr $kc + 1) 391done 392 393report_firmware_mismatch || true 394 395exit 0 396