Richard W.M. Jones
2020-Mar-30 15:03 UTC
[Libguestfs] [PATCH 0/7] Support Windows BitLocker (RHBZ#1808977).
These commits, along with the associated changes to common: https://www.redhat.com/archives/libguestfs/2020-March/msg00286.html support the transparent decryption and inspection of Windows guests encrypted with BitLocker encryption. To do the BitLocker decryption requires cryptsetup 2.3.0 (although cryptsetup 2.3 is not required for existing LUKS use). It also requires a new-ish Linux kernel, I believe 5.3+ This starts by adding new APIs cryptsetup-open and cryptsetup-close. As well as dropping the "luks-" prefix, this allows specifying an optional type parameter, allowing you to select BitLocker encryption. Although also the new API does not require this parameter, and will autodetect the correct type of decryption to apply in every known case. The main work is then in updating list-filesystems and ensuring that inspection still works. This really needs but also lacks tests. See commit message of patch 1/7 for why. I believe it's impossible for us to prove unit testing right now, but hopefully this situation can improve in future. Rich.
Richard W.M. Jones
2020-Mar-30 15:03 UTC
[Libguestfs] [PATCH 1/7] New APIs: cryptsetup-open and cryptsetup-close.
This commit deprecates luks-open/luks-open-ro/luks-close for the more generic sounding names cryptsetup-open/cryptsetup-close, which also correspond directly to the cryptsetup commands. The optional cryptsetup-open readonly flag is used to replace the functionality of luks-open-ro. The optional cryptsetup-open crypttype parameter can be used to select the type (corresponding to cryptsetup open --type), which allows us to open BitLocker-encrypted disks with no extra effort. As a convenience the crypttype parameter may be omitted, and libguestfs will use a heuristic (based on vfs-type output) to try to determine the correct type to use. The deprecated functions and the new functions are all (re-)written in OCaml. There is no new test here, unfortunately. It would be nice to test Windows BitLocker support in this new API, however the Linux tools do not support creating BitLocker disks, and while it is possible to create one under Windows, the smallest compressed disk I could create is 37M because of a mixture of the minimum support size for BitLocker disks and the fact that encrypted parts of NTFS cannot be compressed. --- .gitignore | 1 + daemon/Makefile.am | 3 + daemon/cryptsetup.ml | 83 +++++++++++++++++++++ daemon/luks.c | 83 --------------------- generator/actions_core.ml | 106 +++++++++++++++------------ generator/actions_core_deprecated.ml | 52 +++++++++++++ generator/daemon.ml | 5 +- generator/proc_nr.ml | 2 + gobject/Makefile.inc | 2 + lib/MAX_PROC_NR | 2 +- lib/guestfs.pod | 17 +++-- 11 files changed, 216 insertions(+), 140 deletions(-) diff --git a/.gitignore b/.gitignore index cdc90453c..12bfd45a2 100644 --- a/.gitignore +++ b/.gitignore @@ -147,6 +147,7 @@ Makefile.in /daemon/btrfs.mli /daemon/callbacks.ml /daemon/caml-stubs.c +/daemon/cryptsetup.mli /daemon/daemon_config.ml /daemon/daemon_utils_tests /daemon/devsparts.mli diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 25948dbe9..928916e3d 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -39,6 +39,7 @@ generator_built = \ blkid.mli \ btrfs.mli \ callbacks.ml \ + cryptsetup.mli \ devsparts.mli \ file.mli \ filearch.mli \ @@ -269,6 +270,7 @@ SOURCES_MLI = \ btrfs.mli \ callbacks.mli \ chroot.mli \ + cryptsetup.mli \ daemon.mli \ daemon_config.mli \ devsparts.mli \ @@ -310,6 +312,7 @@ SOURCES_ML = \ chroot.ml \ blkid.ml \ btrfs.ml \ + cryptsetup.ml \ devsparts.ml \ file.ml \ filearch.ml \ diff --git a/daemon/cryptsetup.ml b/daemon/cryptsetup.ml new file mode 100644 index 000000000..e4e6a114c --- /dev/null +++ b/daemon/cryptsetup.ml @@ -0,0 +1,83 @@ +(* guestfs-inspection + * Copyright (C) 2009-2020 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Printf +open Unix + +open Std_utils + +open Utils + +let cryptsetup_open ?(readonly = false) ?crypttype device key mapname + (* /dev/mapper/mapname must not exist already. *) + let devmapper = sprintf "/dev/mapper/%s" mapname in + if is_block_device devmapper then + failwithf "%s: device already exists" devmapper; + + (* Heuristically determine the encryption type. *) + let crypttype + match crypttype with + | Some s -> s + | None -> + let t = Blkid.vfs_type (Mountable.of_device device) in + match t with + | "crypto_LUKS" -> "luks" + | "BitLocker" -> "bitlk" + | _ -> + failwithf "%s: unknown encrypted device type" t in + + (* Write the key to a temporary file. *) + let keyfile, chan = Filename.open_temp_file "crypt" ".key" in + fchmod (descr_of_out_channel chan) 0o600; + output_string chan key; + close_out chan; + + (* Make sure we always remove the temporary file. *) + protect ~f:( + fun () -> + let args = ref [] in + List.push_back_list args ["-d"; keyfile]; + if readonly then List.push_back args "--readonly"; + List.push_back_list args ["open"; device; mapname; "--type"; crypttype]; + ignore (command "cryptsetup" !args) + ) ~finally:(fun () -> unlink keyfile); + + udev_settle () + +let cryptsetup_close device + (* Must be /dev/mapper/... *) + if not (String.is_prefix device "/dev/mapper/") then + failwithf "%s: you must call this on the /dev/mapper device created by cryptsetup-open" device; + + let mapname = String.sub device 12 (String.length device - 12) in + ignore (command "cryptsetup" ["close"; mapname]); + + udev_settle () + +(* Deprecated APIs for backwards compatibility. + * + * For convenient for existing callers we do not specify the + * crypttype parameter. This means that callers of these APIs + * will be able to open BitLocker drives transparently. (If + * we wanted to be strict we would pass ~crypttype:"luks" + * here, but that seems to have only downsides). + *) +let luks_open device key mapname = cryptsetup_open device key mapname +let luks_open_ro device key mapname + cryptsetup_open ~readonly:true device key mapname +let luks_close = cryptsetup_close diff --git a/daemon/luks.c b/daemon/luks.c index d631cb100..51ced585d 100644 --- a/daemon/luks.c +++ b/daemon/luks.c @@ -81,89 +81,6 @@ remove_temp (char *tempfile) free (tempfile); } -static int -luks_open (const char *device, const char *key, const char *mapname, - int readonly) -{ - /* Sanity check: /dev/mapper/mapname must not exist already. Note - * that the device-mapper control device (/dev/mapper/control) is - * always there, so you can't ever have mapname == "control". - */ - CLEANUP_FREE char *devmapper = NULL; - if (asprintf (&devmapper, "/dev/mapper/%s", mapname) == -1) { - reply_with_perror ("asprintf"); - return -1; - } - if (access (devmapper, F_OK) == 0) { - reply_with_error ("%s: device already exists", devmapper); - return -1; - } - - char *tempfile = write_key_to_temp (key); - if (!tempfile) - return -1; - - const char *argv[MAX_ARGS]; - size_t i = 0; - - ADD_ARG (argv, i, "cryptsetup"); - ADD_ARG (argv, i, "-d"); - ADD_ARG (argv, i, tempfile); - if (readonly) ADD_ARG (argv, i, "--readonly"); - ADD_ARG (argv, i, "luksOpen"); - ADD_ARG (argv, i, device); - ADD_ARG (argv, i, mapname); - ADD_ARG (argv, i, NULL); - - CLEANUP_FREE char *err = NULL; - int r = commandv (NULL, &err, (const char * const *) argv); - remove_temp (tempfile); - - if (r == -1) { - reply_with_error ("%s", err); - return -1; - } - - udev_settle (); - - return 0; -} - -int -do_luks_open (const char *device, const char *key, const char *mapname) -{ - return luks_open (device, key, mapname, 0); -} - -int -do_luks_open_ro (const char *device, const char *key, const char *mapname) -{ - return luks_open (device, key, mapname, 1); -} - -int -do_luks_close (const char *device) -{ - /* Must be /dev/mapper/... */ - if (! STRPREFIX (device, "/dev/mapper/")) { - reply_with_error ("luks_close: you must call this on the /dev/mapper device created by luks_open"); - return -1; - } - - const char *mapname = &device[12]; - - CLEANUP_FREE char *err = NULL; - int r = command (NULL, &err, "cryptsetup", "luksClose", mapname, NULL); - if (r == -1) { - reply_with_error ("%s", err); - return -1; - } - - udev_settle (); - - return 0; -} - static int luks_format (const char *device, const char *key, int keyslot, const char *cipher) diff --git a/generator/actions_core.ml b/generator/actions_core.ml index 9a24a8d78..54156b2b8 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -5664,52 +5664,6 @@ will be able to see every block device. This command also clears the LVM cache and performs a volume group scan." }; - { defaults with - name = "luks_open"; added = (1, 5, 1); - style = RErr, [String (Device, "device"); String (Key, "key"); String (PlainString, "mapname")], []; - optional = Some "luks"; - shortdesc = "open a LUKS-encrypted block device"; - longdesc = "\ -This command opens a block device which has been encrypted -according to the Linux Unified Key Setup (LUKS) standard. - -C<device> is the encrypted block device or partition. - -The caller must supply one of the keys associated with the -LUKS block device, in the C<key> parameter. - -This creates a new block device called F</dev/mapper/mapname>. -Reads and writes to this block device are decrypted from and -encrypted to the underlying C<device> respectively. - -If this block device contains LVM volume groups, then -calling C<guestfs_lvm_scan> with the C<activate> -parameter C<true> will make them visible. - -Use C<guestfs_list_dm_devices> to list all device mapper -devices." }; - - { defaults with - name = "luks_open_ro"; added = (1, 5, 1); - style = RErr, [String (Device, "device"); String (Key, "key"); String (PlainString, "mapname")], []; - optional = Some "luks"; - shortdesc = "open a LUKS-encrypted block device read-only"; - longdesc = "\ -This is the same as C<guestfs_luks_open> except that a read-only -mapping is created." }; - - { defaults with - name = "luks_close"; added = (1, 5, 1); - style = RErr, [String (Device, "device")], []; - optional = Some "luks"; - shortdesc = "close a LUKS device"; - longdesc = "\ -This closes a LUKS device that was created earlier by -C<guestfs_luks_open> or C<guestfs_luks_open_ro>. The -C<device> parameter must be the name of the LUKS mapping -device (ie. F</dev/mapper/mapname>) and I<not> the name -of the underlying block device." }; - { defaults with name = "luks_format"; added = (1, 5, 2); style = RErr, [String (Device, "device"); String (Key, "key"); Int "keyslot"], []; @@ -9753,4 +9707,64 @@ is used)." }; longdesc = "\ This returns the UUID of the LUKS device C<device>." }; + { defaults with + name = "cryptsetup_open"; added = (1, 43, 1); + style = RErr, [String (Device, "device"); String (Key, "key"); String (PlainString, "mapname")], [OBool "readonly"; OString "crypttype"]; + impl = OCaml "Cryptsetup.cryptsetup_open"; + optional = Some "luks"; + test_excuse = "no way to format BitLocker, and smallest device is huge"; + shortdesc = "open an encrypted block device"; + longdesc = "\ +This command opens a block device which has been encrypted +according to the Linux Unified Key Setup (LUKS) standard, +Windows BitLocker, or some other types. + +C<device> is the encrypted block device or partition. + +The caller must supply one of the keys associated with the +encrypted block device, in the C<key> parameter. + +This creates a new block device called F</dev/mapper/mapname>. +Reads and writes to this block device are decrypted from and +encrypted to the underlying C<device> respectively. + +If the optional C<crypttype> parameter is not present then +libguestfs tries to guess the correct type (for example +LUKS or BitLocker). However you can override this by +specifying one of the following types: + +=over 4 + +=item C<luks> + +A Linux LUKS device. + +=item C<bitlk> + +A Windows BitLocker device. + +=back + +The optional C<readonly> flag, if set to true, creates a +read-only mapping. + +If this block device contains LVM volume groups, then +calling C<guestfs_lvm_scan> with the C<activate> +parameter C<true> will make them visible. + +Use C<guestfs_list_dm_devices> to list all device mapper +devices." }; + + { defaults with + name = "cryptsetup_close"; added = (1, 43, 1); + style = RErr, [String (Device, "device")], []; + impl = OCaml "Cryptsetup.cryptsetup_close"; + optional = Some "luks"; + shortdesc = "close an encrypted device"; + longdesc = "\ +This closes an encrypted device that was created earlier by +C<guestfs_cryptsetup_open>. The C<device> parameter must be +the name of the mapping device (ie. F</dev/mapper/mapname>) +and I<not> the name of the underlying block device." }; + ] diff --git a/generator/actions_core_deprecated.ml b/generator/actions_core_deprecated.ml index 8556763b7..299f9854b 100644 --- a/generator/actions_core_deprecated.ml +++ b/generator/actions_core_deprecated.ml @@ -847,4 +847,56 @@ allows you to specify the new size (in bytes) explicitly." }; This rescans all block devices and rebuilds the list of LVM physical volumes, volume groups and logical volumes." }; + { defaults with + name = "luks_open"; added = (1, 5, 1); + style = RErr, [String (Device, "device"); String (Key, "key"); String (PlainString, "mapname")], []; + impl = OCaml "Cryptsetup.luks_open"; + optional = Some "luks"; + deprecated_by = Replaced_by "cryptsetup_open"; + shortdesc = "open a LUKS-encrypted block device"; + longdesc = "\ +This command opens a block device which has been encrypted +according to the Linux Unified Key Setup (LUKS) standard. + +C<device> is the encrypted block device or partition. + +The caller must supply one of the keys associated with the +LUKS block device, in the C<key> parameter. + +This creates a new block device called F</dev/mapper/mapname>. +Reads and writes to this block device are decrypted from and +encrypted to the underlying C<device> respectively. + +If this block device contains LVM volume groups, then +calling C<guestfs_lvm_scan> with the C<activate> +parameter C<true> will make them visible. + +Use C<guestfs_list_dm_devices> to list all device mapper +devices." }; + + { defaults with + name = "luks_open_ro"; added = (1, 5, 1); + style = RErr, [String (Device, "device"); String (Key, "key"); String (PlainString, "mapname")], []; + impl = OCaml "Cryptsetup.luks_open_ro"; + optional = Some "luks"; + deprecated_by = Replaced_by "cryptsetup_open"; + shortdesc = "open a LUKS-encrypted block device read-only"; + longdesc = "\ +This is the same as C<guestfs_luks_open> except that a read-only +mapping is created." }; + + { defaults with + name = "luks_close"; added = (1, 5, 1); + style = RErr, [String (Device, "device")], []; + impl = OCaml "Cryptsetup.luks_close"; + optional = Some "luks"; + deprecated_by = Replaced_by "cryptsetup_close"; + shortdesc = "close a LUKS device"; + longdesc = "\ +This closes a LUKS device that was created earlier by +C<guestfs_luks_open> or C<guestfs_luks_open_ro>. The +C<device> parameter must be the name of the LUKS mapping +device (ie. F</dev/mapper/mapname>) and I<not> the name +of the underlying block device." }; + ] diff --git a/generator/daemon.ml b/generator/daemon.ml index 9edef462c..b1047427b 100644 --- a/generator/daemon.ml +++ b/generator/daemon.ml @@ -776,7 +776,8 @@ let generate_daemon_caml_stubs () pr "Val_bool (%s)" n; | OInt _ -> assert false | OInt64 _ -> assert false - | OString _ -> assert false + | OString _ -> + pr "caml_copy_string (%s)" n | OStringList _ -> assert false ); pr ";\n"; @@ -792,7 +793,7 @@ let generate_daemon_caml_stubs () | Bool n -> pr "Val_bool (%s)" n | Int n -> pr "Val_int (%s)" n | Int64 n -> pr "caml_copy_int64 (%s)" n - | String ((PlainString|Device|Pathname|Dev_or_Path), n) -> + | String ((PlainString|Device|Pathname|Dev_or_Path|Key), n) -> pr "caml_copy_string (%s)" n | String ((Mountable|Mountable_or_Path), n) -> pr "guestfs_int_daemon_copy_mountable (%s)" n diff --git a/generator/proc_nr.ml b/generator/proc_nr.ml index d8e7e1282..30e42864f 100644 --- a/generator/proc_nr.ml +++ b/generator/proc_nr.ml @@ -514,6 +514,8 @@ let proc_nr = [ 505, "f2fs_expand"; 506, "lvm_scan"; 507, "luks_uuid"; +508, "cryptsetup_open"; +509, "cryptsetup_close"; ] (* End of list. If adding a new entry, add it at the end of the list diff --git a/gobject/Makefile.inc b/gobject/Makefile.inc index 84ad960ef..650f8ddac 100644 --- a/gobject/Makefile.inc +++ b/gobject/Makefile.inc @@ -69,6 +69,7 @@ guestfs_gobject_headers= \ include/guestfs-gobject/optargs-copy_file_to_device.h \ include/guestfs-gobject/optargs-copy_file_to_file.h \ include/guestfs-gobject/optargs-cpio_out.h \ + include/guestfs-gobject/optargs-cryptsetup_open.h \ include/guestfs-gobject/optargs-disk_create.h \ include/guestfs-gobject/optargs-download_blocks.h \ include/guestfs-gobject/optargs-e2fsck.h \ @@ -162,6 +163,7 @@ guestfs_gobject_sources= \ src/optargs-copy_file_to_device.c \ src/optargs-copy_file_to_file.c \ src/optargs-cpio_out.c \ + src/optargs-cryptsetup_open.c \ src/optargs-disk_create.c \ src/optargs-download_blocks.c \ src/optargs-e2fsck.c \ diff --git a/lib/MAX_PROC_NR b/lib/MAX_PROC_NR index 055b6671a..77afe238f 100644 --- a/lib/MAX_PROC_NR +++ b/lib/MAX_PROC_NR @@ -1 +1 @@ -507 +509 diff --git a/lib/guestfs.pod b/lib/guestfs.pod index d746a41b1..bce9eb79f 100644 --- a/lib/guestfs.pod +++ b/lib/guestfs.pod @@ -585,17 +585,18 @@ Libguestfs allows you to access Linux guests which have been encrypted using whole disk encryption that conforms to the Linux Unified Key Setup (LUKS) standard. This includes nearly all whole disk encryption systems used by modern -Linux guests. +Linux guests. Windows BitLocker is also supported. -Use L</guestfs_vfs_type> to identify LUKS-encrypted block -devices (it returns the string C<crypto_LUKS>). +Use L</guestfs_vfs_type> to identify encrypted block +devices. For LUKS it returns the string C<crypto_LUKS>. +For Windows BitLocker it returns C<BitLocker>. -Then open these devices by calling L</guestfs_luks_open>. +Then open these devices by calling L</guestfs_cryptsetup_open>. Obviously you will require the passphrase! -Opening a LUKS device creates a new device mapper device +Opening an encrypted device creates a new device mapper device called F</dev/mapper/mapname> (where C<mapname> is the -string you supply to L</guestfs_luks_open>). +string you supply to L</guestfs_cryptsetup_open>). Reads and writes to this mapper device are decrypted from and encrypted to the underlying block device respectively. @@ -603,11 +604,11 @@ LVM volume groups on the device can be made visible by calling L</guestfs_vgscan> followed by L</guestfs_vg_activate_all>. The logical volume(s) can now be mounted in the usual way. -Use the reverse process to close a LUKS device. Unmount +Use the reverse process to close an encrypted device. Unmount any logical volumes on it, deactivate the volume groups by calling C<guestfs_vg_activate (g, 0, ["/dev/VG"])>. Then close the mapper device by calling -L</guestfs_luks_close> on the F</dev/mapper/mapname> +L</guestfs_cryptsetup_close> on the F</dev/mapper/mapname> device (I<not> the underlying encrypted block device). =head2 MOUNT LOCAL -- 2.25.0
Richard W.M. Jones
2020-Mar-30 15:03 UTC
[Libguestfs] [PATCH 2/7] fish: Update documentation to refer to cryptsetup-open/close and BitLocker.
--- fish/guestfish.pod | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/fish/guestfish.pod b/fish/guestfish.pod index d98c395f9..47f55cda4 100644 --- a/fish/guestfish.pod +++ b/fish/guestfish.pod @@ -860,34 +860,40 @@ it, eg: Libguestfs has some support for Linux guests encrypted according to the Linux Unified Key Setup (LUKS) standard, which includes nearly all -whole disk encryption systems used by modern Linux guests. Currently -only LVM-on-LUKS is supported. +whole disk encryption systems used by modern Linux guests, and Windows +BitLocker. Identify encrypted block devices and partitions using L</vfs-type>: ><fs> vfs-type /dev/sda2 crypto_LUKS -Then open those devices using L</luks-open>. This creates a -device-mapper device called F</dev/mapper/luksdev>. +or: - ><fs> luks-open /dev/sda2 luksdev + ><fs> vfs-type /dev/sda2 + BitLocker + +Then open those devices using L</cryptsetup-open>. +This creates a device-mapper device called F</dev/mapper/name>. + + ><fs> cryptsetup-open /dev/sda2 name Enter key or passphrase ("key"): <enter the passphrase> -Finally you have to tell LVM to scan for volume groups on -the newly created mapper device: +For Linux guests you have to tell LVM to scan for volume groups on the +newly created mapper device: vgscan vg-activate-all true -The logical volume(s) can now be mounted in the usual way. +The filesystems or logical volumes can now be mounted in the usual way. -Before closing a LUKS device you must unmount any logical volumes on -it and deactivate the volume groups by calling C<vg-activate false VG> -on each one. Then you can close the mapper device: +Before closing an encrypted device you must unmount any logical +volumes on it and deactivate the volume groups by calling +C<vg-activate false VG> on each one. Then you can close the mapper +device: vg-activate false /dev/VG - luks-close /dev/mapper/luksdev + cryptsetup-close /dev/mapper/name =head1 WINDOWS PATHS -- 2.25.0
Richard W.M. Jones
2020-Mar-30 15:03 UTC
[Libguestfs] [PATCH 3/7] daemon: Rewrite list-filesystems implementation imperatively.
Simple refactoring to make the code clearer, should have no other effect. --- daemon/listfs.ml | 64 ++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/daemon/listfs.ml b/daemon/listfs.ml index 903f363d0..3b71471ae 100644 --- a/daemon/listfs.ml +++ b/daemon/listfs.ml @@ -28,38 +28,36 @@ let rec list_filesystems () let has_lvm2 = Optgroups.lvm2_available () in let has_ldm = Optgroups.ldm_available () in + let ret = ref [] in + (* Devices. *) let devices = Devsparts.list_devices () in let devices = List.filter is_not_partitioned_device devices in - let ret = List.filter_map check_with_vfs_type devices in + List.iter (check_with_vfs_type ret) devices; (* Partitions. *) let partitions = Devsparts.list_partitions () in let partitions = List.filter is_partition_can_hold_filesystem partitions in - let ret = ret @ List.filter_map check_with_vfs_type partitions in + List.iter (check_with_vfs_type ret) partitions; (* MD. *) let mds = Md.list_md_devices () in let mds = List.filter is_not_partitioned_device mds in - let ret = ret @ List.filter_map check_with_vfs_type mds in + List.iter (check_with_vfs_type ret) mds; (* LVM. *) - let ret - if has_lvm2 then ( - let lvs = Lvm.lvs () in - ret @ List.filter_map check_with_vfs_type lvs - ) - else ret in + if has_lvm2 then ( + let lvs = Lvm.lvs () in + List.iter (check_with_vfs_type ret) lvs + ); (* LDM. *) - let ret - if has_ldm then ( - let ldmvols = Ldm.list_ldm_volumes () in - ret @ List.filter_map check_with_vfs_type ldmvols - ) - else ret in + if has_ldm then ( + let ldmvols = Ldm.list_ldm_volumes () in + List.iter (check_with_vfs_type ret) ldmvols + ); - List.flatten ret + !ret (* Look to see if device can directly contain filesystem (RHBZ#590167). * Partitioned devices cannot contain filesystem, so we will exclude @@ -120,11 +118,10 @@ and is_mbr_extended parttype device partnum Parted.part_get_mbr_part_type device partnum = "extended" (* Use vfs-type to check for a filesystem of some sort of [device]. - * Returns [Some [device, vfs_type; ...]] if found (there may be - * multiple devices found in the case of btrfs), else [None] if nothing - * is found. + * Appends (device, vfs_type) to the ret parameter (there may be + * multiple devices found in the case of btrfs). *) -and check_with_vfs_type device +and check_with_vfs_type ret device let mountable = Mountable.of_device device in let vfs_type try Blkid.vfs_type mountable @@ -135,18 +132,18 @@ and check_with_vfs_type device "" in if vfs_type = "" then - Some [mountable, "unknown"] + List.push_back ret (mountable, "unknown") (* Ignore all "*_member" strings. In libblkid these are returned * for things which are members of some RAID or LVM set, most * importantly "LVM2_member" which is a PV. *) else if String.is_suffix vfs_type "_member" then - None + () (* Ignore LUKS-encrypted partitions. These are also containers, as above. *) else if vfs_type = "crypto_LUKS" then - None + () (* A single btrfs device can turn into many volumes. *) else if vfs_type = "btrfs" then ( @@ -162,15 +159,18 @@ and check_with_vfs_type device fun { Structs.btrfssubvolume_id = id } -> id <> default_volume ) vols in - Some ( - (mountable, vfs_type) (* whole device = default volume *) - :: List.map ( - fun { Structs.btrfssubvolume_path = path } -> - let mountable = Mountable.of_btrfsvol device path in - (mountable, "btrfs") - ) vols - ) + (* whole device = default volume *) + List.push_back ret (mountable, vfs_type); + + (* subvolumes *) + List.push_back_list ret ( + List.map ( + fun { Structs.btrfssubvolume_path = path } -> + let mountable = Mountable.of_btrfsvol device path in + (mountable, "btrfs") + ) vols + ) ) else - Some [mountable, vfs_type] + List.push_back ret (mountable, vfs_type) -- 2.25.0
Richard W.M. Jones
2020-Mar-30 15:03 UTC
[Libguestfs] [PATCH 4/7] daemon: Ignore BitLocker disks in list-filesystems API.
--- daemon/listfs.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/listfs.ml b/daemon/listfs.ml index 3b71471ae..a4c917d12 100644 --- a/daemon/listfs.ml +++ b/daemon/listfs.ml @@ -141,8 +141,8 @@ and check_with_vfs_type ret device else if String.is_suffix vfs_type "_member" then () - (* Ignore LUKS-encrypted partitions. These are also containers, as above. *) - else if vfs_type = "crypto_LUKS" then + (* Ignore encrypted partitions. These are also containers, as above. *) + else if vfs_type = "crypto_LUKS" || vfs_type = "BitLocker" then () (* A single btrfs device can turn into many volumes. *) -- 2.25.0
Richard W.M. Jones
2020-Mar-30 15:03 UTC
[Libguestfs] [PATCH 5/7] daemon: Reimplement list_dm_devices API in OCaml.
Simple refactoring. The only annoying point is requiring an extra module because of OCaml module dependency restrictions. --- .gitignore | 1 + daemon/Makefile.am | 3 ++ daemon/lvm.c | 76 --------------------------------------- daemon/lvm_dm.ml | 39 ++++++++++++++++++++ generator/actions_core.ml | 1 + 5 files changed, 44 insertions(+), 76 deletions(-) diff --git a/.gitignore b/.gitignore index 12bfd45a2..c6eeed136 100644 --- a/.gitignore +++ b/.gitignore @@ -164,6 +164,7 @@ Makefile.in /daemon/link.mli /daemon/listfs.mli /daemon/lvm.mli +/daemon/lvm_dm.mli /daemon/lvm-tokenization.c /daemon/md.mli /daemon/mount.mli diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 928916e3d..bda61271f 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -50,6 +50,7 @@ generator_built = \ link.mli \ listfs.mli \ lvm.mli \ + lvm_dm.mli \ md.mli \ mount.mli \ optgroups.ml \ @@ -289,6 +290,7 @@ SOURCES_MLI = \ link.mli \ listfs.mli \ lvm.mli \ + lvm_dm.mli \ lvm_utils.mli \ md.mli \ mount.mli \ @@ -321,6 +323,7 @@ SOURCES_ML = \ link.ml \ lvm.ml \ lvm_utils.ml \ + lvm_dm.ml \ findfs.ml \ md.ml \ mount.ml \ diff --git a/daemon/lvm.c b/daemon/lvm.c index a78b344db..039240866 100644 --- a/daemon/lvm.c +++ b/daemon/lvm.c @@ -764,82 +764,6 @@ do_lvm_canonical_lv_name (const char *device) return canonical; /* caller frees */ } -/* List everything in /dev/mapper which *isn't* an LV (RHBZ#688062). */ -char ** -do_list_dm_devices (void) -{ - CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (ret); - struct dirent *d; - DIR *dir; - int r; - - dir = opendir ("/dev/mapper"); - if (!dir) { - reply_with_perror ("opendir: /dev/mapper"); - return NULL; - } - - while (1) { - CLEANUP_FREE char *devname = NULL; - - errno = 0; - d = readdir (dir); - if (d == NULL) break; - - /* Ignore . and .. */ - if (STREQ (d->d_name, ".") || STREQ (d->d_name, "..")) - continue; - - /* Ignore /dev/mapper/control which is used internally by dm. */ - if (STREQ (d->d_name, "control")) - continue; - - if (asprintf (&devname, "/dev/mapper/%s", d->d_name) == -1) { - reply_with_perror ("asprintf"); - closedir (dir); - return NULL; - } - - /* Ignore dm devices which are LVs. */ - r = lv_canonical (devname, NULL); - if (r == -1) { - closedir (dir); - return NULL; - } - if (r) - continue; - - /* Not an LV, so add it. */ - if (add_string (&ret, devname) == -1) { - closedir (dir); - return NULL; - } - } - - /* Did readdir fail? */ - if (errno != 0) { - reply_with_perror ("readdir: /dev/mapper"); - closedir (dir); - return NULL; - } - - /* Close the directory handle. */ - if (closedir (dir) == -1) { - reply_with_perror ("closedir: /dev/mapper"); - return NULL; - } - - /* Sort the output (may be empty). */ - if (ret.size > 0) - sort_strings (ret.argv, ret.size); - - /* NULL-terminate the list. */ - if (end_stringsbuf (&ret) == -1) - return NULL; - - return take_stringsbuf (&ret); -} - char * do_vgmeta (const char *vg, size_t *size_r) { diff --git a/daemon/lvm_dm.ml b/daemon/lvm_dm.ml new file mode 100644 index 000000000..474ba8377 --- /dev/null +++ b/daemon/lvm_dm.ml @@ -0,0 +1,39 @@ +(* guestfs-inspection + * Copyright (C) 2009-2020 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Unix +open Printf + +open Std_utils + +open Utils + +(* List everything in /dev/mapper which *isn't* an LV (RHBZ#688062). *) +let list_dm_devices () + let ds = Sys.readdir "/dev/mapper" in + let ds = Array.to_list ds in + let ds = List.sort compare ds in + + (* Ignore /dev/mapper/control which is used internally by d-m. *) + let ds = List.filter ((<>) "control") ds in + + let ds = List.map ((^) "/dev/mapper/") ds in + + (* Only keep devices which are _not_ LVs. *) + let ds = List.filter (fun d -> Lvm_utils.lv_canonical d = None) ds in + ds diff --git a/generator/actions_core.ml b/generator/actions_core.ml index 54156b2b8..0dec3b86d 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -6180,6 +6180,7 @@ parameter." }; { defaults with name = "list_dm_devices"; added = (1, 11, 15); style = RStringList (RDevice, "devices"), [], []; + impl = OCaml "Lvm_dm.list_dm_devices"; shortdesc = "list device mapper devices"; longdesc = "\ List all device mapper devices. -- 2.25.0
Richard W.M. Jones
2020-Mar-30 15:03 UTC
[Libguestfs] [PATCH 6/7] daemon: Search device-mapper devices for list-filesystems API.
In case any bare filesystems were decrypted using cryptsetup-open, they would appear as /dev/mapper/name devices. Since list-filesystems did not consider those when searching for filesystems, the unencrypted filesystems would not be returned. Note that previously this worked for LUKS because the common case (eg. for Fedora) was that whole devices were encrypted and thoes devices contained LVs, so luks-open + vgactivate would activate the LVs which would then be found by list-filesystems. For Windows BitLocker, the common case seems to be that each separate NTFS filesystem is contained in a separate BitLocker wrapper. --- daemon/listfs.ml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/daemon/listfs.ml b/daemon/listfs.ml index a4c917d12..d8add5005 100644 --- a/daemon/listfs.ml +++ b/daemon/listfs.ml @@ -35,6 +35,14 @@ let rec list_filesystems () let devices = List.filter is_not_partitioned_device devices in List.iter (check_with_vfs_type ret) devices; + (* Device-mapper devices. + * We include these in case any encrypted devices contain + * direct filesystems. + *) + let devices = Lvm_dm.list_dm_devices () in + let devices = List.filter is_not_partitioned_device devices in + List.iter (check_with_vfs_type ret) devices; + (* Partitions. *) let partitions = Devsparts.list_partitions () in let partitions = List.filter is_partition_can_hold_filesystem partitions in @@ -64,6 +72,11 @@ let rec list_filesystems () * such devices. *) and is_not_partitioned_device device + let device + if String.is_prefix device "/dev/mapper/" then + Unix_utils.Realpath.realpath device + else + device in assert (String.is_prefix device "/dev/"); let dev_name = String.sub device 5 (String.length device - 5) in let dev_dir = "/sys/block/" ^ dev_name in -- 2.25.0
Richard W.M. Jones
2020-Mar-30 15:03 UTC
[Libguestfs] [PATCH 7/7] lib/canonical-name.c: Hide errors.
Fixes “XXX” comment. This turns out to be necessary in order to suppress a warning when inspecting Windows BitLocker-encrypted guests. --- lib/canonical-name.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/canonical-name.c b/lib/canonical-name.c index 052bbf12c..11cf6fed6 100644 --- a/lib/canonical-name.c +++ b/lib/canonical-name.c @@ -46,8 +46,9 @@ guestfs_impl_canonical_device_name (guestfs_h *g, const char *device) } else if (STRPREFIX (device, "/dev/mapper/") || STRPREFIX (device, "/dev/dm-")) { - /* XXX hide errors */ + guestfs_push_error_handler (g, NULL, NULL); ret = guestfs_lvm_canonical_lv_name (g, device); + guestfs_pop_error_handler (g); if (ret == NULL) ret = safe_strdup (g, device); } -- 2.25.0
Pino Toscano
2020-Jul-27 17:05 UTC
Re: [Libguestfs] [PATCH 1/7] New APIs: cryptsetup-open and cryptsetup-close.
On Monday, 30 March 2020 17:03:36 CEST Richard W.M. Jones wrote:> This commit deprecates luks-open/luks-open-ro/luks-close for the more > generic sounding names cryptsetup-open/cryptsetup-close, which also > correspond directly to the cryptsetup commands. > > The optional cryptsetup-open readonly flag is used to replace the > functionality of luks-open-ro. > > The optional cryptsetup-open crypttype parameter can be used to select > the type (corresponding to cryptsetup open --type), which allows us to > open BitLocker-encrypted disks with no extra effort. As a convenience > the crypttype parameter may be omitted, and libguestfs will use a > heuristic (based on vfs-type output) to try to determine the correct > type to use.At least in my (non extensive) tests with cryptsetup, it seems it can detect the right format even without --type=format or the luksOpen/etc aliases. What I'd do is: - drop the autodetection: it is a mild duplication of what cryptsetup already does, and adding it in the API now means that it will be stuck there... - maybe (need to think about it) make the crypttype parameter mandatory, so the user has to select the right format> The deprecated functions and the new functions are all (re-)written in > OCaml.Regarding the deprecated functions: wouldn't it be better to have them in the library, rather than in the daemon? The machinery needed in the library is more or less than same anyway, and this way we can remove two RPCs in the daemon. Also, please bump the versions of the new APIs.> +let cryptsetup_open ?(readonly = false) ?crypttype device key mapname > + (* /dev/mapper/mapname must not exist already. *)/* Sanity check: /dev/mapper/mapname must not exist already. Note * that the device-mapper control device (/dev/mapper/control) is * always there, so you can't ever have mapname == "control". */ This whole comment seems useful, it would be a pity to lose it in the conversion. Also, unrelated to this patch: IMHO it would be a good idea to explicitly note this limitation in the API docs.> + (* Write the key to a temporary file. *) > + let keyfile, chan = Filename.open_temp_file "crypt" ".key" in > + fchmod (descr_of_out_channel chan) 0o600;This fchmod is not needed, as temporary files are already 600 by default.> + output_string chan key; > + close_out chan; > + > + (* Make sure we always remove the temporary file. *) > + protect ~f:( > + fun () -> > + let args = ref [] in > + List.push_back_list args ["-d"; keyfile]; > + if readonly then List.push_back args "--readonly"; > + List.push_back_list args ["open"; device; mapname; "--type"; crypttype];Minor nit: even if this way makes sense more from a scope POV, I'd instead build the args list out of the protect. Mostly for ease of reading.> +(* Deprecated APIs for backwards compatibility. > + * > + * For convenient for existing callers we do not specify the > + * crypttype parameter. This means that callers of these APIs > + * will be able to open BitLocker drives transparently. (If > + * we wanted to be strict we would pass ~crypttype:"luks" > + * here, but that seems to have only downsides).I disagree here: if an user uses the luks_open API, IMHO they expect it to be LUKS; if they want to use other types, then they want to switch to the new cryptsetup_open. So I'd pass ~crypttype:"luks" in luks_open.> + { defaults with > + name = "cryptsetup_open"; added = (1, 43, 1); > + style = RErr, [String (Device, "device"); String (Key, "key"); String (PlainString, "mapname")], [OBool "readonly"; OString "crypttype"]; > + impl = OCaml "Cryptsetup.cryptsetup_open"; > + optional = Some "luks"; > + test_excuse = "no way to format BitLocker, and smallest device is huge"; > + shortdesc = "open an encrypted block device"; > + longdesc = "\ > +This command opens a block device which has been encrypted > +according to the Linux Unified Key Setup (LUKS) standard, > +Windows BitLocker, or some other types. > + > +C<device> is the encrypted block device or partition. > + > +The caller must supply one of the keys associated with the > +encrypted block device, in the C<key> parameter. > + > +This creates a new block device called F</dev/mapper/mapname>. > +Reads and writes to this block device are decrypted from and > +encrypted to the underlying C<device> respectively. > + > +If the optional C<crypttype> parameter is not present then > +libguestfs tries to guess the correct type (for example > +LUKS or BitLocker). However you can override this by > +specifying one of the following types: > + > +=over 4 > + > +=item C<luks> > + > +A Linux LUKS device. > + > +=item C<bitlk> > + > +A Windows BitLocker device. > + > +=backMaybe add: Please refer to the L<cryptsetup(8)> documentation for all the supported types. -- Pino Toscano
Pino Toscano
2020-Jul-27 17:06 UTC
Re: [Libguestfs] [PATCH 7/7] lib/canonical-name.c: Hide errors.
On Monday, 30 March 2020 17:03:42 CEST Richard W.M. Jones wrote:> Fixes “XXX” comment. This turns out to be necessary in order to > suppress a warning when inspecting Windows BitLocker-encrypted guests. > --- > lib/canonical-name.c | 3 ++- > 1 file changed, 2 insertions(+), 1 deletion(-) > > diff --git a/lib/canonical-name.c b/lib/canonical-name.c > index 052bbf12c..11cf6fed6 100644 > --- a/lib/canonical-name.c > +++ b/lib/canonical-name.c > @@ -46,8 +46,9 @@ guestfs_impl_canonical_device_name (guestfs_h *g, const char *device) > } > else if (STRPREFIX (device, "/dev/mapper/") || > STRPREFIX (device, "/dev/dm-")) { > - /* XXX hide errors */ > + guestfs_push_error_handler (g, NULL, NULL); > ret = guestfs_lvm_canonical_lv_name (g, device); > + guestfs_pop_error_handler (g);Which warning is it? This also ignores all the errors from the API. -- Pino Toscano
Reasonably Related Threads
- [PATCH v2 0/7] Windows BitLocker support.
- [PATCH v3 0/8] Windows BitLocker support.
- [PATCH 0/1] WIP: Support LUKS-encrypted partitions
- [PATCH v2 1/2] mltools, options: support --allow-discards when decrypting LUKS devices
- Re: [PATCH v2 1/7] New APIs: cryptsetup-open and cryptsetup-close.