Skip to contentSkip to conversation

PCI: Fix pci_register_host_bridge() device_register() error handling

PATCH In review Rob Herring 5 participants 10 messages May 13, 2020 – May 14, 2020 PCI

lore
Share & tools
lore
#0 lore raw

PATCH 1/2 PCI: Fix pci_register_host_bridge() device_register() error handling

If device_register() has an error, we should bail out of pci_register_host_bridge() rather than continuing on.

Fixes 37d6a0a6f470 ("PCI: Add pci_register_host_bridge() interface")
Cc Arnd Bergmann <[email protected]>
Cc Bjorn Helgaas <[email protected]>
---
 drivers/pci/probe.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
drivers/pci/probe.c +3 -2
index 77b8a145c39b..e21dc71b1907 100644
@@ -909,9 +909,10 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
 		goto free;
 
 	err = device_register(&bridge->dev);
-	if (err)
+	if (err) {
 		put_device(&bridge->dev);
-
+		goto free;
+	}
 	bus->bridge = get_device(&bridge->dev);
 	device_enable_async_suspend(bus->bridge);
 	pci_set_bus_of_node(bus);
Signature · 1 line
2.20.1
#1 lore raw

PATCH 2/2 PCI: Fix pci_host_bridge struct device release/free handling

The PCI code has several paths where the struct pci_host_bridge is freed directly. This is wrong because it contains a struct device which is refcounted and should be freed using put_device(). This can result in use-after-free errors. I think this problem has existed since 2012 with commit 7b5436635800 ("PCI: add generic device into pci_host_bridge struct"). It generally hasn't mattered as most host bridge drivers are still built-in and can't unbind.

The problem is a struct device should never be freed directly once device_initialize() is called and a ref is held, but that doesn't happen until pci_register_host_bridge(). There's then a window between allocating the host bridge and pci_register_host_bridge() where kfree should be used. This is fragile and requires callers to do the right thing. To fix this, we need to split device_register() into device_initialize() and device_add() calls, so that the host bridge struct is always freed by using a put_device().

devm_pci_alloc_host_bridge() is using devm_kzalloc() to allocate struct pci_host_bridge which will be freed directly. Instead, we can use a custom devres action to call put_device().

---
 drivers/pci/probe.c  | 36 +++++++++++++++++++-----------------
 drivers/pci/remove.c |  2 +-
 2 files changed, 20 insertions(+), 18 deletions(-)
drivers/pci/probe.c +19 -17
index e21dc71b1907..e064ded6fbec 100644
@@ -565,7 +565,7 @@ static struct pci_bus *pci_alloc_bus(struct pci_bus *parent)
 	return b;
 }
 
-static void devm_pci_release_host_bridge_dev(struct device *dev)
+static void pci_release_host_bridge_dev(struct device *dev)
 {
 	struct pci_host_bridge *bridge = to_pci_host_bridge(dev);
 
@@ -574,12 +574,7 @@ static void devm_pci_release_host_bridge_dev(struct device *dev)
 
 	pci_free_resource_list(&bridge->windows);
 	pci_free_resource_list(&bridge->dma_ranges);
-}
-
-static void pci_release_host_bridge_dev(struct device *dev)
-{
-	devm_pci_release_host_bridge_dev(dev);
-	kfree(to_pci_host_bridge(dev));
+	kfree(bridge);
 }
 
 static void pci_init_host_bridge(struct pci_host_bridge *bridge)
@@ -599,6 +594,8 @@ static void pci_init_host_bridge(struct pci_host_bridge *bridge)
 	bridge->native_pme = 1;
 	bridge->native_ltr = 1;
 	bridge->native_dpc = 1;
+
+	device_initialize(&bridge->dev);
 }
 
 struct pci_host_bridge *pci_alloc_host_bridge(size_t priv)
@@ -616,17 +613,25 @@ struct pci_host_bridge *pci_alloc_host_bridge(size_t priv)
 }
 EXPORT_SYMBOL(pci_alloc_host_bridge);
 
