Laszlo Ersek
2022-Feb-23 16:19 UTC
[Libguestfs] [libguestfs-common PATCH 0/2] options: decrypt LUKS-on-LV devices
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1658126 Laszlo Ersek (2): options: extract & refactor decryption of mountables (partitions) options: decrypt LUKS-on-LV devices options/decrypt.c | 132 +++++++++++++++++++++++++++++----------------- 1 file changed, 83 insertions(+), 49 deletions(-) base-commit: 41126802097f0a864cab8679ae8672b45914d54b -- 2.19.1.3.g30247aa5d201
Laszlo Ersek
2022-Feb-23 16:19 UTC
[Libguestfs] [libguestfs-common PATCH 1/2] options: extract & refactor decryption of mountables (partitions)
The inspect_do_decrypt() function interates over the list of partitions. For each partition, it fetches the potentially matching keys, and tries each key in turn. If no key unlocks the partition, the program exits with an error message. If at least one partition is unlocked, then LVM is rescanned. Decouple "partition" from the above logic, replacing it with "mountable". Call the new function decrypt_mountables(). As a part of the extraction, clean up the following readability warts: - CLEANUP_FREE* and other variable declarations intermixed with code, - a "goto" statement that is not used on an error path, - needless conditions on a one-shot "is_bitlocker" helper variable, - nondescript array indices, - guestfs_int_count_strings() called for counting the length of the full string list, only to check if the string list is non-empty, - a comment about GUESTFS_CRYPTSETUP_OPEN_READONLY placed outside of GUESTFS_HAVE_CRYPTSETUP_OPEN. Regression-checked with: - libguestfs/tests/luks/test-key-option-inspect.sh - guestfs-tools/inspector/test-virt-inspector-luks.sh Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1658126 Signed-off-by: Laszlo Ersek <lersek at redhat.com> --- options/decrypt.c | 128 +++++++++++++++++++++++++++------------------- 1 file changed, 75 insertions(+), 53 deletions(-) diff --git a/options/decrypt.c b/options/decrypt.c index 434b7d58c2a7..9141cf5193ad 100644 --- a/options/decrypt.c +++ b/options/decrypt.c @@ -65,6 +65,78 @@ make_mapname (const char *device, char *mapname, size_t len) *mapname = '\0'; } +static bool +decrypt_mountables (guestfs_h *g, const char * const *mountables, + struct key_store *ks) +{ + bool decrypted_some = false; + const char * const *mnt_scan = mountables; + const char *mountable; + + while ((mountable = *mnt_scan++) != NULL) { + CLEANUP_FREE char *type = NULL; + CLEANUP_FREE char *uuid = NULL; + CLEANUP_FREE_STRING_LIST char **keys = NULL; + char mapname[32]; + const char * const *key_scan; + const char *key; + + type = guestfs_vfs_type (g, mountable); + if (type == NULL) + continue; + + /* "cryptsetup luksUUID" cannot read a UUID on Windows BitLocker disks + * (unclear if this is a limitation of the format or cryptsetup). + */ + if (STREQ (type, "crypto_LUKS")) { +#ifdef GUESTFS_HAVE_LUKS_UUID + uuid = guestfs_luks_uuid (g, mountable); +#endif + } else if (STRNEQ (type, "BitLocker")) + continue; + + /* Grab the keys that we should try with this device, based on device name, + * or UUID (if any). + */ + keys = get_keys (ks, mountable, uuid); + assert (keys[0] != NULL); + + /* Generate a node name for the plaintext (decrypted) device node. */ + make_mapname (mountable, mapname, sizeof mapname); + + /* Try each key in turn. */ + key_scan = (const char * const *)keys; + while ((key = *key_scan++) != NULL) { + int r; + + guestfs_push_error_handler (g, NULL, NULL); +#ifdef GUESTFS_HAVE_CRYPTSETUP_OPEN + /* XXX Should we set GUESTFS_CRYPTSETUP_OPEN_READONLY if readonly is + * set? This might break 'mount_ro'. + */ + r = guestfs_cryptsetup_open (g, mountable, key, mapname, -1); +#else + r = guestfs_luks_open (g, mountable, key, mapname); +#endif + guestfs_pop_error_handler (g); + + if (r == 0) + break; + } + + if (key == NULL) + error (EXIT_FAILURE, 0, + _("could not find key to open LUKS encrypted %s.\n\n" + "Try using --key on the command line.\n\n" + "Original error: %s (%d)"), + mountable, guestfs_last_error (g), guestfs_last_errno (g)); + + decrypted_some = true; + } + + return decrypted_some; +} + /** * Simple implementation of decryption: look for any encrypted * partitions and decrypt them, then rescan for VGs. @@ -73,62 +145,12 @@ void inspect_do_decrypt (guestfs_h *g, struct key_store *ks) { CLEANUP_FREE_STRING_LIST char **partitions = guestfs_list_partitions (g); + bool need_rescan; + if (partitions == NULL) exit (EXIT_FAILURE); - int need_rescan = 0, r; - size_t i, j; - - for (i = 0; partitions[i] != NULL; ++i) { - CLEANUP_FREE char *type = guestfs_vfs_type (g, partitions[i]); - if (type && - (STREQ (type, "crypto_LUKS") || STREQ (type, "BitLocker"))) { - bool is_bitlocker = STREQ (type, "BitLocker"); - char mapname[32]; - make_mapname (partitions[i], mapname, sizeof mapname); - -#ifdef GUESTFS_HAVE_LUKS_UUID - CLEANUP_FREE char *uuid = NULL; - - /* This fails for Windows BitLocker disks because cryptsetup - * luksUUID cannot read a UUID (unclear if this is a limitation - * of the format or cryptsetup). - */ - if (!is_bitlocker) - uuid = guestfs_luks_uuid (g, partitions[i]); -#else - const char *uuid = NULL; -#endif - - CLEANUP_FREE_STRING_LIST char **keys = get_keys (ks, partitions[i], uuid); - assert (guestfs_int_count_strings (keys) > 0); - - /* Try each key in turn. */ - for (j = 0; keys[j] != NULL; ++j) { - /* XXX Should we set GUESTFS_CRYPTSETUP_OPEN_READONLY if readonly - * is set? This might break 'mount_ro'. - */ - guestfs_push_error_handler (g, NULL, NULL); -#ifdef GUESTFS_HAVE_CRYPTSETUP_OPEN - r = guestfs_cryptsetup_open (g, partitions[i], keys[j], mapname, -1); -#else - r = guestfs_luks_open (g, partitions[i], keys[j], mapname); -#endif - guestfs_pop_error_handler (g); - if (r == 0) - goto opened; - } - error (EXIT_FAILURE, 0, - _("could not find key to open LUKS encrypted %s.\n\n" - "Try using --key on the command line.\n\n" - "Original error: %s (%d)"), - partitions[i], guestfs_last_error (g), - guestfs_last_errno (g)); - - opened: - need_rescan = 1; - } - } + need_rescan = decrypt_mountables (g, (const char * const *)partitions, ks); if (need_rescan) { if (guestfs_lvm_scan (g, 1) == -1) -- 2.19.1.3.g30247aa5d201
Laszlo Ersek
2022-Feb-23 16:19 UTC
[Libguestfs] [libguestfs-common PATCH 2/2] options: decrypt LUKS-on-LV devices
Using the previously extracted function decrypt_mountables(), look for LUKS devices on logical volumes (LVs) too. In the LVM-on-LUKS scheme, the names of the plaintext (decrypted) block devices don't matter, as these devices host Physical Volumes, and LVM enumerates PVs automatically -- there are no references to these decrypted block devices in "/etc/fstab", for example. For naming such decrypted devices, continue calling make_mapname(). In the LUKS-on-LVM scheme however, the decrypted devices are supposed to hold filesystems, and "/etc/fstab" may refer to them. Such decrypted devices are commonly called /dev/mapper/luks-<UUID>, where <UUID> is the UUID inside the LUKS header. Employ this naming when decrypting Logical Volumes. Reuse make_mapname() as a fallback in this case. Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1658126 Signed-off-by: Laszlo Ersek <lersek at redhat.com> Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1658126 Signed-off-by: Laszlo Ersek <lersek at redhat.com> --- options/decrypt.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/options/decrypt.c b/options/decrypt.c index 9141cf5193ad..f7c1d876b3ed 100644 --- a/options/decrypt.c +++ b/options/decrypt.c @@ -38,8 +38,11 @@ #include "options.h" /** - * Make a LUKS map name from the partition name, - * eg. C<"/dev/vda2" =E<gt> "cryptvda2"> + * Make a LUKS map name from the partition or logical volume name, eg. + * C<"/dev/vda2" =E<gt> "cryptvda2">, or C<"/dev/vg-ssd/lv-root7" =E<gt> + * "cryptvgssdlvroot7">. Note that, in logical volume device names, + * c_isalnum() eliminates the "/" separator from between the VG and the LV, so + * this mapping is not unique; but for our purposes, it will do. */ static void make_mapname (const char *device, char *mapname, size_t len) @@ -67,7 +70,7 @@ make_mapname (const char *device, char *mapname, size_t len) static bool decrypt_mountables (guestfs_h *g, const char * const *mountables, - struct key_store *ks) + struct key_store *ks, bool name_decrypted_by_uuid) { bool decrypted_some = false; const char * const *mnt_scan = mountables; @@ -77,7 +80,7 @@ decrypt_mountables (guestfs_h *g, const char * const *mountables, CLEANUP_FREE char *type = NULL; CLEANUP_FREE char *uuid = NULL; CLEANUP_FREE_STRING_LIST char **keys = NULL; - char mapname[32]; + char mapname[512]; const char * const *key_scan; const char *key; @@ -102,7 +105,9 @@ decrypt_mountables (guestfs_h *g, const char * const *mountables, assert (keys[0] != NULL); /* Generate a node name for the plaintext (decrypted) device node. */ - make_mapname (mountable, mapname, sizeof mapname); + if (!name_decrypted_by_uuid || uuid == NULL || + snprintf (mapname, sizeof mapname, "luks-%s", uuid) < 0) + make_mapname (mountable, mapname, sizeof mapname); /* Try each key in turn. */ key_scan = (const char * const *)keys; @@ -145,15 +150,22 @@ void inspect_do_decrypt (guestfs_h *g, struct key_store *ks) { CLEANUP_FREE_STRING_LIST char **partitions = guestfs_list_partitions (g); + CLEANUP_FREE_STRING_LIST char **lvs = NULL; bool need_rescan; if (partitions == NULL) exit (EXIT_FAILURE); - need_rescan = decrypt_mountables (g, (const char * const *)partitions, ks); + need_rescan = decrypt_mountables (g, (const char * const *)partitions, ks, + false); if (need_rescan) { if (guestfs_lvm_scan (g, 1) == -1) exit (EXIT_FAILURE); } + + lvs = guestfs_lvs (g); + if (lvs == NULL) + exit (EXIT_FAILURE); + decrypt_mountables (g, (const char * const *)lvs, ks, true); } -- 2.19.1.3.g30247aa5d201