From 94ebecd8d3d481ac0fd720fc393ea3d2e65ad126 Mon Sep 17 00:00:00 2001 From: Alexey Gladkov Date: Sat, 31 Oct 2026 13:38:25 +0100 Subject: [PATCH] luks: Fix regression parsing /etc/luks.keys with empty key-device Commit ba53c1510323 ("luks: Simplify key lookup and unlock fallback handling") changed the /etc/luks.keys parser to: while IFS="$tab" read -u 3 -r path keydev luksdev; do Because the tab character is treated as IFS whitespace in bash, multiple consecutive tabs collapse into a single field delimiter. This breaks documented entries where the optional key-device field is omitted: key-pathUUID=... Instead of leaving keydev empty and populating luksdev, bash assigns the UUID=... token to keydev and leaves luksdev empty: path="key-path" keydev="UUID=..." luksdev="" As a result, findkey() mistakenly attempts to mount the LUKS target device as a key device: mount -r UUID=... /mnt/luks-key and exits with an error instead of matching the device or advancing to subsequent entries. This breaks configurations that rely on empty key-device entries, including PKCS#11 smart card entries created by the lukskeys uevent filter. Restore the previous loop that reads lines without IFS splitting and extracts fields around individual tab delimiters. Also expose the $lukskeys variable (defaulting to /etc/luks.keys) and add unit test coverage in ts0063-luks-keys-parsing. Fixes: ba53c1510323 ("luks: Simplify key lookup and unlock fallback handling") Signed-off-by: Alexey Gladkov --- .../luks/data/lib/uevent/handlers/085-luks | 23 +++-- testing/tests/ts0063-luks-keys-parsing/expect | 21 +++++ testing/tests/ts0063-luks-keys-parsing/run | 87 +++++++++++++++++++ 3 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 testing/tests/ts0063-luks-keys-parsing/expect create mode 100755 testing/tests/ts0063-luks-keys-parsing/run diff --git a/features/luks/data/lib/uevent/handlers/085-luks b/features/luks/data/lib/uevent/handlers/085-luks index 0c1f2a47..2718d42f 100755 --- a/features/luks/data/lib/uevent/handlers/085-luks +++ b/features/luks/data/lib/uevent/handlers/085-luks @@ -17,6 +17,7 @@ mnt="/mnt/luks-key" tab=' ' pkcs11_luks_key="/tmp/pkcs11-luks.key" crypttab=/etc/crypttab +lukskeys="${lukskeys:-/etc/luks.keys}" match_dev_in_array() { [ "$#" = 2 ] || return 2 @@ -93,9 +94,21 @@ freekey() { } findkey() { - local path keydev luksdev prefix rc=0 + local path keydev luksdev prefix rc=0 s v + + while IFS='' read -u 3 -r s; do + for n in path keydev luksdev; do + v= + if [ -n "$s" ] && [ -z "${s##*$tab*}" ]; then + v="${s%%$tab*}" + s="${s#*$tab}" + else + v="$s" + s= + fi + eval "$n=\"\$v\"" + done - while IFS="$tab" read -u 3 -r path keydev luksdev; do if [ -z "$path" ]; then message "ERROR: keypath required." return 1 @@ -130,7 +143,7 @@ findkey() { message "Found keyfile '$path' for '${LUKS_ROOT#/dev/}' encrypted partition." return 0 - done 3< /etc/luks.keys + done 3< "$lukskeys" # Keyfile not found yet. return 2 @@ -284,8 +297,8 @@ handler() { # First of all, we check /etc/luks.keys, which is usually created from # /proc/cmdline. # - if [ -f /etc/luks.keys ]; then - message "$LUKS_ROOT: trying to find the keyfile in the '/etc/luks.keys'." + if [ -f "$lukskeys" ]; then + message "$LUKS_ROOT: trying to find the keyfile in the '$lukskeys'." findkey [ "${LUKS_KEY_FORMAT:-plain}" != plain ] || luks_pipekey=y diff --git a/testing/tests/ts0063-luks-keys-parsing/expect b/testing/tests/ts0063-luks-keys-parsing/expect new file mode 100644 index 00000000..d96390f0 --- /dev/null +++ b/testing/tests/ts0063-luks-keys-parsing/expect @@ -0,0 +1,21 @@ +=== Test 1: Empty keydev with matching luksdev (key-pathUUID=...) === +message: Found keyfile '/keys/target.key' for 'sda2' encrypted partition. +rc=0 +=== Test 2: Skip non-matching entry and find matching (empty keydev) === +message: Found keyfile '/keys/target.key' for 'sda2' encrypted partition. +rc=0 +=== Test 3: PKCS#11 key with empty keydev (filters/lukskeys format) === +read_pkcs11_key: pkcs11:model=test;id=1 +message: Found keyfile '/pkcs11-luks.key' for 'sda2' encrypted partition. +rc=0 +=== Test 4: Single field (key-path only) === +message: Found keyfile '/keys/target.key' for 'sda2' encrypted partition. +rc=0 +=== Test 5: All 3 fields (key-path, keydev, luksdev) === +mount: -r /dev/sdb1 /mnt +message: Found keyfile 'custom/key.bin' for 'sda2' encrypted partition. +rc=0 +=== Test 6: Empty keypath error === +message: ERROR: keypath required. +rc=1 +rc=0 diff --git a/testing/tests/ts0063-luks-keys-parsing/run b/testing/tests/ts0063-luks-keys-parsing/run new file mode 100755 index 00000000..744c4690 --- /dev/null +++ b/testing/tests/ts0063-luks-keys-parsing/run @@ -0,0 +1,87 @@ +#!/bin/bash -efu +# SPDX-License-Identifier: GPL-3.0-or-later + +cwd="${0%/*}" + +tmpdir="$(mktemp -d)" +trap 'rm -rf "$tmpdir"' EXIT + +tab=' ' +mnt="$tmpdir/mnt" +mkdir -p "$mnt" +lukskeys="$tmpdir/luks.keys" +LUKS_ROOT="/dev/sda2" +ID_FS_UUID="target-uuid-1234" + +pkcs11_luks_key="$tmpdir/pkcs11-luks.key" + +# Mock functions needed by findkey +message() { + echo "message: $*" | sed "s#$tmpdir##g" +} + +get_dev() { + local val="$1" + [ "$val" = "UUID=$ID_FS_UUID" ] || [ "$val" = "$LUKS_ROOT" ] +} + +mount() { + echo "mount: $*" | sed "s#$tmpdir##g" + return 1 +} + +read_pkcs11_key() { + echo "read_pkcs11_key: $*" + path="$pkcs11_luks_key" + touch "$path" + return 0 +} + +# Extract findkey from 085-luks +eval "$(sed -n '/^findkey() {/,/^}/p' features/luks/data/lib/uevent/handlers/085-luks)" + +run_test() { + local desc="$1" + echo "=== $desc ===" + local rc=0 + ( findkey ) || rc=$? + echo "rc=$rc" +} + +mkdir -p "$tmpdir/keys" +touch "$tmpdir/keys/target.key" +touch "$tmpdir/keys/other.key" + +# +# Regression tests for commit ba53c151: +# When keydev is empty (key-pathUUID=...), IFS tab-splitting in bash +# must not collapse consecutive tabs. Otherwise, UUID= is assigned to keydev +# instead of luksdev, leading to an invalid mount attempt and early abort. +# + +printf '%s\t\tUUID=%s\n' "$tmpdir/keys/target.key" "$ID_FS_UUID" > "$lukskeys" +run_test "Test 1: Empty keydev with matching luksdev (key-pathUUID=...)" + +{ + printf '%s\t\tUUID=%s\n' "$tmpdir/keys/other.key" "other-uuid-9999" + printf '%s\t\tUUID=%s\n' "$tmpdir/keys/target.key" "$ID_FS_UUID" +} > "$lukskeys" +run_test "Test 2: Skip non-matching entry and find matching (empty keydev)" + +printf 'pkcs11:model=test;id=1\t\tUUID=%s\n' "$ID_FS_UUID" > "$lukskeys" +run_test "Test 3: PKCS#11 key with empty keydev (filters/lukskeys format)" + +printf '%s\n' "$tmpdir/keys/target.key" > "$lukskeys" +run_test "Test 4: Single field (key-path only)" + +mount() { + echo "mount: $*" | sed "s#$tmpdir##g" + return 0 +} +mkdir -p "$tmpdir/mnt/custom" +touch "$tmpdir/mnt/custom/key.bin" +printf 'custom/key.bin\t/dev/sdb1\tUUID=%s\n' "$ID_FS_UUID" > "$lukskeys" +run_test "Test 5: All 3 fields (key-path, keydev, luksdev)" + +printf '\t\tUUID=%s\n' "$ID_FS_UUID" > "$lukskeys" +run_test "Test 6: Empty keypath error" -- 2.56.0