+static void devm_pci_alloc_host_bridge_release(void *data)
+{
+	pci_free_host_bridge(data);
+}
+
 struct pci_host_bridge *devm_pci_alloc_host_bridge(struct device *dev,
 						   size_t priv)
 {
+	int ret;
 	struct pci_host_bridge *bridge;
 
-	bridge = devm_kzalloc(dev, sizeof(*bridge) + priv, GFP_KERNEL);
+	bridge = pci_alloc_host_bridge(priv);
 	if (!bridge)
 		return NULL;
 
-	pci_init_host_bridge(bridge);
-	bridge->dev.release = devm_pci_release_host_bridge_dev;
+	ret = devm_add_action_or_reset(dev, devm_pci_alloc_host_bridge_release,
+				       bridge);
+	if (ret)
+		return NULL;
 
 	return bridge;
 }
@@ -634,10 +639,7 @@ EXPORT_SYMBOL(devm_pci_alloc_host_bridge);
 
 void pci_free_host_bridge(struct pci_host_bridge *bridge)
 {
-	pci_free_resource_list(&bridge->windows);
-	pci_free_resource_list(&bridge->dma_ranges);
-
-	kfree(bridge);
+	put_device(&bridge->dev);
 }
 EXPORT_SYMBOL(pci_free_host_bridge);
 
@@ -908,7 +910,7 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
 	if (err)
 		goto free;
 
-	err = device_register(&bridge->dev);
+	err = device_add(&bridge->dev);
 	if (err) {
 		put_device(&bridge->dev);
 		goto free;
@@ -978,7 +980,7 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
 
 unregister:
 	put_device(&bridge->dev);
-	device_unregister(&bridge->dev);
+	device_del(&bridge->dev);
 
 free:
 	kfree(bus);
@@ -2953,7 +2955,7 @@ struct pci_bus *pci_create_root_bus(struct device *parent, int bus,
 	return bridge->bus;
 
 err_out:
-	kfree(bridge);
+	put_device(&bridge->dev);
 	return NULL;
 }
 EXPORT_SYMBOL_GPL(pci_create_root_bus);
drivers/pci/remove.c +1 -1
index e9c6b120cf45..95dec03d9f2a 100644
@@ -160,6 +160,6 @@ void pci_remove_root_bus(struct pci_bus *bus)
 	host_bridge->bus = NULL;
 
 	/* remove the host bridge */
-	device_unregister(&host_bridge->dev);
+	device_del(&host_bridge->dev);
 }
 EXPORT_SYMBOL_GPL(pci_remove_root_bus);
Signature · 1 line
2.20.1
Tested-by on v1 2/2
#2 lore raw

Re: [PATCH 2/2] PCI: Fix pci_host_bridge struct device release/free handling

Quoted 18 lines · Rob Herring

The PCI code has several paths where the struct pci_host_bridge is freed directly. This is wrong because it contains a struct device which is refcounted and should be freed using put_device(). This can result in use-after-free errors. I think this problem has existed since 2012 with commit 7b5436635800 ("PCI: add generic device into pci_host_bridge struct"). It generally hasn't mattered as most host bridge drivers are still built-in and can't unbind.

The problem is a struct device should never be freed directly once device_initialize() is called and a ref is held, but that doesn't happen until pci_register_host_bridge(). There's then a window between allocating the host bridge and pci_register_host_bridge() where kfree should be used. This is fragile and requires callers to do the right thing. To fix this, we need to split device_register() into device_initialize() and device_add() calls, so that the host bridge struct is always freed by using a put_device().

Quoted 8 lines

devm_pci_alloc_host_bridge() is using devm_kzalloc() to allocate struct pci_host_bridge which will be freed directly. Instead, we can use a custom devres action to call put_device().

Thank you Rob for fixing this so quickly!

I applied both patches and I couldn't see the use-after-free anymore.

Cheers,
Anders

Quoted 124 lines
---
 drivers/pci/probe.c  | 36 +++++++++++++++++++-----------------
 drivers/pci/remove.c |  2 +-
 2 files changed, 20 insertions(+), 18 deletions(-)
drivers/pci/probe.c +19 -17
index e21dc71b1907..e064ded6fbec 100644
@@ -565,7 +565,7 @@ static struct pci_bus *pci_alloc_bus(struct pci_bus *parent)
        return b;
 }
 
