#!/bin/bash # Generic configurable boot script via kexec set -e -o pipefail . /tmp/config . /etc/functions TRACE "Under /bin/kexec-select-boot" add="" remove="" config="*.cfg" unique="n" valid_hash="n" valid_global_hash="n" valid_rollback="n" force_menu="n" gui_menu="n" force_boot="n" skip_confirm="n" while getopts "b:d:p:a:r:c:uimgfs" arg; do case $arg in b) bootdir="$OPTARG" ;; d) paramsdev="$OPTARG" ;; p) paramsdir="$OPTARG" ;; a) add="$OPTARG" ;; r) remove="$OPTARG" ;; c) config="$OPTARG" ;; u) unique="y" ;; m) force_menu="y" ;; i) valid_hash="y"; valid_rollback="y" ;; g) gui_menu="y" ;; f) force_boot="y"; valid_hash="y"; valid_rollback="y" ;; s) skip_confirm="y" ;; esac done if [ -z "$bootdir" ]; then die "Usage: $0 -b /boot" fi if [ -z "$paramsdev" ]; then paramsdev="$bootdir" fi if [ -z "$paramsdir" ]; then paramsdir="$bootdir" fi bootdir="${bootdir%%/}" paramsdev="${paramsdev%%/}" paramsdir="${paramsdir%%/}" PRIMHASH_FILE="$paramsdir/kexec_primhdl_hash.txt" if [ "$CONFIG_TPM2_TOOLS" = "y" ]; then if [ -r "$PRIMHASH_FILE" ]; then sha256sum -c "$PRIMHASH_FILE" \ || { echo "FATAL: Hash of TPM2 primary key handle mismatch!"; echo "If you have not intentionally regenerated TPM2 primary key,"; warn "your system may have been compromised!"; } else echo "WARNING: Hash of TPM2 primary key handle does not exist!" echo "Please rebuild the boot hash tree." default_failed="y" fi fi verify_global_hashes() { echo "+++ Checking verified boot hash file " # Check the hashes of all the files if verify_checksums "$bootdir" "$gui_menu" ; then echo "+++ Verified boot hashes " valid_hash='y' valid_global_hash='y' else if [ "$gui_menu" = "y" ]; then CHANGED_FILES=$(grep -v 'OK$' /tmp/hash_output | cut -f1 -d ':') whiptail $BG_COLOR_ERROR --title 'ERROR: Boot Hash Mismatch' \ --msgbox "The following files failed the verification process:\n${CHANGED_FILES}\nExiting to a recovery shell" 0 80 fi die "$TMP_HASH_FILE: boot hash mismatch" fi # If user enables it, check root hashes before boot as well if [[ "$CONFIG_ROOT_CHECK_AT_BOOT" = "y" && "$force_menu" == "n" ]]; then if root-hashes-gui.sh -c; then echo "+++ Verified root hashes, continuing boot " # if user re-signs, it wipes out saved options, so scan the boot directory and generate if [ ! -r "$TMP_MENU_FILE" ]; then scan_options fi else # root-hashes-gui.sh handles the GUI error menu, just die here if [ "$gui_menu" = "y" ]; then whiptail $BG_COLOR_ERROR --title 'ERROR: Root Hash Mismatch' \ --msgbox "The root hash check failed!\nExiting to a recovery shell" 16 60 fi die "root hash mismatch, see /tmp/hash_output_mismatches for details" fi fi } verify_rollback_counter() { TPM_COUNTER=`grep counter $TMP_ROLLBACK_FILE | cut -d- -f2` if [ -z "$TPM_COUNTER" ]; then die "$TMP_ROLLBACK_FILE: TPM counter not found?" fi read_tpm_counter $TPM_COUNTER \ || die "Failed to read TPM counter" sha256sum -c $TMP_ROLLBACK_FILE \ || die "Invalid TPM counter state" valid_rollback="y" } first_menu="y" get_menu_option() { num_options=`cat $TMP_MENU_FILE | wc -l` if [ $num_options -eq 0 ]; then die "No boot options" fi if [ $num_options -eq 1 -a $first_menu = "y" ]; then option_index=1 elif [ "$gui_menu" = "y" ]; then MENU_OPTIONS="" n=0 while read option do parse_option n=`expr $n + 1` name=$(echo $name | tr " " "_") MENU_OPTIONS="$MENU_OPTIONS $n ${name} " done < $TMP_MENU_FILE whiptail --title "Select your boot option" \ --menu "Choose the boot option [1-$n, a to abort]:" 0 80 8 \ -- $MENU_OPTIONS \ 2>/tmp/whiptail || die "Aborting boot attempt" option_index=$(cat /tmp/whiptail) else echo "+++ Select your boot option:" n=0 while read option do parse_option n=`expr $n + 1` echo "$n. $name [$kernel]" done < $TMP_MENU_FILE read \ -p "Choose the boot option [1-$n, a to abort]: " \ option_index if [ "$option_index" = "a" ]; then die "Aborting boot attempt" fi fi first_menu="n" option=`head -n $option_index $TMP_MENU_FILE | tail -1` parse_option } confirm_menu_option() { if [ "$gui_menu" = "y" ]; then default_text="Make default" [[ "$CONFIG_TPM_NO_LUKS_DISK_UNLOCK" = "y" ]] && default_text="${default_text} and boot" whiptail $BG_COLOR_WARNING --title "Confirm boot details" \ --menu "Confirm the boot details for $name:\n\n$(echo $kernel| fold -s -w 80) \n\n" 0 80 8 \ -- 'd' "${default_text}" 'y' "Boot one time" \ 2>/tmp/whiptail || die "Aborting boot attempt" option_confirm=$(cat /tmp/whiptail) else echo "+++ Please confirm the boot details for $name:" echo $option read \ -n 1 \ -p "Confirm selection by pressing 'y', make default with 'd': " \ option_confirm echo fi } parse_option() { name=`echo $option | cut -d\| -f1` kernel=`echo $option | cut -d\| -f3` } scan_options() { echo "+++ Scanning for unsigned boot options" option_file="/tmp/kexec_options.txt" scan_boot_options "$bootdir" "$config" "$option_file" if [ ! -s $option_file ]; then die "Failed to parse any boot options" fi if [ "$unique" = 'y' ]; then sort -r $option_file | uniq > $TMP_MENU_FILE else cp $option_file $TMP_MENU_FILE fi } save_default_option() { if [ "$gui_menu" != "y" ]; then read \ -n 1 \ -p "Saving a default will modify the disk. Proceed? (Y/n): " \ default_confirm echo fi [ "$default_confirm" = "" ] && default_confirm="y" if [[ "$default_confirm" = "y" || "$default_confirm" = "Y" ]]; then if kexec-save-default \ -b "$bootdir" \ -d "$paramsdev" \ -p "$paramsdir" \ -i "$option_index" \ ; then echo "+++ Saved defaults to device" sleep 2 default_failed="n" force_menu="n" return else echo "Failed to save defaults" fi fi option_confirm="n" } default_select() { # Attempt boot with expected parameters # Check that entry matches that which is expected from menu default_index=`basename "$TMP_DEFAULT_FILE" | cut -d. -f 2` # Check to see if entries have changed - useful for detecting grub update expectedoption=`cat $TMP_DEFAULT_FILE` option=`head -n $default_index $TMP_MENU_FILE | tail -1` if [ "$option" != "$expectedoption" ]; then if [ "$gui_menu" = "y" ]; then whiptail $BG_COLOR_ERROR --title 'ERROR: Boot Entry Has Changed' \ --msgbox "The list of boot entries has changed\n\nPlease set a new default" 16 60 fi warn "!!! Boot entry has changed - please set a new default" return fi parse_option if [ "$CONFIG_BASIC" != "y" ]; then # Enforce that default option hashes are valid echo "+++ Checking verified default boot hash file " # Check the hashes of all the files if ( cd $bootdir && sha256sum -c "$TMP_DEFAULT_HASH_FILE" > /tmp/hash_output ); then echo "+++ Verified default boot hashes " valid_hash='y' else if [ "$gui_menu" = "y" ]; then CHANGED_FILES=$(grep -v 'OK$' /tmp/hash_output | cut -f1 -d ':') whiptail $BG_COLOR_ERROR --title 'ERROR: Default Boot Hash Mismatch' \ --msgbox "The following files failed the verification process:\n${CHANGED_FILES}\nExiting to a recovery shell" 0 80 fi fi fi echo "+++ Executing default boot for $name:" do_boot warn "Failed to boot default option" } user_select() { # No default expected boot parameters, ask user option_confirm="" while [ "$option_confirm" != "y" -a "$option_confirm" != "d" ] do get_menu_option # In force boot mode, no need offer the option to set a default, just boot if [[ "$force_boot" = "y" || "$skip_confirm" = "y" ]]; then do_boot else confirm_menu_option fi if [ "$option_confirm" = 'd' ]; then save_default_option fi done if [ "$option_confirm" = "d" ]; then if [ ! -r "$TMP_KEY_DEVICES" ]; then # continue below to boot the new default option true else echo "+++ Rebooting to start the new default option" sleep 2 if [ "$CONFIG_DEBUG_OUTPUT" != "y" ]; then reboot \ || die "!!! Failed to reboot system" else DEBUG "Rebooting is required prior of booting default boot entry" # Instead of rebooting, drop to a recovery shell # for a chance to inspect debug output recovery "Entering recovery to permit inspection of /tmp/debug.log output, reboot to continue" fi fi fi do_boot } do_boot() { if [ "$CONFIG_BASIC" != "y" ]; then kexec-boot -b "$bootdir" -e "$option" -a "$add" -r "$remove" \ || die "!!! Failed to boot w/ options: $option" fi if [ "$CONFIG_BOOT_REQ_ROLLBACK" = "y" -a "$valid_rollback" = "n" ]; then die "!!! Missing required rollback counter state" fi if [ "$CONFIG_BOOT_REQ_HASH" = "y" -a "$valid_hash" = "n" ]; then die "!!! Missing required boot hashes" fi if [ "$CONFIG_TPM" = "y" ] && [ -r "$TMP_KEY_DEVICES" ]; then INITRD=`kexec-boot -b "$bootdir" -e "$option" -i` \ || die "!!! Failed to extract the initrd from boot option" if [ -z "$INITRD" ]; then die "!!! No initrd file found in boot option" fi kexec-insert-key $INITRD \ || die "!!! Failed to insert disk key into a new initrd" kexec-boot -b "$bootdir" -e "$option" \ -a "$add" -r "$remove" -o "/tmp/secret/initrd.cpio" \ || die "!!! Failed to boot w/ options: $option" else kexec-boot -b "$bootdir" -e "$option" -a "$add" -r "$remove" \ || die "!!! Failed to boot w/ options: $option" fi } while true; do if [ "$force_boot" = "y" -o "$CONFIG_BASIC" = "y" ]; then check_config $paramsdir force else check_config $paramsdir fi TMP_DEFAULT_FILE=`find /tmp/kexec/kexec_default.*.txt 2>/dev/null | head -1` || true TMP_MENU_FILE="/tmp/kexec/kexec_menu.txt" TMP_HASH_FILE="/tmp/kexec/kexec_hashes.txt" TMP_TREE_FILE="/tmp/kexec/kexec_tree.txt" TMP_DEFAULT_HASH_FILE="/tmp/kexec/kexec_default_hashes.txt" TMP_ROLLBACK_FILE="/tmp/kexec/kexec_rollback.txt" TMP_KEY_DEVICES="/tmp/kexec/kexec_key_devices.txt" TMP_KEY_LVM="/tmp/kexec/kexec_key_lvm.txt" # Allow a way for users to ignore warnings and boot into their systems # even if hashes don't match if [ "$force_boot" = "y" ]; then scan_options if [ "$CONFIG_BASIC" != "y" ]; then # Remove boot splash and make background red in the event of a forced boot add="$add vt.default_red=0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff" remove="$remove splash quiet" fi user_select fi if [ "$CONFIG_TPM" = "y" ]; then if [ ! -r "$TMP_KEY_DEVICES" ]; then # Extend PCR4 as soon as possible tpmr extend -ix 4 -ic generic \ || die "Failed to extend PCR 4" fi fi # if no saved options, scan the boot directory and generate if [ ! -r "$TMP_MENU_FILE" ]; then scan_options fi if [ "$CONFIG_TPM" = "y" -a "$CONFIG_BASIC" != "y" ]; then # Optionally enforce device file hashes if [ -r "$TMP_HASH_FILE" ]; then valid_global_hash="n" verify_global_hashes if [ "$valid_global_hash" = "n" ]; then die "Failed to verify global hashes" fi fi if [ "$CONFIG_IGNORE_ROLLBACK" != "y" -a -r "$TMP_ROLLBACK_FILE" ]; then # in the case of iso boot with a rollback file, do not assume valid valid_rollback="n" verify_rollback_counter fi fi if [ "$default_failed" != "y" \ -a "$force_menu" = "n" \ -a -r "$TMP_DEFAULT_FILE" \ -a -r "$TMP_DEFAULT_HASH_FILE" ] \ ; then default_select default_failed="y" else user_select fi done die "!!! Shouldn't get here"