Blob Blame History Raw
diff -rupN cryptsetup-2.3.3.old/man/cryptsetup.8 cryptsetup-2.3.3/man/cryptsetup.8
--- cryptsetup-2.3.3.old/man/cryptsetup.8	2022-01-13 17:19:58.082434394 +0100
+++ cryptsetup-2.3.3/man/cryptsetup.8	2022-01-13 17:20:19.860557992 +0100
@@ -803,6 +803,13 @@ are fixable. This command will only chan
 any key-slot data. You may enforce LUKS version by adding \-\-type
 option.
 
+It also repairs (upgrades) LUKS2 reencryption metadata by adding
+metadata digest that protects it against malicious changes.
+
+If LUKS2 reencryption was interrupted in the middle of writting
+reencryption segment the repair command can be used to perform
+reencryption recovery so that reencryption can continue later.
+
 \fBWARNING:\fR Always create a binary backup of the original
 header before calling this command.
 .PP
diff -rupN cryptsetup-2.3.3.old/src/cryptsetup.c cryptsetup-2.3.3/src/cryptsetup.c
--- cryptsetup-2.3.3.old/src/cryptsetup.c	2022-01-13 17:19:58.064434292 +0100
+++ cryptsetup-2.3.3/src/cryptsetup.c	2022-01-13 17:21:29.108950976 +0100
@@ -1072,17 +1072,59 @@ static int set_keyslot_params(struct cry
 	return crypt_set_pbkdf_type(cd, &pbkdf);
 }
 
-static int _do_luks2_reencrypt_recovery(struct crypt_device *cd)
+static int reencrypt_metadata_repair(struct crypt_device *cd)
+{
+	char *password;
+	size_t passwordLen;
+	int r;
+	struct crypt_params_reencrypt params = {
+		.flags = CRYPT_REENCRYPT_REPAIR_NEEDED
+	};
+
+	if (!opt_batch_mode &&
+	    !yesDialog(_("Unprotected LUKS2 reencryption metadata detected. "
+			 "Please verify the reencryption operation is desirable (see luksDump output)\n"
+			 "and continue (upgrade metadata) only if you acknowledge the operation as genuine."),
+		       _("Operation aborted.\n")))
+		return -EINVAL;
+
+	r = tools_get_key(_("Enter passphrase to protect and uppgrade reencryption metadata: "),
+			  &password, &passwordLen, opt_keyfile_offset,
+			  opt_keyfile_size, opt_key_file, opt_timeout,
+			  _verify_passphrase(0), 0, cd);
+	if (r < 0)
+		return r;
+
+	r = crypt_reencrypt_init_by_passphrase(cd, NULL, password, passwordLen,
+			opt_key_slot, opt_key_slot, NULL, NULL, &params);
+	tools_passphrase_msg(r);
+	if (r < 0)
+		goto out;
+
+	r = crypt_activate_by_passphrase(cd, NULL, opt_key_slot,
+					 password, passwordLen, 0);
+	tools_passphrase_msg(r);
+	if (r >= 0)
+		r = 0;
+
+out:
+	crypt_safe_free(password);
+	return r;
+}
+
+static int luks2_reencrypt_repair(struct crypt_device *cd)
 {
 	int r;
 	size_t passwordLen;
 	const char *msg;
 	char *password = NULL;
-	struct crypt_params_reencrypt recovery_params = {
-		.flags = CRYPT_REENCRYPT_RECOVERY
-	};
+	struct crypt_params_reencrypt params = {};
+
+	crypt_reencrypt_info ri = crypt_reencrypt_status(cd, &params);
+
+	if (params.flags & CRYPT_REENCRYPT_REPAIR_NEEDED)
+		return reencrypt_metadata_repair(cd);
 
-	crypt_reencrypt_info ri = crypt_reencrypt_status(cd, NULL);
 	switch (ri) {
 	case CRYPT_REENCRYPT_NONE:
 		return 0;
@@ -1120,7 +1162,8 @@ static int _do_luks2_reencrypt_recovery(
 	}
 
 	r = crypt_reencrypt_init_by_passphrase(cd, NULL, password, passwordLen,
-			opt_key_slot, opt_key_slot, NULL, NULL, &recovery_params);
+			opt_key_slot, opt_key_slot, NULL, NULL,
+			&(struct crypt_params_reencrypt){ .flags = CRYPT_REENCRYPT_RECOVERY });
 	if (r > 0)
 		r = 0;
 out:
@@ -1155,8 +1198,9 @@ static int action_luksRepair(void)
 	if (r == 0)
 		r = crypt_repair(cd, luksType(opt_type), NULL);
 skip_repair:
+	/* Header is ok, check if reencryption metadata needs repair/recovery. */
 	if (!r && crypt_get_type(cd) && !strcmp(crypt_get_type(cd), CRYPT_LUKS2))
-		r = _do_luks2_reencrypt_recovery(cd);
+		r = luks2_reencrypt_repair(cd);
 out:
 	crypt_free(cd);
 	return r;
diff -rupN cryptsetup-2.3.3.old/tests/luks2-reencryption-mangle-test cryptsetup-2.3.3/tests/luks2-reencryption-mangle-test
--- cryptsetup-2.3.3.old/tests/luks2-reencryption-mangle-test	2022-01-13 17:19:58.073434343 +0100
+++ cryptsetup-2.3.3/tests/luks2-reencryption-mangle-test	2022-01-13 17:20:19.861557997 +0100
@@ -172,6 +172,42 @@ EOF
 [ $? -eq 0 ] || fail "Expect script failed."
 }
 