-static void devm_pci_release_host_bridge_dev(struct device *dev)
+static void pci_release_host_bridge_dev(struct device *dev)
 {
        struct pci_host_bridge *bridge = to_pci_host_bridge(dev);
 
@@ -574,12 +574,7 @@ static void devm_pci_release_host_bridge_dev(struct device *dev)
 
        pci_free_resource_list(&bridge->windows);
        pci_free_resource_list(&bridge->dma_ranges);
-}
-
-static void pci_release_host_bridge_dev(struct device *dev)
-{
-       devm_pci_release_host_bridge_dev(dev);
-       kfree(to_pci_host_bridge(dev));
+       kfree(bridge);
 }
 
 static void pci_init_host_bridge(struct pci_host_bridge *bridge)
@@ -599,6 +594,8 @@ static void pci_init_host_bridge(struct pci_host_bridge *bridge)
        bridge->native_pme = 1;
        bridge->native_ltr = 1;
        bridge->native_dpc = 1;
+
+       device_initialize(&bridge->dev);
 }
 
 struct pci_host_bridge *pci_alloc_host_bridge(size_t priv)
@@ -616,17 +613,25 @@ struct pci_host_bridge *pci_alloc_host_bridge(size_t priv)
 }
 EXPORT_SYMBOL(pci_alloc_host_bridge);
 
+static void devm_pci_alloc_host_bridge_release(void *data)
+{
+       pci_free_host_bridge(data);
+}
+
 struct pci_host_bridge *devm_pci_alloc_host_bridge(struct device *dev,
                                                   size_t priv)
 {
+       int ret;
        struct pci_host_bridge *bridge;
 
-       bridge = devm_kzalloc(dev, sizeof(*bridge) + priv, GFP_KERNEL);
+       bridge = pci_alloc_host_bridge(priv);
        if (!bridge)
                return NULL;
 
-       pci_init_host_bridge(bridge);
-       bridge->dev.release = devm_pci_release_host_bridge_dev;
+       ret = devm_add_action_or_reset(dev, devm_pci_alloc_host_bridge_release,
+                                      bridge);
+       if (ret)
+               return NULL;
 
        return bridge;
 }
@@ -634,10 +639,7 @@ EXPORT_SYMBOL(devm_pci_alloc_host_bridge);
 
 void pci_free_host_bridge(struct pci_host_bridge *bridge)
 {
-       pci_free_resource_list(&bridge->windows);
-       pci_free_resource_list(&bridge->dma_ranges);
-
-       kfree(bridge);
+       put_device(&bridge->dev);
 }
 EXPORT_SYMBOL(pci_free_host_bridge);
 
@@ -908,7 +910,7 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
        if (err)
                goto free;
 
-       err = device_register(&bridge->dev);
+       err = device_add(&bridge->dev);
        if (err) {
                put_device(&bridge->dev);
                goto free;
@@ -978,7 +980,7 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
 
 unregister:
        put_device(&bridge->dev);
-       device_unregister(&bridge->dev);
+       device_del(&bridge->dev);
 
 free:
        kfree(bus);
@@ -2953,7 +2955,7 @@ struct pci_bus *pci_create_root_bus(struct device *parent, int bus,
        return bridge->bus;
 
 err_out:
-       kfree(bridge);
+       put_device(&bridge->dev);
        return NULL;
 }
 EXPORT_SYMBOL_GPL(pci_create_root_bus);
drivers/pci/remove.c +1 -2
index e9c6b120cf45..95dec03d9f2a 100644
@@ -160,6 +160,6 @@ void pci_remove_root_bus(struct pci_bus *bus)
        host_bridge->bus = NULL;
 
        /* remove the host bridge */
-       device_unregister(&host_bridge->dev);
+       device_del(&host_bridge->dev);
 }
 EXPORT_SYMBOL_GPL(pci_remove_root_bus);
--

2.20.1

#3 lore raw

Re: [PATCH 2/2] PCI: Fix pci_host_bridge struct device release/free handling

In reply to Rob Herring
Quoted 117 lines · Rob Herring

