diff mbox series

[v3.1,3/4] initramfs-crypt-hook: Allow speed-up of disk-reencryption

Message ID 20240718104756.1603687-1-stefan-koch@siemens.com (mailing list archive)
State New
Headers show
Series None | expand

Commit Message

Stefan Koch July 18, 2024, 10:47 a.m. UTC
As the reencrypt mechanism doesn't work at file system level
so it wouldn't detect used and free blocks.
This means that the block-wise reencryption process could
take a very long time depending on the partition size.

Using the format mechanism instead of the reencrypt one
would delete all existing data (without wiping). This would be very fast,
because it doesn't matter whether a block is used or free.

Set CRYPT_FAST_REENCRYPTION to "1" to speed-up the reencrypt process.
So, this would be done:
- Obtain used space of the unencrypted userdata partition
- Shrink the partition and resize it to the size of used space (minimum size)
- reencrypt the userdata partition now with smaller size
- Expand the encrypted userdata partition back to the maximum possible size

A temporary loop device is used as wrapper to simulate a shrinked device
because the used cryptsetup takes care of the device size and
the --reduce-device-size parameter is limited to 64 MiB.

However, this speed-up lacks the described security benefit of
implicit data overwrite. In general, there is no support for
explicit random overwrite within the initramfs-crypt-hook, that's
only implicit for the reencrypt case without speed-up.

Some disk encryption implementations like within the Debian installer
will overwrite the entire partition with random data for security reasons
(e.g. wiping old already deleted data, hiding metadata, etc.).

Keep in mind that a power loss while reencryption will cause data loss.
The key is only enrolled after fully succeeded reencryption, yet.
So, no recovery from already encrypted data would be possible.

Signed-off-by: Stefan Koch <stefan-koch@siemens.com>
---
 .../files/encrypt_partition.env.tmpl          |  1 +
 .../files/encrypt_partition.script            | 56 ++++++++++++++++---
 .../initramfs-crypt-hook_0.2.bb               |  8 +++
 3 files changed, 58 insertions(+), 7 deletions(-)
diff mbox series

Patch

diff --git a/recipes-initramfs/initramfs-crypt-hook/files/encrypt_partition.env.tmpl b/recipes-initramfs/initramfs-crypt-hook/files/encrypt_partition.env.tmpl
index bb93361..2b02f55 100644
--- a/recipes-initramfs/initramfs-crypt-hook/files/encrypt_partition.env.tmpl
+++ b/recipes-initramfs/initramfs-crypt-hook/files/encrypt_partition.env.tmpl
@@ -5,3 +5,4 @@  WATCHDOG_DEV="${INITRAMFS_WATCHDOG_DEVICE}"
 HASH_TYPE="${CRYPT_HASH_TYPE}"
 KEY_ALGORITHM="${CRYPT_KEY_ALGORITHM}"
 ENCRYPTION_IS_OPTIONAL="${CRYPT_ENCRYPTION_OPTIONAL}"
+FAST_REENCRYPTION="${CRYPT_FAST_REENCRYPTION}"
diff --git a/recipes-initramfs/initramfs-crypt-hook/files/encrypt_partition.script b/recipes-initramfs/initramfs-crypt-hook/files/encrypt_partition.script
index f943aea..f72889e 100644
--- a/recipes-initramfs/initramfs-crypt-hook/files/encrypt_partition.script
+++ b/recipes-initramfs/initramfs-crypt-hook/files/encrypt_partition.script
@@ -54,6 +54,11 @@  if [ -z "${create_file_system_cmd}" ]; then
 	create_file_system_cmd="mke2fs -t ext4"
 fi
 
+# Path to losetup binary
+# binary must support --sizelimit parameter
+# which is not supported by the busybox variant
+losetup_path="/usr/sbin/losetup"
+
 service_watchdog() {
 	for n in $(seq $(($SETUP_TIMEOUT / 10)) ); do
 		printf '\0'
@@ -62,13 +67,16 @@  service_watchdog() {
 }
 
 reencrypt_existing_partition() {
+	reencrypt_device="$1"
 	part_size_blocks="$(cat /sys/class/block/"$(awk -v dev="$1" 'BEGIN{split(dev,a,"/"); print a[3]}' )"/size)"
-	# reduce the filesystem and partition by 32M to fit the LUKS header
+	part_size_in_kb="$(expr "$part_size_blocks" / 2)" # blocksize 512 byte
+
 	partition_fstype=$(get_fstype "${1}")
+	# reduce the filesystem and partition by 32M to fit the LUKS header
 	reduce_device_size=32768
-	reduced_size="$(expr "$part_size_blocks" - 65536 )"
-	reduced_size_in_byte="$(expr "$reduced_size" \* 512)"
-	reduced_size_in_kb="$(expr "$reduced_size_in_byte" / 1024)K"
+	reduce_device_size_blocks="$(expr "$reduce_device_size" \* 2)" # 512 byte blocks
+	reduced_size="$(expr "$part_size_blocks" - "$reduce_device_size_blocks" )"
+	reduced_size_in_kb="$(expr "$reduced_size" / 2)" # blocksize 512 byte
 	case $partition_fstype in
 	ext*)
 		# reduce the filesystem and partition by 32M to fit the LUKS header
@@ -84,9 +92,32 @@  EOF
 		if ! cryptsetup luksUUID "$1" &> /dev/null; then
 			e2fsck -p -f "$1"
 		fi
-		if ! resize2fs "$1" "${reduced_size_in_kb}"; then
+		# shrink partition temporarily to minimum
+		min_size_fsblocks="$(resize2fs "$1" -P | awk -F ": " '{ print $2 }')"
+		if [ "$FAST_REENCRYPTION" = "1" ] && loop_device="$("$losetup_path" -f)" && [ -n "$min_size_fsblocks" ]; then
+			# set encrypted size for expanding step
+			encrypted_size_in_kb="$reduced_size_in_kb"
+			# minimum partition size
+			min_size_in_kb="$(expr "$min_size_fsblocks" \* 4)" # blocksize 4096 byte
+			# shrinked partition size (reduce_size + minimum partition size)
+			reduced_size_in_kb="$(expr "$reduce_device_size" + "$min_size_in_kb")"
+			# set loop device as reencrypt device
+			reencrypt_device="$loop_device"
+		else
+			# continue with default reencryption in failure case
+			FAST_REENCRYPTION="0"
+		fi
+
+		if ! resize2fs "$1" "${reduced_size_in_kb}K"; then
 			panic "reencryption of filesystem $1 cannot continue!"
 		fi
+
+		if [ "$FAST_REENCRYPTION" = "1" ]; then
+			# use a temporary loop device as wrapper to simulate a shrinked device
+			# because the used cryptsetup takes care of the device size and
+			# the --reduce-device-size parameter is limited to 64 MiB
+			"$losetup_path" --sizelimit "${reduced_size_in_kb}K" "$loop_device" "$1"
+		fi
 		;;
 	squashfs|swap|"")
 		[ "$debug" = "y" ] && echo "skip disk resize as it is not supported or unnecessary for fstype: '$partition_fstype'"
