Add PureBoot Basic Mode

PureBoot Basic mode provides the full Linux userspace in firmware from
Heads without requiring verified boot or a Librem Key.  Basic and
verified boot can be switched freely without changing firmware, such as
if a Librem Key is lost.

PureBoot Basic can apply firmware updates from a USB flash drive, and
having a complete Linux userspace enables more sophisticated recovery
options.

Basic mode boots to the first boot option by default, setting a default
is not required.  This can be configured in the config GUI.

Signed-off-by: Jonathon Hall <jonathon.hall@puri.sm>
This commit is contained in:
Matt DeVillier 2022-03-15 12:05:04 -05:00 committed by Jonathon Hall
parent a5238b5823
commit 4bc6159ab6
No known key found for this signature in database
GPG Key ID: 1E9C3CA91AE25114
8 changed files with 353 additions and 25 deletions

11
initrd/bin/basic-autoboot.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/bash
set -o pipefail
. /etc/functions
BOOT_MENU_OPTIONS=/tmp/basic-autoboot-options
scan_boot_options /boot "grub.cfg" "$BOOT_MENU_OPTIONS"
if [ -s "$BOOT_MENU_OPTIONS" ]; then
kexec-boot -b /boot -e "$(head -1 "$BOOT_MENU_OPTIONS")"
fi

View File

