From 06db19c56c0a4e81596b24a7ab74ed545b422e4c Mon Sep 17 00:00:00 2001 From: Cole Robinson Date: Thu, 12 Jun 2025 14:42:33 -0400 Subject: [PATCH] daemon: inspect: check /etc/crypttab for /dev/mapper/* Encrypted root fs on SUSE distros will present itself like so: ``` /dev/mapper/cr_root / btrfs defaults 0 0 UUID=588905f9-bfa4-47b5-9fe8-893cb8ad4a0b /var btrfs subvol=/@/var 0 0 ... more subvols here ... UUID=8a278363-3042-4dea-a878-592f5e1b7381 swap btrfs defaults 0 0 /dev/mapper/cr_root /.snapshots btrfs subvol=/@/.snapshots 0 0 cr_root UUID=5289379a-a707-41b5-994c-c383f7ed54cc none x-initrd.attach ``` This breaks `-i` inspection, since libguestfs doesn't know what /dev/mapper/cr_root is supposed to be, and nothing in the appliance will autopopulate that path. This isn't a problem on Fedora, where it uses UUID= instead of a /dev/mapper path. Currently when we see /dev/mapper as a mount prefix, we only attempt to do some LVM name mapping. This extends libguestfs to check /etc/crypttab first. If we find an entry for the mapper path, and it points to the encrypted luks UUID, we use that UUID to build the associated /dev/disk/by-id/dm-uuid-CRYPT-* path, which is a symlink to the unencrypted /dev/dm-X path Resolves: https://issues.redhat.com/browse/RHEL-93584 Signed-off-by: Cole Robinson --- daemon/inspect_fs_unix_fstab.ml | 93 +++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 23 deletions(-) diff --git a/daemon/inspect_fs_unix_fstab.ml b/daemon/inspect_fs_unix_fstab.ml index b4652a39d..bd1b8e540 100644 --- a/daemon/inspect_fs_unix_fstab.ml +++ b/daemon/inspect_fs_unix_fstab.ml @@ -41,7 +41,7 @@ let rec check_fstab ?(mdadm_conf = false) (root_mountable : Mountable.t) os_type = let mdadmfiles = if mdadm_conf then ["/etc/mdadm.conf"; "/etc/mdadm/mdadm.conf"] else [] in - let configfiles = "/etc/fstab" :: mdadmfiles in + let configfiles = "/etc/fstab" :: "/etc/crypttab" :: mdadmfiles in (* If verbose, dump the contents of each config file as that can be * useful for debugging. @@ -179,7 +179,7 @@ and check_fstab_entry md_map root_mountable os_type aug entry = root_mountable (* Resolve guest block device names. *) else if String.starts_with "/dev/" spec then - resolve_fstab_device spec md_map os_type + resolve_fstab_device spec md_map os_type aug (* In OpenBSD's fstab you can specify partitions * on a disk by appending a period and a partition * letter to a Disklable Unique Identifier. The @@ -194,7 +194,7 @@ and check_fstab_entry md_map root_mountable os_type aug entry = * assume that this is the first disk. *) let device = sprintf "/dev/sd0%c" part in - resolve_fstab_device device md_map os_type + resolve_fstab_device device md_map os_type aug ) (* Ignore "/.swap" (Pardus) and pseudo-devices * like "tmpfs". If we haven't resolved the device @@ -353,7 +353,7 @@ and parse_md_uuid uuid = * the real VM, which is a reasonable assumption to make. Return * anything we don't recognize unchanged. *) -and resolve_fstab_device spec md_map os_type = +and resolve_fstab_device spec md_map os_type aug = (* In any case where we didn't match a device pattern or there was * another problem, return this default mountable derived from [spec]. *) @@ -366,7 +366,7 @@ and resolve_fstab_device spec md_map os_type = if String.starts_with "/dev/mapper" spec then ( debug_matching "/dev/mapper"; - resolve_dev_mapper spec default + resolve_dev_mapper spec default aug ) else if PCRE.matches re_xdev spec then ( @@ -540,24 +540,71 @@ and resolve_fstab_device spec md_map os_type = default ) -and resolve_dev_mapper spec default = - (* LVM2 does some strange munging on /dev/mapper paths for VGs and - * LVs which contain '-' character: - * - * > lvcreate LV--test VG--test 32 - * > debug ls /dev/mapper - * VG----test-LV----test - * - * This makes it impossible to reverse those paths directly, so - * we have implemented lvm_canonical_lv_name in the daemon. - *) - try - match Lvm_utils.lv_canonical spec with - | None -> default - | Some device -> Mountable.of_device device - with - (* Ignore devices that don't exist. (RHBZ#811872) *) - | Unix.Unix_error (Unix.ENOENT, _, _) -> default +and resolve_dev_mapper spec default aug = + let augpath = + sprintf "/files/etc/crypttab/*[target='%s']/device" + (Filename.basename spec) in + match aug_get_noerrors aug augpath with + | Some device -> + (* /dev/mapper name is present in /etc/crypttab *) + if verbose() then eprintf "mapped to crypttab device=%s\n%!" device; + (* device string is one of: + * + UUID=... without any shell quoting + * + An absolute path + *) + if String.starts_with "UUID=" device then ( + (* We found the UUID for the encrypted LUKS partition, now we use + * that to get the unencrypted /dev/dm-X via + * /dev/disk/by-id/dm-uuid-CRYPT-* automagic paths. The format is + * + * /dev/disk/by-id/dm-uuid-CRYPT-$TYPE-$LUKSUUID-$DMNAME + * + * The fields are + * + $TYPE: `LUKS1` or `LUKS2` + * + $LUKSUUID: The UUID we got from crypttab, but with `-` removed + * + $DMNAME: this would be `cr_root` for `/dev/mapper/cr_root`, but + * we just ignore that. + *) + let byid_dir = "/dev/disk/by-id" in + let uuid = String.sub device 5 (String.length device - 5) in + let short_uuid = String.replace uuid "-" "" in + let regstr = sprintf "^dm-uuid-CRYPT-LUKS.-%s-.*$" short_uuid in + let re_dmcrypt = PCRE.compile regstr in + let entries = Sys.readdir byid_dir |> Array.to_list in + try + let filename = List.find (fun f -> PCRE.matches re_dmcrypt f) entries in + let fullpath = Filename.concat byid_dir filename in + let resolved_path = Unix_utils.Realpath.realpath fullpath in + eprintf("Found crypttab mapping %s -> %s\n%!") fullpath resolved_path; + Mountable.of_device (resolved_path) + with + Failure _ | Not_found -> + eprintf("Failed to find matching regex %s/%s\n%!") byid_dir regstr; + Mountable.of_device spec + ) else ( + Mountable.of_device spec + ) + | None -> + (* Assume /dev/mapper device is LVM *) + + (* LVM2 does some strange munging on /dev/mapper paths for VGs and + * LVs which contain '-' character: + * + * > lvcreate LV--test VG--test 32 + * > debug ls /dev/mapper + * VG----test-LV----test + * + * This makes it impossible to reverse those paths directly, so + * we have implemented lvm_canonical_lv_name in the daemon. + *) + try + match Lvm_utils.lv_canonical spec with + | None -> default + | Some device -> Mountable.of_device device + with + (* Ignore devices that don't exist. (RHBZ#811872) *) + | Unix.Unix_error (Unix.ENOENT, _, _) -> default + (* type: (h|s|v|xv) * disk: [a-z]+ -- 2.47.1