lvmcache: ignore incorrect PV claim from old metadata
authorDavid Teigland <teigland@redhat.com>
Tue, 4 Mar 2025 20:11:54 +0000 (14:11 -0600)
committerDavid Teigland <teigland@redhat.com>
Wed, 5 Mar 2025 20:16:46 +0000 (14:16 -0600)
Outdated VG metadata that appears when an old device is attached
to the system can result in PVs appearing to belong to the
old/wrong VG, and commands are allowed to use (corrupt) the PVs.

- vgcreate old /dev/sda /dev/sdb /dev/sdc
- offline /dev/sda
- vgreduce --removemissing old
- vgremove old
- vgcreate new /dev/sdb /dev/sdc
- online /dev/sda

When sda is reattached, sdb and sdc will appear to be
in VG old again.  An attempt to correct the problem,
e.g. with vgremove old or vgreduce old, would modify
sdb and sdc, removing them from the new VG.

To fix this, check that sdb and sdc contain metadata for
VG old before allowing VG old to claim ownership of them.
With the fix, sdb and sdc are not displayed as part of
VG old, and commands to change VG old will fail as long
as it references incorrect PVs.

To fix VG old (sda), remove the incorrect PVs from VG old
while limiting the command to see only the correct PVs:
vgreduce --removemissing --devices /dev/sda old

lib/cache/lvmcache.c
lib/cache/lvmcache.h
lib/metadata/metadata.c
lib/metadata/metadata.h
test/shell/metadata-old.sh
tools/toollib.c

index 39ab368b149a6b05bac5ebe4c0ec28221014a1a6..cc4ad8d5940dfc089538e8a88bcf1377d44c0203 100644 (file)
@@ -2097,12 +2097,17 @@ int lvmcache_update_vgname_and_id(struct cmd_context *cmd, struct lvmcache_info
                vgid = vgname;
        }
 
-       /* FIXME: remove this, it shouldn't be needed */
-       /* If PV without mdas is already in a real VG, don't make it orphan */
-       if (is_orphan_vg(vgname) && info->vginfo &&
-           mdas_empty_or_ignored(&info->mdas) &&
-           !is_orphan_vg(info->vginfo->vgname) && critical_section())
+       /*
+        * This happens when vgremove does pv_write to make a PV
+        * that was previously part of a VG into a new orphan.
+        * FIXME: change pv_write to not use or update lvmcache,
+        * which should only be updated by label_scan.
+        */
+       if (is_orphan_vg(vgname) && info->vginfo && !is_orphan_vg(info->vginfo->vgname)) {
+               log_debug("lvmcache change %s to orphan from previous VG %s.",
+                          dev_name(info->dev), info->vginfo->vgname);
                return 1;
+       }
 
        /*
         * Creates a new vginfo struct for this vgname/vgid if none exists,
@@ -2265,7 +2270,7 @@ int lvmcache_update_vgname_and_id(struct cmd_context *cmd, struct lvmcache_info
  * using the 'vg'.
  */
 
-int lvmcache_update_vg_from_read(struct volume_group *vg, unsigned precommitted)
+int lvmcache_update_vg_from_read(struct volume_group *vg)
 {
        char pvid[ID_LEN + 1] __attribute__((aligned(8))) = { 0 };
        char vgid[ID_LEN + 1] __attribute__((aligned(8))) = { 0 };
@@ -2288,6 +2293,8 @@ int lvmcache_update_vg_from_read(struct volume_group *vg, unsigned precommitted)
                return 0;
        }
 
