diff --git a/initrd/bin/config-gui.sh b/initrd/bin/config-gui.sh index df8bc9235..4955dac0a 100755 --- a/initrd/bin/config-gui.sh +++ b/initrd/bin/config-gui.sh @@ -39,7 +39,7 @@ while true; do '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' + 'B' " $(get_config_display_action "$CONFIG_ROOT_CHECK_AT_BOOT") root check at boot" 'L' " $(get_config_display_action "$CONFIG_RESTRICTED_BOOT") Restricted Boot" ) @@ -265,10 +265,9 @@ while true; do --msgbox "The root directories to hash was successfully changed to:\n$NEW_CONFIG_ROOT_DIRLIST" 0 80 ;; "B" ) - CURRENT_OPTION="$(load_config_value CONFIG_ROOT_CHECK_AT_BOOT)" - if [ "$CURRENT_OPTION" != "y" ]; then + if [ "$CONFIG_ROOT_CHECK_AT_BOOT" != "y" ]; then # Root device and directories must be set to enable this - if [ -z "$(load_config_value CONFIG_ROOT_DEV)" ] || [ -z "$(load_config_value CONFIG_ROOT_DIRLIST)" ]; then + if [ -z "$CONFIG_ROOT_DEV" ] || [ -z "$CONFIG_ROOT_DIRLIST" ]; then whiptail $BG_COLOR_ERROR --title 'Root Check Not Configured' \ --msgbox "Set the root device and directories to hash before enabling this feature." 0 80 elif (whiptail --title 'Enable Root Hash Check at Boot?' \ @@ -277,8 +276,7 @@ while true; do \na minute or more to the boot time. \n\nDo you want to proceed?" 0 80) then - set_config /etc/config.user "CONFIG_ROOT_CHECK_AT_BOOT" "y" - combine_configs + set_user_config "CONFIG_ROOT_CHECK_AT_BOOT" "y" # check that root hash file exists if [ ! -f ${ROOT_HASH_FILE} ]; then @@ -298,8 +296,7 @@ while true; do --yesno "This will disable checking root hashes each time you boot. \n\nDo you want to proceed?" 0 80) then - set_config /etc/config.user "CONFIG_ROOT_CHECK_AT_BOOT" "n" - combine_configs + set_user_config "CONFIG_ROOT_CHECK_AT_BOOT" "n" whiptail --title 'Config change successful' \ --msgbox "The root device will not be checked at each boot." 0 80 diff --git a/initrd/bin/root-hashes-gui.sh b/initrd/bin/root-hashes-gui.sh index 94533bcb0..1e55018f1 100755 --- a/initrd/bin/root-hashes-gui.sh +++ b/initrd/bin/root-hashes-gui.sh @@ -32,7 +32,9 @@ update_root_checksums() { fi echo "+++ Calculating hashes for all files in $CONFIG_ROOT_DIRLIST_PRETTY " - cd $ROOT_MOUNT && find ${CONFIG_ROOT_DIRLIST} -type f ! -name '*kexec*' -print0 | xargs -0 sha256sum | tee ${HASH_FILE} + # Intentional wordsplit + # shellcheck disable=SC2086 + (cd "$ROOT_MOUNT" && find ${CONFIG_ROOT_DIRLIST} -type f ! -name '*kexec*' -print0 | xargs -0 sha256sum) >"${HASH_FILE}" # switch back to ro mode mount -o ro,remount /boot @@ -86,7 +88,7 @@ check_root_checksums() { fi echo "+++ Checking for new files in $CONFIG_ROOT_DIRLIST_PRETTY " - find ${CONFIG_ROOT_DIRLIST} -type f ! -name '*kexec*' | sort > /tmp/new_file_list + (cd "$ROOT_MOUNT" && find ${CONFIG_ROOT_DIRLIST} -type f ! -name '*kexec*') | sort > /tmp/new_file_list cut -d' ' -f3- ${HASH_FILE} | sort | diff -U0 - /tmp/new_file_list > /tmp/new_file_diff || new_files_found=y if [ "$new_files_found" == "y" ]; then grep -E -v '^[+-]{3}|[@]{2} ' /tmp/new_file_diff > /tmp/new_file_diff2 # strip any output that's not a file @@ -102,7 +104,7 @@ check_root_checksums() { fi echo "+++ Checking hashes for all files in $CONFIG_ROOT_DIRLIST_PRETTY (this might take a while) " - if cd $ROOT_MOUNT && sha256sum -c ${HASH_FILE} > /tmp/hash_output 2>/dev/null; then + if (cd $ROOT_MOUNT && sha256sum -c ${HASH_FILE} > /tmp/hash_output 2>/dev/null); then echo "+++ Verified root hashes " valid_hash='y' unmount_root_device @@ -156,29 +158,234 @@ check_root_checksums() { fi fi } + +# Check if a device is an LVM2 PV, and if so print the VG name +find_lvm_vg_name() { + TRACE_FUNC + local DEVICE VG + DEVICE="$1" + + mkdir -p /tmp/root-hashes-gui + if ! lvm pvs "$DEVICE" >/tmp/root-hashes-gui/lvm_vg 2>/dev/null; then + # It's not an LVM PV + return 1 + fi + + VG="$(tail -n +2 /tmp/root-hashes-gui/lvm_vg | awk '{print $2}')" + if [ -z "$VG" ]; then + DEBUG "Could not find LVM2 VG from lvm pvs output:" + DEBUG "$(cat /tmp/root-hashes-gui/lvm_vg)" + return 1 + fi + + echo "$VG" +} + +# Open an LVM volume group, then continue looking for more layers in the 'root' +# logical volume. +open_block_device_lvm() { + TRACE_FUNC + local VG="$1" + + if ! lvm vgchange -ay "$VG"; then + DEBUG "Can't open LVM VG: $VG" + return 1 + fi + + # Use the LV 'root'. This is the default name used by Qubes. There's no + # way to configure this at the moment. + if ! [ -e "/dev/mapper/$VG-root" ]; then + DEBUG "LVM volume group does not have 'root' logical volume" + return 1 + fi + + # Use the root LV now + open_block_device_layers "/dev/mapper/$VG-root" +} + +# Open a LUKS device, then continue looking for more layers. +open_block_device_luks() { + TRACE_FUNC + local DEVICE="$1" + local LUKSDEV + LUKSDEV="$(basename "$DEVICE")_crypt" + + # Open the LUKS device. This may prompt interactively for the passphrase, so + # hook it up to the console even if stdout/stdin have been redirected. + if ! cryptsetup open "$DEVICE" "$LUKSDEV"; then + DEBUG "Can't open LUKS volume: $DEVICE" + return 1 + fi + + open_block_device_layers "/dev/mapper/$LUKSDEV" +} + +# Open block device layers to access /root recursively. If another layer (LUKS +# or LVM) can be identified, open it and recurse into the new device. When all +# recognized layers are opened, print the final block device and exit +# successfully (open_root_device will try to mount it). +# +# This only fails if we can recognize another LUKS or LVM layer, but cannot open +# it. It succeeds otherwise, even if no layers are recognized, because we +# should try to mount the block device directly in that case. +open_block_device_layers() { + TRACE_FUNC + local DEVICE="$1" + local VG + + if ! [ -e "$DEVICE" ]; then + DEBUG "Block device doesn't exit: $DEVICE" + # This shouldn't really happen, we thought we opened the last layer + # successfully. The call stack reveals what LUKS/LVM2 layers have been + # opened so far. + DEBUG_STACK + return 1 + fi + + # Try to open a LUKS layer + if cryptsetup isLuks "$DEVICE" &>/dev/null; then + open_block_device_luks "$DEVICE" || return 1 + # Try to open an LVM layer + elif VG="$(find_lvm_vg_name "$DEVICE")"; then + open_block_device_lvm "$VG" || return 1 + else + # The given block device exists but is not any layer we understand. Stop + # opening layers and try to mount it. + echo "$DEVICE" + fi +} + +# Try to open a block device as /root. open_block_device_layers() is used to +# open LUKS and LVM layers before mounting the filesystem. +# +# This function does not clean up anything if it is unsuccessful. Use +# try_open_root_device() to also clean up when unsuccessful. +open_root_device_no_clean_up() { + TRACE_FUNC + local DEVICE="$1" + local FS_DEVICE + + # Open LUKS/LVM and get the name of the block device that should contain the + # filesystem. If there are no LUKS/LVM layers, FS_DEVICE is just DEVICE. + FS_DEVICE="$(open_block_device_layers "$DEVICE")" || return 1 + + # Mount the device + if ! mount -o ro "$FS_DEVICE" "$ROOT_MOUNT" &>/dev/null; then + DEBUG "Can't mount filesystem on $FS_DEVICE from $DEVICE" + return 1 + fi + + # The filesystem must have all of the directories configured. (Intentional + # word-split) + # shellcheck disable=SC2086 + if ! (cd "$ROOT_MOUNT" && ls -d $CONFIG_ROOT_DIRLIST &>/dev/null); then + DEBUG "Root filesystem on $DEVICE lacks one of the configured directories: $CONFIG_ROOT_DIRLIST" + return 1 + fi + + # Root is mounted now and the directories are present + return 0 +} + +# If an LVM VG is open, close any layers within it, then close the LVM VG. +close_block_device_lvm() { + TRACE_FUNC + local VG="$1" + + # We always use the LV 'root' currently + local LV="/dev/mapper/$VG-root" + if [ -e "$LV" ]; then + close_block_device_layers "$LV" + fi + + # The LVM VG might be open even if no 'root' LV exists, still try to close it. + lvm vgchange -an "$VG" || \ + DEBUG "Can't close LVM VG: $VG" +} + +# If a LUKS device is open, close any layers within the LUKS device, then close +# the LUKS device. +close_block_device_luks() { + TRACE_FUNC + local DEVICE="$1" + local LUKSDEV + LUKSDEV="$(basename "$DEVICE")_crypt" + + if [ -e "/dev/mapper/$LUKSDEV" ]; then + # Close inner layers before trying to close LUKS + close_block_device_layers "/dev/mapper/$LUKSDEV" + cryptsetup close "$LUKSDEV" || \ + DEBUG "Can't close LUKS volume: $LUKSDEV" + fi +} + +# Close the root device, including unmounting the filesystem and closing all +# layers. This can close a partially-opened device if an error occurs. +close_block_device_layers() { + TRACE_FUNC + local DEVICE="$1" + local VG + + if ! [ -e "$DEVICE" ]; then + DEBUG "Block device doesn't exit: $DEVICE" + # Like in open_root_device(), this shouldn't really happen, show the layers + # up to this point via the call stack. + DEBUG_STACK + return 1 + fi + + if cryptsetup isLuks "$DEVICE"; then + close_block_device_luks "$DEVICE" + elif VG="$(find_lvm_vg_name "$DEVICE")"; then + close_block_device_lvm "$VG" + fi + # Otherwise, we've handled all the layers we understood, there's nothing left + # to do. +} + +# Try to open the root device, and clean up if unsuccessful. +open_root_device() { + TRACE_FUNC + if ! open_root_device_no_clean_up "$1"; then + unmount_root_device + return 1 + fi + + return 0 +} + +# Close the root device, including unmounting the filesystem and closing all +# layers. This can close a partially-opened device if an error occurs. This +# never fails, if an error occurs it still tries to close anything it can. +close_root_device() { + TRACE_FUNC + local DEVICE="$1" + + # Unmount the filesystem if it is mounted. If it is not mounted, ignore the + # failure. If it is mounted but can't be unmounted, this will fail and we + # will fail to close any LUKS/LVM layers too. + umount "$ROOT_MOUNT" &>/dev/null || true + + close_block_device_layers "$DEVICE" || true +} + # detect and set /root device # mount /root if successful detect_root_device() { + TRACE_FUNC + echo "+++ Detecting root device " if [ ! -e $ROOT_MOUNT ]; then mkdir -p $ROOT_MOUNT fi - # unmount $ROOT_MOUNT to be safe - cd / && umount $ROOT_MOUNT 2>/dev/null + # Ensure nothing is opened/mounted + unmount_root_device # check $CONFIG_ROOT_DEV if set/valid - if [ -e "$CONFIG_ROOT_DEV" ]; then - if cryptsetup isLuks $CONFIG_ROOT_DEV >/dev/null 2>&1; then - if cryptsetup open $CONFIG_ROOT_DEV rootdisk; then - if mount -o ro /dev/mapper/rootdisk $ROOT_MOUNT >/dev/null 2>&1; then - if cd $ROOT_MOUNT && ls -d $CONFIG_ROOT_DIRLIST >/dev/null 2>&1; then # CONFIG_ROOT_DEV is valid device and contains an installed OS - return 0 - fi - fi - fi - fi + if [ -e "$CONFIG_ROOT_DEV" ] && open_root_device "$CONFIG_ROOT_DEV"; then + return 0 fi # generate list of possible boot devices @@ -186,7 +393,7 @@ detect_root_device() # filter out extraneous options > /tmp_root_device_list - for i in `cat /tmp/disklist`; do + while IFS= read -r -u 10 i; do # remove block device from list if numeric partitions exist DEV_NUM_PARTITIONS=$((`ls -1 $i* | wc -l`-1)) if [ ${DEV_NUM_PARTITIONS} -eq 0 ]; then @@ -194,33 +401,25 @@ detect_root_device() else ls $i* | tail -${DEV_NUM_PARTITIONS} >> /tmp_root_device_list fi - done - - # iterate thru possible options and check for LUKS - for i in `cat /tmp_root_device_list`; do - if cryptsetup isLuks $i >/dev/null 2>&1; then - if cryptsetup open $i rootdisk; then - if mount -o ro /dev/mapper/rootdisk $ROOT_MOUNT >/dev/null 2>&1; then - if cd $ROOT_MOUNT && ls -d $CONFIG_ROOT_DIRLIST >/dev/null 2>&1; then - # CONFIG_ROOT_DEV is valid device and contains an installed OS - CONFIG_ROOT_DEV="$i" - return 0 - fi - fi - fi + done 10/dev/null - cryptsetup close rootdisk + [ -e "$CONFIG_ROOT_DEV" ] && close_root_device "$CONFIG_ROOT_DEV" } checkonly="n" diff --git a/initrd/etc/functions b/initrd/etc/functions index 637beef0e..da732d9ef 100755 --- a/initrd/etc/functions +++ b/initrd/etc/functions @@ -33,6 +33,28 @@ DO_WITH_DEBUG() { "$@" } +# Trace the current script and function. +TRACE_FUNC() { + # Index [1] for BASH_SOURCE and FUNCNAME give us the caller location. + # FUNCNAME is 'main' if called from a script outside any function. + # BASH_LINENO is offset by 1, it provides the line that the + # corresponding FUNCNAME was _called from_, so BASH_LINENO[0] is the + # location of the caller. + TRACE "${BASH_SOURCE[1]}(${BASH_LINENO[0]}): ${FUNCNAME[1]}" +} + +# Show the entire current call stack in debug output - useful if a catastrophic +# error or something very unexpected occurs, like totally invalid parameters. +DEBUG_STACK() { + local FRAMES + FRAMES="${#FUNCNAME[@]}" + DEBUG "call stack: ($((FRAMES-1)) frames)" + # Don't print DEBUG_STACK itself, start from 1 + for i in $(seq 1 "$((FRAMES-1))"); do + DEBUG "- $((i-1)) - ${BASH_SOURCE[$i]}(${BASH_LINENO[$((i-1))]}): ${FUNCNAME[$i]}" + done +} + pcrs() { if [ "$CONFIG_TPM2_TOOLS" = "y" ]; then tpm2 pcrread sha256 diff --git a/modules/bash b/modules/bash index 327b5dd50..e29fed726 100644 --- a/modules/bash +++ b/modules/bash @@ -7,6 +7,9 @@ bash_tar := bash-$(bash_version).tar.gz bash_url := https://ftpmirror.gnu.org/bash/$(bash_tar) bash_hash := 5bac17218d3911834520dad13cd1f85ab944e1c09ae1aba55906be1f8192f558 +# --enable-debugger: Enables BASH_SOURCE tracing through functions as well as +# BASH_ARGV/BASH_ARGC. (Otherwise BASH_SOURCE[0] is empty when calling a +# function, it's only set in top level script code.) bash_configure := CFLAGS="-g0 -Os" LDFLAGS="-s" ./configure \ $(CROSS_TOOLS) \ --host $(target) \ @@ -16,11 +19,11 @@ bash_configure := CFLAGS="-g0 -Os" LDFLAGS="-s" ./configure \ --mandir=/usr/share/man \ --without-bash-malloc \ --disable-coprocesses \ - --disable-debugger \ + --enable-debugger \ --disable-net-redirections \ --enable-single-help-strings \ --disable-nls \ - --disable-readline \ + --enable-readline \ bash_target := $(MAKE_JOBS) \ && $(MAKE) -C $(build)/$(bash_dir) \ diff --git a/modules/lvm2 b/modules/lvm2 index 4bf9f172b..be1435a36 100644 --- a/modules/lvm2 +++ b/modules/lvm2 @@ -33,6 +33,7 @@ lvm2_configure := \ --disable-cache_check_needs_check \ --disable-thin_check_needs_check \ --with-cluster=none \ + --with-thin-check= \ # not sure why LIB_SUFFIX is not defined in the cross build lvm2_target := \