diff --git a/man/cryptsetup.8 b/man/cryptsetup.8 index 961477ff..96b75761 100644 --- a/man/cryptsetup.8 +++ b/man/cryptsetup.8 @@ -834,6 +834,13 @@ are fixable. This command will only change the LUKS header, not 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 --git a/src/cryptsetup.c b/src/cryptsetup.c index f7fc1346..50af3edf 100644 --- a/src/cryptsetup.c +++ b/src/cryptsetup.c @@ -1024,17 +1024,59 @@ static int action_benchmark(void) return r; } -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 (!ARG_SET(OPT_BATCH_MODE_ID) && + !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, ARG_UINT64(OPT_KEYFILE_OFFSET_ID), + ARG_UINT32(OPT_KEYFILE_SIZE_ID), ARG_STR(OPT_KEY_FILE_ID), ARG_UINT32(OPT_TIMEOUT_ID), + verify_passphrase(0), 0, cd); + if (r < 0) + return r; + + r = crypt_reencrypt_init_by_passphrase(cd, NULL, password, passwordLen, + ARG_INT32(OPT_KEY_SLOT_ID), ARG_INT32(OPT_KEY_SLOT_ID), NULL, NULL, ¶ms); + tools_passphrase_msg(r); + if (r < 0) + goto out; + + r = crypt_activate_by_passphrase(cd, NULL, ARG_INT32(OPT_KEY_SLOT_ID), + 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; @@ -1072,7 +1114,8 @@ static int _do_luks2_reencrypt_recovery(struct crypt_device *cd) } r = crypt_reencrypt_init_by_passphrase(cd, NULL, password, passwordLen, - ARG_INT32(OPT_KEY_SLOT_ID), ARG_INT32(OPT_KEY_SLOT_ID), NULL, NULL, &recovery_params); + ARG_INT32(OPT_KEY_SLOT_ID), ARG_INT32(OPT_KEY_SLOT_ID), NULL, NULL, + &(struct crypt_params_reencrypt){ .flags = CRYPT_REENCRYPT_RECOVERY }); if (r > 0) r = 0; out: @@ -1113,9 +1156,9 @@ static int action_luksRepair(void) else r = crypt_repair(cd, luksType(device_type), NULL); out: - /* Header is ok, check if possible interrupted reencryption need repairs. */ + /* Header is ok, check if reencryption metadata needs repair/recovery. */ if (!r && isLUKS2(crypt_get_type(cd))) - r = _do_luks2_reencrypt_recovery(cd); + r = luks2_reencrypt_repair(cd); crypt_free(cd); return r; diff --git a/tests/luks2-reencryption-mangle-test b/tests/luks2-reencryption-mangle-test index 369d7f6b..dc719df0 100755 --- a/tests/luks2-reencryption-mangle-test +++ b/tests/luks2-reencryption-mangle-test @@ -170,6 +170,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 @@ -210,10 +246,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)" @@ -234,7 +270,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 @@ -256,7 +292,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)" @@ -287,7 +323,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 @@ -319,7 +355,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)" @@ -342,7 +378,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) @@ -358,7 +394,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)'