+       log_debug_cache("lvmcache_update_vg %s vginfo from metadata", vg->name);
+
        /*
         * The label scan doesn't know when a PV with old metadata has been
         * removed from the VG.  Now with the vg we can tell, so remove the
@@ -2326,8 +2333,21 @@ int lvmcache_update_vg_from_read(struct volume_group *vg, unsigned precommitted)
                        continue;
                }
 
-               log_debug_cache("lvmcache_update_vg %s for info %s",
-                               vg->name, dev_name(info->dev));
+               /*
+                * If this PV info is already attached to a different VG, don't
+                * override that.  The info/vginfo map a PV to a VG based on the
+                * metadata which appears on the PV itself.  That has precedence
+                * over a different mapping of PV to another VG (the vg arg here)
+                * which is likely outdated metadata from some other device.
+                */
+               if (info->vginfo && !is_orphan_vg(info->vginfo->vgname) &&
+                   (strcmp(info->vginfo->vgname, vg->name) || memcmp(info->vginfo->vgid, &vg->id, ID_LEN))) {
+                       log_warn("WARNING: PV %s %s belongs to VG %s, ignoring claim from VG %s.",
+                                dev_name(info->dev), pvid, info->vginfo->vgname, vg->name);
+                       continue;
+               }
+
+               log_debug_cache("lvmcache_update_vg %s for %s", vg->name, dev_name(info->dev));
                
                /*
                 * FIXME: use a different function that just attaches info's that
@@ -3203,6 +3223,73 @@ bool lvmcache_is_outdated_dev(struct cmd_context *cmd,
        return false;
 }
 
+/*
+ * Metadata is being processed which shows 'vg' containing 'pv'.
+ * Verify that this is consistent with the headers/metadata that
+ * were scanned from PV.  The headers/metadata scanned from the
+ * actual PV could be different from what 'vg' metadata claims,
+ * if the 'vg' metadata is old/outdated.
+ */
+
+int lvmcache_verify_info_in_vg(struct volume_group *vg, struct lvmcache_info *info)
+{
+       char vgid[ID_LEN + 1] __attribute__((aligned(8))) = { 0 };
+
+       memcpy(vgid, &vg->id, ID_LEN);
+
+       if (!info->dev) {
+               log_error(INTERNAL_ERROR "Verify PV info in %s: skip, no dev", vg->name);
+               return 1;
+       }
+
+       if (!info->dev->pvid) {
+               log_debug("Verify PV %s in %s: uncertain, no pvid",
+                         dev_name(info->dev), vg->name);
+               return 1;
+       }
+
+       if (!info->vginfo) {
+               log_debug("Verify PV %s %s in %s: uncertain, no vginfo",
+                         info->dev->pvid, dev_name(info->dev), vg->name);
+               return 1;
+       }
+
+       if (strcmp(vg->name, info->vginfo->vgname)) {
+               log_debug("Verify PV %s %s in %s: fail, other VG %s",
+                         info->dev->pvid, dev_name(info->dev), vg->name, info->vginfo->vgname);
+               return 0;
+       }
+
+       if (memcmp(vgid, info->vginfo->vgid, ID_LEN)) {
+               log_debug("Verify PV %s %s in %s: fail, other vgid %s",
+                         info->dev->pvid, dev_name(info->dev), vg->name, info->vginfo->vgid);
+               return 0;
+       }
+
+       return 1;
+}
+
+
+int lvmcache_verify_pv_in_vg(struct volume_group *vg, struct physical_volume *pv)
+{
+       struct lvmcache_info *info;
+       char pvid[ID_LEN + 1] __attribute__((aligned(8))) = { 0 };
+
+       memcpy(&pvid, &pv->id.uuid, ID_LEN);
+
+       if (!(info = lvmcache_info_from_pvid(pvid, NULL, 0))) {
+               log_debug("Verify PV %s in %s: skip, no info", pvid, vg->name);
+               return 1;
+       }
+
+       if (pv->dev != info->dev) {
+               log_debug("Verify PV %s in %s: skip, different devs", info->dev->pvid, vg->name);
+               return 1;
+       }
+
+       return lvmcache_verify_info_in_vg(vg, info);
+}
+
 const char *dev_filtered_reason(struct device *dev)
 {
        if (dev->filtered_flags & DEV_FILTERED_REGEX)
index 43f5288b5b0b9ab642fbbe0fb25295487c53cde3..c4e648b3aa763c0f97131ecdebf999416537b588 100644 (file)
@@ -83,7 +83,7 @@ void lvmcache_del_dev(struct device *dev);
 /* Update things */
 int lvmcache_update_vgname_and_id(struct cmd_context *cmd, struct lvmcache_info *info,
                                  struct lvmcache_vgsummary *vgsummary);
-int lvmcache_update_vg_from_read(struct volume_group *vg, unsigned precommitted);
+int lvmcache_update_vg_from_read(struct volume_group *vg);
 
 void lvmcache_lock_vgname(const char *vgname, int read_only);
 void lvmcache_unlock_vgname(const char *vgname);
@@ -190,6 +190,9 @@ void lvmcache_save_metadata_size(uint64_t val);
 
 bool lvmcache_has_old_metadata(struct cmd_context *cmd, const char *vgname, const char *vgid, struct device *dev);
 
+int lvmcache_verify_pv_in_vg(struct volume_group *vg, struct physical_volume *pv);
+int lvmcache_verify_info_in_vg(struct volume_group *vg, struct lvmcache_info *info);
+
 void lvmcache_get_outdated_devs(struct cmd_context *cmd,
                                 const char *vgname, const char *vgid,
                                 struct dm_list *devs);
index f2740796f00b5ae978bd03965c3515fe9e4ca940..6497dc702fda3c4cb91869e66466275e67700f79 100644 (file)
@@ -67,6 +67,13 @@ static int _check_pv_ext(struct cmd_context *cmd, struct volume_group *vg)
                if (!(info = lvmcache_info_from_pvid(pvl->pv->dev->pvid, pvl->pv->dev, 0)))
                        continue;
 
+               /*
+                * If this vg metadata is old/outdated, it may no longer reflect the
+                * actual VG containing this PV, in which case we ignore this check.
+                */
+               if (!lvmcache_verify_info_in_vg(vg, info))
+                       continue;
+
                ext_version = lvmcache_ext_version(info);
                if (ext_version < PV_HEADER_EXTENSION_VSN) {
                        log_warn("WARNING: PV %s in VG %s is using an old PV header, modify the VG to update.",
@@ -3636,29 +3643,6 @@ int pv_write(struct cmd_context *cmd,
        return 1;
 }
 
-int pv_write_orphan(struct cmd_context *cmd, struct physical_volume *pv)
-{
-       const char *old_vg_name = pv->vg_name;
-
-       pv->vg_name = cmd->fmt->orphan_vg_name;
-       pv->status = ALLOCATABLE_PV;
-       pv->pe_alloc_count = 0;
-
-       if (!dev_get_size(pv->dev, &pv->size)) {
-               log_error("%s: Couldn't get size.", pv_dev_name(pv));
-               return 0;
-       }
-
-       if (!pv_write(cmd, pv, 0)) {
-               log_error("Failed to clear metadata from physical "
-                         "volume \"%s\" after removal from \"%s\"",
-                         pv_dev_name(pv), old_vg_name);
-               return 0;
-       }
-
-       return 1;
-}
-
 /**
  * is_orphan_vg - Determine whether a vg_name is an orphan
  * @vg_name: pointer to the vg_name
@@ -4915,7 +4899,7 @@ static struct volume_group *_vg_read(struct cmd_context *cmd,
         * Also, outdated PVs that have been removed from the VG were incorrectly
         * attached to the vginfo during label_scan, and now need to be detached.
         */
-       lvmcache_update_vg_from_read(vg_ret, vg_ret->status & PRECOMMITTED);
+       lvmcache_update_vg_from_read(vg_ret);
 
        /*
         * lvmcache_update_vg identified outdated mdas that we read above that
@@ -5120,6 +5104,22 @@ struct volume_group *vg_read(struct cmd_context *cmd, const char *vg_name, const
                }
        }
 
+       /*
+        * If the VG metadata is referencing PVs that don't belong to it,
+        * then don't allow the VG to be modified or activated, to avoid
+        * damanging those PVs.
+        */
+       if (!is_orphan_vg(vg->name) && (writing || activating)) {
+               dm_list_iterate_items(pvl, &vg->pvs) {
+                       if (!lvmcache_verify_pv_in_vg(vg, pvl->pv)) {
+                               log_error("Cannot use VG %s referencing incorrect PVs.", vg->name);
+                               log_error("(See vgreduce --removemissing with --devices options.)");
+                               failure |= FAILED_NOT_ENABLED;
+                               goto bad;
+                       }
+               }
+       }
+
        if (!check_pv_dev_sizes(vg))
                log_warn("WARNING: One or more devices used as PVs in VG %s have changed sizes.", vg->name);
 
index d860eb65556c9db52f87b94d623770dfc3538855..1a6c417bd92bcc792317adaf7958ee6b4a7a805b 100644 (file)
@@ -381,8 +381,6 @@ int get_default_pvmetadatasize_sectors(void);
 void set_pe_align(struct physical_volume *pv, uint64_t data_alignment);
 void set_pe_align_offset(struct physical_volume *pv, uint64_t data_alignment_offset);
 
-int pv_write_orphan(struct cmd_context *cmd, struct physical_volume *pv);
-
 int check_dev_block_size_for_vg(struct device *dev, const struct volume_group *vg,
                                unsigned int *max_logical_block_size_found);
 int check_pv_dev_sizes(struct volume_group *vg);
index 9a0351c234ad97471a3c3c140ccc1a2a6858bdcb..f0213f8a21de5fa52dd2ccc14678bfa7d8af8db8 100644 (file)
@@ -219,3 +219,114 @@ grep $lv1 out
 grep $lv2 out
 
 vgremove -ff $vg
+
+# Old metadata is reattached that references PVs
+# which are now used by other VGs.  Commands run
+# on the VG from the old metadata should not
+# clobber the other PVs.
+
+vgcreate $SHARED $vg1 "$dev1" "$dev2" "$dev3"
+aux disable_dev "$dev1"
+vgreduce --removemissing $vg1
+vgremove $vg1
+vgcreate $vg2 "$dev2"
+vgcreate $vg3 "$dev3"
+aux enable_dev "$dev1"
+pvs | tee out
+grep "$dev1" out | tee out1
+grep "$dev2" out | tee out2
+grep "$dev3" out | tee out3
+grep $vg1 out1
+grep $vg2 out2
+grep $vg3 out3
+
+# The old VG cannot be used until the invalid PV references
+# are removed from it.
+not lvcreate -l1 $vg1
+not vgextend --restoremissing $vg1 "$dev1"
+not vgextend --restoremissing $vg1 "$dev2"
+not vgmerge $vg1 $vg2
+not vgrename $vg1 foo
+not vgremove $vg1
+not vgremove -ff $vg1
+not vgreduce $vg1 "$dev2"
+not vgreduce $vg1 "$dev3"
+not vgreduce --removemissing $vg1
+not vgreduce --removemissing --force $vg1
+not vgchange -ay $vg1
+
+# To modify the old VG on the reattached device,
+# removemissing together with --devices so the
+# command only sees the old device.
+vgreduce --removemissing --devices "$dev1" $vg1
+pvs | tee out
+grep "$dev1" out | tee out1
+grep "$dev2" out | tee out2
+grep "$dev3" out | tee out3
+grep $vg1 out1
+grep $vg2 out2
+grep $vg3 out3
+
+vgremove $vg1
+pvs | tee out
+not grep $vg1 out
+grep "$dev2" out | tee out2
+grep "$dev3" out | tee out3
+grep $vg2 out2
+grep $vg3 out3
+vgremove $vg2
+vgremove $vg3
+
+# Repeat the previous, but with two reattached/old PVs
+# referencing one other PV that's been reused for something
+# else.
+
+vgcreate $SHARED $vg1 "$dev1" "$dev2" "$dev3"
+aux disable_dev "$dev1"
+aux disable_dev "$dev2"
+vgreduce --removemissing $vg1
+vgremove $vg1
+vgcreate $vg3 "$dev3"
+aux enable_dev "$dev1"
+aux enable_dev "$dev2"
+pvs | tee out
+grep "$dev1" out | tee out1
+grep "$dev2" out | tee out2
+grep "$dev3" out | tee out3
+grep $vg1 out1
+grep $vg1 out2
+grep $vg3 out3
+
+# The old VG cannot be used until the invalid PV references
+# are removed from it.
+not lvcreate -l1 $vg1
+not vgextend --restoremissing $vg1 "$dev3"
+not vgmerge $vg1 $vg3
+not vgrename $vg1 foo
+not vgremove $vg1
+not vgremove -ff $vg1
+not vgreduce $vg1 "$dev2"
+not vgreduce $vg1 "$dev3"
+not vgreduce --removemissing $vg1
+not vgreduce --removemissing --force $vg1
+not vgchange -ay $vg1
+
+# To modify the old VG on the reattached device,
+# removemissing together with the correct PVs
+# specified with --devices.
+vgreduce --removemissing --devices "$dev1","$dev2" $vg1
+pvs | tee out
+grep "$dev1" out | tee out1
+grep "$dev2" out | tee out2
+grep "$dev3" out | tee out3
+grep $vg1 out1
+grep $vg1 out2
+grep $vg3 out3
+
+vgremove $vg1
+pvs | tee out
+not grep $vg1 out
+grep "$dev3" out | tee out3
+grep $vg3 out3
+vgremove $vg3
+
index 51f096630045784bd0f45e9d9e6820557be304f7..3839975ae829859785b160e88f3bbe85b352cf81 100644 (file)
@@ -4527,6 +4527,23 @@ static int _process_pvs_in_vg(struct cmd_context *cmd,
                pv = pvl->pv;
                pv_name = pv_dev_name(pv);
 
+               /*
+                * The VG metadata being processed here believes that this PV
+                * is part of the VG, but the headers/metadata on the PV may
+                * say differently.  This can happen if the VG metadata being
+                * processed here is outdated, e.g. from an old device that
+                * has been reattached and has outdated metadata that no longer
+                * reflects how other PVs are used.
+                *
+                * So, before processing PV as part of VG, check that the
+                * metadata from PV shows the same.  Info about what's on
+                * PV is kept in lvmcache info struct for PV.
+                */
+               if (!is_orphan_vg(vg->name) && !lvmcache_verify_pv_in_vg(vg, pv)) {
+                       log_debug("ignoring claim of PV %s by VG %s.", pv_name, vg->name);
+                       continue;
+               }
+
                log_set_report_object_name_and_id(pv_name, &pv->id);
 
                process_pv = process_all_pvs;
This page took 0.076751 seconds and 5 git commands to generate.