nfit, libnvdimm: fix interleave set cookie calculation

The interleave-set cookie is a sum that sanity checks the composition of
an interleave set has not changed from when the namespace was initially
created. The checksum is calculated by sorting the DIMMs by their
location in the interleave-set. The comparison for the sort must be
64-bit wide, not byte-by-byte as performed by memcmp() in the broken
case.

Fix the implementation to accept correct cookie values in addition to
the Linux "memcmp" order cookies, but only allow correct cookies to be
generated going forward. It does mean that namespaces created by
third-party-tooling, or created by newer kernels with this fix, will not
validate on older kernels. However, there are a couple mitigating
conditions:

1/ platforms with namespace-label capable NVDIMMs are not widely
available.

2/ interleave-sets with a single-dimm are by definition not affected
(nothing to sort). This covers the QEMU-KVM NVDIMM emulation case.

The cookie stored in the namespace label will be fixed by any write the
namespace label, the most straightforward way to achieve this is to
write to the "alt_name" attribute of a namespace in sysfs.

Cc: <stable@vger.kernel.org>
Fixes: eaf961536e16 ("libnvdimm, nfit: add interleave-set state-tracking infrastructure")
Reported-by: Nicholas Moulin <nicholas.w.moulin@linux.intel.com>
Tested-by: Nicholas Moulin <nicholas.w.moulin@linux.intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>

+41 -5
+15 -1
drivers/acpi/nfit/core.c
··· 1603 1603 + num_mappings * sizeof(struct nfit_set_info_map); 1604 1604 } 1605 1605 1606 - static int cmp_map(const void *m0, const void *m1) 1606 + static int cmp_map_compat(const void *m0, const void *m1) 1607 1607 { 1608 1608 const struct nfit_set_info_map *map0 = m0; 1609 1609 const struct nfit_set_info_map *map1 = m1; 1610 1610 1611 1611 return memcmp(&map0->region_offset, &map1->region_offset, 1612 1612 sizeof(u64)); 1613 + } 1614 + 1615 + static int cmp_map(const void *m0, const void *m1) 1616 + { 1617 + const struct nfit_set_info_map *map0 = m0; 1618 + const struct nfit_set_info_map *map1 = m1; 1619 + 1620 + return map0->region_offset - map1->region_offset; 1613 1621 } 1614 1622 1615 1623 /* Retrieve the nth entry referencing this spa */ ··· 1675 1667 sort(&info->mapping[0], nr, sizeof(struct nfit_set_info_map), 1676 1668 cmp_map, NULL); 1677 1669 nd_set->cookie = nd_fletcher64(info, sizeof_nfit_set_info(nr), 0); 1670 + 1671 + /* support namespaces created with the wrong sort order */ 1672 + sort(&info->mapping[0], nr, sizeof(struct nfit_set_info_map), 1673 + cmp_map_compat, NULL); 1674 + nd_set->altcookie = nd_fletcher64(info, sizeof_nfit_set_info(nr), 0); 1675 + 1678 1676 ndr_desc->nd_set = nd_set; 1679 1677 devm_kfree(dev, info); 1680 1678
+14 -4
drivers/nvdimm/namespace_devs.c
··· 1700 1700 struct device *create_namespace_pmem(struct nd_region *nd_region, 1701 1701 struct nd_namespace_label *nd_label) 1702 1702 { 1703 + u64 altcookie = nd_region_interleave_set_altcookie(nd_region); 1703 1704 u64 cookie = nd_region_interleave_set_cookie(nd_region); 1704 1705 struct nd_label_ent *label_ent; 1705 1706 struct nd_namespace_pmem *nspm; ··· 1719 1718 if (__le64_to_cpu(nd_label->isetcookie) != cookie) { 1720 1719 dev_dbg(&nd_region->dev, "invalid cookie in label: %pUb\n", 1721 1720 nd_label->uuid); 1722 - return ERR_PTR(-EAGAIN); 1721 + if (__le64_to_cpu(nd_label->isetcookie) != altcookie) 1722 + return ERR_PTR(-EAGAIN); 1723 + 1724 + dev_dbg(&nd_region->dev, "valid altcookie in label: %pUb\n", 1725 + nd_label->uuid); 1723 1726 } 1724 1727 1725 1728 nspm = kzalloc(sizeof(*nspm), GFP_KERNEL); ··· 1738 1733 res->name = dev_name(&nd_region->dev); 1739 1734 res->flags = IORESOURCE_MEM; 1740 1735 1741 - for (i = 0; i < nd_region->ndr_mappings; i++) 1742 - if (!has_uuid_at_pos(nd_region, nd_label->uuid, cookie, i)) 1743 - break; 1736 + for (i = 0; i < nd_region->ndr_mappings; i++) { 1737 + if (has_uuid_at_pos(nd_region, nd_label->uuid, cookie, i)) 1738 + continue; 1739 + if (has_uuid_at_pos(nd_region, nd_label->uuid, altcookie, i)) 1740 + continue; 1741 + break; 1742 + } 1743 + 1744 1744 if (i < nd_region->ndr_mappings) { 1745 1745 struct nvdimm_drvdata *ndd = to_ndd(&nd_region->mapping[i]); 1746 1746
+1
drivers/nvdimm/nd.h
··· 328 328 int nd_region_to_nstype(struct nd_region *nd_region); 329 329 int nd_region_register_namespaces(struct nd_region *nd_region, int *err); 330 330 u64 nd_region_interleave_set_cookie(struct nd_region *nd_region); 331 + u64 nd_region_interleave_set_altcookie(struct nd_region *nd_region); 331 332 void nvdimm_bus_lock(struct device *dev); 332 333 void nvdimm_bus_unlock(struct device *dev); 333 334 bool is_nvdimm_bus_locked(struct device *dev);
+9
drivers/nvdimm/region_devs.c
··· 505 505 return 0; 506 506 } 507 507 508 + u64 nd_region_interleave_set_altcookie(struct nd_region *nd_region) 509 + { 510 + struct nd_interleave_set *nd_set = nd_region->nd_set; 511 + 512 + if (nd_set) 513 + return nd_set->altcookie; 514 + return 0; 515 + } 516 + 508 517 void nd_mapping_free_labels(struct nd_mapping *nd_mapping) 509 518 { 510 519 struct nd_label_ent *label_ent, *e;
+2
include/linux/libnvdimm.h
··· 70 70 71 71 struct nd_interleave_set { 72 72 u64 cookie; 73 + /* compatibility with initial buggy Linux implementation */ 74 + u64 altcookie; 73 75 }; 74 76 75 77 struct nd_mapping_desc {