scripts/image_signing: Add ensure_amd_psp_flags script

Currently there is no script to ensure that AMD PSP flags are set
correctly in a firmware image. This commit adds ensure_amd_psps_flags.sh
to handle that functionality. The script can check that certain flags
are set as well as checking that certain flags are not set.

BRANCH=none
BUG=b:202397678
TEST=Ran script with grunt, zork, MI and skyrim images, verified that
it responds correctly to PSP flag values

Signed-off-by: Robert Zieba <robertzieba@google.com>
Change-Id: Ie0864544b9b97704ee901d893b4d833c1ab068b9
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/3526100
Reviewed-by: Mike Frysinger <vapier@chromium.org>
diff --git a/scripts/image_signing/ensure_amd_psp_flags.sh b/scripts/image_signing/ensure_amd_psp_flags.sh
new file mode 100755
index 0000000..efccb69
--- /dev/null
+++ b/scripts/image_signing/ensure_amd_psp_flags.sh
@@ -0,0 +1,115 @@
+#!/bin/bash
+# Copyright 2022 The ChromiumOS Authors.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Load common constants and variables.
+. "$(dirname "$0")/common.sh"
+
+# Abort on error and uninitialized variables.
+set -eu
+
+declare -A -r REQUIRED_BIT_MASKS=(
+  # Bit 58 - PSP_S0I3_RESUME_VERSTAGE - Run PSP verstage during S0i3 resume.
+  # Checks that FW images have not been tampered with when exiting S0i3.
+  [guybrush]="$((1 << 58))"
+  [zork]="0x0"
+)
+
+declare -A -r FORBIDDEN_BIT_MASKS=(
+  [guybrush]="0x0"
+  [zork]="0x0"
+)
+
+# Grunt uses an old firmware format that amdfwread cannot read.
+# See b/233787191 for skyrim.
+BOARD_IGNORE_LIST=(grunt skyrim)
+
+usage() {
+  echo "$0: Validate AMD PSP soft-fuse flags contained in a ChromeOS image." \
+    "These flags can have security implications and control debug features."
+  echo "Usage $0 <board> <image>"
+}
+
+main() {
+  if [[ $# -ne 2 ]]; then
+    usage
+    exit 1
+  fi
+
+  local board="$1"
+  local image="$2"
+
+  # Check the ignore list.
+  if [[ " ${BOARD_IGNORE_LIST[*]} " == *" ${board} "* ]]; then
+   echo "Skipping ignore-listed board ${board}"
+   exit 0
+  fi
+
+  # Mount the image.
+  local loopdev rootfs
+  if [[ -d "${image}" ]]; then
+    rootfs="${image}"
+  else
+    rootfs="$(make_temp_dir)"
+    loopdev="$(loopback_partscan "${image}")"
+    mount_loop_image_partition_ro "${loopdev}" 3 "${rootfs}"
+  fi
+
+  local firmware_bundle shellball_dir
+  firmware_bundle="${rootfs}/usr/sbin/chromeos-firmwareupdate"
+  shellball_dir="$(make_temp_dir)"
+
+  # Get the board specific bit masks.
+  local required_bit_mask forbidden_bit_mask
+
+  if [[ ! -v "REQUIRED_BIT_MASKS[${board}]" ]]; then
+    die "Missing PSP required bit mask set for ${board}"
+  fi
+
+  if [[ ! -v "FORBIDDEN_BIT_MASKS[${board}]" ]]; then
+    die "Missing PSP forbidden bit mask set for ${board}"
+  fi
+
+  required_bit_mask="${REQUIRED_BIT_MASKS[${board}]}"
+  forbidden_bit_mask="${FORBIDDEN_BIT_MASKS[${board}]}"
+
+  # Extract our firmware.
+  if ! extract_firmware_bundle "${firmware_bundle}" "${shellball_dir}"; then
+    die "Failed to extract firmware bundle"
+  fi
+
+  # Find our images and check the soft-fuse bits in each.
+  declare -a images
+  readarray -t images < <(find "${shellball_dir}" -iname 'bios-*')
+
+  local image
+  for image in "${images[@]}"; do
+    local soft_fuse soft_fuse_output forbidden_set missing_set
+    if ! soft_fuse_output="$(amdfwread --soft-fuse "${image}")"; then
+      die "'amdfwread --soft-fuse ${image}' failed"
+    fi
+
+    # Output format from amdfwread is Soft-fuse:value, where value is in hex.
+    soft_fuse="$(echo "${soft_fuse_output}" | \
+      sed -E -n 's/Soft-fuse:(0[xX][0-9a-fA-F]+)/\1/p')"
+    if [[ -z "${soft_fuse}" ]]; then
+      die "Could not parse Soft-fuse value from output: '${soft_fuse_output}'"
+    fi
+
+    forbidden_set="$((soft_fuse & forbidden_bit_mask))"
+    if [[ "${forbidden_set}" != 0 ]]; then
+      local forbidden_hex
+      forbidden_hex="$(printf %#x "${forbidden_set}")"
+      die "${image}: Forbidden AMD PSP soft-fuse bits set: ${forbidden_hex}"
+    fi
+
+    missing_set="$((~soft_fuse & required_bit_mask))"
+    if [[ "${missing_set}" != 0 ]]; then
+      local missing_hex
+      missing_hex="$(printf %#x "${missing_set}")"
+      die "${image}: Required AMD PSP soft-fuse bits not set: ${missing_hex}"
+    fi
+  done
+}
+main "$@"