From cdd2e7759c994dae2682d12fadfdf57316da0b0d Mon Sep 17 00:00:00 2001 From: Nadzeya Hutsko Date: Fri, 24 Apr 2026 21:54:18 +0200 Subject: [PATCH] fix(crypt): set need_shutdown for LUKS under systemd-cryptsetup cryptroot-ask.sh calls need_shutdown() in the legacy path, but systemd-cryptsetup has no equivalent. Without the flag, dracut-initramfs-restore exits early, the initramfs is not pivoted to at shutdown, and dm-shutdown.sh never runs, leaving LUKS stacks busy and blocking poweroff. Add a cleanup hook that sets the flag whenever a LUKS mapping is active, matching the mdraid-needshutdown.sh / multipathd-needshutdown.sh pattern. Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/dracut/+bug/2147476 --- modules.d/70crypt/crypt-needshutdown.sh | 18 +++++ modules.d/70crypt/module-setup.sh | 1 + test/TEST-26-ENC-RAID-LVM/assertion.sh | 11 +++ .../setup-shutdown-env.sh | 32 ++++++++ test/TEST-26-ENC-RAID-LVM/test.sh | 73 ++++++++++++++++++- 5 files changed, 131 insertions(+), 4 deletions(-) create mode 100755 modules.d/70crypt/crypt-needshutdown.sh create mode 100755 test/TEST-26-ENC-RAID-LVM/assertion.sh create mode 100755 test/TEST-26-ENC-RAID-LVM/setup-shutdown-env.sh diff --git a/modules.d/70crypt/crypt-needshutdown.sh b/modules.d/70crypt/crypt-needshutdown.sh new file mode 100755 index 0000000000..f12baa1dfd --- /dev/null +++ b/modules.d/70crypt/crypt-needshutdown.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +command -v getarg > /dev/null || . /lib/dracut-lib.sh + +# The legacy crypt path calls need_shutdown from cryptroot-ask.sh, but +# systemd-cryptsetup does not, so ensure the flag is set here whenever a LUKS +# mapping is active. Otherwise dracut-initramfs-restore exits early and +# dm-shutdown.sh never runs, leaving LUKS stacks busy at shutdown +for _dev in /sys/block/dm-*; do + [ -e "${_dev}/dm/uuid" ] || continue + case $(cat "${_dev}/dm/uuid") in + CRYPT-LUKS*) + need_shutdown + break + ;; + esac +done +unset _dev diff --git a/modules.d/70crypt/module-setup.sh b/modules.d/70crypt/module-setup.sh index a6154c4203..1a7d12d601 100755 --- a/modules.d/70crypt/module-setup.sh +++ b/modules.d/70crypt/module-setup.sh @@ -84,6 +84,7 @@ install() { fi inst_hook cmdline 30 "$moddir/parse-crypt.sh" + inst_hook cleanup 29 "$moddir/crypt-needshutdown.sh" if dracut_module_included "systemd"; then inst_script "$moddir/crypt-generator.sh" "$systemdutildir"/system-generators/dracut-crypt-generator else diff --git a/test/TEST-26-ENC-RAID-LVM/assertion.sh b/test/TEST-26-ENC-RAID-LVM/assertion.sh new file mode 100755 index 0000000000..c77eeb6b93 --- /dev/null +++ b/test/TEST-26-ENC-RAID-LVM/assertion.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +# required binaries: test + +# Verify that dracut flagged .need_shutdown during boot. Without the flag, +# dracut-initramfs-restore exits early on shutdown, the initramfs is not +# pivoted to, and dm-shutdown.sh never runs leaving LUKS stacks +# (e.g. LVM-on-LUKS) busy and blocking poweroff +if [ ! -e /run/initramfs/.need_shutdown ]; then + echo "/run/initramfs/.need_shutdown was not created during boot" >> /run/failed +fi diff --git a/test/TEST-26-ENC-RAID-LVM/setup-shutdown-env.sh b/test/TEST-26-ENC-RAID-LVM/setup-shutdown-env.sh new file mode 100755 index 0000000000..052894e7a9 --- /dev/null +++ b/test/TEST-26-ENC-RAID-LVM/setup-shutdown-env.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +# Creates a single-disk LVM-on-LUKS root filesystem for the shutdown test + +trap 'poweroff -f' EXIT +set -ex + +printf verySecurePassword > keyfile +cryptsetup --pbkdf pbkdf2 -q luksFormat /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_disk1 /keyfile +cryptsetup luksOpen /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_disk1 vault < /keyfile + +lvm pvcreate -ff -y /dev/mapper/vault +lvm vgcreate dracut /dev/mapper/vault +lvm lvcreate --yes -l 100%FREE -n root dracut +lvm vgchange -ay + +mkfs.ext4 -q /dev/dracut/root +mkdir -p /sysroot +mount -t ext4 /dev/dracut/root /sysroot +cp -a -t /sysroot /source/* +umount /sysroot + +lvm lvchange -a n /dev/dracut/root +lvm vgchange -an +cryptsetup luksClose vault + +{ + echo "dracut-root-block-created" + udevadm info --query=property --name=/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_disk1 \ + | grep 'ID_FS_UUID=' +} | dd oflag=direct of=/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_marker status=none +sync diff --git a/test/TEST-26-ENC-RAID-LVM/test.sh b/test/TEST-26-ENC-RAID-LVM/test.sh index e4ce775c67..cb2a6b33df 100755 --- a/test/TEST-26-ENC-RAID-LVM/test.sh +++ b/test/TEST-26-ENC-RAID-LVM/test.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -eu # shellcheck disable=SC2034 -TEST_DESCRIPTION="root filesystem on LVM on encrypted partitions of a RAID" +TEST_DESCRIPTION="root filesystem on LVM on encrypted partitions (with and without RAID)" # Uncomment this to debug failures #DEBUGFAIL="rd.shell rd.break" # udev.log-priority=debug @@ -14,6 +14,11 @@ test_check() { return 1 fi + if ! type -p lvm &> /dev/null; then + echo "Test needs lvm for lvm module... Skipping" + return 1 + fi + if ! type -p mdadm &> /dev/null; then echo "Test needs mdadm for mdraid module ... Skipping" return 1 @@ -44,9 +49,47 @@ test_run() { check_qemu_log client_test_end + if [ -f "$TESTDIR"/initramfs.testing.enc-lvm ]; then + client_test_start "LVM-on-LUKS with systemd (need_shutdown set)" + + declare -a enc_lvm_disk_args=() + qemu_add_drive enc_lvm_disk_args "$TESTDIR"/disk-enc-lvm.img disk1 + + LUKSARGS_ENC_LVM=$(cat "$TESTDIR"/luks-enc-lvm.txt) + "$testdir"/run-qemu \ + "${enc_lvm_disk_args[@]}" \ + -append "$TEST_KERNEL_CMDLINE root=/dev/dracut/root ro rd.auto rootwait $LUKSARGS_ENC_LVM" \ + -initrd "$TESTDIR"/initramfs.testing.enc-lvm + check_qemu_log + client_test_end + fi + return 0 } +make_enc_lvm_rootfs() { + # Build a root filesystem on a single encrypted disk (LVM-on-LUKS, no RAID) + build_client_rootfs "$TESTDIR/overlay-enc-lvm/source" ./assertion.sh + + call_dracut -i "$TESTDIR/overlay-enc-lvm" / \ + --add-confdir test-makeroot \ + -a "bash crypt lvm" \ + -I "grep cryptsetup" \ + -i ./setup-shutdown-env.sh /usr/lib/dracut/hooks/initqueue/01-create-root.sh \ + -f "$TESTDIR"/initramfs.makeroot-enc-lvm + + declare -a disk_args=() + qemu_add_drive disk_args "$TESTDIR"/marker-enc-lvm.img marker 1 + qemu_add_drive disk_args "$TESTDIR"/disk-enc-lvm.img disk1 1 + + "$testdir"/run-qemu \ + "${disk_args[@]}" \ + -append "root=/dev/fakeroot quiet" \ + -initrd "$TESTDIR"/initramfs.makeroot-enc-lvm + test_marker_check dracut-root-block-created marker-enc-lvm.img + rm -rf "$TESTDIR/overlay-enc-lvm" +} + make_test_rootfs() { # Create what will eventually be our root filesystem onto an overlay build_client_rootfs "$TESTDIR/overlay/source" @@ -76,6 +119,30 @@ make_test_rootfs() { } test_setup() { + echo -n verySecurePassword > /tmp/key + chmod 0600 /tmp/key + + # The LVM-on-LUKS systemd case needs the systemd-cryptsetup binary. Skip + # that case on hosts without it + if [ -x /usr/lib/systemd/systemd-cryptsetup ] || [ -x /lib/systemd/systemd-cryptsetup ]; then + # Build the no-RAID rootfs and systemd-based initramfs first, then + # rename it aside, the RAID build reuses $TESTDIR/initramfs.testing + make_enc_lvm_rootfs + + luks_uuid=$(grep -F -a -m 1 ID_FS_UUID "$TESTDIR"/marker-enc-lvm.img | cut -d= -f2) + printf 'rd.luks.uuid=luks-%s' "$luks_uuid" > "$TESTDIR"/luks-enc-lvm.txt + printf 'luks-%s /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_disk1 /etc/key timeout=0\n' "$luks_uuid" > /tmp/crypttab-enc-lvm + + test_dracut -f \ + -a "crypt lvm" \ + --add "systemd systemd-cryptsetup" \ + -i "/tmp/crypttab-enc-lvm" "/etc/crypttab" \ + -i "/tmp/key" "/etc/key" + mv "$TESTDIR/initramfs.testing" "$TESTDIR/initramfs.testing.enc-lvm" + else + echo "systemd-cryptsetup not found, skipping LVM-on-LUKS systemd build" + fi + make_test_rootfs cryptoUUIDS=$(grep -F -a -m 3 ID_FS_UUID "$TESTDIR"/marker.img) @@ -90,10 +157,8 @@ test_setup() { printf 'luks-%s /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_disk%s /etc/key timeout=0\n' "$ID_FS_UUID" $i ((i += 1)) done > /tmp/crypttab - echo -n verySecurePassword > /tmp/key - chmod 0600 /tmp/key - test_dracut \ + test_dracut -f \ -a "crypt lvm mdraid" \ -i "/tmp/crypttab" "/etc/crypttab" \ -i "/tmp/key" "/etc/key"