Added the ability to persist a default boot option

Similar to qubes-update, it will save then verify the hashes of
the kexec files. Once TOTP is verified, a normal boot will verify
that the file hashes and all the kexec params match and if
successful, boot directly to OS.

Also added a config option to require hash verification for
non-recovery boots, failing to recovery not met.
This commit is contained in:
Francis Lam 2017-07-04 19:49:14 -04:00
parent ce4b91cad9
commit 8004b5df2a
No known key found for this signature in database
GPG Key ID: 0A59C698920806EB
11 changed files with 244 additions and 94 deletions

View File

@ -20,5 +20,6 @@ CONFIG_LINUX_E1000E=y
CONFIG_BOOTSCRIPT=/bin/local-init CONFIG_BOOTSCRIPT=/bin/local-init
CONFIG_BOOT_REQ_HASH=n
CONFIG_BOOT_DEV="/dev/sda1" CONFIG_BOOT_DEV="/dev/sda1"
CONFIG_USB_BOOT_DEV="/dev/sdb1" CONFIG_USB_BOOT_DEV="/dev/sdb1"

View File

@ -1,12 +1,14 @@
#!/bin/sh #!/bin/sh
. /etc/functions . /etc/functions
while getopts "b:e:r:a:" arg; do dryrun="n"
while getopts "b:e:r:a:d" arg; do
case $arg in case $arg in
b) bootdir="$OPTARG" ;; b) bootdir="$OPTARG" ;;
e) entry="$OPTARG" ;; e) entry="$OPTARG" ;;
r) cmdremove="$OPTARG" ;; r) cmdremove="$OPTARG" ;;
a) cmdadd="$OPTARG" ;; a) cmdadd="$OPTARG" ;;
d) dryrun="y" ;;
esac esac
done done
@ -19,6 +21,8 @@ cmdadd="intel_iommu=on $cmdadd"
cmdremove="quiet $cmdremove" cmdremove="quiet $cmdremove"
fix_file_path() { fix_file_path() {
if [ "$dryrun" = "y" ]; then echo ".$firstval"; fi
filepath=`find $bootdir -path "*$firstval" | tail -1` filepath=`find $bootdir -path "*$firstval" | tail -1`
if ! [ -r $filepath ]; then if ! [ -r $filepath ]; then
die "failed to find file $firstval" die "failed to find file $firstval"
@ -44,20 +48,23 @@ do
firstval=`echo $line | cut -d\ -f2` firstval=`echo $line | cut -d\ -f2`
restval=`echo $line | cut -d\ -f3-` restval=`echo $line | cut -d\ -f3-`
if [ "$key" = "kernel" ]; then if [ "$key" = "kernel" ]; then
fix_file_path if [ "$kexectype" = "xen" ]; then
if [ "$kexectype" = "multiboot" ]; then
# always overload xen and with custom arguments # always overload xen and with custom arguments
# TODO: control this replacement via flag
kexeccmd="$kexeccmd -l /bin/xen.gz" kexeccmd="$kexeccmd -l /bin/xen.gz"
kexeccmd="$kexeccmd --command-line \"no-real-mode reboot=no\"" kexeccmd="$kexeccmd --command-line \"no-real-mode reboot=no\""
elif [ "$kexectype" = "multiboot" ]; then
fix_file_path
kexeccmd="$kexeccmd -l $filepath"
kexeccmd="$kexeccmd --command-line \"$restval\""
else else
fix_file_path
kexeccmd="$kexeccmd -l $filepath" kexeccmd="$kexeccmd -l $filepath"
fi fi
fi fi
if [ "$key" = "module" ]; then if [ "$key" = "module" ]; then
fix_file_path fix_file_path
cmdline="$restval" cmdline="$restval"
if [ -n $first_module ]; then if [ -n "$first_module" ]; then
adjust_cmd_line adjust_cmd_line
unset first_module unset first_module
fi fi
@ -76,6 +83,8 @@ done << EOF
$kexecparams $kexecparams
EOF EOF
if [ "$dryrun" = "y" ]; then exit 0; fi
echo "Loading the new kernel:" echo "Loading the new kernel:"
echo "$kexeccmd" echo "$kexeccmd"
eval "$kexeccmd" \ eval "$kexeccmd" \

View File

@ -36,7 +36,8 @@ REMOVE_FILE=/tmp/kexec/kexec_iso_remove.txt
if [ -r $REMOVE_FILE ]; then if [ -r $REMOVE_FILE ]; then
NEW_REMOVE=`cat $REMOVE_FILE` NEW_REMOVE=`cat $REMOVE_FILE`
REMOVE=$(eval "echo \"$NEW_REMOVE\"") REMOVE=$(eval "echo \"$NEW_REMOVE\"")
echo "+++ Overriding standard ISO kernel remove arguments: $REMOVE"x echo "+++ Overriding standard ISO kernel remove arguments: $REMOVE"
fi fi
kexec-select-boot /boot "$ADD" "$REMOVE" # Call kexec and indicate that hashes have been verified
kexec-select-boot -b /boot/ -d /media/ -p "/media/kexec_iso/$ISO_PATH/" -a "$ADD" -r "$REMOVE" -c "*.cfg" -u -h

View File

@ -1,6 +1,5 @@
#!/bin/sh #!/bin/sh
file=$1 file="$1"
basefile=$(basename $file)
reset_entry() { reset_entry() {
name="" name=""
@ -21,7 +20,7 @@ echo_entry() {
echo $(eval "echo \"$entry\"") echo $(eval "echo \"$entry\"")
fi fi
if [ "$kexectype" = "multiboot" ]; then if [ "$kexectype" = "multiboot" -o "$kexectype" = "xen" ]; then
if [ -z "$kernel" ]; then return; fi if [ -z "$kernel" ]; then return; fi
echo $(eval "echo \"$name|$kexectype|kernel $kernel$modules\"") echo $(eval "echo \"$name|$kexectype|kernel $kernel$modules\"")
@ -39,6 +38,7 @@ search_entry() {
label* | LABEL* ) label* | LABEL* )
state="syslinux" state="syslinux"
reset_entry reset_entry
name=`echo $line | cut -c6- `
esac esac
} }
@ -55,7 +55,8 @@ grub_entry() {
val=`echo $trimcmd | cut -d\ -f2-` val=`echo $trimcmd | cut -d\ -f2-`
case $cmd in case $cmd in
multiboot*) multiboot*)
kexectype="multiboot" # TODO: differentiate between Xen and other multiboot kernels
kexectype="xen"
kernel="$val" kernel="$val"
;; ;;
module*) module*)
@ -130,12 +131,13 @@ syslinux_entry() {
menu* | MENU* ) menu* | MENU* )
cmd2=`echo $trimcmd | cut -d \ -f2` cmd2=`echo $trimcmd | cut -d \ -f2`
if [ "$cmd2" = "label" -o "$cmd2" = "LABEL" ]; then if [ "$cmd2" = "label" -o "$cmd2" = "LABEL" ]; then
name=`echo ${trimcmd:11} | tr -d '^'` name=`echo $trimcmd | cut -c11- | tr -d '^'`
fi fi
;; ;;
linux* | LINUX* | kernel* | KERNEL* ) linux* | LINUX* | kernel* | KERNEL* )
case $val in case $val in
*mboot.c32) kexectype="multiboot" ;; # TODO: differentiate between Xen and other multiboot kernels
*mboot.c32) kexectype="xen" ;;
*.c32) *.c32)
# skip this entry # skip this entry
state="search" state="search"
@ -148,7 +150,7 @@ syslinux_entry() {
initrd="$val" initrd="$val"
;; ;;
append* | APPEND* ) append* | APPEND* )
if [ "$kexectype" = "multiboot" ]; then if [ "$kexectype" = "multiboot" -o "$kexectype" = "xen" ]; then
syslinux_multiboot_append syslinux_multiboot_append
else else
append="$val" append="$val"

