History

PCI: s390: Fix use-after-free of PCI resources with per-function hotplug

On s390 PCI functions may be hotplugged individually even when they belong to a multi-function device. In particular on an SR-IOV device VFs may be removed and later re-added. In commit a50297cf8235 ("s390/pci: separate zbus creation from scanning") it was missed however that struct pci_bus and struct zpci_bus's resource list retained a reference to the PCI functions MMIO resources even though those resources are released and freed on hot-unplug. These stale resources may subsequently be claimed when the PCI function re-appears resulting in use-after-free. One idea of fixing this use-after-free in s390 specific code that was investigated was to simply keep resources around from the moment a PCI function first appeared until the whole virtual PCI bus created for a multi-function device disappears. The problem with this however is that due to the requirement of artificial MMIO addreesses (address cookies) extra logic is then needed to keep the address cookies compatible on re-plug. At the same time the MMIO resources semantically belong to the PCI function so tying their lifecycle to the function seems more logical. Instead a simpler approach is to remove the resources of an individually hot-unplugged PCI function from the PCI bus's resource list while keeping the resources of other PCI functions on the PCI bus untouched. This is done by introducing pci_bus_remove_resource() to remove an individual resource. Similarly the resource also needs to be removed from the struct zpci_bus's resource list. It turns out however, that there is really no need to add the MMIO resources to the struct zpci_bus's resource list at all and instead we can simply use the zpci_bar_struct's resource pointer directly.

Niklas Schnelle committed 3 years ago

Commit ab909501 parent a52e5cd5 files · +38 −15Browse files

Unified · Split

5 files
  1. arch/s390/pci/pci.c +10 −6
  2. arch/s390/pci/pci_bus.c +5 −7
  3. arch/s390/pci/pci_bus.h +1 −2
  4. drivers/pci/bus.c +21 −0
  5. include/linux/pci.h +1 −0
