mirror of
https://github.com/linuxboot/heads.git
synced 2024-12-29 17:28:54 +00:00
00ce2f4d1c
Log the board and version when entering the recovery shell. Extract the firmware version logic from init. Currently this is the only way to get the debug log. If we add a way from the GUI, we may want to log the board and version somewhere else too. Signed-off-by: Jonathon Hall <jonathon.hall@puri.sm>
357 lines
12 KiB
Bash
357 lines
12 KiB
Bash
#!/bin/sh
|
|
#
|
|
# Core shell functions that do not require bash. These functions are used with
|
|
# busybox ash on legacy-flash boards, and with bash on all other boards.
|
|
|
|
die() {
|
|
if [ "$CONFIG_DEBUG_OUTPUT" = "y" ];then
|
|
echo " !!! ERROR: $* !!!" | tee -a /tmp/debug.log /dev/kmsg > /dev/null;
|
|
else
|
|
echo >&2 "!!! ERROR: $* !!!";
|
|
fi
|
|
sleep 2;
|
|
exit 1;
|
|
}
|
|
|
|
warn() {
|
|
if [ "$CONFIG_DEBUG_OUTPUT" = "y" ];then
|
|
echo " *** WARNING: $* ***" | tee -a /tmp/debug.log /dev/kmsg > /dev/null;
|
|
else
|
|
echo >&2 " *** WARNING: $* ***";
|
|
fi
|
|
sleep 1;
|
|
}
|
|
|
|
DEBUG() {
|
|
if [ "$CONFIG_DEBUG_OUTPUT" = "y" ]; then
|
|
# fold -s -w 960 will wrap lines at 960 characters on the last space before the limit
|
|
echo "DEBUG: $*" | fold -s -w 960 | while read line; do
|
|
echo "$line" | tee -a /tmp/debug.log /dev/kmsg >/dev/null
|
|
done
|
|
fi
|
|
}
|
|
|
|
TRACE() {
|
|
if [ "$CONFIG_ENABLE_FUNCTION_TRACING_OUTPUT" = "y" ];then
|
|
echo "TRACE: $*" | tee -a /tmp/debug.log /dev/kmsg > /dev/null;
|
|
fi
|
|
}
|
|
|
|
# Write directly to the debug log (but not kmsg), never appears on console
|
|
LOG() {
|
|
echo "LOG: $*" >>/tmp/debug.log
|
|
}
|
|
|
|
fw_version() {
|
|
local FW_VER=$(dmesg | grep 'DMI' | grep -o 'BIOS.*' | cut -f2- -d ' ')
|
|
# chop off date, since will always be epoch w/timeless builds
|
|
echo "${FW_VER::-10}"
|
|
}
|
|
|
|
preserve_rom() {
|
|
TRACE "Under /etc/ash_functions:preserve_rom"
|
|
new_rom="$1"
|
|
old_files=`cbfs -t 50 -l 2>/dev/null | grep "^heads/"`
|
|
|
|
for old_file in `echo $old_files`; do
|
|
new_file=`cbfs.sh -o $1 -l | grep -x $old_file`
|
|
if [ -z "$new_file" ]; then
|
|
echo "+++ Adding $old_file to $1"
|
|
cbfs -t 50 -r $old_file >/tmp/rom.$$ \
|
|
|| die "Failed to read cbfs file from ROM"
|
|
cbfs.sh -o $1 -a $old_file -f /tmp/rom.$$ \
|
|
|| die "Failed to write cbfs file to new ROM file"
|
|
fi
|
|
done
|
|
}
|
|
|
|
confirm_gpg_card() {
|
|
TRACE "Under /etc/ash_functions:confirm_gpg_card"
|
|
#Skip prompts if we are currently using a known GPG key material Thumb drive backup and keys are unlocked pinentry
|
|
#TODO: probably export CONFIG_GPG_KEY_BACKUP_IN_USE but not under /etc/user.config?
|
|
#Toggle to come in next PR, but currently we don't have a way to toggle it back to n if config.user flashed back in rom
|
|
if [[ "$CONFIG_HAVE_GPG_KEY_BACKUP" == "y" && "$CONFIG_GPG_KEY_BACKUP_IN_USE" == "y" ]]; then
|
|
DEBUG "Using known GPG key material Thumb drive backup and keys are unlocked and useable through pinentry"
|
|
return
|
|
fi
|
|
|
|
if [ "$CONFIG_HAVE_GPG_KEY_BACKUP" == "y" ]; then
|
|
message="Please confirm that your GPG card is inserted(Y/n) or your GPG key material (b)backup thumbdrive is inserted [Y/n/b]: "
|
|
else
|
|
# Generic message if no known key material backup
|
|
message="Please confirm that your GPG card is inserted [Y/n]: "
|
|
fi
|
|
|
|
read \
|
|
-n 1 \
|
|
-p "$message" \
|
|
card_confirm
|
|
echo
|
|
|
|
if [ "$card_confirm" != "y" \
|
|
-a "$card_confirm" != "Y" \
|
|
-a "$card_confirm" != "b" \
|
|
-a -n "$card_confirm" ] \
|
|
; then
|
|
die "gpg card not confirmed"
|
|
fi
|
|
|
|
# If user has known GPG key material Thumb drive backup and asked to use it
|
|
if [[ "$CONFIG_HAVE_GPG_KEY_BACKUP" == "y" && "$card_confirm" == "b" ]]; then
|
|
#Only mount and import GPG key material thumb drive backup once
|
|
if [ ! "$CONFIG_GPG_KEY_BACKUP_IN_USE" == "y" ]; then
|
|
CR_NONCE="/tmp/secret/cr_nonce"
|
|
CR_SIG="$CR_NONCE.sig"
|
|
|
|
#Wipe any previous CR_NONCE and CR_SIG
|
|
shred -n 10 -z -u "$CR_NONCE" "$CR_SIG" >/dev/null 2>&1 || true
|
|
|
|
#Prompt user for configured GPG Admin PIN that will be passed along to mount-usb and to import gpg subkeys
|
|
echo
|
|
gpg_admin_pin=""
|
|
while [ -z "$gpg_admin_pin" ]; do
|
|
#TODO: change all passphrase prompts in codebase to include -r to prevent backslash escapes
|
|
read -r -s -p "Please enter GPG Admin PIN needed to use the GPG backup thumb drive: " gpg_admin_pin
|
|
echo
|
|
done
|
|
#prompt user to select the proper encrypted partition, which should the first one on next prompt
|
|
warn "Please select encrypted LUKS on GPG key material backup thumb drive (not public labeled one)"
|
|
mount-usb --pass "$gpg_admin_pin" || die "Unable to mount USB with provided GPG Admin PIN"
|
|
echo "++++ Testing detach-sign operation and verifiying against fused public key in ROM"
|
|
gpg --pinentry-mode=loopback --passphrase-file <(echo -n "${gpg_admin_pin}") --import /media/subkeys.sec >/dev/null 2>&1 ||
|
|
die "Unable to import GPG private subkeys"
|
|
#Do a detach signature to ensure gpg material is usable and cache passphrase to sign /boot from caller functions
|
|
dd if=/dev/urandom of="$CR_NONCE" bs=20 count=1 >/dev/null 2>&1 ||
|
|
die "Unable to create $CR_NONCE to be detach-signed with GPG private signing subkey"
|
|
gpg --pinentry-mode=loopback --passphrase-file <(echo -n "${gpg_admin_pin}") --detach-sign "$CR_NONCE" >/dev/null 2>&1 ||
|
|
die "Unable to detach-sign $CR_NONCE with GPG private signing subkey using GPG Admin PIN"
|
|
#verify detached signature against public key in rom
|
|
gpg --verify "$CR_SIG" "$CR_NONCE" > /dev/null 2>&1 && \
|
|
echo "++++ Local GPG keyring can be used to sign/encrypt/authenticate in this boot session ++++" || \
|
|
die "Unable to verify $CR_SIG detached signature against public key in ROM"
|
|
#Wipe any previous CR_NONCE and CR_SIG
|
|
shred -n 10 -z -u "$CR_NONCE" "$CR_SIG" >/dev/null 2>&1 || true
|
|
#TODO: maybe just an export instead of setting /etc/user.config otherwise could be flashed in weird corner case situation
|
|
set_user_config "CONFIG_GPG_KEY_BACKUP_IN_USE" "y"
|
|
umount /media || die "Unable to unmount USB"
|
|
return
|
|
fi
|
|
fi
|
|
|
|
# setup the USB so we can reach the USB Security Dongle's smartcard
|
|
enable_usb
|
|
|
|
echo -e "\nVerifying presence of GPG card...\n"
|
|
# ensure we don't exit without retrying
|
|
errexit=$(set -o | grep errexit | awk '{print $2}')
|
|
set +e
|
|
gpg --card-status >/dev/null
|
|
if [ $? -ne 0 ]; then
|
|
# prompt for reinsertion and try a second time
|
|
read -n1 -r -p \
|
|
"Can't access GPG key; remove and reinsert, then press Enter to retry. " \
|
|
ignored
|
|
# restore prev errexit state
|
|
if [ "$errexit" = "on" ]; then
|
|
set -e
|
|
fi
|
|
# retry card status
|
|
gpg --card-status >/dev/null ||
|
|
die "gpg card read failed"
|
|
fi
|
|
# restore prev errexit state
|
|
if [ "$errexit" = "on" ]; then
|
|
set -e
|
|
fi
|
|
}
|
|
|
|
gpg_auth() {
|
|
if [[ "$CONFIG_HAVE_GPG_KEY_BACKUP" == "y" ]]; then
|
|
TRACE "Under /etc/ash_functions:gpg_auth"
|
|
# If we have a GPG key backup, we can use it to authenticate even if the card is lost
|
|
echo >&2 "!!!!! Please authenticate with OpenPGP smartcard/backup media to prove you are the owner of this machine !!!!!"
|
|
|
|
# Wipe any existing nonce and signature
|
|
shred -n 10 -z -u "$CR_NONCE" "$CR_SIG" 2>/dev/null || true
|
|
|
|
# In case of gpg_auth, we require confirmation of the card, so loop with confirm_gpg_card until we get it
|
|
false
|
|
while [ $? -ne 0 ]; do
|
|
# Call confirm_gpg_card in subshell to ensure GPG key material presence
|
|
( confirm_gpg_card )
|
|
done
|
|
|
|
# Perform a signing-based challenge-response,
|
|
# to authencate that the card plugged in holding
|
|
# the key to sign the list of boot files.
|
|
|
|
CR_NONCE="/tmp/secret/cr_nonce"
|
|
CR_SIG="$CR_NONCE.sig"
|
|
|
|
# Generate a random nonce
|
|
dd \
|
|
if=/dev/urandom \
|
|
of="$CR_NONCE" \
|
|
count=1 \
|
|
bs=20 \
|
|
2>/dev/null \
|
|
|| die "Unable to generate 20 random bytes"
|
|
|
|
# Sign the nonce
|
|
for tries in 1 2 3; do
|
|
if gpg --digest-algo SHA256 \
|
|
--detach-sign \
|
|
-o "$CR_SIG" \
|
|
"$CR_NONCE" > /dev/null 2>&1 \
|
|
&& gpg --verify "$CR_SIG" "$CR_NONCE" > /dev/null 2>&1 \
|
|
; then
|
|
shred -n 10 -z -u "$CR_NONCE" "$CR_SIG" 2>/dev/null || true
|
|
DEBUG "Under /etc/ash_functions:gpg_auth: success"
|
|
return 0
|
|
else
|
|
shred -n 10 -z -u "$CR_SIG" 2>/dev/null || true
|
|
if [ "$tries" -lt 3 ]; then
|
|
echo >&2 "!!!!! GPG authentication failed, please try again !!!!!"
|
|
continue
|
|
else
|
|
die "GPG authentication failed, please reboot and try again"
|
|
fi
|
|
fi
|
|
done
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
recovery() {
|
|
TRACE "Under /etc/ash_functions:recovery"
|
|
echo >&2 "!!!!! $*"
|
|
|
|
# Remove any temporary secret files that might be hanging around
|
|
# but recreate the directory so that new tools can use it.
|
|
|
|
#safe to always be true. Otherwise "set -e" would make it exit here
|
|
shred -n 10 -z -u /tmp/secret/* 2> /dev/null || true
|
|
rm -rf /tmp/secret
|
|
mkdir -p /tmp/secret
|
|
|
|
# ensure /tmp/config exists for recovery scripts that depend on it
|
|
touch /tmp/config
|
|
. /tmp/config
|
|
|
|
DEBUG "Board $CONFIG_BOARD - version $(fw_version)"
|
|
|
|
if [ "$CONFIG_TPM" = "y" ]; then
|
|
DEBUG "Extending TPM PCR 4 for recovery shell access"
|
|
tpmr extend -ix 4 -ic recovery
|
|
fi
|
|
|
|
if [ "$CONFIG_RESTRICTED_BOOT" = y ]; then
|
|
echo >&2 "Restricted Boot enabled, recovery console disabled, rebooting in 5 seconds"
|
|
sleep 5
|
|
/bin/reboot
|
|
fi
|
|
while [ true ]
|
|
do
|
|
#Going to recovery shell should be authenticated if supported
|
|
gpg_auth
|
|
|
|
echo >&2 "!!!!! Starting recovery shell"
|
|
sleep 1
|
|
|
|
if [ -x /bin/setsid ]; then
|
|
/bin/setsid -c /bin/sh
|
|
else
|
|
/bin/sh
|
|
fi
|
|
done
|
|
}
|
|
|
|
pause_recovery() {
|
|
TRACE "Under /etc/ash_functions:pause_recovery"
|
|
read -p $'!!! Hit enter to proceed to recovery shell !!!\n'
|
|
recovery $*
|
|
}
|
|
|
|
combine_configs() {
|
|
TRACE "Under /etc/ash_functions:combine_configs"
|
|
cat /etc/config* > /tmp/config
|
|
}
|
|
|
|
replace_config() {
|
|
TRACE "Under /etc/functions:replace_config"
|
|
CONFIG_FILE=$1
|
|
CONFIG_OPTION=$2
|
|
NEW_SETTING=$3
|
|
|
|
touch $CONFIG_FILE
|
|
# first pull out the existing option from the global config and place in a tmp file
|
|
awk "gsub(\"^export ${CONFIG_OPTION}=.*\",\"export ${CONFIG_OPTION}=\\\"${NEW_SETTING}\\\"\")" /tmp/config >${CONFIG_FILE}.tmp
|
|
awk "gsub(\"^${CONFIG_OPTION}=.*\",\"${CONFIG_OPTION}=\\\"${NEW_SETTING}\\\"\")" /tmp/config >>${CONFIG_FILE}.tmp
|
|
|
|
# then copy any remaining settings from the existing config file, minus the option you changed
|
|
grep -v "^export ${CONFIG_OPTION}=" ${CONFIG_FILE} | grep -v "^${CONFIG_OPTION}=" >>${CONFIG_FILE}.tmp || true
|
|
sort ${CONFIG_FILE}.tmp | uniq >${CONFIG_FILE}
|
|
rm -f ${CONFIG_FILE}.tmp
|
|
}
|
|
|
|
# Set a config variable in a specific file to a given value - replace it if it
|
|
# exists, or add it. If added, the variable will be exported.
|
|
set_config() {
|
|
CONFIG_FILE="$1"
|
|
CONFIG_OPTION="$2"
|
|
NEW_SETTING="$3"
|
|
|
|
if grep -q "$CONFIG_OPTION" "$CONFIG_FILE"; then
|
|
replace_config "$CONFIG_FILE" "$CONFIG_OPTION" "$NEW_SETTING"
|
|
else
|
|
echo "export $CONFIG_OPTION=\"$NEW_SETTING\"" >>"$CONFIG_FILE"
|
|
fi
|
|
}
|
|
|
|
# Set a value in config.user, re-combine configs, and update configs in the
|
|
# environment.
|
|
set_user_config() {
|
|
CONFIG_OPTION="$1"
|
|
NEW_SETTING="$2"
|
|
|
|
set_config /etc/config.user "$CONFIG_OPTION" "$NEW_SETTING"
|
|
combine_configs
|
|
. /tmp/config
|
|
}
|
|
|
|
# Load a config value to a variable, defaulting to empty. Does not fail if the
|
|
# config is not set (since it would expand to empty by default).
|
|
load_config_value() {
|
|
local config_name="$1"
|
|
if grep -q "$config_name=" /tmp/config; then
|
|
grep "$config_name=" /tmp/config | tail -n1 | cut -f2 -d '=' | tr -d '"'
|
|
fi
|
|
}
|
|
|
|
enable_usb()
|
|
{
|
|
TRACE "Under /etc/ash_functions:enable_usb"
|
|
#insmod ehci_hcd prior of uhdc_hcd and ohci_hcd to suppress dmesg warning
|
|
insmod /lib/modules/ehci-hcd.ko || die "ehci_hcd: module load failed"
|
|
|
|
if [ "$CONFIG_LINUX_USB_COMPANION_CONTROLLER" = y ]; then
|
|
insmod /lib/modules/uhci-hcd.ko || die "uhci_hcd: module load failed"
|
|
insmod /lib/modules/ohci-hcd.ko || die "ohci_hcd: module load failed"
|
|
insmod /lib/modules/ohci-pci.ko || die "ohci_pci: module load failed"
|
|
fi
|
|
insmod /lib/modules/ehci-pci.ko || die "ehci_pci: module load failed"
|
|
insmod /lib/modules/xhci-hcd.ko || die "xhci_hcd: module load failed"
|
|
insmod /lib/modules/xhci-pci.ko || die "xhci_pci: module load failed"
|
|
sleep 2
|
|
|
|
# For resiliency, test CONFIG_USB_KEYBOARD_REQUIRED explicitly rather
|
|
# than having it imply CONFIG_USER_USB_KEYBOARD at build time.
|
|
# Otherwise, if a user got CONFIG_USER_USB_KEYBOARD=n in their
|
|
# config.user by mistake (say, by copying config.user from a laptop to a
|
|
# desktop/server), they could lock themselves out, only recoverable by
|
|
# hardware flash.
|
|
if [ "$CONFIG_USB_KEYBOARD_REQUIRED" = y ] || [ "$CONFIG_USER_USB_KEYBOARD" = y ]; then
|
|
insmod /lib/modules/usbhid.ko || die "usbhid: module load failed"
|
|
fi
|
|
}
|