Control: reassign -1 libblockdev-crypto2 Control: found -1 2.20-7 Control: fixed -1 2.22-1 Control: affects -1 gnome-disk-utility Control: affects -1 udisks2 Control: tag -1 + patch
Hi, it turns out this is caused by a bug in libblockdev, which is fixed in sid already (although it seems like upstream applied the fix for unrelated reasons and it's not clear whether they realized this bug was a possibility). The attached patch fixes it for me: - current sid (libblockdev 2.22-1): unreproducible - current sid with Buster's libblockdev 2.20-7: reproduced the bug - current sid with Buster's libblockdev + the attached patch: unreproducible I think we should get this into Buster (10.1, if not via stable-updates). As explained in the changelog entry, this fixes the data loss problem but it might still leave udisks2's LUKS2 passphrase changing broken in some cases, although broken in a way that leaves the old passphrase working, which is better than making the device totally unusable. Guilhem (Cc'ed) and I are investigating this possible problems and potential solutions. Cheers, -- intrigeri
diff --git a/debian/changelog b/debian/changelog index c9bfefa..a5a8c2c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,29 @@ +libblockdev (2.20-7.1) unstable; urgency=medium + + * Cherry-pick upstream fix: use existing cryptsetup API for atomically + changing a keyslot passphrase, instead of deleting the old keyslot + before adding the new one. This avoids data loss when attempting to + change the passphrase of a LUKS2 device via udisks2, e.g. from GNOME + Disks (Closes: #928893). + + Deleting a keyslot and then adding one is risky: if anything goes + wrong before the new keyslot is successfully added, no usable keyslot + is left and the device cannot be unlocked anymore. There's little + chances this causes actual problems with LUKS1, but LUKS2 defaults to + the memory-hard Argon2 key derivation algorithm, which is implemented + in cryptsetup with the assumption that it runs as root with no MEMLOCK + ulimit; this assumption is wrong when run by udisks2.service under + LimitMEMLOCK=65536, which breaks adding the new keyslot, and makes us + hit the problematic situation (user data loss) every time. + + With this change, changing a LUKS2 passphrase via udisks2 will still + fail in some cases, until the MEMLOCK ulimit problem is solved in + cryptsetup or workaround'ed in udisks2, which Guilhem Moulin and I are + working on. But at least, if it fails, it will fail _atomically_ and + the original passphrase will still work. + + -- intrigeri <intrig...@debian.org> Sat, 20 Jul 2019 01:01:29 +0000 + libblockdev (2.20-7) unstable; urgency=medium * Cherry-pick Use-512bit-keys-in-LUKS-by-default.patch: diff --git a/debian/patches/series b/debian/patches/series index 94d3e03..ddf2825 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1 +1,2 @@ Use-512bit-keys-in-LUKS-by-default.patch +use-existing-cryptsetup-api-for-changing.patch diff --git a/debian/patches/use-existing-cryptsetup-api-for-changing.patch b/debian/patches/use-existing-cryptsetup-api-for-changing.patch new file mode 100644 index 0000000..45b4fd4 --- /dev/null +++ b/debian/patches/use-existing-cryptsetup-api-for-changing.patch @@ -0,0 +1,84 @@ +From: Vojtech Trefny <vtre...@redhat.com> +Date: Tue, 12 Mar 2019 09:28:05 +0100 +X-Dgit-Generated: 2.20-7.1 8590f51cdbabf22c2b9250c108cf11446e6e91c4 +Subject: Use existing cryptsetup API for changing keyslot passphrase + +Instead of manually removing the keyslot and adding new a one. +Our old code also doesn't work in FIPS mode. + +--- + +--- libblockdev-2.20.orig/src/plugins/crypto.c ++++ libblockdev-2.20/src/plugins/crypto.c +@@ -1359,8 +1359,6 @@ gboolean bd_crypto_luks_remove_key (cons + gboolean bd_crypto_luks_change_key_blob (const gchar *device, const guint8 *pass_data, gsize data_len, const guint8 *npass_data, gsize ndata_len, GError **error) { + struct crypt_device *cd = NULL; + gint ret = 0; +- gchar *volume_key = NULL; +- gsize vk_size = 0; + guint64 progress_id = 0; + gchar *msg = NULL; + +@@ -1385,41 +1383,21 @@ gboolean bd_crypto_luks_change_key_blob + return FALSE; + } + +- vk_size = crypt_get_volume_key_size(cd); +- volume_key = (gchar *) g_malloc (vk_size); +- +- ret = crypt_volume_key_get (cd, CRYPT_ANY_SLOT, volume_key, &vk_size, (char*) pass_data, data_len); +- if (ret < 0) { +- g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE, +- "Failed to load device's volume key: %s", strerror_l(-ret, c_locale)); +- crypt_free (cd); +- g_free (volume_key); +- bd_utils_report_finished (progress_id, (*error)->message); +- return FALSE; +- } +- +- /* ret is the number of the slot with the given pass */ +- ret = crypt_keyslot_destroy (cd, ret); +- if (ret != 0) { +- g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_REMOVE_KEY, +- "Failed to remove the old passphrase: %s", strerror_l(-ret, c_locale)); +- crypt_free (cd); +- g_free (volume_key); +- bd_utils_report_finished (progress_id, (*error)->message); +- return FALSE; +- } +- +- ret = crypt_keyslot_add_by_volume_key (cd, ret, volume_key, vk_size, (char*) npass_data, ndata_len); ++ ret = crypt_keyslot_change_by_passphrase (cd, CRYPT_ANY_SLOT, CRYPT_ANY_SLOT, ++ (char*) pass_data, data_len, ++ (char*) npass_data, ndata_len); + if (ret < 0) { +- g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_ADD_KEY, +- "Failed to add the new passphrase: %s", strerror_l(-ret, c_locale)); ++ if (ret == -EPERM) ++ g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE, ++ "Failed to change the passphrase: No keyslot with given passphrase found."); ++ else ++ g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_ADD_KEY, ++ "Failed to change the passphrase: %s", strerror_l (-ret, c_locale)); + crypt_free (cd); +- g_free (volume_key); + bd_utils_report_finished (progress_id, (*error)->message); + return FALSE; + } + +- g_free (volume_key); + crypt_free (cd); + bd_utils_report_finished (progress_id, "Completed"); + return TRUE; +--- libblockdev-2.20.orig/tests/crypto_test.py ++++ libblockdev-2.20/tests/crypto_test.py +@@ -401,6 +401,9 @@ class CryptoTestChangeKey(CryptoTestCase + succ = create_fn(self.loop_dev, PASSWD, None) + self.assertTrue(succ) + ++ with six.assertRaisesRegex(self, GLib.GError, r"No keyslot with given passphrase found."): ++ BlockDev.crypto_luks_change_key(self.loop_dev, "wrong-passphrase", PASSWD2) ++ + succ = BlockDev.crypto_luks_change_key(self.loop_dev, PASSWD, PASSWD2) + self.assertTrue(succ) + diff --git a/src/plugins/crypto.c b/src/plugins/crypto.c index 6b5be9d..205499f 100644 --- a/src/plugins/crypto.c +++ b/src/plugins/crypto.c @@ -1359,8 +1359,6 @@ gboolean bd_crypto_luks_remove_key (const gchar *device, const gchar *pass, cons gboolean bd_crypto_luks_change_key_blob (const gchar *device, const guint8 *pass_data, gsize data_len, const guint8 *npass_data, gsize ndata_len, GError **error) { struct crypt_device *cd = NULL; gint ret = 0; - gchar *volume_key = NULL; - gsize vk_size = 0; guint64 progress_id = 0; gchar *msg = NULL; @@ -1385,41 +1383,21 @@ gboolean bd_crypto_luks_change_key_blob (const gchar *device, const guint8 *pass return FALSE; } - vk_size = crypt_get_volume_key_size(cd); - volume_key = (gchar *) g_malloc (vk_size); - - ret = crypt_volume_key_get (cd, CRYPT_ANY_SLOT, volume_key, &vk_size, (char*) pass_data, data_len); - if (ret < 0) { - g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE, - "Failed to load device's volume key: %s", strerror_l(-ret, c_locale)); - crypt_free (cd); - g_free (volume_key); - bd_utils_report_finished (progress_id, (*error)->message); - return FALSE; - } - - /* ret is the number of the slot with the given pass */ - ret = crypt_keyslot_destroy (cd, ret); - if (ret != 0) { - g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_REMOVE_KEY, - "Failed to remove the old passphrase: %s", strerror_l(-ret, c_locale)); - crypt_free (cd); - g_free (volume_key); - bd_utils_report_finished (progress_id, (*error)->message); - return FALSE; - } - - ret = crypt_keyslot_add_by_volume_key (cd, ret, volume_key, vk_size, (char*) npass_data, ndata_len); + ret = crypt_keyslot_change_by_passphrase (cd, CRYPT_ANY_SLOT, CRYPT_ANY_SLOT, + (char*) pass_data, data_len, + (char*) npass_data, ndata_len); if (ret < 0) { - g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_ADD_KEY, - "Failed to add the new passphrase: %s", strerror_l(-ret, c_locale)); + if (ret == -EPERM) + g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE, + "Failed to change the passphrase: No keyslot with given passphrase found."); + else + g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_ADD_KEY, + "Failed to change the passphrase: %s", strerror_l (-ret, c_locale)); crypt_free (cd); - g_free (volume_key); bd_utils_report_finished (progress_id, (*error)->message); return FALSE; } - g_free (volume_key); crypt_free (cd); bd_utils_report_finished (progress_id, "Completed"); return TRUE; diff --git a/tests/crypto_test.py b/tests/crypto_test.py index c048570..747b702 100644 --- a/tests/crypto_test.py +++ b/tests/crypto_test.py @@ -401,6 +401,9 @@ class CryptoTestChangeKey(CryptoTestCase): succ = create_fn(self.loop_dev, PASSWD, None) self.assertTrue(succ) + with six.assertRaisesRegex(self, GLib.GError, r"No keyslot with given passphrase found."): + BlockDev.crypto_luks_change_key(self.loop_dev, "wrong-passphrase", PASSWD2) + succ = BlockDev.crypto_luks_change_key(self.loop_dev, PASSWD, PASSWD2) self.assertTrue(succ)