48
initrd/bin/kexec-save-default Executable file
View File

@ -0,0 +1,48 @@
#!/bin/sh
# Save these options to be the persistent default
. /etc/functions
while getopts "b:d:p:e:i:" arg; do
case $arg in
b) bootdir="$OPTARG" ;;
d) paramsdev="$OPTARG" ;;
p) paramsdir="$OPTARG" ;;
e) entry="$OPTARG" ;;
i) index="$OPTARG" ;;
esac
done
if [ -z "$bootdir" -o -z "$entry" -o -z "$index" ]; then
die "Usage: $0 -b /boot/ -e \"boot params|...\" -i 1 "
fi
if [ -z "$paramsdev" ]; then
paramsdev="$bootdir"
fi
if [ -z "$paramsdir" ]; then
paramsdir="$bootdir"
fi
ENTRY_FILE="$paramsdir/kexec_default.$index.txt"
HASH_FILE="$paramsdir/kexec_default_hashes.txt"
# try to switch to rw mode
mount -o rw,remount $paramsdev
if [ ! -d $paramsdir ]; then
mkdir -p $paramsdir
fi
rm "$paramsdir/kexec_default.*.txt" 2>/dev/null
echo "$entry" > $ENTRY_FILE
cd $bootdir && kexec-boot -e "$entry" -d | xargs sha256sum > $HASH_FILE
if [ ! -r $ENTRY_FILE -o ! -r $HASH_FILE ]; then
die "Failed to write default config"
fi
if ! kexec-sign-config $paramsdir; then
die "Failed to sign default config"
fi
# switch back to ro mode
mount -o ro,remount $paramsdev