@ -27,14 +27,32 @@ while true; do
menu_choice=${param::1}
unset param
else
# check current PureBoot Mode
BASIC_MODE="$(load_config_value CONFIG_PUREBOOT_BASIC)"
BASIC_NO_AUTOMATIC_DEFAULT="$(load_config_value CONFIG_BASIC_NO_AUTOMATIC_DEFAULT)"
dynamic_config_options=()
if [ "$BASIC_MODE" = "y" ]; then
dynamic_config_options+=( \
'P' " $(get_config_display_action "$BASIC_MODE") PureBoot Basic Mode" \
'A' " $(get_inverted_config_display_action "$BASIC_NO_AUTOMATIC_DEFAULT") automatic default boot" \
)
else
dynamic_config_options+=( \
'r' ' Clear GPG key(s) and reset all user settings' \
'R' ' Change the root device for hashing' \
'D' ' Change the root directories to hash' \
'B' ' Check root hashes at boot' \
'P' " $(get_config_display_action "$BASIC_MODE") PureBoot Basic Mode" \
)
fi
unset menu_choice
whiptail $BG_COLOR_MAIN_MENU --title "Config Management Menu" \
--menu "This menu lets you change settings for the current BIOS session.\n\nAll changes will revert after a reboot,\n\nunless you also save them to the running BIOS." 0 80 10 \
'b' ' Change the /boot device' \
'r' ' Clear GPG key(s) and reset all user settings' \
'R' ' Change the root device for hashing' \
'D' ' Change the root directories to hash' \
'B' ' Check root hashes at boot' \
"${dynamic_config_options[@]}" \
's' ' Save the current configuration to the running BIOS' \
'x' ' Return to Main Menu' \
2>/tmp/whiptail || recovery "GUI menu failed"
@ -228,6 +246,61 @@ while true; do
fi
fi
;;
"P" )
if [ "$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.
\n\nDo you want to proceed?" 0 80) then
set_config /etc/config.user "CONFIG_PUREBOOT_BASIC" "y"
combine_configs
whiptail --title 'Config change successful' \
--msgbox "PureBoot Basic mode enabled;\nsave the config change and reboot for it to go into effect." 0 80
fi
else
if (whiptail --title 'Disable PureBoot Basic Mode?' \
--yesno "This will enable all signature checking on the firmware
\nand boot files, and enable use of the Librem Key.
\n\nDo you want to proceed?" 0 80) then
set_config /etc/config.user "CONFIG_PUREBOOT_BASIC" "n"
combine_configs
whiptail --title 'Config change successful' \
--msgbox "PureBoot Basic mode has been disabled;\nsave the config change and reboot for it to go into effect." 0 80
fi
fi
;;
"A" )
if [ "$BASIC_NO_AUTOMATIC_DEFAULT" = "n" ]; then
if (whiptail --title 'Disable automatic default boot?' \
--yesno "You will need to select a default boot option.
\nIf the boot options are changed, such as for an OS update,
\nyou will be prompted to select a new default.
\n\nDo you want to proceed?" 0 80) then
set_config /etc/config.user "CONFIG_BASIC_NO_AUTOMATIC_DEFAULT" "y"
combine_configs
whiptail --title 'Config change successful' \
--msgbox "Automatic default boot disabled;\nsave the config change and reboot for it to go into effect." 0 80
fi
else
if (whiptail --title 'Enable automatic default boot?' \
--yesno "The first boot option will be used automatically.
\n\nDo you want to proceed?" 0 80) then
set_config /etc/config.user "CONFIG_BASIC_NO_AUTOMATIC_DEFAULT" "n"
combine_configs
whiptail --title 'Config change successful' \
--msgbox "Automatic default boot enabled;\nsave the config change and reboot for it to go into effect." 0 80
fi
fi
;;
esac
done

213
initrd/bin/gui-init-basic Executable file
View File

@ -0,0 +1,213 @@
#!/bin/bash
# Boot from a local disk installation
BOARD_NAME=${CONFIG_BOARD_NAME:-${CONFIG_BOARD}}
MAIN_MENU_TITLE="${BOARD_NAME} | PureBoot Basic Boot Menu"
export BG_COLOR_MAIN_MENU=""
. /etc/functions
. /etc/gui_functions
. /tmp/config
# skip_to_menu is set if the user selects "continue to the main menu" from any
# error, so we will indeed go to the main menu even if other errors occur. It's
# reset when we reach the main menu so the user can retry from the main menu and
# # see errors again.
skip_to_menu="false"
mount_boot()
{
TRACE "Under /bin/gui-init:mount_boot"
# Mount local disk if it is not already mounted
while ! grep -q /boot /proc/mounts ; do
# try to mount if CONFIG_BOOT_DEV exists
if [ -e "$CONFIG_BOOT_DEV" ]; then
mount -o ro $CONFIG_BOOT_DEV /boot
[[ $? -eq 0 ]] && continue
fi
# CONFIG_BOOT_DEV doesn't exist or couldn't be mounted, so give user options
BG_COLOR_MAIN_MENU=$BG_COLOR_ERROR
whiptail $BG_COLOR_ERROR --title "ERROR: No Bootable OS Found!" \
--menu " No bootable OS was found on the default boot device $CONFIG_BOOT_DEV.
How would you like to proceed?" 0 80 4 \
'b' ' Select a new boot device' \
'u' ' Boot from USB' \
'm' ' Continue to the main menu' \
'x' ' Exit to recovery shell' \
2>/tmp/whiptail || recovery "GUI menu failed"
option=$(cat /tmp/whiptail)
case "$option" in
b )
config-gui.sh boot_device_select
if [ $? -eq 0 ]; then
# update CONFIG_BOOT_DEV
. /tmp/config
BG_COLOR_MAIN_MENU=""
fi
;;
u )
exec /bin/usb-init
;;
m )
skip_to_menu="true"
break
;;
* )
recovery "User requested recovery shell"
;;
esac
done
}
prompt_auto_default_boot()
{
TRACE "Under /bin/gui-init:prompt_auto_default_boot"
# save IFS before changing, restore after read
IFS_DEF=$IFS
IFS=''
first_pass=false
echo -e "\n\n"
read -t $CONFIG_AUTO_BOOT_TIMEOUT -s -n 1 -p "Automatic boot in $CONFIG_AUTO_BOOT_TIMEOUT seconds unless interrupted by keypress... "
if [[ $? -ne 0 ]]; then
IFS=$IFS_DEF
echo -e "\n\nAttempting default boot...\n\n"
attempt_default_boot
fi
IFS=$IFS_DEF
}
show_main_menu()
{
TRACE "Under /bin/gui-init:show_main_menu"
date=`date "+%Y-%m-%d %H:%M:%S %Z"`
whiptail $BG_COLOR_MAIN_MENU --title "$MAIN_MENU_TITLE" \
--menu "$date" 0 80 10 \
'd' ' Default boot' \
'o' ' Options -->' \
's' ' System Info' \
'p' ' Power Off' \
2>/tmp/whiptail || recovery "GUI menu failed"
option=$(cat /tmp/whiptail)
case "$option" in
d )
attempt_default_boot
;;
o )
show_options_menu
;;
s )
show_system_info
;;
p )
poweroff
;;
esac
}
show_options_menu()
{
TRACE "Under /bin/gui-init:show_options_menu"
whiptail $BG_COLOR_MAIN_MENU --title "PureBoot Basic Options" \
--menu "" 0 80 10 \
'b' ' Boot Options -->' \
'c' ' Change configuration settings -->' \
'f' ' Flash/Update the BIOS -->' \
'x' ' Exit to recovery shell' \
'r' ' <-- Return to main menu' \
2>/tmp/whiptail || recovery "GUI menu failed"
option=$(cat /tmp/whiptail)
case "$option" in
b )
show_boot_options_menu
;;
c )
config-gui.sh
;;
f )
flash-gui.sh
;;
x )
recovery "User requested recovery shell"
;;
r )
;;
esac
}
show_boot_options_menu()
{
TRACE "Under /bin/gui-init:show_boot_options_menu"
whiptail $BG_COLOR_MAIN_MENU --title "Boot Options" \
--menu "Select A Boot Option" 0 80 10 \
'm' ' Show OS boot menu' \
'u' ' USB boot' \
'r' ' <-- Return to main menu' \
2>/tmp/whiptail || recovery "GUI menu failed"
option=$(cat /tmp/whiptail)
case "$option" in
m )
# select a kernel from the menu
select_os_boot_option
;;
u )
exec /bin/usb-init
;;
r )
;;
esac
}
select_os_boot_option()
{
TRACE "Under /bin/gui-init:select_os_boot_option"
mount_boot
kexec-select-boot -m -b /boot -c "grub.cfg" -g -i
}
attempt_default_boot()
{
TRACE "Under /bin/gui-init:attempt_default_boot"
mount_boot
DEFAULT_FILE=`find /boot/kexec_default.*.txt 2>/dev/null | head -1`
# Basic by default boots automatically to the first menu option. This allows
# kernel updates to work in Basic by default without prompting to select a
# new default boot option.
if [ "$CONFIG_BASIC_NO_AUTOMATIC_DEFAULT" != "y" ]; then
basic-autoboot.sh
elif [ -r "$DEFAULT_FILE" ]; then
kexec-select-boot -b /boot -c "grub.cfg" -g -i -s \
|| recovery "Failed default boot"
elif (whiptail $BG_COLOR_WARNING --title 'No Default Boot Option Configured' \
--yesno "There is no default boot option configured yet.\nWould you like to load a menu of boot options?\nOtherwise you will return to the main menu." 0 80) then
kexec-select-boot -m -b /boot -c "grub.cfg" -g -i
fi
}
# gui-init-basic start
TRACE "Under /bin/gui-init, start"
if ! detect_boot_device ; then
# can't determine /boot device or no OS installed,
# so fall back to interactive selection
mount_boot
fi
if [ "$skip_to_menu" != "true" -a -n "$CONFIG_AUTO_BOOT_TIMEOUT" ]; then
prompt_auto_default_boot
fi
while true; do
TRACE "Under gui-init:while true loop"
skip_to_menu="false"
show_main_menu
done
recovery "Something failed during boot"