The PCI code has several paths where the struct pci_host_bridge is freed directly. This is wrong because it contains a struct device which is refcounted and should be freed using put_device(). This can result in use-after-free errors. I think this problem has existed since 2012 with commit 7b5436635800 ("PCI: add generic device into pci_host_bridge struct"). It generally hasn't mattered as most host bridge drivers are still built-in and can't unbind.

The problem is a struct device should never be freed directly once device_initialize() is called and a ref is held, but that doesn't happen until pci_register_host_bridge(). There's then a window between allocating the host bridge and pci_register_host_bridge() where kfree should be used. This is fragile and requires callers to do the right thing. To fix this, we need to split device_register() into device_initialize() and device_add() calls, so that the host bridge struct is always freed by using a put_device().

devm_pci_alloc_host_bridge() is using devm_kzalloc() to allocate struct pci_host_bridge which will be freed directly. Instead, we can use a custom devres action to call put_device().

---
 drivers/pci/probe.c  | 36 +++++++++++++++++++-----------------
 drivers/pci/remove.c |  2 +-
 2 files changed, 20 insertions(+), 18 deletions(-)
drivers/pci/probe.c +17 -15
index e21dc71b1907..e064ded6fbec 100644
@@ -565,7 +565,7 @@ static struct pci_bus *pci_alloc_bus(struct pci_bus *parent)
 	return b;
 }
 
-static void devm_pci_release_host_bridge_dev(struct device *dev)
+static void pci_release_host_bridge_dev(struct device *dev)
 {
 	struct pci_host_bridge *bridge = to_pci_host_bridge(dev);
 
@@ -574,12 +574,7 @@ static void devm_pci_release_host_bridge_dev(struct device *dev)
 
 	pci_free_resource_list(&bridge->windows);
 	pci_free_resource_list(&bridge->dma_ranges);
-}
-
-static void pci_release_host_bridge_dev(struct device *dev)
-{
-	devm_pci_release_host_bridge_dev(dev);
-	kfree(to_pci_host_bridge(dev));
+	kfree(bridge);
 }
 
 static void pci_init_host_bridge(struct pci_host_bridge *bridge)
@@ -599,6 +594,8 @@ static void pci_init_host_bridge(struct pci_host_bridge *bridge)
 	bridge->native_pme = 1;
 	bridge->native_ltr = 1;
 	bridge->native_dpc = 1;
+
+	device_initialize(&bridge->dev);
 }
 
 struct pci_host_bridge *pci_alloc_host_bridge(size_t priv)
@@ -616,17 +613,25 @@ struct pci_host_bridge *pci_alloc_host_bridge(size_t priv)
 }
 EXPORT_SYMBOL(pci_alloc_host_bridge);
 
+static void devm_pci_alloc_host_bridge_release(void *data)
+{
+	pci_free_host_bridge(data);
+}
+
 struct pci_host_bridge *devm_pci_alloc_host_bridge(struct device *dev,
 						   size_t priv)
 {
+	int ret;
 	struct pci_host_bridge *bridge;
 
-	bridge = devm_kzalloc(dev, sizeof(*bridge) + priv, GFP_KERNEL);
+	bridge = pci_alloc_host_bridge(priv);
 	if (!bridge)
 		return NULL;
 
-	pci_init_host_bridge(bridge);
-	bridge->dev.release = devm_pci_release_host_bridge_dev;
+	ret = devm_add_action_or_reset(dev, devm_pci_alloc_host_bridge_release,
+				       bridge);
+	if (ret)
+		return NULL;
 
 	return bridge;
 }
@@ -634,10 +639,7 @@ EXPORT_SYMBOL(devm_pci_alloc_host_bridge);
 
 void pci_free_host_bridge(struct pci_host_bridge *bridge)
 {
-	pci_free_resource_list(&bridge->windows);
-	pci_free_resource_list(&bridge->dma_ranges);
-
-	kfree(bridge);
+	put_device(&bridge->dev);
 }
 EXPORT_SYMBOL(pci_free_host_bridge);
 
@@ -908,7 +910,7 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
 	if (err)
 		goto free;
 
