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, ¶ms); + 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, ¶ms); + + 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 </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)'