View File

@ -1,14 +1,36 @@
#!/bin/sh #!/bin/sh
. /etc/config
. /etc/functions . /etc/functions
bootdir=$1 add=""
add=$2 remove=""
remove=$3 config="*.cfg"
unique="n"
hashed="n"
while getopts "b:d:p:a:r:c:uh" 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" ;;
h) hashed="y" ;;
esac
done
MENU_NAME="kexec_menu.txt" if [ -z "$bootdir" ]; then
HASH_NAME="kexec_hashes.txt" die "Usage: $0 -b /boot/"
TMP_MENU_FILE=/tmp/kexec/$MENU_NAME fi
TMP_HASH_FILE=/tmp/kexec/$HASH_NAME
if [ -z "$paramsdev" ]; then
paramsdev="$bootdir"
fi
if [ -z "$paramsdir" ]; then
paramsdir="$bootdir"
fi
first_menu="y" first_menu="y"
get_menu_option() { get_menu_option() {
@ -49,7 +71,7 @@ confirm_menu_option() {
read \ read \
-n 1 \ -n 1 \
-p "Confirm selection by pressing 'y': " \ -p "Confirm selection by pressing 'y', make default with 'd': " \
option_confirm option_confirm
echo echo
} }
@ -59,35 +81,112 @@ parse_option() {
kernel=`echo $option | cut -d\| -f3` kernel=`echo $option | cut -d\| -f3`
} }
# optionally enforce file hashes scan_options() {
if [ -r $TMP_HASH_FILE ]; then
echo "+++ Checking verified boot hash file "
# Check the hashes of all the files
if cd $bootdir && sha256sum -c "$TMP_HASH_FILE" ; then
echo "+++ Verified boot hashes "
else
recovery "$TMP_HASH_FILE: boot hash mismatch"
fi
fi
# if no saved options, scan the boot directory and generate
if [ ! -r $TMP_MENU_FILE ]; then
echo "+++ Scanning for unsigned boot options" echo "+++ Scanning for unsigned boot options"
option_file="/tmp/kexec_options.txt" option_file="/tmp/kexec_options.txt"
for i in `find $bootdir -name "*.cfg"`; do if [ -r $option_file ]; then rm $option_file; fi
for i in `find $bootdir -name "$config"`; do
kexec-parse-boot $i >> $option_file kexec-parse-boot $i >> $option_file
done done
if [ ! -r $option_file ]; then if [ ! -r $option_file ]; then
recovery "Failed to parse any boot options" recovery "Failed to parse any boot options"
fi fi
if [ "$unique" = 'y' ]; then
sort $option_file | uniq > $TMP_MENU_FILE sort $option_file | uniq > $TMP_MENU_FILE
fi else
cp $option_file $TMP_MENU_FILE
fi
}
option_confirm="" default_select() {
while [ "$option_confirm" != "y" ] # Attempt boot with expected parameters
do
# 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
recovery "Boot entry has changed: expected $expectedoption, found $option"
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" ; then
echo "+++ Verified default boot hashes "
else
recovery "$TMP_DEFAULT_HASH_FILE: default boot hash mismatch"
fi
echo "+++ Executing default boot for $name:"
kexec-boot -b "$bootdir" -e "$option" -a "$add" -r "$remove"
recovery "Something failed"
}
user_select() {
# No default expected boot parameters, ask user
# Optionally enforce device file hashes
if [ -r $TMP_HASH_FILE ]; then
echo "+++ Checking verified boot hash file "
# Check the hashes of all the files
if cd $bootdir && sha256sum -c "$TMP_HASH_FILE" ; then
echo "+++ Verified boot hashes "
hashed='y'
else
recovery "$TMP_HASH_FILE: boot hash mismatch"
fi
fi
option_confirm=""
while [ "$option_confirm" != "y" -a "$option_confirm" != "d" ]
do
get_menu_option get_menu_option
confirm_menu_option confirm_menu_option
done
kexec-boot -b $bootdir -e "$option" -a "$add" -r "$remove" if [ "$option_confirm" = 'd' ]; then
if ! kexec-save-default -b "$bootdir" -d "$paramsdev" -p "$paramsdir" -e "$option" -i "$option_index"; then
echo "!!!!!! Failed to save defaults"
else
echo "+++ Saved defaults to device"
sleep 2
fi
fi
done
if [ "$option_confirm" = "d" ]; then
# reload settings to reflect new default
continue
fi
if [ "$CONFIG_BOOT_REQ_HASH" = "y" -a "$hashed" = "n" ]; then
recovery "!!!!!! Missing required boot hashes"
fi
kexec-boot -b "$bootdir" -e "$option" -a "$add" -r "$remove"
recovery "Something failed"
}
while true; do
kexec-check-config $paramsdir
TMP_MENU_FILE="/tmp/kexec/kexec_menu.txt"
TMP_HASH_FILE="/tmp/kexec/kexec_hashes.txt"
TMP_DEFAULT_FILE=`find /tmp/kexec/kexec_default.*.txt 2>/dev/null | head -1`
TMP_DEFAULT_HASH_FILE="/tmp/kexec/kexec_default_hashes.txt"
# if no saved options, scan the boot directory and generate
if [ ! -r $TMP_MENU_FILE ]; then
scan_options
fi
if [ -r "$TMP_DEFAULT_FILE" -a -r "$TMP_DEFAULT_HASH_FILE" ]; then
default_select
else
user_select
fi
recovery "Something failed again"
done