-	err = device_register(&bridge->dev);
+	err = device_add(&bridge->dev);
 	if (err) {
 		put_device(&bridge->dev);
 		goto free;

drivers/pci/probe.c static int pci_register_host_bridge(struct pci_host_bridge *bridge) · line 983

Quoted 5 lines
drivers/pci/probe.c +1 -1
@@ -978,7 +980,7 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
 
 unregister:
 	put_device(&bridge->dev);
-	device_unregister(&bridge->dev);
+	device_del(&bridge->dev);

I think we need to execute device_del() first, then put_device().

Thank you for fixing this code path.

Lorenzo

Quoted 26 lines
drivers/pci/probe.c +1 -1
 free:
 	kfree(bus);
@@ -2953,7 +2955,7 @@ struct pci_bus *pci_create_root_bus(struct device *parent, int bus,
 	return bridge->bus;
 
 err_out:
-	kfree(bridge);
+	put_device(&bridge->dev);
 	return NULL;
 }
 EXPORT_SYMBOL_GPL(pci_create_root_bus);
drivers/pci/remove.c +1 -1
index e9c6b120cf45..95dec03d9f2a 100644
@@ -160,6 +160,6 @@ void pci_remove_root_bus(struct pci_bus *bus)
 	host_bridge->bus = NULL;
 
 	/* remove the host bridge */
-	device_unregister(&host_bridge->dev);
+	device_del(&host_bridge->dev);
 }
 EXPORT_SYMBOL_GPL(pci_remove_root_bus);
Signature · 1 line
2.20.1
#4 lore raw

Re: [PATCH 2/2] PCI: Fix pci_host_bridge struct device release/free handling

Quoted 31 lines · Lorenzo Pieralisi
Quoted 29 lines · Rob Herring

The PCI code has several paths where the struct pci_host_bridge is freed directly. This is wrong because it contains a struct device which is refcounted and should be freed using put_device(). This can result in use-after-free errors. I think this problem has existed since 2012 with commit 7b5436635800 ("PCI: add generic device into pci_host_bridge struct"). It generally hasn't mattered as most host bridge drivers are still built-in and can't unbind.

The problem is a struct device should never be freed directly once device_initialize() is called and a ref is held, but that doesn't happen until pci_register_host_bridge(). There's then a window between allocating the host bridge and pci_register_host_bridge() where kfree should be used. This is fragile and requires callers to do the right thing. To fix this, we need to split device_register() into device_initialize() and device_add() calls, so that the host bridge struct is always freed by using a put_device().

devm_pci_alloc_host_bridge() is using devm_kzalloc() to allocate struct pci_host_bridge which will be freed directly. Instead, we can use a custom devres action to call put_device().

---
 drivers/pci/probe.c  | 36 +++++++++++++++++++-----------------
 drivers/pci/remove.c |  2 +-
 2 files changed, 20 insertions(+), 18 deletions(-)

[...]

Quoted 11 lines
Quoted 11 lines
drivers/pci/probe.c +1 -1
@@ -908,7 +910,7 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
      if (err)
              goto free;
 
-     err = device_register(&bridge->dev);
+     err = device_add(&bridge->dev);
      if (err) {
              put_device(&bridge->dev);
              goto free;
@@ -978,7 +980,7 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
 
Quoted 6 lines

drivers/pci/probe.c static int pci_register_host_bridge(struct pci_host_bridge *bridge) · line 983

Quoted 4 lines
drivers/pci/probe.c +1 -1
 unregister:
      put_device(&bridge->dev);
-     device_unregister(&bridge->dev);
+     device_del(&bridge->dev);

I think we need to execute device_del() first, then put_device().

No, because after the device_add, there's a get_device() adding the bridge ptr to the bus. So the put_device here is for that. The final put_device() is in the bridge free or devm action.

Rob

Reviewed-by on v1 2/2
#5 lore raw

Re: [PATCH 2/2] PCI: Fix pci_host_bridge struct device release/free handling

In reply to Rob Herring
Quoted 21 lines · Rob Herring

The PCI code has several paths where the struct pci_host_bridge is freed directly. This is wrong because it contains a struct device which is refcounted and should be freed using put_device(). This can result in use-after-free errors. I think this problem has existed since 2012 with commit 7b5436635800 ("PCI: add generic device into pci_host_bridge struct"). It generally hasn't mattered as most host bridge drivers are still built-in and can't unbind.

The problem is a struct device should never be freed directly once device_initialize() is called and a ref is held, but that doesn't happen until pci_register_host_bridge(). There's then a window between allocating the host bridge and pci_register_host_bridge() where kfree should be used. This is fragile and requires callers to do the right thing. To fix this, we need to split device_register() into device_initialize() and device_add() calls, so that the host bridge struct is always freed by using a put_device().

devm_pci_alloc_host_bridge() is using devm_kzalloc() to allocate struct pci_host_bridge which will be freed directly. Instead, we can use a custom devres action to call put_device().

Quoted 8 lines
---
 drivers/pci/probe.c  | 36 +++++++++++++++++++-----------------
 drivers/pci/remove.c |  2 +-
 2 files changed, 20 insertions(+), 18 deletions(-)
Quoted 119 lines
drivers/pci/probe.c +19 -17
index e21dc71b1907..e064ded6fbec 100644
@@ -565,7 +565,7 @@ static struct pci_bus *pci_alloc_bus(struct pci_bus *parent)
 	return b;
 }
 
