From 79da79a5e488e2db692376be9ba0faa659f2baa8 Mon Sep 17 00:00:00 2001 From: Kyle Rankin Date: Tue, 25 Oct 2022 15:09:15 -0700 Subject: [PATCH] Implement Restricted Boot Mode Restricted Boot mode only allows booting from signed files, whether that is signed kernels in /boot or signed ISOs on mounted USB disks. This disables booting from abitrary USB disks as well as the forced "unsafe" boot mode. This also disables the recovery console so you can't bypass this mode simply by running kexec manually. Signed-off-by: Jonathon Hall --- initrd/bin/config-gui.sh | 63 +++++++++++++++++++++++++++++++++++++++- initrd/bin/flash-gui.sh | 6 ++++ initrd/bin/gui-init | 4 +++ initrd/bin/media-scan | 4 +++ initrd/bin/tpmr | 30 +++++++++++++++++++ initrd/bin/wipe-totp | 14 +++++++++ initrd/etc/ash_functions | 5 ++++ 7 files changed, 125 insertions(+), 1 deletion(-) create mode 100755 initrd/bin/wipe-totp diff --git a/initrd/bin/config-gui.sh b/initrd/bin/config-gui.sh index 03841551..45283eb9 100755 --- a/initrd/bin/config-gui.sh +++ b/initrd/bin/config-gui.sh @@ -29,6 +29,8 @@ while true; do else # check current PureBoot Mode BASIC_MODE="$(load_config_value CONFIG_PUREBOOT_BASIC)" + # check current Restricted Boot Mode + RESTRICTED_BOOT="$(load_config_value CONFIG_RESTRICTED_BOOT)" BASIC_NO_AUTOMATIC_DEFAULT="$(load_config_value CONFIG_BASIC_NO_AUTOMATIC_DEFAULT)" dynamic_config_options=() @@ -45,6 +47,7 @@ while true; do 'D' ' Change the root directories to hash' \ 'B' ' Check root hashes at boot' \ 'P' " $(get_config_display_action "$BASIC_MODE") PureBoot Basic Mode" \ + 'L' " $(get_config_display_action "$RESTRICTED_BOOT") Restricted Boot" \ ) fi @@ -247,7 +250,10 @@ while true; do fi ;; "P" ) - if [ "$BASIC_MODE" = "n" ]; then + if ! [ "$RESTRICTED_BOOT" = n ]; then + whiptail $BG_COLOR_ERROR --title 'Restricted Boot Active' \ + --msgbox "Disable Restricted Boot to enable Basic Mode." 0 80 + elif [ "$BASIC_MODE" = "n" ]; then if (whiptail --title 'Enable PureBoot Basic Mode?' \ --yesno "This will remove all signature checking on the firmware \nand boot files, and disable use of the Librem Key. @@ -274,6 +280,61 @@ while true; do fi fi ;; + "L" ) + if [ "$RESTRICTED_BOOT" = "n" ]; then + if (whiptail --title 'Enable Restricted Boot Mode?' \ + --yesno "This will disable booting from any unsigned files, + \nincluding kernels that have not yet been signed, + \n.isos without signatures, raw USB disks, + \nand will disable failsafe boot mode. + \n\nThis will also disable the recovery console. + \n\nDo you want to proceed?" 0 80) then + + set_config /etc/config.user "CONFIG_RESTRICTED_BOOT" "y" + combine_configs + + whiptail --title 'Config change successful' \ + --msgbox "Restricted Boot mode enabled;\nsave the config change and reboot for it to go into effect." 0 80 + + fi + else + if (whiptail --title 'Disable Restricted Boot Mode?' \ + --yesno "This will allow booting from unsigned devices, + \nand will re-enable failsafe boot mode. + \n\nThis will also RESET the TPM and re-enable the recovery console. + \n\nProceeding will automatically update the boot firmware, reset TPM and reboot! + \n\nDo you want to proceed?" 0 80) then + + # Wipe the TPM TOTP/HOTP secret before flashing. Otherwise, enabling + # Restricted Boot again might restore the firmware to an identical + # state, and there would be no evidence that it had been temporarily + # disabled. + if ! wipe-totp >/dev/null 2>/tmp/error; then + ERROR=$(tail -n 1 /tmp/error | fold -s) + whiptail $BG_COLOR_ERROR --title 'ERROR: erasing TOTP secret' \ + --msgbox "Erasing TOTP Secret Failed\n\n${ERROR}" 0 80 + exit 1 + fi + + # We can't allow Restricted Boot to be disabled without flashing the + # firmware - this would allow the use of unrestricted mode without + # leaving evidence in the firmware. Disable it by flashing the new + # config directly. + FLASH_USER_CONFIG=/tmp/config-gui-config-user + cp /etc/config.user "$FLASH_USER_CONFIG" + set_config "$FLASH_USER_CONFIG" "CONFIG_RESTRICTED_BOOT" "n" + + read_rom /tmp/config-gui.rom + + replace_rom_file /tmp/config-gui.rom "heads/initrd/etc/config.user" "$FLASH_USER_CONFIG" + + /bin/flash.sh /tmp/config-gui.rom + whiptail --title 'BIOS Updated Successfully' \ + --msgbox "BIOS updated successfully.\n\nIf your keys have changed, be sure to re-sign all files in /boot\nafter you reboot.\n\nPress Enter to reboot" 0 80 + /bin/reboot + fi + fi + ;; "A" ) if [ "$BASIC_NO_AUTOMATIC_DEFAULT" = "n" ]; then if (whiptail --title 'Disable automatic default boot?' \ diff --git a/initrd/bin/flash-gui.sh b/initrd/bin/flash-gui.sh index e952272f..b9bc4883 100755 --- a/initrd/bin/flash-gui.sh +++ b/initrd/bin/flash-gui.sh @@ -7,6 +7,12 @@ set -e -o pipefail TRACE "Under /bin/flash-gui.sh" +if [ "$CONFIG_RESTRICTED_BOOT" = y ]; then + whiptail $BG_COLOR_ERROR --title 'Restricted Boot Active' \ + --msgbox "Disable Restricted Boot to flash new firmware." 16 60 + exit 1 +fi + while true; do unset menu_choice whiptail $BG_COLOR_MAIN_MENU --title "Firmware Management Menu" \ diff --git a/initrd/bin/gui-init b/initrd/bin/gui-init index 8b904019..eb64d2cd 100755 --- a/initrd/bin/gui-init +++ b/initrd/bin/gui-init @@ -603,6 +603,10 @@ attempt_default_boot() force_unsafe_boot() { TRACE "Under /bin/gui-init:force_unsafe_boot" + if [ "$CONFIG_RESTRICTED_BOOT" = y ]; then + whiptail $BG_COLOR_ERROR --title 'ERROR: Restricted Boot Enabled' --msgbox "Restricted Boot is Enabled, forced boot not allowed.\n\nPress OK to return to the Main Menu" 0 80 + return + fi # Run the menu selection in "force" mode, bypassing hash checks if (whiptail $BG_COLOR_WARNING --title 'Unsafe Forced Boot Selected!' \ --yesno "WARNING: You have chosen to skip all tamper checks and boot anyway.\n\nThis is an unsafe option!\n\nDo you want to proceed?" 0 80) then diff --git a/initrd/bin/media-scan b/initrd/bin/media-scan index d64be05d..592aa5a4 100755 --- a/initrd/bin/media-scan +++ b/initrd/bin/media-scan @@ -92,6 +92,10 @@ if [ `cat /tmp/iso_menu.txt | wc -l` -gt 0 ]; then fi fi +if [ "$CONFIG_RESTRICTED_BOOT" = y ]; then + die "ISO boot failed in Restricted Boot mode." +fi + echo "!!! Could not find any ISO, trying bootable USB" # Attempt to pull verified config from device if [ -x /bin/whiptail ]; then diff --git a/initrd/bin/tpmr b/initrd/bin/tpmr index c392d306..da71a069 100755 --- a/initrd/bin/tpmr +++ b/initrd/bin/tpmr @@ -250,6 +250,32 @@ cleanup_shred() { shred -n 10 -z -u "$1" 2>/dev/null || true } +# tpm2_destroy: Destroy a sealed file in the TPM. The mechanism differs by +# TPM version - TPM2 evicts the file object, so it no longer exists. +tpm2_destroy() { + index="$1" # Index of the sealed file + size="$2" # Size of zeroes to overwrite for TPM1 (unused in TPM2) + + # Pad with up to 6 zeros, i.e. '0x81000001', '0x81001234', etc. + handle="$(printf "0x81%6s" "$index" | tr ' ' 0)" + + # remove possible data occupying this handle + tpm2 evictcontrol -Q -C p -c "$handle" 2>/dev/null \ + || die "Unable to evict secret" +} + +# tpm1_destroy: Destroy a sealed file in the TPM. The mechanism differs by +# TPM version - TPM1 overwrites the file with zeroes, since this can be done +# without authorization. (Deletion requires authorization.) +tpm1_destroy() { + index="$1" # Index of the sealed file + size="$2" # Size of zeroes to overwrite for TPM1 + + dd if=/dev/zero bs="$size" count=1 of=/tmp/wipe-totp-zero + tpm nv_writevalue -in "$index" -if /tmp/wipe-totp-zero \ + || die "Unable to wipe sealed secret" +} + # tpm2_seal: Seal a file against PCR values and, optionally, a password. # If a password is given, both the PCRs and password are required to unseal the # file. PCRs are provided as a PCR list and data file. PCR data must be @@ -576,6 +602,8 @@ if [ "$CONFIG_TPM2_TOOLS" != "y" ]; then echo "$PCR_SIZE";; calcfuturepcr) shift; tpm1_calcfuturepcr "$@";; + destroy) + shift; tpm1_destroy "$@";; seal) shift; tpm1_seal "$@";; startsession) @@ -615,6 +643,8 @@ case "$subcmd" in tpm2_counter_inc "$@";; counter_create) tpm2_counter_cre "$@";; + destroy) + tpm2_destroy "$@";; seal) tpm2_seal "$@";; startsession) diff --git a/initrd/bin/wipe-totp b/initrd/bin/wipe-totp new file mode 100755 index 00000000..1a70cefa --- /dev/null +++ b/initrd/bin/wipe-totp @@ -0,0 +1,14 @@ +#!/bin/bash +# Wipe the sealed TOTP/HOTP secret. The secret is overwritten with all-0, +# rather than deleted, because deletion requires authorization. Wiping the +# secret will cause the next boot to prompt to regenerate the secret. + +. /etc/functions + +TPM_NVRAM_SPACE=4d47 +TPM_SIZE=312 + +if [ "$CONFIG_TPM" = "y" ]; then + tpmr destroy "$TPM_NVRAM_SPACE" "$TPM_SIZE" \ + || die "Unable to wipe sealed secret" +fi diff --git a/initrd/etc/ash_functions b/initrd/etc/ash_functions index ffe88ec3..963c5cc1 100644 --- a/initrd/etc/ash_functions +++ b/initrd/etc/ash_functions @@ -62,6 +62,11 @@ recovery() { 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 echo >&2 "!!!!! Starting recovery shell"