View File

@ -194,8 +194,9 @@ if [ "$CONFIG_TPM" = "y" ];then
extparam=-r
fi
fi
kexec-sign-config -p $paramsdir $extparam \
|| die "Failed to sign default config"
if [ "$CONFIG_PUREBOOT_BASIC" != "y" ]; then
kexec-sign-config -p $paramsdir $extparam \
|| die "Failed to sign default config"
fi
# switch back to ro mode
mount -o ro,remount $paramsdev

View File

@ -257,19 +257,20 @@ default_select() {
fi
parse_option
# 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
if [ "$CONFIG_PUREBOOT_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
die "!!! $TMP_DEFAULT_HASH_FILE: default boot hash mismatch"
fi
echo "+++ Executing default boot for $name:"
@ -320,6 +321,11 @@ user_select() {
do_boot()
{
if [ "$CONFIG_PUREBOOT_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
@ -348,7 +354,7 @@ do_boot()
}
while true; do
if [ "$force_boot" = "y" ]; then
if [ "$force_boot" = "y" -o "$CONFIG_PUREBOOT_BASIC" = "y" ]; then
check_config $paramsdir force
else
check_config $paramsdir
@ -366,9 +372,11 @@ while true; do
# even if hashes don't match
if [ "$force_boot" = "y" ]; then
scan_options
# 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"
if [ "$CONFIG_PUREBOOT_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
@ -385,7 +393,7 @@ while true; do
scan_options
fi
if [ "$CONFIG_TPM" = "y" ]; then
if [ "$CONFIG_TPM" = "y" -a "$CONFIG_PUREBOOT_BASIC" != "y" ]; then
# Optionally enforce device file hashes
if [ -r "$TMP_HASH_FILE" ]; then
valid_global_hash="n"

View File

@ -266,7 +266,7 @@ check_config() {
|| die 'Failed to empty kexec tmp dir'
fi
if [ ! -r $1/kexec.sig ]; then
if [ ! -r $1/kexec.sig -a "$CONFIG_PUREBOOT_BASIC" != "y" ]; then
return
fi

View File

@ -119,3 +119,16 @@ get_config_display_action()
{
[ "$1" = "y" ] && echo "Disable" || echo "Enable"
}
# Invert a config value
invert_config()
{
[ "$1" = "y" ] && echo "n" || echo "y"
}
# Get "Enable" or "Disable" for a config that internally is inverted (because it
# disables a behavior that is on by default).
get_inverted_config_display_action()
{
get_config_display_action "$(invert_config "$1")"
}

View File

@ -131,6 +131,10 @@ echo "export CONFIG_TPM2_TOOLS=\"$CONFIG_TPM2_TOOLS\"" >> /etc/config.user
combine_configs
. /tmp/config
if [ "$CONFIG_PUREBOOT_BASIC" = "y" ]; then
echo -e "***** BASIC mode: tamper detection disabled\n" > /dev/tty0
fi
# export firmware version
export FW_VER=$(dmesg | grep 'DMI' | grep -o 'BIOS.*' | cut -f2- -d ' ')
# chop off date, since will always be epoch w/timeless builds
@ -142,6 +146,11 @@ if [ ! -z "$CONFIG_BOOT_DEV" ]; then
echo >> /etc/fstab "$CONFIG_BOOT_DEV /boot auto defaults,ro 0 0"
fi
if [ "$CONFIG_PUREBOOT_BASIC" = "y" ]; then
CONFIG_BOOTSCRIPT=/bin/gui-init-basic
export CONFIG_HOTPKEY=n
fi
if [ ! -x "$CONFIG_BOOTSCRIPT" -a ! -x "$CONFIG_BOOTSCRIPT_NETWORK" ]; then
recovery 'Boot script missing? Entering recovery shell'
else