From 3614044fffbf702c49e79e48e12dcd9988a8cbc3 Mon Sep 17 00:00:00 2001 From: Francis Lam Date: Sun, 2 Jul 2017 23:01:04 -0400 Subject: [PATCH] Added a generic boot config and persistent params Refactored boot parsing code and applied that in local-init to scan /boot for grub options and allow the user to unsafely boot anything. This goes a long way to addressing #196. Optionally the user can customize those boot parameters or enforce arbitrary hashes on the boot device by creating and signing config files in /boot/ or /media/ or /media/kexec_iso/ISO_FILENAME/. --- config/x230-generic.config | 24 ++++++ initrd/bin/{usb-boot => kexec-boot} | 4 + initrd/bin/kexec-check-config | 22 +++++ initrd/bin/kexec-iso-init | 42 ++++++++++ .../bin/{usb-parse-boot => kexec-parse-boot} | 0 initrd/bin/kexec-select-boot | 82 +++++++++++++++++++ initrd/bin/kexec-sign-config | 45 ++++++++++ initrd/bin/local-init | 29 +++++++ initrd/bin/usb-init | 16 ++-- initrd/bin/usb-iso-init | 27 ------ initrd/bin/usb-select-boot | 59 ------------- 11 files changed, 259 insertions(+), 91 deletions(-) create mode 100644 config/x230-generic.config rename initrd/bin/{usb-boot => kexec-boot} (94%) create mode 100755 initrd/bin/kexec-check-config create mode 100755 initrd/bin/kexec-iso-init rename initrd/bin/{usb-parse-boot => kexec-parse-boot} (100%) create mode 100755 initrd/bin/kexec-select-boot create mode 100755 initrd/bin/kexec-sign-config create mode 100755 initrd/bin/local-init delete mode 100755 initrd/bin/usb-iso-init delete mode 100755 initrd/bin/usb-select-boot diff --git a/config/x230-generic.config b/config/x230-generic.config new file mode 100644 index 00000000..272c97e2 --- /dev/null +++ b/config/x230-generic.config @@ -0,0 +1,24 @@ +# Configuration for a x230 running non-Qubes +BOARD=x230 + +CONFIG_CRYPTSETUP=y +CONFIG_FLASHROM=y +CONFIG_GPG=y +CONFIG_KEXEC=y +CONFIG_UTIL_LINUX=y +CONFIG_LVM2=y +CONFIG_MBEDTLS=y +CONFIG_PCIUTILS=y +CONFIG_POPT=y +CONFIG_QRENCODE=y +CONFIG_TPMTOTP=y +CONFIG_XEN=y +CONFIG_DROPBEAR=y + +CONFIG_LINUX_USB=y +CONFIG_LINUX_E1000E=y + +CONFIG_BOOTSCRIPT=/bin/local-init + +CONFIG_BOOT_DEV="/dev/sda1" +CONFIG_USB_BOOT_DEV="/dev/sdb1" diff --git a/initrd/bin/usb-boot b/initrd/bin/kexec-boot similarity index 94% rename from initrd/bin/usb-boot rename to initrd/bin/kexec-boot index 5b1fa06d..eb9d095d 100755 --- a/initrd/bin/usb-boot +++ b/initrd/bin/kexec-boot @@ -14,6 +14,10 @@ kexectype=`echo $entry | cut -d\| -f2` kexecparams=`echo $entry | cut -d\| -f3- | tr '|' '\n'` kexeccmd="kexec" +# TODO: make this configurable +cmdadd="intel_iommu=on $cmdadd" +cmdremove="quiet $cmdremove" + fix_file_path() { filepath=`find $bootdir -path "*$firstval" | tail -1` if ! [ -r $filepath ]; then diff --git a/initrd/bin/kexec-check-config b/initrd/bin/kexec-check-config new file mode 100755 index 00000000..386aebe1 --- /dev/null +++ b/initrd/bin/kexec-check-config @@ -0,0 +1,22 @@ +#!/bin/sh +# Check for valid kexec params and copy to tmp +. /etc/functions + +MEDIA="$1" +if [ ! -d /tmp/kexec ]; then + mkdir /tmp/kexec \ + || recovery 'failed to make kexec tmp dir' +fi + +if [ ! -r $MEDIA/kexec.sig ]; then + exit 0 +fi + +if [ -z `find $MEDIA/kexec*.txt` ]; then + exit 0 +fi + +if sha256sum `find $MEDIA/kexec*.txt` | gpg --verify $MEDIA/kexec.sig - ; then + echo "+++ Found verified kexec boot params" + cp $MEDIA/kexec*.txt /tmp/kexec +fi diff --git a/initrd/bin/kexec-iso-init b/initrd/bin/kexec-iso-init new file mode 100755 index 00000000..ab8ffa17 --- /dev/null +++ b/initrd/bin/kexec-iso-init @@ -0,0 +1,42 @@ +#!/bin/sh +# Boot from signed ISO + +. /etc/functions +. /etc/config + +MOUNTED_ISO_PATH="$1" +ISO_PATH="$2" +DEV="$3" + +echo '+++ Verifying ISO' +# Verify the signature on the hashes +ISOSIG="$MOUNTED_ISO_PATH.sig" +if ! [ -r "$ISOSIG" ]; then + ISOSIG="$MOUNTED_ISO_PATH.asc" +fi + +gpgv "$ISOSIG" "$MOUNTED_ISO_PATH" \ + || recovery 'ISO signature failed' + +echo '+++ Mounting ISO and booting' +mount -t iso9660 -o loop $MOUNTED_ISO_PATH /boot \ + || recovery '$MOUNTED_ISO_PATH: Unable to mount /boot' + +DEV_UUID=`blkid $DEV | tail -1 | tr " " "\n" | grep UUID | cut -d\" -f2` +ADD="fromiso=/dev/disk/by-uuid/$DEV_UUID/$ISO_PATH" +REMOVE="" + +ADD_FILE=/tmp/kexec/kexec_iso_add.txt +if [ -r $ADD_FILE ]; then + NEW_ADD=`cat $ADD_FILE` + ADD=$(eval "echo \"$NEW_ADD\"") + echo "+++ Overriding standard ISO kernel add arguments: $ADD" +fi +REMOVE_FILE=/tmp/kexec/kexec_iso_remove.txt +if [ -r $REMOVE_FILE ]; then + NEW_REMOVE=`cat $REMOVE_FILE` + REMOVE=$(eval "echo \"$NEW_REMOVE\"") + echo "+++ Overriding standard ISO kernel remove arguments: $REMOVE"x +fi + +kexec-select-boot /boot "$ADD" "$REMOVE" diff --git a/initrd/bin/usb-parse-boot b/initrd/bin/kexec-parse-boot similarity index 100% rename from initrd/bin/usb-parse-boot rename to initrd/bin/kexec-parse-boot diff --git a/initrd/bin/kexec-select-boot b/initrd/bin/kexec-select-boot new file mode 100755 index 00000000..3848dc71 --- /dev/null +++ b/initrd/bin/kexec-select-boot @@ -0,0 +1,82 @@ +#!/bin/sh +. /etc/functions + +bootdir=$1 +add=$2 +remove=$3 + +MENU_NAME="kexec_menu.txt" +HASH_NAME="kexec_hashes.txt" +TMP_MENU_FILE=/tmp/kexec/$MENU_NAME +TMP_HASH_FILE=/tmp/kexec/$HASH_NAME + +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_MENU_FILE + + 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_MENU_FILE | 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` +} + +# optionally enforce 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 " + else + recovery "$TMP_HASH_FILE: boot hash mismatch" + fi +fi + +# otherwise scan the boot directory and generate options +if [ ! -r $TMP_MENU_FILE ]; then + echo "+++ Scanning for unsigned boot options" + option_file="/tmp/kexec_options.txt" + for i in `find $bootdir -name "*.cfg"`; do + kexec-parse-boot $i >> $option_file + done + if [ ! -r $option_file ]; then + recovery "Failed to parse any boot options" + fi + sort $option_file | uniq > $TMP_MENU_FILE +fi + +option_confirm="" +while [ "$option_confirm" != "y" ] +do + get_menu_option + confirm_menu_option +done + +kexec-boot -b $bootdir -e "$option" -a "$add" -r "$remove" diff --git a/initrd/bin/kexec-sign-config b/initrd/bin/kexec-sign-config new file mode 100755 index 00000000..3a7b6e61 --- /dev/null +++ b/initrd/bin/kexec-sign-config @@ -0,0 +1,45 @@ +#!/bin/sh +# Sign a valid directory of kexec params +. /etc/functions + +MEDIA="$1" + +if [ -z "$MEDIA" ]; then + die "Usage: $0 /boot " +fi + +# 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" + +for tries in 1 2 3; do + if sha256sum `find $MEDIA/kexec*.txt` | gpg \ + --digest-algo SHA256 \ + --detach-sign \ + -a \ + > $MEDIA/kexec.sig \ + ; then + exit 0 + fi +done + +warn "$MEDIA: Unable to sign boot hashes" +exit 1 diff --git a/initrd/bin/local-init b/initrd/bin/local-init new file mode 100755 index 00000000..559bda60 --- /dev/null +++ b/initrd/bin/local-init @@ -0,0 +1,29 @@ +#!/bin/sh +# Boot from a local disk 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 local + +if [ ! "$totp_confirm" = "y" ]; then + recovery "Failed to confirm validity of TOTP" +fi + +# Mount local disk +if ! grep -q /boot /proc/mounts ; then + mount -o ro "$CONFIG_BOOT_DEV" /boot \ + || recovery '$CONFIG_BOOT_DEV: Unable to mount /boot' +fi + +# Attempt to pull verified config from device +kexec-check-config /boot +exec kexec-select-boot /boot + +recovery "Something failed..." diff --git a/initrd/bin/usb-init b/initrd/bin/usb-init index 974e9552..09c46aa4 100755 --- a/initrd/bin/usb-init +++ b/initrd/bin/usb-init @@ -1,5 +1,5 @@ #!/bin/sh -# Boot a Tails installation +# Boot a USB installation . /etc/functions . /etc/config @@ -16,8 +16,9 @@ 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" +# Mount the USB boot device +mount-usb "$CONFIG_USB_BOOT_DEV" \ + || recovery '$CONFIG_USB_BOOT_DEV: Unable to mount /media' # Check for ISO first get_menu_option() { @@ -55,11 +56,16 @@ if [ `wc -l /tmp/iso_menu.txt | cut -d\ -f1` -gt 0 ]; then done if [ -n "$option" ]; then - exec usb-iso-init $option + MOUNTED_ISO=$option + ISO=${option:7} # remove /media/ to get device relative path + kexec-check-config /media/kexec_iso/$ISO + exec kexec-iso-init $MOUNTED_ISO $ISO $CONFIG_USB_BOOT_DEV fi fi echo "!!! Could not find any ISO, trying bootable USB" -exec usb-select-boot /media +# Attempt to pull verified config from device +kexec-check-config /media +exec kexec-select-boot /media recovery "Something failed..." diff --git a/initrd/bin/usb-iso-init b/initrd/bin/usb-iso-init deleted file mode 100755 index 7e225f48..00000000 --- a/initrd/bin/usb-iso-init +++ /dev/null @@ -1,27 +0,0 @@ -#!/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" diff --git a/initrd/bin/usb-select-boot b/initrd/bin/usb-select-boot deleted file mode 100755 index 9f44673c..00000000 --- a/initrd/bin/usb-select-boot +++ /dev/null @@ -1,59 +0,0 @@ -#!/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"