-static void devm_pci_release_host_bridge_dev(struct device *dev)
+static void pci_release_host_bridge_dev(struct device *dev)
 {
 	struct pci_host_bridge *bridge = to_pci_host_bridge(dev);
 
@@ -574,12 +574,7 @@ static void devm_pci_release_host_bridge_dev(struct device *dev)
 
 	pci_free_resource_list(&bridge->windows);
 	pci_free_resource_list(&bridge->dma_ranges);
-}
-
-static void pci_release_host_bridge_dev(struct device *dev)
-{
-	devm_pci_release_host_bridge_dev(dev);
-	kfree(to_pci_host_bridge(dev));
+	kfree(bridge);
 }
 
 static void pci_init_host_bridge(struct pci_host_bridge *bridge)
@@ -599,6 +594,8 @@ static void pci_init_host_bridge(struct pci_host_bridge *bridge)
 	bridge->native_pme = 1;
 	bridge->native_ltr = 1;
 	bridge->native_dpc = 1;
+
+	device_initialize(&bridge->dev);
 }
 
 struct pci_host_bridge *pci_alloc_host_bridge(size_t priv)
@@ -616,17 +613,25 @@ struct pci_host_bridge *pci_alloc_host_bridge(size_t priv)
 }
 EXPORT_SYMBOL(pci_alloc_host_bridge);
 
+static void devm_pci_alloc_host_bridge_release(void *data)
+{
+	pci_free_host_bridge(data);
+}
+
 struct pci_host_bridge *devm_pci_alloc_host_bridge(struct device *dev,
 						   size_t priv)
 {
+	int ret;
 	struct pci_host_bridge *bridge;
 
-	bridge = devm_kzalloc(dev, sizeof(*bridge) + priv, GFP_KERNEL);
+	bridge = pci_alloc_host_bridge(priv);
 	if (!bridge)
 		return NULL;
 
-	pci_init_host_bridge(bridge);
-	bridge->dev.release = devm_pci_release_host_bridge_dev;
+	ret = devm_add_action_or_reset(dev, devm_pci_alloc_host_bridge_release,
+				       bridge);
+	if (ret)
+		return NULL;
 
 	return bridge;
 }
@@ -634,10 +639,7 @@ EXPORT_SYMBOL(devm_pci_alloc_host_bridge);
 
 void pci_free_host_bridge(struct pci_host_bridge *bridge)
 {
-	pci_free_resource_list(&bridge->windows);
-	pci_free_resource_list(&bridge->dma_ranges);
-
-	kfree(bridge);
+	put_device(&bridge->dev);
 }
 EXPORT_SYMBOL(pci_free_host_bridge);
 
@@ -908,7 +910,7 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
 	if (err)
 		goto free;
 
