libguestfs/0007-daemon-inspect-check-etc-crypttab-for-dev-mapper.patch
Richard W.M. Jones 583fc558f3 Fix conversion of SLES15 with encrypted btrfs filesystem
resolves: RHEL-93584
Add guestfs_setfiles API
resolves: RHEL-108832
2025-08-13 18:09:25 +01:00

179 lines
7.2 KiB
Diff

From 06db19c56c0a4e81596b24a7ab74ed545b422e4c Mon Sep 17 00:00:00 2001
From: Cole Robinson <crobinso@redhat.com>
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 <crobinso@redhat.com>
---
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:
- *
- * ><fs> lvcreate LV--test VG--test 32
- * ><fs> 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:
+ *
+ * ><fs> lvcreate LV--test VG--test 32
+ * ><fs> 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