adds a USB boot option with basic parsing to kexec

Supports booting from USB media using either the root device or
a signed ISO as the boot device.  Boot options are parsed with
quick/dirty shell scripts to infer kexec params.

Closes #195 and begins to address #196
This commit is contained in:
Francis Lam 2017-04-29 13:40:34 -04:00
parent 7f600072ad
commit efd662c63a
No known key found for this signature in database
GPG Key ID: 0A59C698920806EB
9 changed files with 443 additions and 1 deletions

View File

@ -19,6 +19,8 @@ CONFIG_LINUX_USB=y
CONFIG_BOOTSCRIPT=/bin/qubes-init
CONFIG_USB_BOOT_DEV="/dev/sdb1"
# Disks encrypted by the TPM LUKS key
CONFIG_QUBES_BOOT_DEV="/dev/sda1"
CONFIG_QUBES_VG="qubes_dom0"

View File

@ -20,6 +20,8 @@ CONFIG_LINUX_E1000E=y
CONFIG_BOOTSCRIPT=/bin/qubes-init
CONFIG_USB_BOOT_DEV="/dev/sdb1"
# Disks encrypted by the TPM LUKS key
CONFIG_QUBES_BOOT_DEV="/dev/sda1"
CONFIG_QUBES_VG="qubes_dom0"

73
initrd/bin/usb-boot Executable file
View File

@ -0,0 +1,73 @@
#!/bin/sh
. /etc/functions
while getopts "b:e:r:a:" arg; do
case $arg in
b) bootdir="$OPTARG" ;;
e) entry="$OPTARG" ;;
r) cmdremove="$OPTARG" ;;
a) cmdadd="$OPTARG" ;;
esac
done
kexectype=`echo $entry | cut -d\| -f2`
kexecparams=`echo $entry | cut -d\| -f3- | tr '|' '\n'`
kexeccmd="kexec"
fix_file_path() {
filepath=`find $bootdir -path "*$firstval" | tail -1`
if ! [ -r $filepath ]; then
die "failed to find file $firstval"
fi
}
adjust_cmd_line() {
if [ -n "$cmdremove" ]; then
for i in $cmdremove; do
cmdline="${cmdline//$i/}"
done
fi
if [ -n "$cmdadd" ]; then
cmdline="$cmdline $cmdadd"
fi
}
while read line
do
key=`echo $line | cut -d\ -f1`
firstval=`echo $line | cut -d\ -f2`
restval=`echo $line | cut -d\ -f3-`
if [ "$key" = "kernel" ]; then
fix_file_path
kexeccmd="$kexeccmd -l $filepath"
if [ "$kexectype" = "multiboot" ]; then
cmdline="$restval"
adjust_cmd_line
kexeccmd="$kexeccmd --command-line \"$cmdline\""
fi
fi
if [ "$key" = "module" ]; then
fix_file_path
kexeccmd="$kexeccmd --module \"$filepath $restval\""
fi
if [ "$key" = "initrd" ]; then
fix_file_path
kexeccmd="$kexeccmd --initrd=$filepath"
fi
if [ "$key" = "append" ]; then
cmdline="$firstval $restval"
adjust_cmd_line
kexeccmd="$kexeccmd --append=\"$cmdline\""
fi
done << EOF
$kexecparams
EOF
echo "Loading the new kernel:"
echo "$kexeccmd"
eval "$kexeccmd" \
|| die "Failed to load the new kernel"
echo "Starting the new kernel"
exec kexec -e

65
initrd/bin/usb-init Executable file
View File