-	err = device_register(&bridge->dev);
+	err = device_add(&bridge->dev);
 	if (err) {
 		put_device(&bridge->dev);
 		goto free;
@@ -978,7 +980,7 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
 
 unregister:
 	put_device(&bridge->dev);
-	device_unregister(&bridge->dev);
+	device_del(&bridge->dev);
 
 free:
 	kfree(bus);
@@ -2953,7 +2955,7 @@ struct pci_bus *pci_create_root_bus(struct device *parent, int bus,
 	return bridge->bus;
 
 err_out:
-	kfree(bridge);
+	put_device(&bridge->dev);
 	return NULL;
 }
 EXPORT_SYMBOL_GPL(pci_create_root_bus);
drivers/pci/remove.c +1 -1
index e9c6b120cf45..95dec03d9f2a 100644
@@ -160,6 +160,6 @@ void pci_remove_root_bus(struct pci_bus *bus)
 	host_bridge->bus = NULL;
 
 	/* remove the host bridge */
-	device_unregister(&host_bridge->dev);
+	device_del(&host_bridge->dev);
 }
 EXPORT_SYMBOL_GPL(pci_remove_root_bus);
Signature · 1 line
2.20.1
Acked-by on v1 2/2
#6 lore raw

Re: [PATCH 2/2] PCI: Fix pci_host_bridge struct device release/free handling

In reply to Rob Herring
Quoted 18 lines · Rob Herring

The PCI code has several paths where the struct pci_host_bridge is freed directly. This is wrong because it contains a struct device which is refcounted and should be freed using put_device(). This can result in use-after-free errors. I think this problem has existed since 2012 with commit 7b5436635800 ("PCI: add generic device into pci_host_bridge struct"). It generally hasn't mattered as most host bridge drivers are still built-in and can't unbind.

The problem is a struct device should never be freed directly once device_initialize() is called and a ref is held, but that doesn't happen until pci_register_host_bridge(). There's then a window between allocating the host bridge and pci_register_host_bridge() where kfree should be used. This is fragile and requires callers to do the right thing. To fix this, we need to split device_register() into device_initialize() and device_add() calls, so that the host bridge struct is always freed by using a put_device().

Quoted 8 lines

devm_pci_alloc_host_bridge() is using devm_kzalloc() to allocate struct pci_host_bridge which will be freed directly. Instead, we can use a custom devres action to call put_device().

Thanks for working your way through that bit of code and fixing my mistakes!

Your description makes a lot of sense and the code looks reasonable, but I don't understand my own work enough any more to be sure I didn't miss anything.

Reviewed-by on v1 1/2
#7 lore raw
In reply to Rob Herring
Quoted 10 lines · Rob Herring

If device_register() has an error, we should bail out of pci_register_host_bridge() rather than continuing on.

Fixes 37d6a0a6f470 ("PCI: Add pci_register_host_bridge() interface")
Cc Arnd Bergmann <[email protected]>
Cc Bjorn Helgaas <[email protected]>
---
 drivers/pci/probe.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
Quoted 20 lines
drivers/pci/probe.c +3 -2
index 77b8a145c39b..e21dc71b1907 100644
@@ -909,9 +909,10 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
 		goto free;
 
 	err = device_register(&bridge->dev);
-	if (err)
+	if (err) {
 		put_device(&bridge->dev);
-
+		goto free;
+	}
 	bus->bridge = get_device(&bridge->dev);
 	device_enable_async_suspend(bus->bridge);
 	pci_set_bus_of_node(bus);
Signature · 1 line
2.20.1
#9 lore raw
In reply to Rob Herring
Quoted 7 lines · Rob Herring

If device_register() has an error, we should bail out of pci_register_host_bridge() rather than continuing on.

Fixes 37d6a0a6f470 ("PCI: Add pci_register_host_bridge() interface")
Cc Arnd Bergmann <[email protected]>
Cc Bjorn Helgaas <[email protected]>

Both applied to pci/enumeration for v5.8, thanks!

Quoted 24 lines
---
 drivers/pci/probe.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
drivers/pci/probe.c +3 -2
index 77b8a145c39b..e21dc71b1907 100644
@@ -909,9 +909,10 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
 		goto free;
 
 	err = device_register(&bridge->dev);
-	if (err)
+	if (err) {
 		put_device(&bridge->dev);
-
+		goto free;
+	}
 	bus->bridge = get_device(&bridge->dev);
 	device_enable_async_suspend(bus->bridge);
 	pci_set_bus_of_node(bus);
Signature · 1 line
2.20.1