initrd/bin/root-hashes-gui.sh: Qubes support, faster hash creation

Don't spew the root hashes to the console when creating the hash file.
This speeds up hash creation significantly.  A basic Qubes install on a
cheap (slow) SATA SSD reduced from about 1.5 minutes to just under 1
minute, and a PureOS install on a fast NVMe disk reduced from 2.5
minutes to 1 minute.

Support opening LVM volume groups to find the root disk.  If an LVM PV
is found, its group is opened and the 'root' volume is used.  There is
no way to set the volume name in this iteration; this is the default
name used by Qubes and probably common to many LVM OS installations.
LUKS and LVM can be mixed.  Tested LUKS (PureOS) and LUKS+LVM (Qubes).

Always cd to "$ROOT_MOUNT" in a subshell, improves robustness of
scripts (previously some functions only worked if they were called
after another function had cd'd to "$ROOT_MOUNT").

Signed-off-by: Jonathon Hall <jonathon.hall@puri.sm>
This commit is contained in:
Jonathon Hall 2024-01-10 17:13:44 -05:00
parent 70d249ae46
commit 80b57eb60d
No known key found for this signature in database
GPG Key ID: 1E9C3CA91AE25114

View File

@ -32,7 +32,9 @@ update_root_checksums() {
fi fi
echo "+++ Calculating hashes for all files in $CONFIG_ROOT_DIRLIST_PRETTY " 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 # switch back to ro mode
mount -o ro,remount /boot mount -o ro,remount /boot
@ -86,7 +88,7 @@ check_root_checksums() {
fi fi
echo "+++ Checking for new files in $CONFIG_ROOT_DIRLIST_PRETTY " 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 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 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 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 fi
echo "+++ Checking hashes for all files in $CONFIG_ROOT_DIRLIST_PRETTY (this might take a while) " 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 " echo "+++ Verified root hashes "
valid_hash='y' valid_hash='y'
unmount_root_device unmount_root_device
@ -156,29 +158,234 @@ check_root_checksums() {
fi fi
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
DEBUG "Did not detect an LVM2 PV: $DEVICE"
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 # detect and set /root device
# mount /root if successful # mount /root if successful
detect_root_device() detect_root_device()
{ {
TRACE_FUNC
echo "+++ Detecting root device " echo "+++ Detecting root device "
if [ ! -e $ROOT_MOUNT ]; then if [ ! -e $ROOT_MOUNT ]; then
mkdir -p $ROOT_MOUNT mkdir -p $ROOT_MOUNT
fi fi
# unmount $ROOT_MOUNT to be safe # Ensure nothing is opened/mounted
cd / && umount $ROOT_MOUNT 2>/dev/null unmount_root_device
# check $CONFIG_ROOT_DEV if set/valid # check $CONFIG_ROOT_DEV if set/valid
if [ -e "$CONFIG_ROOT_DEV" ]; then if [ -e "$CONFIG_ROOT_DEV" ] && open_root_device "$CONFIG_ROOT_DEV"; then
if cryptsetup isLuks $CONFIG_ROOT_DEV >/dev/null 2>&1; then return 0
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
fi fi
# generate list of possible boot devices # generate list of possible boot devices
@ -186,7 +393,7 @@ detect_root_device()
# filter out extraneous options # filter out extraneous options
> /tmp_root_device_list > /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 # remove block device from list if numeric partitions exist
DEV_NUM_PARTITIONS=$((`ls -1 $i* | wc -l`-1)) DEV_NUM_PARTITIONS=$((`ls -1 $i* | wc -l`-1))
if [ ${DEV_NUM_PARTITIONS} -eq 0 ]; then if [ ${DEV_NUM_PARTITIONS} -eq 0 ]; then
@ -194,33 +401,25 @@ detect_root_device()
else else
ls $i* | tail -${DEV_NUM_PARTITIONS} >> /tmp_root_device_list ls $i* | tail -${DEV_NUM_PARTITIONS} >> /tmp_root_device_list
fi fi
done done 10</tmp/disklist
# iterate thru possible options and check for LUKS # iterate through possible options
for i in `cat /tmp_root_device_list`; do while IFS= read -r -u 10 i; do
if cryptsetup isLuks $i >/dev/null 2>&1; then if open_root_device "$i"; then
if cryptsetup open $i rootdisk; then # CONFIG_ROOT_DEV is valid device and contains an installed OS
if mount -o ro /dev/mapper/rootdisk $ROOT_MOUNT >/dev/null 2>&1; then CONFIG_ROOT_DEV="$i"
if cd $ROOT_MOUNT && ls -d $CONFIG_ROOT_DIRLIST >/dev/null 2>&1; then return 0
# CONFIG_ROOT_DEV is valid device and contains an installed OS
CONFIG_ROOT_DEV="$i"
return 0
fi
fi
fi
fi fi
done done 10</tmp_root_device_list
# no valid root device found # no valid root device found
echo "Unable to locate $ROOT_MOUNT files on any mounted disk" echo "Unable to locate $ROOT_MOUNT files on any mounted disk"
unmount_root_device
return 1 return 1
} }
unmount_root_device() unmount_root_device()
{ {
cd / [ -e "$CONFIG_ROOT_DEV" ] && close_root_device "$CONFIG_ROOT_DEV"
umount $ROOT_MOUNT 2>/dev/null
cryptsetup close rootdisk
} }
checkonly="n" checkonly="n"