gui-init: Implement blob jail feature

Blob jail provides device firmware blobs to the OS, so the OS does not
have to ship them.  The firmware is passed through the initrd to
/run/firmware, so it works with both installed and live OSes, and there
are no race conditions between firmware load and firmware availability.

The injection method in the initrd is specific to the style of init
script used by PureOS, since it must add a copy command to copy the
firmware from the initrd to /run.  If the init script is not of this
type, boot proceeds without device firmware.

This feature can be enabled or disabled from the config GUI.

Blob jail is enabled automatically if the Intel AX200 Wi-Fi module is
installed and the feature hasn't been explicitly configured.

Signed-off-by: Matt DeVillier <matt.devillier@puri.sm>
This commit is contained in:
Matt DeVillier 2022-01-07 13:30:57 -06:00 committed by Jonathon Hall
parent 2d3ecfa41e
commit 87eff7b775
No known key found for this signature in database
GPG Key ID: 1E9C3CA91AE25114
7 changed files with 173 additions and 33 deletions

View File

@ -31,34 +31,44 @@ while true; do
BASIC_MODE="$(load_config_value CONFIG_PUREBOOT_BASIC)"
# check current Restricted Boot Mode
RESTRICTED_BOOT="$(load_config_value CONFIG_RESTRICTED_BOOT)"
# check current state of blob jail
USE_JAIL="$(load_config_value CONFIG_USE_BLOB_JAIL)"
AUTOMATIC_POWERON="$(load_config_value CONFIG_AUTOMATIC_POWERON)"
BASIC_NO_AUTOMATIC_DEFAULT="$(load_config_value CONFIG_BASIC_NO_AUTOMATIC_DEFAULT)"
BASIC_USB_AUTOBOOT="$(load_config_value CONFIG_BASIC_USB_AUTOBOOT)"
AUTOMATIC_POWERON="$(load_config_value CONFIG_AUTOMATIC_POWERON)"
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" \
'U' " $(get_config_display_action "$BASIC_USB_AUTOBOOT") USB automatic 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" \
'L' " $(get_config_display_action "$RESTRICTED_BOOT") Restricted Boot" \
)
fi
# Options that don't apply to basic mode
[ "$BASIC_MODE" != "y" ] && 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'
'L' " $(get_config_display_action "$RESTRICTED_BOOT") Restricted Boot"
)
if [ "$CONFIG_SUPPORT_AUTOMATIC_POWERON" = "y" ]; then
dynamic_config_options+=( \
'N' " $(get_config_display_action "$AUTOMATIC_POWERON") Automatic Power-On" \
)
fi
# Basic itself is always available
dynamic_config_options+=(
'P' " $(get_config_display_action "$BASIC_MODE") PureBoot Basic Mode"
)
# Blob jail is only offered if this is a configuration with the blobs in
# firmware
[ "$CONFIG_SUPPORT_BLOB_JAIL" = "y" ] && dynamic_config_options+=(
'J' " $(get_config_display_action "$USE_JAIL") Firmware Blob Jail"
)
# Basic-only options for automatic boot
[ "$BASIC_MODE" = "y" ] && dynamic_config_options+=(
'A' " $(get_inverted_config_display_action "$BASIC_NO_AUTOMATIC_DEFAULT") automatic default boot"
'U' " $(get_config_display_action "$BASIC_USB_AUTOBOOT") USB automatic boot"
)
# Automatic power on - requires board support
[ "$CONFIG_SUPPORT_AUTOMATIC_POWERON" = "y" ] && dynamic_config_options+=(
'N' " $(get_config_display_action "$AUTOMATIC_POWERON") Automatic Power-On"
)
unset menu_choice
whiptail $BG_COLOR_MAIN_MENU --title "Config Management Menu" \
@ -344,6 +354,31 @@ while true; do
fi
fi
;;
"J" )
if [ "$USE_JAIL" = "n" ]; then
if (whiptail --title 'Enable Firmware Blob Jail?' \
--yesno "This will enable loading of firmware from flash on each boot
\n\nDo you want to proceed?" 0 80) then
toggle_config /etc/config.user "CONFIG_USE_BLOB_JAIL"
combine_configs
whiptail --title 'Config change successful' \
--msgbox "Firmware Blob Jail use has been enabled;\nsave the config change and reboot for it to go into effect." 16 60
fi
else
if (whiptail --title 'Disable Firmware Blob Jail?' \
--yesno "This will disable loading of firmware from flash on each boot.
\n\nDo you want to proceed?" 0 80) then
toggle_config /etc/config.user "CONFIG_USE_BLOB_JAIL"
combine_configs
whiptail --title 'Config change successful' \
--msgbox "Firmware Blob Jail use 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?' \

View File

@ -6,6 +6,7 @@ MAIN_MENU_TITLE="${BOARD_NAME} | Heads Boot Menu"
export BG_COLOR_MAIN_MENU=""
. /etc/functions
. /etc/gui_functions
. /etc/luks-functions
. /tmp/config

89
initrd/bin/inject_firmware.sh Executable file
View File

@ -0,0 +1,89 @@
#!/bin/bash
# If blob jail is enabled, copy initrd and inject firmware.
# Prints new initrd path (in memory) if firmware was injected.
#
# This does not alter the initrd on disk:
# * Signatures are not invalidated
# * If the injection fails for any reason, we just proceed with the original
# initrd (lacking firmware, but still booting).
# * If, somehow, this injection malfunctions (without failing outright) and
# prevents a boot, the user can work around it just by disabling blob jail.
# We do not risk ruining the real initrd.
#
# The injection has some requirements on the initrd that are all true for
# Debian:
# * initrd must be a gzipped cpio (Linux supports other compression methods)
# * /init must be a shell script (so we can inject a command to copy firmware)
# * There must be an 'exec run-init ... ${rootmnt} ...' line that moves the
# real root to / and invokes init
#
# If the injection can't be performed, boot will continue with no firmware.
set -e -o pipefail
. /tmp/config
. /etc/functions
if [ "$(load_config_value CONFIG_USE_BLOB_JAIL)" != "y" ]; then
# Blob jail not active, nothing to do
exit 0
fi
ORIG_INITRD="$1"
# Extract the init script from the initrd
INITRD_ROOT="/tmp/inject_firmware_initrd_root"
rm -rf "$INITRD_ROOT" || true
mkdir "$INITRD_ROOT"
gunzip <"$ORIG_INITRD" | (cd "$INITRD_ROOT"; cpio -i init 2>/dev/null)
# Copy the firmware into the initrd
for f in $(cbfs -l | grep firmware); do
mkdir -p "$INITRD_ROOT/$(dirname "$f")"
cbfs -r "$f" > "$INITRD_ROOT/$f"
if [[ "$f" == *.lzma ]]; then
lzma -d "$INITRD_ROOT/$f"
fi
done
# awk will happily pass through a binary file, so look for the match we want
# before modifying init to ensure it's a shell script and not an ELF, etc.
if ! grep -E -q '^exec run-init .*\$\{rootmnt\}' "$INITRD_ROOT/init"; then
exit 0
fi
# The initrd's /init has to copy the firmware to /run/firmware, so it will be
# present when the real root is moved to /.
# * Wi-Fi/BT firmware loading doesn't happen during the initrd - these modules
# aren't in the initrd anyway, typically.
# * /run is a tmpfs mount, so this works even if the root filesystem is
# read-only, and it doesn't persist anything.
#
# kexec-boot will add a kernel parameter for the kernel to look for firmware in
# /run/firmware.
#
# Debian's init script ends with an "exec run-init ..." (followed by a few lines
# to print a message in case it fails). At that point, root is mounted, and
# run-init will move it to / and then exec init. We can copy the firmware just
# before that, so we don't have to know anything about how root was mounted.
#
# The root path is in ${rootmnt}, which should appear in the run-init command.
# If it doesn't, then we don't understand the init script.
AWK_INSERT_CP='
BEGIN{inserted=0}
/^exec run-init .*\$\{rootmnt\}/ && inserted==0 {print "cp -r /firmware ${rootmnt}/run/firmware"; inserted=1}
{print $0}'
awk -e "$AWK_INSERT_CP" "$INITRD_ROOT/init" >"$INITRD_ROOT/init_fw"
mv "$INITRD_ROOT/init_fw" "$INITRD_ROOT/init"
chmod a+x "$INITRD_ROOT/init"
# Pad the original initrd to 512 byte blocks, the last gzip blob is often not
# padded. (If it is not gzip-compressed, we would already have failed above.)
FW_INITRD="/tmp/inject_firmware_initrd.cpio.gz"
dd if="$ORIG_INITRD" of="$FW_INITRD" bs=512 conv=sync status=none
# Pack up the new contents and append to the initrd
(cd "$INITRD_ROOT"; find . | cpio -o -H newc) | gzip >>"$FW_INITRD"
# Use this initrd
echo "$FW_INITRD"

View File

@ -34,6 +34,10 @@ kexeccmd="kexec"
cmdadd="$CONFIG_BOOT_KERNEL_ADD $cmdadd"
cmdremove="$CONFIG_BOOT_KERNEL_REMOVE $cmdremove"
if [ "$(load_config_value CONFIG_USE_BLOB_JAIL)" = "y" ]; then
cmdadd="$cmdadd firmware_class.path=/run/firmware/"
fi
fix_file_path() {
if [ "$printfiles" = "y" ]; then
# output file relative to local boot directory
@ -120,6 +124,10 @@ do
if [ -n "$override_initrd" ]; then
filepath="$override_initrd"
fi
firmware_initrd="$(inject_firmware.sh "$filepath" || true)"
if [ -n "$firmware_initrd" ]; then
filepath="$firmware_initrd"
fi
kexeccmd="$kexeccmd --initrd=$filepath"
fi
if [ "$key" = "append" ]; then

View File

@ -344,6 +344,17 @@ set_config() {
fi
}
# Load a config value to a variable, defaulting to 'n'
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 '"'
else
echo n
fi
}
# Generate secret value using first 20 chars of ROM SHA256 hash
secret_from_rom_hash() {
local ROM_IMAGE="/tmp/coreboot-notpm.rom"

View File

@ -102,17 +102,6 @@ show_system_info()
--msgbox "${BOARD_NAME}\n\nFW_VER: ${FW_VER}\nKernel: ${kernel}\n\nCPU: ${cpustr}\nRAM: ${memtotal} GB\n$battery_status\n$(fdisk -l | grep -e '/dev/sd.:' -e '/dev/nvme.*:' | sed 's/B,.*/B/')" 16 60
}
# Load a config value to a variable, defaulting to 'n'
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 '"'
else
echo n
fi
}
# Get "Enable" or "Disable" to display in the configuration menu, based on a
# setting value
get_config_display_action()

View File

@ -120,6 +120,13 @@ if [ "$boot_option" = "r" ]; then
exit
fi
# Override CONFIG_USE_BLOB_JAIL if needed and persist via user config
if lspci -n | grep -q "8086:2723"; then
if ! cat /etc/config.user 2>/dev/null | grep -q "USE_BLOB_JAIL"; then
echo "CONFIG_USE_BLOB_JAIL=y" >> /etc/config.user
fi
fi
# Override CONFIG_TPM and CONFIG_TPM2_TOOLS from /etc/config with runtime value
# determined above.
#