View File

@ -8,27 +8,7 @@ if [ -z "$MEDIA" ]; then
die "Usage: $0 /boot " die "Usage: $0 /boot "
fi fi
# setup the USB so we can reach the GPG card confirm_gpg_card
if ! lsmod | grep -q ehci_hcd; then
insmod /lib/modules/ehci-hcd.ko \
|| die "ehci_hcd: module load failed"
fi
if ! lsmod | grep -q ehci_pci; then
insmod /lib/modules/ehci-pci.ko \
|| die "ehci_pci: module load failed"
fi
if ! lsmod | grep -q xhci_hcd; then
insmod /lib/modules/xhci-hcd.ko \
|| die "ehci_hcd: module load failed"
fi
if ! lsmod | grep -q xhci_pci; then
insmod /lib/modules/xhci-pci.ko \
|| die "ehci_pci: module load failed"
sleep 2
fi
gpg --card-status \
|| die "gpg card read failed"
for tries in 1 2 3; do for tries in 1 2 3; do
if sha256sum `find $MEDIA/kexec*.txt` | gpg \ if sha256sum `find $MEDIA/kexec*.txt` | gpg \

View File

@ -23,7 +23,6 @@ if ! grep -q /boot /proc/mounts ; then
fi fi
# Attempt to pull verified config from device # Attempt to pull verified config from device
kexec-check-config /boot/ kexec-select-boot -b /boot/ -c "grub.cfg"
kexec-select-boot /boot/
recovery "Something failed..." recovery "Something failed..."