@@ -96,9 +127,14 @@  EOF
 		;;
 	esac
 	if [ -x /usr/sbin/cryptsetup-reencrypt ]; then
-		/usr/sbin/cryptsetup-reencrypt --new --reduce-device-size "$reduce_device_size"k "$1" < "$2"
+		/usr/sbin/cryptsetup-reencrypt --new --reduce-device-size "$reduce_device_size"k "$reencrypt_device" < "$2"
 	else
-		/usr/sbin/cryptsetup reencrypt --encrypt --reduce-device-size "$reduce_device_size"k "$1" < "$2"
+		/usr/sbin/cryptsetup reencrypt --encrypt --reduce-device-size "$reduce_device_size"k "$reencrypt_device" < "$2"
+	fi
+
+	if [ "$FAST_REENCRYPTION" = "1" ]; then
+		# remove temporarily loop device
+		"$losetup_path" -d "$loop_device"
 	fi
 }
 for candidate in /dev/tpm*; do
@@ -182,6 +218,12 @@  for partition_set in $partition_sets; do
 			reencrypt_existing_partition "$part_device" "$tmp_key"
 			enroll_tpm2_token "$part_device" "$tmp_key" "$tpm_device" "$tpm_key_algorithm" "$pcr_bank_hash_type"
 			open_tpm2_partition "$part_device" "$crypt_mount_name" "$tpm_device"
+			if [ "$FAST_REENCRYPTION" = "1" ]; then
+				# expand encrypted partition to maximum
+				/usr/sbin/cryptsetup resize "$decrypted_part"
+				# expand filesystem within encrypted layer to maximum
+				resize2fs "$decrypted_part" "${encrypted_size_in_kb}K"
+			fi
 			log_end_msg
 		;;
 		"format")
diff --git a/recipes-initramfs/initramfs-crypt-hook/initramfs-crypt-hook_0.2.bb b/recipes-initramfs/initramfs-crypt-hook/initramfs-crypt-hook_0.2.bb
index 3497d95..e3dd515 100644
--- a/recipes-initramfs/initramfs-crypt-hook/initramfs-crypt-hook_0.2.bb
+++ b/recipes-initramfs/initramfs-crypt-hook/initramfs-crypt-hook_0.2.bb
@@ -57,6 +57,13 @@  CRYPT_PARTITIONS ??= "home:/home:reencrypt var:/var:reencrypt"
 # CRYPT_CREATE_FILE_SYSTEM_CMD contains the shell command to create the filesystem
 # in a newly formatted LUKS Partition
 CRYPT_CREATE_FILE_SYSTEM_CMD ??= "/usr/sbin/mke2fs -t ext4"
+# Fast reencryption state
+# It uses temporary partition resize,
+# consider security and data reliablity aspects when enabling
+# (e.g. wiping old already deleted data, hiding metadata, etc.)
+# Keep in mind that a power loss while reencryption will cause data loss
+# (with or without fast reencryption).
+CRYPT_FAST_REENCRYPTION ??= "0"
 # Timeout for creating / re-encrypting partitions on first boot
 CRYPT_SETUP_TIMEOUT ??= "600"
 # Watchdog to service during the initial setup of the crypto partitions
@@ -68,6 +75,7 @@  CRYPT_ENCRYPTION_OPTIONAL ??= "false"
 
 TEMPLATE_VARS = "CRYPT_PARTITIONS CRYPT_CREATE_FILE_SYSTEM_CMD \
     CRYPT_SETUP_TIMEOUT INITRAMFS_WATCHDOG_DEVICE CRYPT_HASH_TYPE \
+    CRYPT_FAST_REENCRYPTION \
     CRYPT_KEY_ALGORITHM CRYPT_ENCRYPTION_OPTIONAL"
 TEMPLATE_FILES = "encrypt_partition.env.tmpl"