#3483 Issue closed
: LUKS password not protected with { ... ; } 2>>/dev/$SECRET_OUTPUT_DEV¶
Labels: bug
, fixed / solved / done
jsmeix opened issue at 2025-06-26 12:48:¶
ReaR version¶
master
Describe the ReaR bug in detail¶
See
https://github.com/rear/rear/issues/3474#issuecomment-2912653292
which reads (excerpt):
From what I see in the code in
https://github.com/rear/rear/blob/master/usr/share/rear/layout/prepare/GNU/Linux/160_include_luks_code.sh#L91
it seems there is support for some predefined password value.
As far as I understand the code that predefined password value
can be specified in the disklayout.conf file
in the crypt entries, see
https://github.com/rear/rear/blob/master/doc/user-guide/06-layout-configuration.adoc#luks-devices
LUKS Devices
crypt /dev/mapper/<name> ... [password=<password>] ...
The current code at
https://github.com/rear/rear/blob/master/usr/share/rear/layout/prepare/GNU/Linux/160_include_luks_code.sh#L91
is (excerpts)
for option in $options ; do
# $option is of the form keyword=value and
# we assume keyword has no '=' character but value could be anything that may have a '=' character
# so we split keyword=value at the leftmost '=' character so that
# e.g. keyword=foo=bar gets split into key="keyword" and value="foo=bar":
key=${option%%=*}
value=${option#*=}
# The "cryptseup luksFormat" command does not require any of the type, cipher, key-size, hash, uuid option values
# because if omitted a cryptseup default value is used so we treat those values as optional.
# Using plain test to ensure the value is a single non empty and non blank word
# without quoting because test " " would return zero exit code
# cf. "Beware of the emptiness" in https://github.com/rear/rear/wiki/Coding-Style
case "$key" in
(type)
test $value && cryptsetup_options+=" --type $value"
;;
(cipher)
test $value && cryptsetup_options+=" --cipher $value"
;;
(key_size)
test $value && cryptsetup_options+=" --key-size $value"
;;
(hash)
test $value && cryptsetup_options+=" --hash $value"
;;
(uuid)
test $value && cryptsetup_options+=" --uuid $value"
;;
(keyfile)
test $value && keyfile=$value
;;
(password)
test $value && password=$value
;;
(*)
LogPrintError "Skipping unsupported LUKS cryptsetup option '$key' in 'crypt $target_device $source_device' entry in $LAYOUT_FILE"
;;
esac
done
...
elif [ -n "$password" ] ; then
echo "echo \"$password\" | cryptsetup luksFormat --batch-mode $cryptsetup_options $source_device"
echo "echo \"$password\" | cryptsetup luksOpen $source_device $mapping_name"
...
for option in $options ; do
# $option is of the form keyword=value and
# we assume keyword has no '=' character but value could be anything that may have a '=' character
# so we split keyword=value at the leftmost '=' character so that
# e.g. keyword=foo=bar gets split into key="keyword" and value="foo=bar":
key=${option%%=*}
value=${option#*=}
case "$key" in
(keyfile)
test $value && keyfile=$value
;;
(password)
test $value && password=$value
;;
esac
done
...
elif [ -n "$password" ] ; then
echo "echo \"$password\" | cryptsetup luksOpen $source_device $mapping_name"
It should be something like
{ for option in $options ; do
# $option is of the form keyword=value and
# we assume keyword has no '=' character but value could be anything that may have a '=' character
# so we split keyword=value at the leftmost '=' character so that
# e.g. keyword=foo=bar gets split into key="keyword" and value="foo=bar":
key=${option%%=*}
value=${option#*=}
# The "cryptseup luksFormat" command does not require any of the type, cipher, key-size, hash, uuid option values
# because if omitted a cryptseup default value is used so we treat those values as optional.
# Using plain test to ensure the value is a single non empty and non blank word
# without quoting because test " " would return zero exit code
# cf. "Beware of the emptiness" in https://github.com/rear/rear/wiki/Coding-Style
case "$key" in
(type)
test $value && cryptsetup_options+=" --type $value"
;;
(cipher)
test $value && cryptsetup_options+=" --cipher $value"
;;
(key_size)
test $value && cryptsetup_options+=" --key-size $value"
;;
(hash)
test $value && cryptsetup_options+=" --hash $value"
;;
(uuid)
test $value && cryptsetup_options+=" --uuid $value"
;;
(keyfile)
test $value && keyfile=$value
;;
(password)
test $value && password=$value
;;
(*)
LogPrintError "Skipping unsupported LUKS cryptsetup option '$key' in 'crypt $target_device $source_device' entry in $LAYOUT_FILE"
;;
esac
done
} 2>>/dev/$SECRET_OUTPUT_DEV
...
elif { [ -n "$password" ] ; then
echo "echo \"$password\" | cryptsetup luksFormat --batch-mode $cryptsetup_options $source_device"
echo "echo \"$password\" | cryptsetup luksOpen $source_device $mapping_name"
} 2>>/dev/$SECRET_OUTPUT_DEV
...
{ for option in $options ; do
# $option is of the form keyword=value and
# we assume keyword has no '=' character but value could be anything that may have a '=' character
# so we split keyword=value at the leftmost '=' character so that
# e.g. keyword=foo=bar gets split into key="keyword" and value="foo=bar":
key=${option%%=*}
value=${option#*=}
case "$key" in
(keyfile)
test $value && keyfile=$value
;;
(password)
test $value && password=$value
;;
esac
done
} 2>>/dev/$SECRET_OUTPUT_DEV
...
elif { [ -n "$password" ] ; then
echo "echo \"$password\" | cryptsetup luksOpen $source_device $mapping_name"
} 2>>/dev/$SECRET_OUTPUT_DEV
jsmeix commented at 2025-06-26 13:25:¶
https://github.com/rear/rear/pull/3430
should be merged first to avoid possible conflicts in
/usr/share/rear/layout/prepare/GNU/Linux/160_include_luks_code.sh
jsmeix commented at 2025-07-01 10:58:¶
Ugh!
This issue is bigger than expected.
I tested "rear -D recover" with this original test VM:
# parted -s /dev/vda unit MiB print
Model: Virtio Block Device (virtblk)
Disk /dev/vda: 15360MiB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags: pmbr_boot
Number Start End Size File system Name Flags
1 1.00MiB 9.00MiB 8.00MiB bios_grub
2 9.00MiB 13312MiB 13303MiB legacy_boot
3 13312MiB 15360MiB 2048MiB swap
# lsblk -ipo NAME,KNAME,TYPE,FSTYPE,SIZE,MOUNTPOINTS /dev/vda
NAME KNAME TYPE FSTYPE SIZE MOUNTPOINTS
/dev/vda /dev/vda disk 15G
|-/dev/vda1 /dev/vda1 part 8M
|-/dev/vda2 /dev/vda2 part crypto_LUKS 13G
| `-/dev/mapper/cr_root /dev/dm-0 crypt ext4 13G /
`-/dev/vda3 /dev/vda3 part crypto_LUKS 2G
`-/dev/mapper/cr_swap /dev/dm-1 crypt swap 2G [SWAP]
On a replacement VM with same disk type and size
I booted the ReaR recovery system and therein
I manually added 'password=johannes' in disklayout.conf like
crypt /dev/mapper/cr_root /dev/vda2 type=luks1 ... password=johannes
crypt /dev/mapper/cr_swap /dev/vda3 type=luks1 ... password=johannes
Additionally I needed in etc/rear/local.conf
LUKS_CRYPTSETUP_OPTIONS+=" --force-password"
because of the weak LUKS password 'johannes'
that was used only for this test here.
Then I did "rear -D recover"
where the LUKS setup worked without manual interaction:
RESCUE localhost:~ # rear -D recover
...
Start system layout restoration.
Disk '/dev/vda': creating 'gpt' partition table
Disk '/dev/vda': creating partition number 1 with name ''vda1''
Disk '/dev/vda': creating partition number 2 with name ''vda2''
Disk '/dev/vda': creating partition number 3 with name ''vda3''
Creating LUKS volume cr_root on /dev/vda2
Creating filesystem of type ext4 with mount point / on /dev/mapper/cr_root.
Mounting filesystem /
Creating LUKS volume cr_swap on /dev/vda3
Creating swap on /dev/mapper/cr_swap
Disk layout created.
...
BUT:
The LUKS password 'johannes' appears at more places than expected
in the "rear -D recover" debugscript mode log file:
RESCUE localhost:~ # egrep '+ source |johannes' /var/log/rear/rear-localhost.log | grep -B1 johannes
+ builtin source /usr/share/rear/layout/prepare/default/510_list_dependencies.sh
+++ echo '/dev/mapper/cr_root /dev/vda2 type=luks1 cipher=aes-xts-plain64 key_size=512 hash=sha256 uuid=b37bfd65-b8cd-42cb-b2a7-b3ae772bc2c5 password=johannes'
+++ echo '/dev/mapper/cr_root /dev/vda2 type=luks1 cipher=aes-xts-plain64 key_size=512 hash=sha256 uuid=b37bfd65-b8cd-42cb-b2a7-b3ae772bc2c5 password=johannes'
+++ echo '/dev/mapper/cr_swap /dev/vda3 type=luks1 cipher=aes-xts-plain64 key_size=512 hash=sha256 uuid=84838219-dfbe-449b-95df-28f3e177de94 password=johannes'
+++ echo '/dev/mapper/cr_swap /dev/vda3 type=luks1 cipher=aes-xts-plain64 key_size=512 hash=sha256 uuid=84838219-dfbe-449b-95df-28f3e177de94 password=johannes'
--
+ builtin source /usr/share/rear/layout/prepare/default/540_generate_device_code.sh
++ value=johannes
++ test johannes
++ password=johannes
++ '[' -n johannes ']'
++ echo 'echo "johannes" | cryptsetup luksFormat --batch-mode --type luks1 --cipher aes-xts-plain64 --key-size 512 --hash sha256 --uuid b37bfd65-b8cd-42cb-b2a7-b3ae772bc2c5 --iter-time 2000 --use-random --force-password /dev/vda2'
++ echo 'echo "johannes" | cryptsetup luksOpen /dev/vda2 cr_root'
++ value=johannes
++ test johannes
++ password=johannes
++ '[' -n johannes ']'
++ echo 'echo "johannes" | cryptsetup luksFormat --batch-mode --type luks1 --cipher aes-xts-plain64 --key-size 512 --hash sha256 --uuid 84838219-dfbe-449b-95df-28f3e177de94 --iter-time 2000 --use-random --force-password /dev/vda3'
++ echo 'echo "johannes" | cryptsetup luksOpen /dev/vda3 cr_swap'
--
++ builtin source /var/lib/rear/layout/diskrestore.sh
+++ echo johannes
+++ echo johannes
+++ echo johannes
+++ echo johannes
So things need to be also fixed in the
generate_layout_dependencies() function in
usr/share/rear/lib/layout-functions.sh
and in
usr/share/rear/layout/prepare/GNU/Linux/160_include_luks_code.sh
where a proper fix in 160_include_luks_code.sh
will avoid that the password gets logged during
source /var/lib/rear/layout/diskrestore.sh
because diskrestore.sh does set -x
on its own
and it contains those code parts
# Create /dev/mapper/cr_root (crypt)
LogPrint "Creating LUKS volume cr_root on /dev/vda2"
echo "johannes" | cryptsetup luksFormat --batch-mode --type luks1 --cipher aes-xts-plain64 --key-size 512 --hash sha256 --uuid b37bfd65-b8cd-42cb-b2a7-b3ae772bc2c5 --iter-time
2000 --use-random --force-password /dev/vda2
echo "johannes" | cryptsetup luksOpen /dev/vda2 cr_root
...
# Create /dev/mapper/cr_swap (crypt)
LogPrint "Creating LUKS volume cr_swap on /dev/vda3"
echo "johannes" | cryptsetup luksFormat --batch-mode --type luks1 --cipher aes-xts-plain64 --key-size 512 --hash sha256 --uuid 84838219-dfbe-449b-95df-28f3e177de94 --iter-time 2000 --use-random --force-password /dev/vda3
echo "johannes" | cryptsetup luksOpen /dev/vda3 cr_swap
jsmeix commented at 2025-07-01 11:10:¶
Even more Ugh!
In the rebooted recreated system I find the LUKS password 'johannes' in
/var/log/rear/recover/layout/diskrestore.sh
/var/log/rear/recover/layout/disklayout.conf
/var/log/rear/recover/layout/disklayout.conf.20250701120719.recover.796.orig
/var/log/rear/recover/rear-localhost.log
At least /var/log/rear/recover/ has reasonably secure permissions
# ls -ld /var/log/rear/recover/
drwx------ 5 root root 4096 Jul 1 12:08 /var/log/rear/recover/
jsmeix commented at 2025-07-03 14:00:¶
I keep this issue open to continue with further fixes
to protect the LUKS password against leaking out
as far as possible with reasonable effort.
jsmeix commented at 2025-07-03 14:15:¶
The next fix is for
echo "$password" | cryptsetup ...
see
https://github.com/rear/rear/pull/3485#issuecomment-3027807100
which reads (excerpt)
My immediate idea is to use
-------------------------------------------------------------
COMMAND ... <<< "$password"
-------------------------------------------------------------
On simple commandline with COMMAND 'cat' that seems to work:
-------------------------------------------------------------
# password="my password"
# ( set -x ; echo "$password" | cat -n )
+ echo 'my password'
+ cat -n
1 my password
# ( set -x ; cat -n <<< "$password" )
+ cat -n
1 my password
-------------------------------------------------------------
In this test cat -n intentionally shows the password value
to prove that COMMAND got the password value as input.
The point of this test is to avoid that the COMMAND input
gets shown in any case with 'set -x'.
This works as expected with adapted code in
layout/prepare/GNU/Linux/160_include_luks_code.sh
elif { test "$password" ; } 2>>/dev/$SECRET_OUTPUT_DEV ; then
{ echo "{ cryptsetup luksFormat --batch-mode $cryptsetup_options $source_device <<< \"$password\" ; } 2>/dev/null"
echo "{ cryptsetup luksOpen $source_device $mapping_name <<< \"$password\" ; } 2>/dev/null"
} 2>>/dev/$SECRET_OUTPUT_DEV
...
elif { test "$password" ; } 2>>/dev/$SECRET_OUTPUT_DEV ; then
{ echo "{ cryptsetup luksOpen $source_device $mapping_name <<< \"$password\" ; } 2>/dev/null"
} 2>>/dev/$SECRET_OUTPUT_DEV
which avoids that the cryptsetup password input
gets shown by 'set -x' in the ReaR log file
because
RESCUE localhost:~ # rear -D recover
...
Start system layout restoration.
Disk '/dev/vda': creating 'gpt' partition table
Disk '/dev/vda': creating partition number 1 with name ''vda1''
Disk '/dev/vda': creating partition number 2 with name ''vda2''
Disk '/dev/vda': creating partition number 3 with name ''vda3''
Creating LUKS volume cr_root on /dev/vda2
Creating filesystem of type ext4 with mount point / on /dev/mapper/cr_root.
Mounting filesystem /
Creating LUKS volume cr_swap on /dev/vda3
Creating swap on /dev/mapper/cr_swap
Disk layout created.
...
RESCUE localhost:~ # grep johannes /var/log/rear/rear-localhost.log
[no output]
BUT
RESCUE localhost:~ # grep johannes /root/rear.github.master/var/lib/rear/layout/diskrestore.sh
{ cryptsetup luksFormat ... /dev/vda2 <<< "johannes" ; } 2>/dev/null
{ cryptsetup luksOpen /dev/vda2 cr_root <<< "johannes" ; } 2>/dev/null
{ cryptsetup luksFormat ... /dev/vda3 <<< "johannes" ; } 2>/dev/null
{ cryptsetup luksOpen /dev/vda3 cr_swap <<< "johannes" ; } 2>/dev/null
so the cryptsetup password input is still there
in the diskrestore.sh script.
So
cryptsetup ... <<< "$password"
is a step forward into the right direction
but likely not yet what is possible with reasonable effort.
My next idea how to improve things with reasonable effort
is using a temporary file which contains the password value
to avoid that a password variable needs to be evaluated like
# echo "my password" >password_file
# ( set -x ; cat -n <password_file )
+ cat -n
1 my password
jsmeix commented at 2025-07-03 16:05:¶
https://github.com/rear/rear/pull/3489
implements
https://github.com/rear/rear/issues/3483#issuecomment-3032444609
and - by the way - that makes the code
simpler and more straightforward because those awkward
{ ... ; } 2>>/dev/$SECRET_OUTPUT_DEV
"code wrappers"
are less often needed - i.e. as soon as the secret vaule
is stored in a file which is sufficiently securely located
further things get simpler and more straightforward.
jsmeix commented at 2025-07-03 16:21:¶
The next thing I will think about is
to remove the LUKS password value from disklayout.conf
after it was read from there and stored in a file
during "rear recover", for example via something like
sed -e '/^crypt/s/password=.*/password=XXX/'
which replaces password=johannes
by password=XXX
so it is still visible that some LUKS password was set
during "rear recover" but its secret value got removed.
jsmeix commented at 2025-07-07 14:40:¶
With the changes in
https://github.com/rear/rear/pull/3490
all works well for me.
jsmeix commented at 2025-07-11 13:19:¶
With
https://github.com/rear/rear/pull/3490
merged this issue should be sufficiently fixed.
[Export of Github issue for rear/rear.]