xref: /aosp_15_r20/external/vboot_reference/utility/dev_debug_vboot (revision 8617a60d3594060b7ecbd21bc622a7c14f3cf2bc)
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