fix recovery of LUKS encrypted systems with multiple keyslots

Resolves: RHEL-106762
This commit is contained in:
Lukáš Zaoral 2025-07-31 10:27:15 +02:00
parent 92d1a3ed37
commit cef3700c9e
No known key found for this signature in database
GPG Key ID: 39157506DD67752D
2 changed files with 169 additions and 0 deletions

View File

@ -0,0 +1,165 @@
From e9ce93f096e505968cc728a7eb5a06e25dc8d88b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Renaud=20M=C3=A9trich?=
<1163635+rmetrich@users.noreply.github.com>
Date: Fri, 27 Jun 2025 14:39:01 +0200
Subject: [PATCH] LUKS2: try to handle multiple key slots (#3430)
LUKS2: try to handle multiple key slots:
The current code doesn't deal properly with multiple key slots nor
extracts the Hash that was used at LUKS creation properly.
With current code, when multiple key slots are found or PBKDF algorithm
is "pbkdf2" (instead of default "argon2id"), the disk layout file
contains 2 lines for 'crypt' parameters, causing havoc during recovery.
This new code tries to do better:
It searches for the Hash in Keyslots section only
(and falls back to Digests section if not found).
It warns the admin if multiple keyslots are in use
(e.g. because of multiple passphrases or Clevis binding).
It handles the PBKDF algorithm.
It makes sure that the 'crypt' parameters are always on one line.
---
doc/user-guide/06-layout-configuration.adoc | 2 +-
.../GNU/Linux/160_include_luks_code.sh | 3 +
.../layout/save/GNU/Linux/260_crypt_layout.sh | 69 ++++++++++++++++---
3 files changed, 62 insertions(+), 12 deletions(-)
diff --git a/doc/user-guide/06-layout-configuration.adoc b/doc/user-guide/06-layout-configuration.adoc
index d2a0a4c8b9..f1f4a47326 100644
--- a/doc/user-guide/06-layout-configuration.adoc
+++ b/doc/user-guide/06-layout-configuration.adoc
@@ -666,7 +666,7 @@ lvmvol <volume_group> <name> <size(bytes)> <layout> [key:value ...]
=== LUKS Devices ===
----------------------------------
-crypt /dev/mapper/<name> <device> [type=<type>] [cipher=<cipher>] [key_size=<key size>] [hash=<hash function>] [uuid=<uuid>] [keyfile=<keyfile>] [password=<password>]
+crypt /dev/mapper/<name> <device> [type=<type>] [cipher=<cipher>] [key_size=<key size>] [hash=<hash function>] [uuid=<uuid>] [pbkdf=<pbkdf algo> ] [keyfile=<keyfile>] [password=<password>]
----------------------------------
=== DRBD ===
diff --git a/usr/share/rear/layout/prepare/GNU/Linux/160_include_luks_code.sh b/usr/share/rear/layout/prepare/GNU/Linux/160_include_luks_code.sh
index 0c662f6770..3b0b34cd12 100644
--- a/usr/share/rear/layout/prepare/GNU/Linux/160_include_luks_code.sh
+++ b/usr/share/rear/layout/prepare/GNU/Linux/160_include_luks_code.sh
@@ -62,6 +62,9 @@ create_crypt() {
(uuid)
test $value && cryptsetup_options+=" --uuid $value"
;;
+ (pbkdf)
+ test $value && cryptsetup_options+=" --pbkdf $value"
+ ;;
(keyfile)
test $value && keyfile=$value
;;
diff --git a/usr/share/rear/layout/save/GNU/Linux/260_crypt_layout.sh b/usr/share/rear/layout/save/GNU/Linux/260_crypt_layout.sh
index 7410165a5e..c3470f3244 100644
--- a/usr/share/rear/layout/save/GNU/Linux/260_crypt_layout.sh
+++ b/usr/share/rear/layout/save/GNU/Linux/260_crypt_layout.sh
@@ -61,31 +61,73 @@ while read target_name junk ; do
fi
luks_type=luks$version
+ luksDump_cmd="cryptsetup luksDump $source_device"
+
# Gather crypt information:
- if ! cryptsetup luksDump $source_device >$TMP_DIR/cryptsetup.luksDump ; then
- LogPrintError "Error: Cannot get LUKS$version values for $target_name ('cryptsetup luksDump $source_device' failed)"
+ if ! $luksDump_cmd >$TMP_DIR/cryptsetup.luksDump ; then
+ LogPrintError "Error: Cannot get LUKS$version values for $target_name ('$luksDump_cmd' failed)"
continue
fi
+
uuid=$( grep "UUID" $TMP_DIR/cryptsetup.luksDump | sed -r 's/^.+:\s*(.+)$/\1/' )
keyfile_option=$( [ -f /etc/crypttab ] && awk '$1 == "'"$target_name"'" && $3 != "none" && $3 != "-" && $3 != "" { print "keyfile=" $3; }' /etc/crypttab )
+ pbkdf_option=""
+
if test $luks_type = "luks1" ; then
+
cipher_name=$( grep "Cipher name" $TMP_DIR/cryptsetup.luksDump | sed -r 's/^.+:\s*(.+)$/\1/' )
cipher_mode=$( grep "Cipher mode" $TMP_DIR/cryptsetup.luksDump | cut -d: -f2- | awk '{printf("%s",$1)};' )
cipher=$cipher_name-$cipher_mode
key_size=$( grep "MK bits" $TMP_DIR/cryptsetup.luksDump | sed -r 's/^.+:\s*(.+)$/\1/' )
hash=$( grep "Hash spec" $TMP_DIR/cryptsetup.luksDump | sed -r 's/^.+:\s*(.+)$/\1/' )
+
elif test $luks_type = "luks2" ; then
- cipher=$( grep "cipher:" $TMP_DIR/cryptsetup.luksDump | sed -r 's/^.+:\s*(.+)$/\1/' )
- # More than one keyslot may be defined - use key_size from the first slot.
+
+ keyslots_section=$( awk '/^Keyslots:/ {include=1;next} /^[[:upper:]]/ && include {exit} include' $TMP_DIR/cryptsetup.luksDump )
+ if [ -z "$keyslots_section" ]; then
+ LogPrintError "Error: No Keyslots section found in '$luksDump_cmd' output"
+ continue
+ fi
+ if [ $( grep -c -P '^\s+\d+: luks2' <<< "$keyslots_section" ) -gt 1 ]; then
+ LogPrintError "Warning: More than one luks2 keyslot found in '$luksDump_cmd' output, will only consider the first keyslot during recovery"
+ fi
+ luks2_section=$( awk '/^[[:blank:]]+[[:digit:]]+:/ && include {exit} /^[[:blank:]]+[[:digit:]]+: luks2/{include=1} include' <<< "$keyslots_section")
+
+ cipher=$( grep "Cipher:" <<< "$luks2_section" | sed -r 's/^.+:\s*(.+)$/\1/' )
+ pbkdf=$( grep "PBKDF:" <<< "$luks2_section" | sed -r 's/^.+:\s*(.+)$/\1/' )
+ if [ -z "$pbkdf" ]; then
+ LogPrintError "Warning: no PBKDF found in luks2 keyslot of '$luksDump_cmd' output, will use defaults during recovery"
+ else
+ pbkdf_option="pbkdf=$pbkdf"
+ fi
+
# Depending on the version the "cryptsetup luksDump" command outputs the key_size value
# as a line like
# Key: 512 bits
# and/or as a line like
# Cipher key: 512 bits
# cf. https://github.com/rear/rear/pull/2504#issuecomment-718729198 and subsequent comments
- # so we grep for both lines but use only the first match from the first slot:
- key_size=$( grep -E -m 1 "Key:|Cipher key:" $TMP_DIR/cryptsetup.luksDump | sed -r 's/^.+:\s*(.+) bits$/\1/' )
- hash=$( grep "Hash" $TMP_DIR/cryptsetup.luksDump | sed -r 's/^.+:\s*(.+)$/\1/' )
+ key_size=$( grep -E "Key:|Cipher key:" <<< "$luks2_section" | sed -r 's/^.+:\s*(.+) bits$/\1/' | sort -u )
+ if [ -z "$key_size" ]; then
+ LogPrintError "Error: No key size found in luks2 keyslot of '$luksDump_cmd' output"
+ elif [ $( wc -w <<< "$key_size" ) -gt 1 ]; then
+ LogPrintError "Error: Too many key sizes found in luks2 keyslot of '$luksDump_cmd' output"
+ key_size=""
+ fi
+
+ hash=$( grep "AF hash:" <<< "$luks2_section" | sed -r 's/^.+:\s*(.+)$/\1/' )
+ if [ -z "$hash" ]; then
+ # Fallback to using Hash field found in Digests section
+ digests_section=$( awk '/^Digests:/ {include=1;next} /^[[:upper:]]/ && include {exit} include' $TMP_DIR/cryptsetup.luksDump )
+ hash=$( grep "Hash:" <<< "$digests_section" | sed -r 's/^.+:\s*(.+)$/\1/' | sort -u )
+ if [ -z "$hash" ]; then
+ LogPrintError "Warning: No Hash found in Digests section of '$luksDump_cmd' output, will use default type during recovery"
+ elif [ $( wc -w <<< "$hash" ) -gt 1 ]; then
+ hash=$( head -1 <<< "$hash" )
+ LogPrintError "Warning: Too many Hash found in Digests section of '$luksDump_cmd' output, will use '$hash' during recovery"
+ fi
+ fi
+
fi
# Basic checks that the cipher key_size hash uuid values exist
@@ -95,9 +137,9 @@ while read target_name junk ; do
# and it seems cryptsetup fails when options with empty values are specified
# cf. https://github.com/rear/rear/pull/2504#issuecomment-719479724
# For example a LUKS1 crypt entry in disklayout.conf looks like
- # crypt /dev/mapper/luks1test /dev/sda7 type=luks1 cipher=aes-xts-plain64 key_size=256 hash=sha256 uuid=1b4198c9-d9b0-4c57-b9a3-3433e391e706
- # and a LUKS1 crypt entry in disklayout.conf looks like
- # crypt /dev/mapper/luks2test /dev/sda8 type=luks2 cipher=aes-xts-plain64 key_size=256 hash=sha256 uuid=3e874a28-7415-4f8c-9757-b3f28a96c4d2
+ # crypt /dev/mapper/luks1test /dev/sda7 type=luks1 cipher=aes-xts-plain64 key_size=256 hash=sha256 uuid=1b4198c9-d9b0-4c57-b9a3-3433e391e706
+ # and a LUKS2 crypt entry in disklayout.conf looks like
+ # crypt /dev/mapper/luks2test /dev/sda8 type=luks2 cipher=aes-xts-plain64 key_size=256 hash=sha256 uuid=3e874a28-7415-4f8c-9757-b3f28a96c4d2 pbkdf=argon2id
# Only the keyfile_option value is optional and the luks_type value is already tested above.
# Using plain test to ensure a value is a single non empty and non blank word
# without quoting because test " " would return zero exit code
@@ -127,7 +169,12 @@ while read target_name junk ; do
LogPrintError "Error: No 'uuid' value for LUKS$version volume $target_name in $source_device (mounting it or booting the recreated system may fail)"
fi
- echo "crypt /dev/mapper/$target_name $source_device type=$luks_type cipher=$cipher key_size=$key_size hash=$hash uuid=$uuid $keyfile_option" >> $DISKLAYOUT_FILE
+ {
+ echo -n "crypt /dev/mapper/$target_name $source_device type=$luks_type cipher=$cipher key_size=$key_size hash=$hash uuid=$uuid"
+ [ -n "$keyfile_option" ] && echo -n " $keyfile_option"
+ [ -n "$pbkdf_option" ] && echo -n " $pbkdf_option"
+ echo
+ } >> $DISKLAYOUT_FILE
done < <( dmsetup ls --target crypt )

View File

@ -34,6 +34,10 @@ Patch122: rear-skip-longhorn-iscsi-RHEL-83551.patch
# https://github.com/rear/rear/commit/1ca518c2a0e675ace956ef71bc79d67e4990562b
Patch123: rear-detect-prep-boot-on-gpt-RHEL-82098.patch
# fix recovery of LUKS encrypted systems with multiple keyslots
# https://github.com/rear/rear/commit/e9ce93f096e505968cc728a7eb5a06e25dc8d88b
Patch124: rear-support-multi-keyslot-luks-RHEL-83776.patch
######################
# downstream patches #
######################