arch/s390/pci/pci.c+10−6
@@ -544,8 +544,7 @@ static struct resource *__alloc_res(struct zpci_dev *zdev, unsigned long start, · 543 unmodified lines
544 return r;544 return r;
545}545}
546 546
547int zpci_setup_bus_resources(struct zpci_dev *zdev,547int zpci_setup_bus_resources(struct zpci_dev *zdev)
548 struct list_head *resources)
549{548{
550 unsigned long addr, size, flags;549 unsigned long addr, size, flags;
551 struct resource *res;550 struct resource *res;
@@ -581,7 +580,6 @@ int zpci_setup_bus_resources(struct zpci_dev *zdev, · 29 unmodified lines
581 return -ENOMEM;580 return -ENOMEM;
582 }581 }
583 zdev->bars[i].res = res;582 zdev->bars[i].res = res;
584 pci_add_resource(resources, res);
585 }583 }
586 zdev->has_resources = 1;584 zdev->has_resources = 1;
587 585
@@ -590,17 +588,23 @@ int zpci_setup_bus_resources(struct zpci_dev *zdev, · 2 unmodified lines
590 588
591static void zpci_cleanup_bus_resources(struct zpci_dev *zdev)589static void zpci_cleanup_bus_resources(struct zpci_dev *zdev)
592{590{
591 struct resource *res;
593 int i;592 int i;
594 593
594 pci_lock_rescan_remove();
595 for (i = 0; i < PCI_STD_NUM_BARS; i++) {595 for (i = 0; i < PCI_STD_NUM_BARS; i++) {
596 if (!zdev->bars[i].size || !zdev->bars[i].res)596 res = zdev->bars[i].res;
597 if (!res)
597 continue;598 continue;
598 599
600 release_resource(res);
601 pci_bus_remove_resource(zdev->zbus->bus, res);
599 zpci_free_iomap(zdev, zdev->bars[i].map_idx);602 zpci_free_iomap(zdev, zdev->bars[i].map_idx);
600 release_resource(zdev->bars[i].res);603 zdev->bars[i].res = NULL;
601 kfree(zdev->bars[i].res);604 kfree(res);
602 }605 }
603 zdev->has_resources = 0;606 zdev->has_resources = 0;
607 pci_unlock_rescan_remove();
604}608}
605 609
606int pcibios_device_add(struct pci_dev *pdev)610int pcibios_device_add(struct pci_dev *pdev)
arch/s390/pci/pci_bus.c+5−7
@@ -41,9 +41,7 @@ static int zpci_nb_devices; · 40 unmodified lines
41 */41 */
42static int zpci_bus_prepare_device(struct zpci_dev *zdev)42static int zpci_bus_prepare_device(struct zpci_dev *zdev)
43{43{
44 struct resource_entry *window, *n;44 int rc, i;
45 struct resource *res;
46 int rc;
47 45
48 if (!zdev_enabled(zdev)) {46 if (!zdev_enabled(zdev)) {
49 rc = zpci_enable_device(zdev);47 rc = zpci_enable_device(zdev);
@@ -57,10 +55,10 @@ static int zpci_bus_prepare_device(struct zpci_dev *zdev) · 7 unmodified lines
57 }55 }
58 56
59 if (!zdev->has_resources) {57 if (!zdev->has_resources) {
60 zpci_setup_bus_resources(zdev, &zdev->zbus->resources);58 zpci_setup_bus_resources(zdev);
61 resource_list_for_each_entry_safe(window, n, &zdev->zbus->resources) {59 for (i = 0; i < PCI_STD_NUM_BARS; i++) {
62 res = window->res;60 if (zdev->bars[i].res)
63 pci_bus_add_resource(zdev->zbus->bus, res, 0);61 pci_bus_add_resource(zdev->zbus->bus, zdev->bars[i].res, 0);
64 }62 }
65 }63 }
66 64
arch/s390/pci/pci_bus.h+1−2
@@ -30,8 +30,7 @@ static inline void zpci_zdev_get(struct zpci_dev *zdev) · 29 unmodified lines
30 30
31int zpci_alloc_domain(int domain);31int zpci_alloc_domain(int domain);
32void zpci_free_domain(int domain);32void zpci_free_domain(int domain);
33int zpci_setup_bus_resources(struct zpci_dev *zdev,33int zpci_setup_bus_resources(struct zpci_dev *zdev);
34 struct list_head *resources);
35 34
36static inline struct zpci_dev *zdev_from_bus(struct pci_bus *bus,35static inline struct zpci_dev *zdev_from_bus(struct pci_bus *bus,
37 unsigned int devfn)36 unsigned int devfn)
drivers/pci/bus.c+21−0
@@ -76,6 +76,27 @@ struct resource *pci_bus_resource_n(const struct pci_bus *bus, int n) · 75 unmodified lines
76}76}
77EXPORT_SYMBOL_GPL(pci_bus_resource_n);77EXPORT_SYMBOL_GPL(pci_bus_resource_n);
78 78
79void pci_bus_remove_resource(struct pci_bus *bus, struct resource *res)
80{
81 struct pci_bus_resource *bus_res, *tmp;
82 int i;
83
84 for (i = 0; i < PCI_BRIDGE_RESOURCE_NUM; i++) {
85 if (bus->resource[i] == res) {
86 bus->resource[i] = NULL;
87 return;
88 }
89 }
90
91 list_for_each_entry_safe(bus_res, tmp, &bus->resources, list) {
92 if (bus_res->res == res) {
93 list_del(&bus_res->list);
94 kfree(bus_res);
95 return;
96 }
97 }
98}
99
79void pci_bus_remove_resources(struct pci_bus *bus)100void pci_bus_remove_resources(struct pci_bus *bus)
80{101{
81 int i;102 int i;
include/linux/pci.h+1−0
@@ -1438,6 +1438,7 @@ void pci_bus_add_resource(struct pci_bus *bus, struct resource *res, · 1437 unmodified lines
1438 unsigned int flags);1438 unsigned int flags);
1439struct resource *pci_bus_resource_n(const struct pci_bus *bus, int n);1439struct resource *pci_bus_resource_n(const struct pci_bus *bus, int n);
1440void pci_bus_remove_resources(struct pci_bus *bus);1440void pci_bus_remove_resources(struct pci_bus *bus);
1441void pci_bus_remove_resource(struct pci_bus *bus, struct resource *res);
1441int devm_request_pci_bus_resources(struct device *dev,1442int devm_request_pci_bus_resources(struct device *dev,
1442 struct list_head *resources);1443 struct list_head *resources);
1443 1444