@ -0,0 +1,65 @@
#!/bin/sh
# Boot a Tails installation
. /etc/functions
. /etc/config
# Confirm we have a good TOTP unseal
if ! confirm_totp ; then
recovery 'Failed to unseal TOTP'
fi
# Extend PCR4 as soon as possible
tpm extend -ix 4 -ic usb
if [ ! "$totp_confirm" = "y" ]; then
recovery "Failed to confirm validity of TOTP"
fi
# TODO: Do a scan of USB devices to detect the Tails USB
mount-usb "$CONFIG_USB_BOOT_DEV"
# Check for ISO first
get_menu_option() {
echo "+++ Select your ISO boot option:"
n=0
while read option
do
n=`expr $n + 1`
echo "$n. $option"
done < /tmp/iso_menu.txt
read \
-p "Choose the ISO boot option [1-$n, s for standard boot, a to abort]: " \
option_index
if [ "$option_index" = "a" ]; then
recovery "Aborting boot attempt"
fi
if [ "$option_index" = "s" ]; then
option=""
return
fi
option=`head -n $option_index /tmp/iso_menu.txt | tail -1`
}
# create ISO menu options
ls -1r /media/*.iso 2>/dev/null > /tmp/iso_menu.txt
if [ `wc -l /tmp/iso_menu.txt | cut -d\ -f1` -gt 0 ]; then
option_confirm=""
while [ -z "$option" -a "$option_index" != "s" ]
do
get_menu_option
done
if [ -n "$option" ]; then
exec usb-iso-init $option
fi
fi
echo "!!! Could not find any ISO, trying bootable USB"
exec usb-select-boot /media
recovery "Something failed..."

27
initrd/bin/usb-iso-init Executable file
View File

@ -0,0 +1,27 @@
#!/bin/sh
# Boot from signed ISO
. /etc/functions
. /etc/config
ISO="$1"
echo '+++ Verifying ISO'
# Verify the signature on the hashes
ISOSIG="$ISO.sig"
if ! [ -r "$ISOSIG" ]; then
ISOSIG="$ISO.asc"
fi
gpgv "$ISOSIG" "$ISO" \
|| recovery 'ISO signature failed'
echo '+++ Mounting ISO and booting'
mount -t iso9660 -o loop $ISO /boot
ISO_FILE=${ISO:7}
DEV=$CONFIG_USB_BOOT_DEV
DEV_UUID=`blkid $DEV | tail -1 | tr " " "\n" | grep UUID | cut -d\" -f2`
ADD="fromiso=/dev/disk/by-uuid/$DEV_UUID/$ISO_FILE"
/bin/usb-select-boot /boot "$ADD"

171
initrd/bin/usb-parse-boot Executable file
View File

@ -0,0 +1,171 @@
#!/bin/sh
file=$1
basefile=$(basename $file)
reset_entry() {
name=""
kexectype="elf"
kernel=""
initrd=""
modules=""
append=""
}
echo_entry() {
if [ "$kexectype" = "elf" ]; then
if [ -z "$kernel" -o -z "$initrd" ]; then return; fi
entry="$name|$kexectype|kernel $kernel|initrd $initrd"
if [ -n "$append" ]; then entry="$entry|append $append"; fi
echo "$entry"
fi
if [ "$kexectype" = "multiboot" ]; then
if [ -z "$kernel" ]; then return; fi
echo "$name|$kexectype|kernel $kernel$modules"
fi
}
search_entry() {
case $line in
menuentry* | MENUENTRY* )
state="grub"
reset_entry
name=`echo $line | tr "'" "\"" | cut -d\" -f 2`
;;
label* | LABEL* )
state="syslinux"
reset_entry
esac
}
grub_entry() {
if [ "$line" = "}" ]; then
echo_entry
state="search"
return
fi
# add info to menuentry
trimcmd=`echo $line | tr '\t ' ' ' | tr -s ' '`
cmd=`echo $trimcmd | cut -d\ -f1`
val=`echo $trimcmd | cut -d\ -f2-`
case $cmd in
multiboot*)
kexectype="multiboot"
kernel="$val"
;;
module*)
modules="$modules|module $val"
;;
linux*)
kernel=`echo $trimcmd | cut -d\ -f2`
append=`echo $trimcmd | cut -d\ -f3-`
;;
initrd*)
initrd="$val"
;;
esac
}
syslinux_end() {
# finish menuentry
# attempt to parse out of append if missing initrd
if [ -z "$initrd" ]; then
newappend=""
for param in $append; do
case $param in
initrd=*)
initrd=`echo $param | cut -d\= -f2`
;;
*) newappend="$newappend $param" ;;
esac
done
append="$newappend"
fi
echo_entry
state="search"
}
syslinux_multiboot_append() {
splitval=`echo "${val// --- /|}" | tr '|' '\n'`
while read line
do
if [ -z "$kernel" ]; then
kernel="$line"
else
modules="$modules|module $line"
fi
done << EOF
$splitval
EOF
}
syslinux_entry() {
label_line=${line:0:5}
if [ -z "$line" ]; then
syslinux_end
fi
if [ "$label_line" = "label" -o "$label_line" = "LABEL" ]; then
search_entry
return
fi
# add info to menuentry
trimcmd=`echo $line | tr '\t ' ' ' | tr -s ' '`
cmd=`echo $trimcmd | cut -d\ -f1`
val=`echo $trimcmd | cut -d\ -f2-`
case $trimcmd in
menu* | MENU* )
cmd2=`echo $trimcmd | cut -d \ -f2`
if [ "$cmd2" = "label" -o "$cmd2" = "LABEL" ]; then
name=`echo ${trimcmd:11} | tr -d '^'`
fi
;;
linux* | LINUX* | kernel* | KERNEL* )
case $val in
*mboot.c32) kexectype="multiboot" ;;
*.c32)
# skip this entry
state="search"
;;
*)
kernel="$val"
esac
;;
initrd* | INITRD* )
initrd="$val"
;;
append* | APPEND* )
if [ "$kexectype" = "multiboot" ]; then
syslinux_multiboot_append
else
append="$val"
fi
;;
esac
}
state="search"
while read line
do
case $state in
search)
search_entry
;;
grub)
grub_entry
;;
syslinux)
syslinux_entry
;;
esac
done < $file
# handle EOF case
if [ "$state" = "syslinux" ]; then
syslinux_end
fi

59
initrd/bin/usb-select-boot Executable file
View File

@ -0,0 +1,59 @@
#!/bin/sh
. /etc/functions
bootdir=$1
add=$2
get_menu_option() {
echo "+++ Select your boot option:"
n=0
while read option
do
parse_option
n=`expr $n + 1`
echo "$n. $name [$kernel]"
done < /tmp/usb_menu.txt
read \
-p "Choose the boot option [1-$n, a to abort]: " \
option_index
if [ "$option_index" = "a" ]; then
recovery "Aborting boot attempt"
fi
option=`head -n $option_index /tmp/usb_menu.txt | tail -1`
parse_option
}
confirm_menu_option() {
echo "+++ Please confirm the boot details for $name:"
echo $option
read \
-n 1 \
-p "Confirm selection by pressing 'y': " \
option_confirm
echo
}
parse_option() {
name=`echo $option | cut -d\| -f1`
kernel=`echo $option | cut -d\| -f3`
}
echo "+++ Scanning USB media boot options"
for i in `find $bootdir -name "*.cfg"`; do usb-parse-boot $i >> /tmp/usb_options.txt; done
if [ ! -r /tmp/usb_options.txt ]; then
recovery "Failed to parse any boot options"
fi
sort /tmp/usb_options.txt | uniq > /tmp/usb_menu.txt
option_confirm=""
while [ "$option_confirm" != "y" ]
do
get_menu_option
confirm_menu_option
done
usb-boot -b $bootdir -e "$option" -a "intel_iommu=on $add" -r "quiet"

View File

@ -28,3 +28,38 @@ recovery() {
pcrs() {
head -7 /sys/class/tpm/tpm0/pcrs
}
confirm_totp()
{
last_half=X
while true; do
# update the TOTP code every thirty seconds
date=`date "+%Y-%m-%d %H:%M:%S"`
seconds=`date "+%s"`
half=`expr \( $seconds % 60 \) / 30`
if [ "$half" != "$last_half" ]; then
last_half=$half;
TOTP=`unseal-totp` \
|| recovery "TOTP code generation failed"
fi
echo -n "$date $TOTP: "
# read the first character, non-blocking
read \
-t 1 \
-n 1 \
-s \
-p "Confirm TOTP with a 'y': " \
totp_confirm \
&& break
# nothing typed, redraw the line
echo -ne '\r'
done
# clean up with a newline
echo
}

View File

@ -39,7 +39,7 @@ fi
# Give the user a second to enter a recovery shell
read \
-t "1" \
-p "Press 'r' for recovery shell: " \
-p "Press 'r' for recovery shell or 'u' for usb: " \
-n 1 \
boot_option
echo
@ -52,6 +52,14 @@ if [ "$boot_option" = "r" ]; then
exec /bin/ash
fi
if [ "$boot_option" = "u" ]; then
echo '***** USB boot'
exec /bin/usb-init
# just in case...
tpm extend -ix 4 -ic recovery
exec /bin/ash
fi
echo '***** Normal boot'
exec "$CONFIG_BOOTSCRIPT"