+function img_run_reenc_fail()
+{
+local EXPECT_TIMEOUT=5
+[ -n "$VALG" ] && EXPECT_TIMEOUT=60
+# For now, we cannot run reencryption in batch mode for non-block device. Just fake the terminal here.
+expect_run - >/dev/null <<EOF
+proc abort {} { send_error "Timeout. "; exit 42 }
+set timeout $EXPECT_TIMEOUT
+eval spawn $CRYPTSETUP_RAW reencrypt $IMG $CS_PWPARAMS --disable-locks
+expect timeout abort "Are you sure? (Type 'yes' in capital letters):"
+send "YES\n"
+expect timeout abort eof
+catch wait result
+exit [lindex \$result 3]
+EOF
+local ret=$?
+[ $ret -eq 0 ] &&  fail "Reencryption passed (should have failed)."
+[ $ret -eq 42 ] && fail "Expect script failed."
+img_hash_unchanged
+}
+
+function img_check_fail_repair_ok()
+{
+	if [ $(id -u) == 0 ]; then
+		$CRYPTSETUP open $CS_PWPARAMS $IMG $DEV_NAME 2>/dev/null && fail
+	fi
+
+	img_run_reenc_fail
+
+	# repair metadata
+	$CRYPTSETUP repair $IMG $CS_PARAMS || fail
+
+	img_check_ok
+	img_run_reenc_ok
+}
+
 function valgrind_setup()
 {
 	bin_check valgrind
@@ -212,10 +248,10 @@ img_check_ok
 img_run_reenc_ok
 img_check_ok
 
-# Simulate old reencryption with no digest
+# Simulate old reencryption with no digest (repairable)
 img_prepare
 img_update_json 'del(.digests."2") | .config.requirements.mandatory = ["online-reencrypt"]'
-img_check_fail
+img_check_fail_repair_ok
 
 # This must fail for new releases
 echo "[2] Old reencryption in-progress (journal)"
@@ -236,7 +272,7 @@ img_update_json '
 	.digests."0".segments = ["1","2"] |
 	.digests."1".segments = ["0","3"] |
 	.config.requirements.mandatory = ["online-reencrypt"]'
-img_check_fail
+img_check_fail_repair_ok
 
 echo "[3] Old reencryption in-progress (checksum)"
 img_prepare
@@ -258,7 +294,7 @@ img_update_json '
 	.digests."0".segments = ["1","2"] |
 	.digests."1".segments = ["0","3"] |
 	.config.requirements.mandatory = ["online-reencrypt"]'
-img_check_fail
+img_check_fail_repair_ok
 
 # Note: older tools cannot create this from commandline
 echo "[4] Old decryption in-progress (journal)"
@@ -289,7 +325,7 @@ img_update_json '
 	} |
 	.digests."0".segments = ["1","2"] |
 	.config.requirements.mandatory = ["online-reencrypt"]'
-img_check_fail
+img_check_fail_repair_ok
 
 echo "[5] Old decryption in-progress (checksum)"
 img_prepare
@@ -321,7 +357,7 @@ img_update_json '
 	} |
 	.digests."0".segments = ["1","2"] |
 	.config.requirements.mandatory = ["online-reencrypt"]'
-img_check_fail
+img_check_fail_repair_ok
 
 # Note - offset is set to work with the old version (with a datashift bug)
 echo "[6] Old reencryption in-progress (datashift)"
@@ -344,7 +380,7 @@ img_update_json '
 	.digests."0".segments = ["0","2"] |
 	.digests."1".segments = ["1","3"] |
 	.config.requirements.mandatory = ["online-reencrypt"]'
-img_check_fail
+img_check_fail_repair_ok
 
 #
 # NEW metadata (with reenc digest)
@@ -360,7 +396,7 @@ img_check_ok
 # Repair must validate not only metadata, but also reencryption digest.
 img_prepare
 img_update_json 'del(.digests."2")'
-img_check_fail
+img_check_fail_repair_ok
 
 img_prepare '--reduce-device-size 2M'
 img_update_json '.keyslots."2".area.shift_size = ((.keyslots."2".area.shift_size|tonumber / 2)|tostring)'