View File

@ -12,27 +12,7 @@ if [ -z "$XEN" -o -z "$KERNEL" -o -z "$INITRD" ]; then
die "Usage: $0 /boot/xen... /boot/vmlinuz... /boot/initramfs..." die "Usage: $0 /boot/xen... /boot/vmlinuz... /boot/initramfs..."
fi fi
# setup the USB so we can reach the GPG card confirm_gpg_card
if ! lsmod | grep -q ehci_hcd; then
insmod /lib/modules/ehci-hcd.ko \
|| die "ehci_hcd: module load failed"
fi
if ! lsmod | grep -q ehci_pci; then
insmod /lib/modules/ehci-pci.ko \
|| die "ehci_pci: module load failed"
fi
if ! lsmod | grep -q xhci_hcd; then
insmod /lib/modules/xhci-hcd.ko \
|| die "ehci_hcd: module load failed"
fi
if ! lsmod | grep -q xhci_pci; then
insmod /lib/modules/xhci-pci.ko \
|| die "ehci_pci: module load failed"
sleep 2
fi
gpg --card-status \
|| die "gpg card read failed"
# if the /boot.hashes file already exists, read the TPM counter ID # if the /boot.hashes file already exists, read the TPM counter ID
# from it. # from it.

View File

@ -4,9 +4,17 @@
. /etc/functions . /etc/functions
. /etc/config . /etc/config
# Unmount any previous boot device
if grep -q /boot /proc/mounts ; then
umount /boot \
|| recovery '$CONFIG_USB_BOOT_DEV: Unable to unmount /boot'
fi
# Mount the USB boot device # Mount the USB boot device
mount-usb "$CONFIG_USB_BOOT_DEV" \ if ! grep -q /media /proc/mounts ; then
mount-usb "$CONFIG_USB_BOOT_DEV" \
|| recovery '$CONFIG_USB_BOOT_DEV: Unable to mount /media' || recovery '$CONFIG_USB_BOOT_DEV: Unable to mount /media'
fi
# Check for ISO first # Check for ISO first
get_menu_option() { get_menu_option() {
@ -46,7 +54,6 @@ if [ `cat /tmp/iso_menu.txt | wc -l` -gt 0 ]; then
if [ -n "$option" ]; then if [ -n "$option" ]; then
MOUNTED_ISO=$option MOUNTED_ISO=$option
ISO=${option:7} # remove /media/ to get device relative path ISO=${option:7} # remove /media/ to get device relative path
kexec-check-config /media/kexec_iso/$ISO/
kexec-iso-init $MOUNTED_ISO $ISO $CONFIG_USB_BOOT_DEV kexec-iso-init $MOUNTED_ISO $ISO $CONFIG_USB_BOOT_DEV
recovery "Something failed..." recovery "Something failed..."
@ -55,7 +62,6 @@ fi
echo "!!! Could not find any ISO, trying bootable USB" echo "!!! Could not find any ISO, trying bootable USB"
# Attempt to pull verified config from device # Attempt to pull verified config from device
kexec-check-config /media/ kexec-select-boot -b /media/ -c "*.cfg" -u
kexec-select-boot /media/
recovery "Something failed..." recovery "Something failed..."

View File

@ -63,3 +63,28 @@ confirm_totp()
# clean up with a newline # clean up with a newline
echo echo
} }
confirm_gpg_card()
{
# setup the USB so we can reach the GPG card
if ! lsmod | grep -q ehci_hcd; then
insmod /lib/modules/ehci-hcd.ko \
|| die "ehci_hcd: module load failed"
fi
if ! lsmod | grep -q ehci_pci; then
insmod /lib/modules/ehci-pci.ko \
|| die "ehci_pci: module load failed"
fi
if ! lsmod | grep -q xhci_hcd; then
insmod /lib/modules/xhci-hcd.ko \
|| die "ehci_hcd: module load failed"
fi
if ! lsmod | grep -q xhci_pci; then
insmod /lib/modules/xhci-pci.ko \
|| die "ehci_pci: module load failed"
sleep 2
fi
gpg --card-status \
|| die "gpg card read failed"
}